Skip to content

Commit

Permalink
Introduce Clean Architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
m110 committed Sep 1, 2020
1 parent f89da08 commit e986305
Show file tree
Hide file tree
Showing 32 changed files with 1,124 additions and 519 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ openapi: openapi_http openapi_js

.PHONY: openapi_http
openapi_http:
oapi-codegen -generate types -o internal/trainings/openapi_types.gen.go -package main api/openapi/trainings.yml
oapi-codegen -generate chi-server -o internal/trainings/openapi_api.gen.go -package main api/openapi/trainings.yml
oapi-codegen -generate types -o internal/trainings/ports/openapi_types.gen.go -package ports api/openapi/trainings.yml
oapi-codegen -generate chi-server -o internal/trainings/ports/openapi_api.gen.go -package ports api/openapi/trainings.yml

oapi-codegen -generate types -o internal/trainer/openapi_types.gen.go -package main api/openapi/trainer.yml
oapi-codegen -generate chi-server -o internal/trainer/openapi_api.gen.go -package main api/openapi/trainer.yml
oapi-codegen -generate types -o internal/trainer/ports/openapi_types.gen.go -package ports api/openapi/trainer.yml
oapi-codegen -generate chi-server -o internal/trainer/ports/openapi_api.gen.go -package ports api/openapi/trainer.yml

oapi-codegen -generate types -o internal/users/openapi_types.gen.go -package main api/openapi/users.yml
oapi-codegen -generate chi-server -o internal/users/openapi_api.gen.go -package main api/openapi/users.yml
Expand Down
4 changes: 2 additions & 2 deletions internal/common/auth/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"strings"

"firebase.google.com/go/auth"
commonerrors "github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/server/httperr"
"github.com/pkg/errors"
)

type FirebaseHttpMiddleware struct {
Expand Down Expand Up @@ -71,7 +71,7 @@ const (
var (
// if we expect that the user of the function may be interested with concrete error,
// it's a good idea to provide variable with this error
NoUserInContextError = errors.New("no user in context")
NoUserInContextError = commonerrors.NewAuthorizationError("no user in context", "no-user-found")
)

func UserFromCtx(ctx context.Context) (User, error) {
Expand Down
53 changes: 53 additions & 0 deletions internal/common/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package errors

type ErrorType struct {
t string
}

var (
ErrorTypeUnknown = ErrorType{"unknown"}
ErrorTypeAuthorization = ErrorType{"authorization"}
ErrorTypeIncorrectInput = ErrorType{"incorrect-input"}
)

type SlugError struct {
error string
slug string
errorType ErrorType
}

func (s SlugError) Error() string {
return s.error
}

func (s SlugError) Slug() string {
return s.slug
}

func (s SlugError) ErrorType() ErrorType {
return s.errorType
}

func NewSlugError(error string, slug string) SlugError {
return SlugError{
error: error,
slug: slug,
errorType: ErrorTypeUnknown,
}
}

func NewAuthorizationError(error string, slug string) SlugError {
return SlugError{
error: error,
slug: slug,
errorType: ErrorTypeAuthorization,
}
}

func NewIncorrectInputError(error string, slug string) SlugError {
return SlugError{
error: error,
slug: slug,
errorType: ErrorTypeIncorrectInput,
}
}
18 changes: 18 additions & 0 deletions internal/common/server/httperr/http_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package httperr
import (
"net/http"

"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/errors"
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/common/logs"
"github.com/go-chi/render"
)
Expand All @@ -19,6 +20,23 @@ func BadRequest(slug string, err error, w http.ResponseWriter, r *http.Request)
httpRespondWithError(err, slug, w, r, "Bad request", http.StatusBadRequest)
}

func RespondWithSlugError(err error, w http.ResponseWriter, r *http.Request) {
slugError, ok := err.(errors.SlugError)
if !ok {
InternalError("internal-server-error", err, w, r)
return
}

switch slugError.ErrorType() {
case errors.ErrorTypeAuthorization:
Unauthorised(slugError.Slug(), slugError, w, r)
case errors.ErrorTypeIncorrectInput:
BadRequest(slugError.Slug(), slugError, w, r)
default:
InternalError(slugError.Slug(), slugError, w, r)
}
}

func httpRespondWithError(err error, slug string, w http.ResponseWriter, r *http.Request, logMSg string, status int) {
logs.GetLogEntry(r).WithError(err).WithField("error-slug", slug).Warn(logMSg)
resp := ErrorResponse{slug, status}
Expand Down
99 changes: 99 additions & 0 deletions internal/trainer/adapters/dates_firestore_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package adapters

import (
"context"
"time"

"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/app"

"cloud.google.com/go/firestore"
"google.golang.org/api/iterator"
)

type DateModel struct {
Date time.Time `firestore:"Date"`
HasFreeHours bool `firestore:"HasFreeHours"`
Hours []HourModel `firestore:"Hours"`
}

type HourModel struct {
Available bool `firestore:"Available"`
HasTrainingScheduled bool `firestore:"HasTrainingScheduled"`
Hour time.Time `firestore:"Hour"`
}

type DatesFirestoreRepository struct {
firestoreClient *firestore.Client
}

func NewDatesFirestoreRepository(firestoreClient *firestore.Client) DatesFirestoreRepository {
if firestoreClient == nil {
panic("missing firestoreClient")
}

return DatesFirestoreRepository{
firestoreClient: firestoreClient,
}
}

func (d DatesFirestoreRepository) trainerHoursCollection() *firestore.CollectionRef {
return d.firestoreClient.Collection("trainer-hours")
}

func (d DatesFirestoreRepository) DocumentRef(dateTimeToUpdate time.Time) *firestore.DocumentRef {
return d.trainerHoursCollection().Doc(dateTimeToUpdate.Format("2006-01-02"))
}

func (d DatesFirestoreRepository) GetDates(ctx context.Context, from time.Time, to time.Time) ([]app.Date, error) {
iter := d.
trainerHoursCollection().
Where("Date", ">=", from).
Where("Date", "<=", to).
Documents(ctx)

var dates []app.Date

for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}

date := DateModel{}
if err := doc.DataTo(&date); err != nil {
return nil, err
}
dates = append(dates, dateModelToApp(date))
}

return dates, nil
}

func dateModelToApp(dm DateModel) app.Date {
var hours []app.Hour
for _, h := range dm.Hours {
hours = append(hours, app.Hour{
Available: h.Available,
HasTrainingScheduled: h.HasTrainingScheduled,
Hour: h.Hour,
})
}

return app.Date{
Date: dm.Date,
HasFreeHours: dm.HasFreeHours,
Hours: hours,
}
}

func (d DatesFirestoreRepository) CanLoadFixtures(ctx context.Context, daysToSet int) (bool, error) {
documents, err := d.trainerHoursCollection().Limit(daysToSet).Documents(ctx).GetAll()
if err != nil {
return false, err
}

return len(documents) < daysToSet, nil
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package adapters

import (
"context"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package adapters

import (
"context"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package adapters

import (
"context"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main_test
package adapters_test

import (
"context"
Expand All @@ -9,8 +9,9 @@ import (
"testing"
"time"

"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/adapters"

"cloud.google.com/go/firestore"
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer"
"github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/internal/trainer/domain/hour"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -69,7 +70,7 @@ func createRepositories(t *testing.T) []Repository {
},
{
Name: "memory",
Repository: main.NewMemoryHourRepository(testHourFactory),
Repository: adapters.NewMemoryHourRepository(testHourFactory),
},
}
}
Expand Down Expand Up @@ -126,7 +127,7 @@ func testUpdateHour(t *testing.T, repository hour.Repository) {
}

func testUpdateHour_parallel(t *testing.T, repository hour.Repository) {
if _, ok := repository.(*main.FirestoreHourRepository); ok {
if _, ok := repository.(*adapters.FirestoreHourRepository); ok {
// todo - enable after fix of https://github.com/googleapis/google-cloud-go/issues/2604
t.Skip("because of emulator bug, it's not working in Firebase")
}
Expand Down Expand Up @@ -272,7 +273,7 @@ func TestNewDateDTO(t *testing.T) {

for _, c := range testCases {
t.Run(c.Time.String(), func(t *testing.T) {
dateDTO := main.NewEmptyDateDTO(c.Time)
dateDTO := adapters.NewEmptyDateDTO(c.Time)
assert.True(t, dateDTO.Date.Equal(c.ExpectedDateTime), "%s != %s", dateDTO.Date, c.ExpectedDateTime)
})
}
Expand All @@ -288,18 +289,18 @@ var testHourFactory = hour.MustNewFactory(hour.FactoryConfig{
MaxUtcHour: 24,
})

func newFirebaseRepository(t *testing.T, ctx context.Context) *main.FirestoreHourRepository {
func newFirebaseRepository(t *testing.T, ctx context.Context) *adapters.FirestoreHourRepository {
firebaseClient, err := firestore.NewClient(ctx, os.Getenv("GCP_PROJECT"))
require.NoError(t, err)

return main.NewFirestoreHourRepository(firebaseClient, testHourFactory)
return adapters.NewFirestoreHourRepository(firebaseClient, testHourFactory)
}

func newMySQLRepository(t *testing.T) *main.MySQLHourRepository {
db, err := main.NewMySQLConnection()
func newMySQLRepository(t *testing.T) *adapters.MySQLHourRepository {
db, err := adapters.NewMySQLConnection()
require.NoError(t, err)

return main.NewMySQLHourRepository(db, testHourFactory)
return adapters.NewMySQLHourRepository(db, testHourFactory)
}

func newValidAvailableHour(t *testing.T) *hour.Hour {
Expand Down
39 changes: 33 additions & 6 deletions internal/trainer/model.go → internal/trainer/app/date.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
package main
package app

import (
"time"
)

type Date struct {
Date time.Time
HasFreeHours bool
Hours []Hour
}

type Hour struct {
Available bool
HasTrainingScheduled bool
Hour time.Time
}

func (d Date) FindHourInDate(timeToCheck time.Time) (*Hour, bool) {
for i, hour := range d.Hours {
if hour.Hour == timeToCheck {
return &d.Hours[i], true
}
}

return nil, false
}

type AvailableHoursRequest struct {
DateFrom time.Time
DateTo time.Time
}

const (
minHour = 12
maxHour = 20
)

// setDefaultAvailability adds missing hours to Date model if they were not set
func setDefaultAvailability(date DateModel) DateModel {
func setDefaultAvailability(date Date) Date {

HoursLoop:
for hour := minHour; hour <= maxHour; hour++ {
Expand All @@ -21,7 +48,7 @@ HoursLoop:
continue HoursLoop
}
}
newHour := HourModel{
newHour := Hour{
Available: false,
Hour: hour,
}
Expand All @@ -32,8 +59,8 @@ HoursLoop:
return date
}

func addMissingDates(params *GetTrainerAvailableHoursParams, dates []DateModel) []DateModel {
for day := params.DateFrom.UTC(); day.Before(params.DateTo) || day.Equal(params.DateTo); day = day.Add(time.Hour * 24) {
func addMissingDates(dates []Date, from time.Time, to time.Time) []Date {
for day := from.UTC(); day.Before(to) || day.Equal(to); day = day.Add(time.Hour * 24) {
found := false
for _, date := range dates {
if date.Date.Equal(day) {
Expand All @@ -43,7 +70,7 @@ func addMissingDates(params *GetTrainerAvailableHoursParams, dates []DateModel)
}

if !found {
date := DateModel{
date := Date{
Date: day,
}
date = setDefaultAvailability(date)
Expand Down
Loading

0 comments on commit e986305

Please sign in to comment.