From 42f35d5f86d90870cebf39f499b5c399693ff919 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Wed, 14 Jun 2023 11:55:27 +0400 Subject: [PATCH 01/13] refactor: address PR review comments (#2) - take 1 --- cmd/port-service/main.go | 6 +++--- internal/app/adapters/model.go | 20 ------------------- internal/app/app.go | 4 ++-- internal/app/command/create_or_update_port.go | 6 +++--- .../create_or_update_port_integration_test.go | 1 - internal/app/query/get_port.go | 6 +++--- {config => internal/config}/config.go | 0 internal/{app/store => repository}/port.go | 2 +- .../port_store_inmem.go | 16 +++++++-------- .../port_store_inmem_test.go | 3 +-- internal/{app/ui => transport}/http.go | 6 +++--- .../{app/ui => transport}/http_e2e_test.go | 8 ++++---- internal/{app/ui => transport}/model.go | 2 +- .../testfixtures/ports_request.json | 0 .../testfixtures/ports_response.json | 0 internal/{app/ui => transport}/utils.go | 2 +- 16 files changed, 29 insertions(+), 53 deletions(-) delete mode 100644 internal/app/adapters/model.go rename {config => internal/config}/config.go (100%) rename internal/{app/store => repository}/port.go (98%) rename internal/{app/adapters => repository}/port_store_inmem.go (84%) rename internal/{app/adapters => repository}/port_store_inmem_test.go (95%) rename internal/{app/ui => transport}/http.go (96%) rename internal/{app/ui => transport}/http_e2e_test.go (93%) rename internal/{app/ui => transport}/model.go (96%) rename internal/{app/ui => transport}/testfixtures/ports_request.json (100%) rename internal/{app/ui => transport}/testfixtures/ports_response.json (100%) rename internal/{app/ui => transport}/utils.go (98%) diff --git a/cmd/port-service/main.go b/cmd/port-service/main.go index 7be56ba..231113e 100644 --- a/cmd/port-service/main.go +++ b/cmd/port-service/main.go @@ -9,9 +9,9 @@ import ( "syscall" "time" - "github.com/evt/port-api/config" "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/ui" + "github.com/evt/port-api/internal/config" + "github.com/evt/port-api/internal/transport" "github.com/gorilla/mux" ) @@ -33,7 +33,7 @@ func run() error { application := app.New(ctx) // create http server with application injected - httpServer := ui.NewHttpServer(application) + httpServer := transport.NewHttpServer(application) // create http router router := mux.NewRouter() diff --git a/internal/app/adapters/model.go b/internal/app/adapters/model.go deleted file mode 100644 index 5ebf247..0000000 --- a/internal/app/adapters/model.go +++ /dev/null @@ -1,20 +0,0 @@ -package adapters - -import "time" - -type PortModel struct { - ID string - Name string - Code string - City string - Country string - Alias []any - Regions []any - Coordinates []float64 - Province string - Timezone string - Unlocs []string - - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/internal/app/app.go b/internal/app/app.go index af41221..a7b9ad1 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -3,10 +3,10 @@ package app import ( "context" - "github.com/evt/port-api/internal/app/adapters" "github.com/evt/port-api/internal/app/command" "github.com/evt/port-api/internal/app/query" "github.com/evt/port-api/internal/domain" + "github.com/evt/port-api/internal/repository" ) type CommandHandler[C any] interface { @@ -32,7 +32,7 @@ type Queries struct { } func New(_ context.Context) *Application { - portStore := adapters.NewPortStoreInMem() + portStore := repository.NewPortStoreInMem() return &Application{ Commands: Commands{ diff --git a/internal/app/command/create_or_update_port.go b/internal/app/command/create_or_update_port.go index 2558dbb..00eaf3b 100644 --- a/internal/app/command/create_or_update_port.go +++ b/internal/app/command/create_or_update_port.go @@ -3,8 +3,8 @@ package command import ( "context" - "github.com/evt/port-api/internal/app/store" "github.com/evt/port-api/internal/domain" + "github.com/evt/port-api/internal/repository" ) type CreateOrUpdatePort struct { @@ -24,12 +24,12 @@ func NewCreateOrUpdatePortHandler(store CreateOrUpdatePortStore) *CreateOrUpdate } type CreateOrUpdatePortStore interface { - CreateOrUpdatePort(ctx context.Context, port *store.Port) error + CreateOrUpdatePort(ctx context.Context, port *repository.Port) error } func (h *CreateOrUpdatePortHandler) Handle(ctx context.Context, cmd CreateOrUpdatePort) error { // convert domain port to db port - dbPort := store.DomainToPort(cmd.Port) + dbPort := repository.DomainToPort(cmd.Port) // create or update port in the store return h.store.CreateOrUpdatePort(ctx, dbPort) diff --git a/internal/app/command/create_or_update_port_integration_test.go b/internal/app/command/create_or_update_port_integration_test.go index 358623e..4fbbb6c 100644 --- a/internal/app/command/create_or_update_port_integration_test.go +++ b/internal/app/command/create_or_update_port_integration_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/evt/port-api/internal/app/adapters" "github.com/evt/port-api/internal/app/command" "github.com/evt/port-api/internal/app/store" "github.com/evt/port-api/internal/domain" diff --git a/internal/app/query/get_port.go b/internal/app/query/get_port.go index 60220c6..8eb7788 100644 --- a/internal/app/query/get_port.go +++ b/internal/app/query/get_port.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/evt/port-api/internal/app/store" "github.com/evt/port-api/internal/domain" + "github.com/evt/port-api/internal/repository" ) type GetPort struct { @@ -25,7 +25,7 @@ func NewGetPortHandler(store GetPortStore) *GetPortHandler { } type GetPortStore interface { - GetPort(ctx context.Context, id string) (*store.Port, error) + GetPort(ctx context.Context, id string) (*repository.Port, error) } func (h *GetPortHandler) Handle(ctx context.Context, q GetPort) (*domain.Port, error) { @@ -36,7 +36,7 @@ func (h *GetPortHandler) Handle(ctx context.Context, q GetPort) (*domain.Port, e } // convert to domain port - port, err := store.PortToDomain(dbPort) + port, err := repository.PortToDomain(dbPort) if err != nil { return nil, fmt.Errorf("store.PortToDomain failed: %w", err) } diff --git a/config/config.go b/internal/config/config.go similarity index 100% rename from config/config.go rename to internal/config/config.go diff --git a/internal/app/store/port.go b/internal/repository/port.go similarity index 98% rename from internal/app/store/port.go rename to internal/repository/port.go index 11e9cfb..c657a15 100644 --- a/internal/app/store/port.go +++ b/internal/repository/port.go @@ -1,4 +1,4 @@ -package store +package repository import ( "time" diff --git a/internal/app/adapters/port_store_inmem.go b/internal/repository/port_store_inmem.go similarity index 84% rename from internal/app/adapters/port_store_inmem.go rename to internal/repository/port_store_inmem.go index ad9cf05..62712ec 100644 --- a/internal/app/adapters/port_store_inmem.go +++ b/internal/repository/port_store_inmem.go @@ -1,12 +1,10 @@ -package adapters +package repository import ( "context" "errors" "sync" "time" - - "github.com/evt/port-api/internal/app/store" ) var ( @@ -15,17 +13,17 @@ var ( ) type PortStoreInMem struct { - data map[string]*store.Port + data map[string]*Port mu sync.RWMutex } func NewPortStoreInMem() *PortStoreInMem { return &PortStoreInMem{ - data: make(map[string]*store.Port), + data: make(map[string]*Port), } } -func (s *PortStoreInMem) GetPort(_ context.Context, id string) (*store.Port, error) { +func (s *PortStoreInMem) GetPort(_ context.Context, id string) (*Port, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -44,7 +42,7 @@ func (s *PortStoreInMem) CountPorts(_ context.Context) (int, error) { return len(s.data), nil } -func (s *PortStoreInMem) CreateOrUpdatePort(ctx context.Context, p *store.Port) error { +func (s *PortStoreInMem) CreateOrUpdatePort(ctx context.Context, p *Port) error { if p == nil { return ErrNil } @@ -60,7 +58,7 @@ func (s *PortStoreInMem) CreateOrUpdatePort(ctx context.Context, p *store.Port) } } -func (s *PortStoreInMem) createPort(_ context.Context, p *store.Port) error { +func (s *PortStoreInMem) createPort(_ context.Context, p *Port) error { if p == nil { return ErrNil } @@ -76,7 +74,7 @@ func (s *PortStoreInMem) createPort(_ context.Context, p *store.Port) error { return nil } -func (s *PortStoreInMem) updatePort(_ context.Context, p *store.Port) error { +func (s *PortStoreInMem) updatePort(_ context.Context, p *Port) error { if p == nil { return ErrNil } diff --git a/internal/app/adapters/port_store_inmem_test.go b/internal/repository/port_store_inmem_test.go similarity index 95% rename from internal/app/adapters/port_store_inmem_test.go rename to internal/repository/port_store_inmem_test.go index b8c0bdc..9254afc 100644 --- a/internal/app/adapters/port_store_inmem_test.go +++ b/internal/repository/port_store_inmem_test.go @@ -1,10 +1,9 @@ -package adapters_test +package repository_test import ( "context" "testing" - "github.com/evt/port-api/internal/app/adapters" "github.com/evt/port-api/internal/app/store" "github.com/google/uuid" "github.com/stretchr/testify/require" diff --git a/internal/app/ui/http.go b/internal/transport/http.go similarity index 96% rename from internal/app/ui/http.go rename to internal/transport/http.go index e42fef4..5439f4d 100644 --- a/internal/app/ui/http.go +++ b/internal/transport/http.go @@ -1,4 +1,4 @@ -package ui +package transport import ( "errors" @@ -6,10 +6,10 @@ import ( "net/http" "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/adapters" "github.com/evt/port-api/internal/app/command" "github.com/evt/port-api/internal/app/query" "github.com/evt/port-api/internal/common/server" + "github.com/evt/port-api/internal/repository" ) // HttpServer is a HTTP server for ports @@ -39,7 +39,7 @@ func (h *HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { port, err := h.app.Queries.GetPort.Handle(r.Context(), query.GetPort{ID: r.URL.Query().Get("id")}) if err != nil { - if errors.Is(err, adapters.ErrNotFound) { + if errors.Is(err, repository.ErrNotFound) { server.NotFound("port-not-found", err, w, r) return } diff --git a/internal/app/ui/http_e2e_test.go b/internal/transport/http_e2e_test.go similarity index 93% rename from internal/app/ui/http_e2e_test.go rename to internal/transport/http_e2e_test.go index 00b148b..52836b0 100644 --- a/internal/app/ui/http_e2e_test.go +++ b/internal/transport/http_e2e_test.go @@ -1,4 +1,4 @@ -package ui_test +package transport_test import ( "bytes" @@ -12,7 +12,7 @@ import ( "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/app/query" - "github.com/evt/port-api/internal/app/ui" + "github.com/evt/port-api/internal/transport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -20,7 +20,7 @@ import ( type HttpTestSuite struct { suite.Suite app *app.Application - httpServer *ui.HttpServer + httpServer *transport.HttpServer } func NewHttpTestSuite() *HttpTestSuite { @@ -33,7 +33,7 @@ func NewHttpTestSuite() *HttpTestSuite { suite.app = app.New(ctx) // create http server with application injected - suite.httpServer = ui.NewHttpServer(suite.app) + suite.httpServer = transport.NewHttpServer(suite.app) return suite } diff --git a/internal/app/ui/model.go b/internal/transport/model.go similarity index 96% rename from internal/app/ui/model.go rename to internal/transport/model.go index 4147a0f..27e4df2 100644 --- a/internal/app/ui/model.go +++ b/internal/transport/model.go @@ -1,4 +1,4 @@ -package ui +package transport type Port struct { ID string `json:"id"` diff --git a/internal/app/ui/testfixtures/ports_request.json b/internal/transport/testfixtures/ports_request.json similarity index 100% rename from internal/app/ui/testfixtures/ports_request.json rename to internal/transport/testfixtures/ports_request.json diff --git a/internal/app/ui/testfixtures/ports_response.json b/internal/transport/testfixtures/ports_response.json similarity index 100% rename from internal/app/ui/testfixtures/ports_response.json rename to internal/transport/testfixtures/ports_response.json diff --git a/internal/app/ui/utils.go b/internal/transport/utils.go similarity index 98% rename from internal/app/ui/utils.go rename to internal/transport/utils.go index 178a57c..00225ac 100644 --- a/internal/app/ui/utils.go +++ b/internal/transport/utils.go @@ -1,4 +1,4 @@ -package ui +package transport import ( "context" From 2fb71c7c2cbe4a75a871148222e4f5eb0422a102 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Wed, 14 Jun 2023 12:01:08 +0400 Subject: [PATCH 02/13] chore: make linter happy --- .../app/command/create_or_update_port_integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/command/create_or_update_port_integration_test.go b/internal/app/command/create_or_update_port_integration_test.go index 4fbbb6c..c42acd9 100644 --- a/internal/app/command/create_or_update_port_integration_test.go +++ b/internal/app/command/create_or_update_port_integration_test.go @@ -5,15 +5,15 @@ import ( "testing" "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/store" "github.com/evt/port-api/internal/domain" + "github.com/evt/port-api/internal/repository" "github.com/stretchr/testify/require" ) func TestCreateOrUpdatePort(t *testing.T) { t.Parallel() - portStore := adapters.NewPortStoreInMem() + portStore := repository.NewPortStoreInMem() handler := command.NewCreateOrUpdatePortHandler(portStore) port, err := domain.NewPort("port id", "port name", "port code", "port city", @@ -27,7 +27,7 @@ func TestCreateOrUpdatePort(t *testing.T) { storePort, err := portStore.GetPort(context.Background(), port.ID()) require.NoError(t, err) - portFromStore, err := store.PortToDomain(storePort) + portFromStore, err := repository.PortToDomain(storePort) require.NoError(t, err) require.Equal(t, portFromStore, port) From 944134c57a1e939631938de69c96b1c65b804ea9 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Wed, 14 Jun 2023 12:04:44 +0400 Subject: [PATCH 03/13] chore: make linter even happier --- internal/repository/port_store_inmem_test.go | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/repository/port_store_inmem_test.go b/internal/repository/port_store_inmem_test.go index 9254afc..f67f843 100644 --- a/internal/repository/port_store_inmem_test.go +++ b/internal/repository/port_store_inmem_test.go @@ -4,24 +4,24 @@ import ( "context" "testing" - "github.com/evt/port-api/internal/app/store" + "github.com/evt/port-api/internal/repository" "github.com/google/uuid" "github.com/stretchr/testify/require" ) func TestPortStore_CreateOrUpdatePort(t *testing.T) { t.Parallel() - store := adapters.NewPortStoreInMem() + repo := repository.NewPortStoreInMem() t.Run("create port", func(t *testing.T) { t.Parallel() port := newRandomPort(t) - err := store.CreateOrUpdatePort(context.Background(), port) + err := repo.CreateOrUpdatePort(context.Background(), port) require.NoError(t, err) - port, err = store.GetPort(context.Background(), port.ID) + port, err = repo.GetPort(context.Background(), port.ID) require.NoError(t, err) require.Equal(t, port.CreatedAt, port.UpdatedAt) @@ -31,33 +31,33 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { port := newRandomPort(t) - err := store.CreateOrUpdatePort(context.Background(), port) + err := repo.CreateOrUpdatePort(context.Background(), port) require.NoError(t, err) - beforeUpdatePort, err := store.GetPort(context.Background(), port.ID) + beforeUpdatePort, err := repo.GetPort(context.Background(), port.ID) require.NoError(t, err) port.Name = "updated name" - err = store.CreateOrUpdatePort(context.Background(), port) + err = repo.CreateOrUpdatePort(context.Background(), port) require.NoError(t, err) - updatedPort, err := store.GetPort(context.Background(), port.ID) + updatedPort, err := repo.GetPort(context.Background(), port.ID) require.NoError(t, err) require.NotEqual(t, beforeUpdatePort.Name, updatedPort.Name) }) t.Run("nil port", func(t *testing.T) { t.Parallel() - err := store.CreateOrUpdatePort(context.Background(), nil) - require.ErrorIs(t, err, adapters.ErrNil) + err := repo.CreateOrUpdatePort(context.Background(), nil) + require.ErrorIs(t, err, repository.ErrNil) }) } -func newRandomPort(t *testing.T) *store.Port { +func newRandomPort(t *testing.T) *repository.Port { t.Helper() randomID := uuid.New().String() - port := &store.Port{ + port := &repository.Port{ ID: randomID, Name: randomID, City: randomID, From 456fc97ff34ac6bb8b2d3a100b4a6d893bfcc877 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 00:39:44 +0400 Subject: [PATCH 04/13] refactor: address PR review comments - take 2 --- cmd/port-service/main.go | 11 ++- internal/app/app.go | 39 +++------- internal/app/command/create_or_update_port.go | 36 ---------- .../create_or_update_port_integration_test.go | 34 --------- internal/app/query/count_ports.go | 27 ------- internal/app/query/get_port.go | 45 ------------ internal/domain/port.go | 23 ++++-- internal/repository/{ => inmem}/port.go | 22 +++--- .../{ => inmem}/port_store_inmem.go | 46 +++++++----- .../repository/inmem/port_store_inmem_test.go | 70 ++++++++++++++++++ internal/repository/port_store_inmem_test.go | 72 ------------------- internal/services/portservice/service.go | 41 +++++++++++ internal/transport/http.go | 25 +++---- internal/transport/http_e2e_test.go | 20 +++--- internal/transport/model.go | 4 +- internal/transport/utils.go | 4 +- 16 files changed, 209 insertions(+), 310 deletions(-) delete mode 100644 internal/app/command/create_or_update_port.go delete mode 100644 internal/app/command/create_or_update_port_integration_test.go delete mode 100644 internal/app/query/count_ports.go delete mode 100644 internal/app/query/get_port.go rename internal/repository/{ => inmem}/port.go (73%) rename internal/repository/{ => inmem}/port_store_inmem.go (56%) create mode 100644 internal/repository/inmem/port_store_inmem_test.go delete mode 100644 internal/repository/port_store_inmem_test.go create mode 100644 internal/services/portservice/service.go diff --git a/cmd/port-service/main.go b/cmd/port-service/main.go index 231113e..35e7189 100644 --- a/cmd/port-service/main.go +++ b/cmd/port-service/main.go @@ -11,6 +11,8 @@ import ( "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/config" + "github.com/evt/port-api/internal/repository/inmem" + "github.com/evt/port-api/internal/services/portservice" "github.com/evt/port-api/internal/transport" "github.com/gorilla/mux" ) @@ -26,11 +28,14 @@ func run() error { // read config from env cfg := config.Read() - // default context - ctx := context.Background() + // create port repository + portStoreRepo := inmem.NewPortStore() + + // create port service + portService := portservice.New(portStoreRepo) // create application - application := app.New(ctx) + application := app.New(portService) // create http server with application injected httpServer := transport.NewHttpServer(application) diff --git a/internal/app/app.go b/internal/app/app.go index a7b9ad1..a473a0c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -3,44 +3,21 @@ package app import ( "context" - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/query" "github.com/evt/port-api/internal/domain" - "github.com/evt/port-api/internal/repository" ) -type CommandHandler[C any] interface { - Handle(ctx context.Context, cmd C) error -} - -type QueryHandler[Q any, R any] interface { - Handle(ctx context.Context, q Q) (R, error) +type Service interface { + GetPort(ctx context.Context, id string) (*domain.Port, error) + CountPorts(ctx context.Context) (int, error) + CreateOrUpdatePort(ctx context.Context, port *domain.Port) error } type Application struct { - Commands Commands - Queries Queries -} - -type Commands struct { - CreateOrUpdatePort CommandHandler[command.CreateOrUpdatePort] + Service Service } -type Queries struct { - GetPort QueryHandler[query.GetPort, *domain.Port] - CountPorts QueryHandler[query.CountPorts, int] -} - -func New(_ context.Context) *Application { - portStore := repository.NewPortStoreInMem() - - return &Application{ - Commands: Commands{ - CreateOrUpdatePort: command.NewCreateOrUpdatePortHandler(portStore), - }, - Queries: Queries{ - GetPort: query.NewGetPortHandler(portStore), - CountPorts: query.NewCountPortsHandler(portStore), - }, +func New(service Service) Application { + return Application{ + Service: service, } } diff --git a/internal/app/command/create_or_update_port.go b/internal/app/command/create_or_update_port.go deleted file mode 100644 index 00eaf3b..0000000 --- a/internal/app/command/create_or_update_port.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "context" - - "github.com/evt/port-api/internal/domain" - "github.com/evt/port-api/internal/repository" -) - -type CreateOrUpdatePort struct { - Port *domain.Port -} - -type CreateOrUpdatePortHandler struct { - store CreateOrUpdatePortStore -} - -func NewCreateOrUpdatePortHandler(store CreateOrUpdatePortStore) *CreateOrUpdatePortHandler { - if store == nil { - panic("create or update port store is not defined") - } - - return &CreateOrUpdatePortHandler{store: store} -} - -type CreateOrUpdatePortStore interface { - CreateOrUpdatePort(ctx context.Context, port *repository.Port) error -} - -func (h *CreateOrUpdatePortHandler) Handle(ctx context.Context, cmd CreateOrUpdatePort) error { - // convert domain port to db port - dbPort := repository.DomainToPort(cmd.Port) - - // create or update port in the store - return h.store.CreateOrUpdatePort(ctx, dbPort) -} diff --git a/internal/app/command/create_or_update_port_integration_test.go b/internal/app/command/create_or_update_port_integration_test.go deleted file mode 100644 index c42acd9..0000000 --- a/internal/app/command/create_or_update_port_integration_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package command_test - -import ( - "context" - "testing" - - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/domain" - "github.com/evt/port-api/internal/repository" - "github.com/stretchr/testify/require" -) - -func TestCreateOrUpdatePort(t *testing.T) { - t.Parallel() - - portStore := repository.NewPortStoreInMem() - handler := command.NewCreateOrUpdatePortHandler(portStore) - - port, err := domain.NewPort("port id", "port name", "port code", "port city", - "port country", []any{"alias1", "alias2"}, []any{"region 1", "region 2"}, []float64{1.0, 2.0}, - "port province", "port tz", []string{"unlock 1", "unlock 2"}) - require.NoError(t, err) - - err = handler.Handle(context.Background(), command.CreateOrUpdatePort{Port: port}) - require.NoError(t, err) - - storePort, err := portStore.GetPort(context.Background(), port.ID()) - require.NoError(t, err) - - portFromStore, err := repository.PortToDomain(storePort) - require.NoError(t, err) - - require.Equal(t, portFromStore, port) -} diff --git a/internal/app/query/count_ports.go b/internal/app/query/count_ports.go deleted file mode 100644 index a278e67..0000000 --- a/internal/app/query/count_ports.go +++ /dev/null @@ -1,27 +0,0 @@ -package query - -import ( - "context" -) - -type CountPorts struct{} - -type CountPortsHandler struct { - store CountPortsStore -} - -func NewCountPortsHandler(store CountPortsStore) *CountPortsHandler { - if store == nil { - panic("count ports store is not defined") - } - - return &CountPortsHandler{store: store} -} - -type CountPortsStore interface { - CountPorts(ctx context.Context) (int, error) -} - -func (h *CountPortsHandler) Handle(ctx context.Context, _ CountPorts) (int, error) { - return h.store.CountPorts(ctx) -} diff --git a/internal/app/query/get_port.go b/internal/app/query/get_port.go deleted file mode 100644 index 8eb7788..0000000 --- a/internal/app/query/get_port.go +++ /dev/null @@ -1,45 +0,0 @@ -package query - -import ( - "context" - "fmt" - - "github.com/evt/port-api/internal/domain" - "github.com/evt/port-api/internal/repository" -) - -type GetPort struct { - ID string -} - -type GetPortHandler struct { - store GetPortStore -} - -func NewGetPortHandler(store GetPortStore) *GetPortHandler { - if store == nil { - panic("get port store is not defined") - } - - return &GetPortHandler{store: store} -} - -type GetPortStore interface { - GetPort(ctx context.Context, id string) (*repository.Port, error) -} - -func (h *GetPortHandler) Handle(ctx context.Context, q GetPort) (*domain.Port, error) { - // get port from the store - dbPort, err := h.store.GetPort(ctx, q.ID) - if err != nil { - return nil, fmt.Errorf("store.GetPort failed: %w", err) - } - - // convert to domain port - port, err := repository.PortToDomain(dbPort) - if err != nil { - return nil, fmt.Errorf("store.PortToDomain failed: %w", err) - } - - return port, nil -} diff --git a/internal/domain/port.go b/internal/domain/port.go index 74e0d03..4d9bfb4 100644 --- a/internal/domain/port.go +++ b/internal/domain/port.go @@ -1,6 +1,8 @@ package domain -import "fmt" +import ( + "fmt" +) // Port is a domain port. type Port struct { @@ -9,8 +11,8 @@ type Port struct { code string city string country string - alias []any - regions []any + alias []string + regions []string coordinates []float64 province string timezone string @@ -18,7 +20,7 @@ type Port struct { } // NewPort creates a new port. -func NewPort(id, name, code, city, country string, alias, regions []any, coords []float64, +func NewPort(id, name, code, city, country string, alias, regions []string, coords []float64, province, tz string, unlocs []string) (*Port, error) { if id == "" { return nil, fmt.Errorf("%w: port id is required", ErrRequired) @@ -58,6 +60,15 @@ func (p *Port) Name() string { return p.name } +// SetName sets the port name. +func (p *Port) SetName(name string) error { + if name == "" { + return fmt.Errorf("%w: port name is required", ErrRequired) + } + p.name = name + return nil +} + // Code returns the port code. func (p *Port) Code() string { return p.code @@ -74,12 +85,12 @@ func (p *Port) Country() string { } // Alias returns the port alias. -func (p *Port) Alias() []any { +func (p *Port) Alias() []string { return p.alias } // Regions returns the port regions. -func (p *Port) Regions() []any { +func (p *Port) Regions() []string { return p.regions } diff --git a/internal/repository/port.go b/internal/repository/inmem/port.go similarity index 73% rename from internal/repository/port.go rename to internal/repository/inmem/port.go index c657a15..64e7fab 100644 --- a/internal/repository/port.go +++ b/internal/repository/inmem/port.go @@ -1,4 +1,4 @@ -package repository +package inmem import ( "time" @@ -12,8 +12,8 @@ type Port struct { Code string City string Country string - Alias []any - Regions []any + Alias []string + Regions []string Coordinates []float64 Province string Timezone string @@ -30,8 +30,8 @@ func (p *Port) Copy() *Port { Code: p.Code, City: p.City, Country: p.Country, - Alias: append([]any(nil), p.Alias...), - Regions: append([]any(nil), p.Regions...), + Alias: append([]string(nil), p.Alias...), + Regions: append([]string(nil), p.Regions...), Coordinates: append([]float64(nil), p.Coordinates...), Province: p.Province, Timezone: p.Timezone, @@ -41,15 +41,15 @@ func (p *Port) Copy() *Port { } } -func PortToDomain(p *Port) (*domain.Port, error) { +func portToDomain(p *Port) (*domain.Port, error) { return domain.NewPort( p.ID, p.Name, p.Code, p.City, p.Country, - append([]any(nil), p.Alias...), - append([]any(nil), p.Regions...), + append([]string(nil), p.Alias...), + append([]string(nil), p.Regions...), append([]float64(nil), p.Coordinates...), p.Province, p.Timezone, @@ -57,15 +57,15 @@ func PortToDomain(p *Port) (*domain.Port, error) { ) } -func DomainToPort(p *domain.Port) *Port { +func domainToPort(p *domain.Port) *Port { return &Port{ ID: p.ID(), Name: p.Name(), Code: p.Code(), City: p.City(), Country: p.Country(), - Alias: append([]any(nil), p.Alias()...), - Regions: append([]any(nil), p.Regions()...), + Alias: append([]string(nil), p.Alias()...), + Regions: append([]string(nil), p.Regions()...), Coordinates: append([]float64(nil), p.Coordinates()...), Province: p.Province(), Timezone: p.Timezone(), diff --git a/internal/repository/port_store_inmem.go b/internal/repository/inmem/port_store_inmem.go similarity index 56% rename from internal/repository/port_store_inmem.go rename to internal/repository/inmem/port_store_inmem.go index 62712ec..7b0219f 100644 --- a/internal/repository/port_store_inmem.go +++ b/internal/repository/inmem/port_store_inmem.go @@ -1,10 +1,13 @@ -package repository +package inmem import ( "context" "errors" + "fmt" "sync" "time" + + "github.com/evt/port-api/internal/domain" ) var ( @@ -12,18 +15,18 @@ var ( ErrNil = errors.New("nil data") ) -type PortStoreInMem struct { +type PortStore struct { data map[string]*Port mu sync.RWMutex } -func NewPortStoreInMem() *PortStoreInMem { - return &PortStoreInMem{ +func NewPortStore() *PortStore { + return &PortStore{ data: make(map[string]*Port), } } -func (s *PortStoreInMem) GetPort(_ context.Context, id string) (*Port, error) { +func (s *PortStore) GetPort(_ context.Context, id string) (*domain.Port, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -32,49 +35,54 @@ func (s *PortStoreInMem) GetPort(_ context.Context, id string) (*Port, error) { return nil, ErrNotFound } - return storePort.Copy(), nil + domainPort, err := portToDomain(storePort) + if err != nil { + return nil, fmt.Errorf("portToDomain failed: %w", err) + } + + return domainPort, nil } -func (s *PortStoreInMem) CountPorts(_ context.Context) (int, error) { +func (s *PortStore) CountPorts(_ context.Context) (int, error) { s.mu.RLock() defer s.mu.RUnlock() return len(s.data), nil } -func (s *PortStoreInMem) CreateOrUpdatePort(ctx context.Context, p *Port) error { +func (s *PortStore) CreateOrUpdatePort(ctx context.Context, p *domain.Port) error { if p == nil { return ErrNil } + storePort := domainToPort(p) + s.mu.Lock() defer s.mu.Unlock() - _, exists := s.data[p.ID] + _, exists := s.data[storePort.ID] if exists { - return s.updatePort(ctx, p) + return s.updatePort(ctx, storePort) } else { - return s.createPort(ctx, p) + return s.createPort(ctx, storePort) } } -func (s *PortStoreInMem) createPort(_ context.Context, p *Port) error { - if p == nil { +func (s *PortStore) createPort(_ context.Context, storePort *Port) error { + if storePort == nil { return ErrNil } - storePort := p.Copy() - // set created and updated at storePort.CreatedAt = time.Now() storePort.UpdatedAt = storePort.CreatedAt - s.data[p.ID] = storePort + s.data[storePort.ID] = storePort return nil } -func (s *PortStoreInMem) updatePort(_ context.Context, p *Port) error { +func (s *PortStore) updatePort(_ context.Context, p *Port) error { if p == nil { return ErrNil } @@ -91,8 +99,8 @@ func (s *PortStoreInMem) updatePort(_ context.Context, p *Port) error { storePortCopy.Code = p.Code storePortCopy.City = p.City storePortCopy.Country = p.Country - storePortCopy.Alias = append([]any(nil), p.Alias...) - storePortCopy.Regions = append([]any(nil), p.Regions...) + storePortCopy.Alias = append([]string(nil), p.Alias...) + storePortCopy.Regions = append([]string(nil), p.Regions...) storePortCopy.Coordinates = append([]float64(nil), p.Coordinates...) storePortCopy.Province = p.Province storePortCopy.Timezone = p.Timezone diff --git a/internal/repository/inmem/port_store_inmem_test.go b/internal/repository/inmem/port_store_inmem_test.go new file mode 100644 index 0000000..b81836b --- /dev/null +++ b/internal/repository/inmem/port_store_inmem_test.go @@ -0,0 +1,70 @@ +package inmem_test + +import ( + "context" + "fmt" + "testing" + + "github.com/evt/port-api/internal/domain" + "github.com/evt/port-api/internal/repository/inmem" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestPortStore_CreateOrUpdatePort(t *testing.T) { + t.Parallel() + store := inmem.NewPortStore() + + t.Run("create port", func(t *testing.T) { + t.Parallel() + + randomPort := newRandomPort(t) + + err := store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + port, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + + require.Equal(t, port, randomPort) + }) + t.Run("update port", func(t *testing.T) { + t.Parallel() + + randomPort := newRandomPort(t) + + err := store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + beforeUpdatePort, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + + require.Equal(t, beforeUpdatePort, randomPort) + + err = randomPort.SetName("updated name") + require.NoError(t, err) + + err = store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + updatedPort, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + fmt.Printf("updatedPort.Name = %s\n", updatedPort.Name()) + require.NotEqual(t, beforeUpdatePort.Name(), updatedPort.Name()) + }) + t.Run("nil port", func(t *testing.T) { + t.Parallel() + err := store.CreateOrUpdatePort(context.Background(), nil) + require.ErrorIs(t, err, inmem.ErrNil) + }) +} + +func newRandomPort(t *testing.T) *domain.Port { + t.Helper() + randomID := uuid.New().String() + port, err := domain.NewPort(randomID, randomID, randomID, randomID, randomID, + []string{randomID}, []string{randomID}, []float64{1.0, 2.0}, randomID, randomID, nil) + require.NoError(t, err) + + return port +} diff --git a/internal/repository/port_store_inmem_test.go b/internal/repository/port_store_inmem_test.go deleted file mode 100644 index f67f843..0000000 --- a/internal/repository/port_store_inmem_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package repository_test - -import ( - "context" - "testing" - - "github.com/evt/port-api/internal/repository" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestPortStore_CreateOrUpdatePort(t *testing.T) { - t.Parallel() - repo := repository.NewPortStoreInMem() - - t.Run("create port", func(t *testing.T) { - t.Parallel() - - port := newRandomPort(t) - - err := repo.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - port, err = repo.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - require.Equal(t, port.CreatedAt, port.UpdatedAt) - }) - t.Run("update port", func(t *testing.T) { - t.Parallel() - - port := newRandomPort(t) - - err := repo.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - beforeUpdatePort, err := repo.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - port.Name = "updated name" - - err = repo.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - updatedPort, err := repo.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - require.NotEqual(t, beforeUpdatePort.Name, updatedPort.Name) - }) - t.Run("nil port", func(t *testing.T) { - t.Parallel() - err := repo.CreateOrUpdatePort(context.Background(), nil) - require.ErrorIs(t, err, repository.ErrNil) - }) -} - -func newRandomPort(t *testing.T) *repository.Port { - t.Helper() - randomID := uuid.New().String() - port := &repository.Port{ - ID: randomID, - Name: randomID, - City: randomID, - Country: randomID, - Alias: []any{randomID}, - Regions: []any{randomID}, - Coordinates: []float64{1.0, 2.0}, - Province: randomID, - Timezone: randomID, - } - return port -} diff --git a/internal/services/portservice/service.go b/internal/services/portservice/service.go new file mode 100644 index 0000000..1ff3536 --- /dev/null +++ b/internal/services/portservice/service.go @@ -0,0 +1,41 @@ +package portservice + +import ( + "context" + + "github.com/evt/port-api/internal/domain" +) + +// PortRepository is a port repository for the port service +type PortRepository interface { + CreateOrUpdatePort(ctx context.Context, port *domain.Port) error + CountPorts(ctx context.Context) (int, error) + GetPort(ctx context.Context, id string) (*domain.Port, error) +} + +// Service is a port service +type Service struct { + repo PortRepository +} + +// New creates a new port service +func New(repo PortRepository) Service { + return Service{ + repo: repo, + } +} + +// GetPort returns a port by id +func (s Service) GetPort(ctx context.Context, id string) (*domain.Port, error) { + return s.repo.GetPort(ctx, id) +} + +// CountPorts returns the number of ports +func (s Service) CountPorts(ctx context.Context) (int, error) { + return s.repo.CountPorts(ctx) +} + +// CreateOrUpdatePort creates or updates a port +func (s Service) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error { + return s.repo.CreateOrUpdatePort(ctx, port) +} diff --git a/internal/transport/http.go b/internal/transport/http.go index 5439f4d..f8db9fa 100644 --- a/internal/transport/http.go +++ b/internal/transport/http.go @@ -6,27 +6,25 @@ import ( "net/http" "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/query" "github.com/evt/port-api/internal/common/server" - "github.com/evt/port-api/internal/repository" + "github.com/evt/port-api/internal/repository/inmem" ) // HttpServer is a HTTP server for ports type HttpServer struct { - app *app.Application + app app.Application } // NewHttpServer creates a new HTTP server for ports -func NewHttpServer(application *app.Application) *HttpServer { - return &HttpServer{ +func NewHttpServer(application app.Application) HttpServer { + return HttpServer{ app: application, } } // CountPorts returns total ports stored in DB -func (h *HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { - total, err := h.app.Queries.CountPorts.Handle(r.Context(), query.CountPorts{}) +func (h HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { + total, err := h.app.Service.CountPorts(r.Context()) if err != nil { server.RespondWithError(err, w, r) return @@ -36,10 +34,10 @@ func (h *HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { } // GetPort returns a port by ID -func (h *HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { - port, err := h.app.Queries.GetPort.Handle(r.Context(), query.GetPort{ID: r.URL.Query().Get("id")}) +func (h HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { + port, err := h.app.Service.GetPort(r.Context(), r.URL.Query().Get("id")) if err != nil { - if errors.Is(err, repository.ErrNotFound) { + if errors.Is(err, inmem.ErrNotFound) { server.NotFound("port-not-found", err, w, r) return } @@ -64,7 +62,7 @@ func (h *HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { } // UploadPorts reads ports from JSON file and creates/updates them in DB -func (h *HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { +func (h HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { log.Println("uploading ports") portChan := make(chan Port) @@ -101,8 +99,7 @@ func (h *HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { server.BadRequest("port-to-domain", err, w, r) return } - cmd := command.CreateOrUpdatePort{Port: p} - if err := h.app.Commands.CreateOrUpdatePort.Handle(r.Context(), cmd); err != nil { + if err := h.app.Service.CreateOrUpdatePort(r.Context(), p); err != nil { server.RespondWithError(err, w, r) return } diff --git a/internal/transport/http_e2e_test.go b/internal/transport/http_e2e_test.go index 52836b0..9a4ca3b 100644 --- a/internal/transport/http_e2e_test.go +++ b/internal/transport/http_e2e_test.go @@ -11,7 +11,8 @@ import ( "testing" "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/query" + "github.com/evt/port-api/internal/repository/inmem" + "github.com/evt/port-api/internal/services/portservice" "github.com/evt/port-api/internal/transport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -19,18 +20,21 @@ import ( type HttpTestSuite struct { suite.Suite - app *app.Application - httpServer *transport.HttpServer + app app.Application + httpServer transport.HttpServer } func NewHttpTestSuite() *HttpTestSuite { - // default context - ctx := context.Background() - suite := &HttpTestSuite{} + // create port repository + portStoreRepo := inmem.NewPortStore() + + // create port service + portService := portservice.New(portStoreRepo) + // create application - suite.app = app.New(ctx) + suite.app = app.New(portService) // create http server with application injected suite.httpServer = transport.NewHttpServer(suite.app) @@ -70,7 +74,7 @@ func (suite *HttpTestSuite) TestUploadPorts() { require.Equal(suite.T(), portsResponse, data) // count ports in storage - storedPortsTotal, err := suite.app.Queries.CountPorts.Handle(context.Background(), query.CountPorts{}) + storedPortsTotal, err := suite.app.Service.CountPorts(context.Background()) require.NoError(suite.T(), err) // compare number of ports in request and storage diff --git a/internal/transport/model.go b/internal/transport/model.go index 27e4df2..e3d20a5 100644 --- a/internal/transport/model.go +++ b/internal/transport/model.go @@ -6,8 +6,8 @@ type Port struct { Code string `json:"code"` City string `json:"city"` Country string `json:"country"` - Alias []any `json:"alias"` - Regions []any `json:"regions"` + Alias []string `json:"alias"` + Regions []string `json:"regions"` Coordinates []float64 `json:"coordinates"` Province string `json:"province"` Timezone string `json:"timezone"` diff --git a/internal/transport/utils.go b/internal/transport/utils.go index 00225ac..ee08e54 100644 --- a/internal/transport/utils.go +++ b/internal/transport/utils.go @@ -16,8 +16,8 @@ func portHttpToDomain(p *Port) (*domain.Port, error) { p.Code, p.City, p.Country, - append([]any(nil), p.Alias...), - append([]any(nil), p.Regions...), + append([]string(nil), p.Alias...), + append([]string(nil), p.Regions...), append([]float64(nil), p.Coordinates...), p.Province, p.Timezone, From 3259ddb497134122b35fb3b70b8aec096ada5ee5 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 00:43:13 +0400 Subject: [PATCH 05/13] chore: renamed interface --- internal/app/app.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index a473a0c..a3f8778 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,17 +6,17 @@ import ( "github.com/evt/port-api/internal/domain" ) -type Service interface { +type PortService interface { GetPort(ctx context.Context, id string) (*domain.Port, error) CountPorts(ctx context.Context) (int, error) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error } type Application struct { - Service Service + Service PortService } -func New(service Service) Application { +func New(service PortService) Application { return Application{ Service: service, } From 20fe694b9154cec7fed99a3e605f34593664220d Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 00:47:24 +0400 Subject: [PATCH 06/13] docs: added comments --- internal/app/app.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/app/app.go b/internal/app/app.go index a3f8778..328d381 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,16 +6,19 @@ import ( "github.com/evt/port-api/internal/domain" ) +// PortService is a port service type PortService interface { GetPort(ctx context.Context, id string) (*domain.Port, error) CountPorts(ctx context.Context) (int, error) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error } +// Application is a port application type Application struct { Service PortService } +// New creates a new port application with the given service func New(service PortService) Application { return Application{ Service: service, From 9d2663a7447bb3023cf24081bca5fbd50b385fa8 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 01:17:13 +0400 Subject: [PATCH 07/13] refactor: more renamings --- cmd/port-service/main.go | 6 +++--- internal/app/app.go | 4 ++-- internal/repository/inmem/port.go | 4 ++-- internal/repository/inmem/port_store_inmem.go | 6 +++--- internal/services/{portservice => }/service.go | 18 +++++++++--------- internal/transport/http_e2e_test.go | 6 +++--- 6 files changed, 22 insertions(+), 22 deletions(-) rename internal/services/{portservice => }/service.go (58%) diff --git a/cmd/port-service/main.go b/cmd/port-service/main.go index 35e7189..7647b57 100644 --- a/cmd/port-service/main.go +++ b/cmd/port-service/main.go @@ -12,7 +12,7 @@ import ( "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/config" "github.com/evt/port-api/internal/repository/inmem" - "github.com/evt/port-api/internal/services/portservice" + "github.com/evt/port-api/internal/services" "github.com/evt/port-api/internal/transport" "github.com/gorilla/mux" ) @@ -32,10 +32,10 @@ func run() error { portStoreRepo := inmem.NewPortStore() // create port service - portService := portservice.New(portStoreRepo) + portService := services.NewPortService(portStoreRepo) // create application - application := app.New(portService) + application := app.NewApplication(portService) // create http server with application injected httpServer := transport.NewHttpServer(application) diff --git a/internal/app/app.go b/internal/app/app.go index 328d381..a716494 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -18,8 +18,8 @@ type Application struct { Service PortService } -// New creates a new port application with the given service -func New(service PortService) Application { +// NewApplication creates a new port application with the given service +func NewApplication(service PortService) Application { return Application{ Service: service, } diff --git a/internal/repository/inmem/port.go b/internal/repository/inmem/port.go index 64e7fab..febc1be 100644 --- a/internal/repository/inmem/port.go +++ b/internal/repository/inmem/port.go @@ -41,7 +41,7 @@ func (p *Port) Copy() *Port { } } -func portToDomain(p *Port) (*domain.Port, error) { +func portStoreToDomain(p *Port) (*domain.Port, error) { return domain.NewPort( p.ID, p.Name, @@ -57,7 +57,7 @@ func portToDomain(p *Port) (*domain.Port, error) { ) } -func domainToPort(p *domain.Port) *Port { +func portDomainToStore(p *domain.Port) *Port { return &Port{ ID: p.ID(), Name: p.Name(), diff --git a/internal/repository/inmem/port_store_inmem.go b/internal/repository/inmem/port_store_inmem.go index 7b0219f..2c821b2 100644 --- a/internal/repository/inmem/port_store_inmem.go +++ b/internal/repository/inmem/port_store_inmem.go @@ -35,9 +35,9 @@ func (s *PortStore) GetPort(_ context.Context, id string) (*domain.Port, error) return nil, ErrNotFound } - domainPort, err := portToDomain(storePort) + domainPort, err := portStoreToDomain(storePort) if err != nil { - return nil, fmt.Errorf("portToDomain failed: %w", err) + return nil, fmt.Errorf("portStoreToDomain failed: %w", err) } return domainPort, nil @@ -55,7 +55,7 @@ func (s *PortStore) CreateOrUpdatePort(ctx context.Context, p *domain.Port) erro return ErrNil } - storePort := domainToPort(p) + storePort := portDomainToStore(p) s.mu.Lock() defer s.mu.Unlock() diff --git a/internal/services/portservice/service.go b/internal/services/service.go similarity index 58% rename from internal/services/portservice/service.go rename to internal/services/service.go index 1ff3536..92aae98 100644 --- a/internal/services/portservice/service.go +++ b/internal/services/service.go @@ -1,4 +1,4 @@ -package portservice +package services import ( "context" @@ -13,29 +13,29 @@ type PortRepository interface { GetPort(ctx context.Context, id string) (*domain.Port, error) } -// Service is a port service -type Service struct { +// PortService is a port service +type PortService struct { repo PortRepository } -// New creates a new port service -func New(repo PortRepository) Service { - return Service{ +// NewPortService creates a new port service +func NewPortService(repo PortRepository) PortService { + return PortService{ repo: repo, } } // GetPort returns a port by id -func (s Service) GetPort(ctx context.Context, id string) (*domain.Port, error) { +func (s PortService) GetPort(ctx context.Context, id string) (*domain.Port, error) { return s.repo.GetPort(ctx, id) } // CountPorts returns the number of ports -func (s Service) CountPorts(ctx context.Context) (int, error) { +func (s PortService) CountPorts(ctx context.Context) (int, error) { return s.repo.CountPorts(ctx) } // CreateOrUpdatePort creates or updates a port -func (s Service) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error { +func (s PortService) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error { return s.repo.CreateOrUpdatePort(ctx, port) } diff --git a/internal/transport/http_e2e_test.go b/internal/transport/http_e2e_test.go index 9a4ca3b..07ca372 100644 --- a/internal/transport/http_e2e_test.go +++ b/internal/transport/http_e2e_test.go @@ -12,7 +12,7 @@ import ( "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/repository/inmem" - "github.com/evt/port-api/internal/services/portservice" + "github.com/evt/port-api/internal/services" "github.com/evt/port-api/internal/transport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -31,10 +31,10 @@ func NewHttpTestSuite() *HttpTestSuite { portStoreRepo := inmem.NewPortStore() // create port service - portService := portservice.New(portStoreRepo) + portService := services.NewPortService(portStoreRepo) // create application - suite.app = app.New(portService) + suite.app = app.NewApplication(portService) // create http server with application injected suite.httpServer = transport.NewHttpServer(suite.app) From bc8cfee4963b2a967fcf4c2e9d6d66400766818e Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 01:37:33 +0400 Subject: [PATCH 08/13] chore: return domain errors from repository --- internal/domain/errors.go | 2 ++ internal/repository/inmem/port_store_inmem.go | 16 +++++----------- .../repository/inmem/port_store_inmem_test.go | 2 +- internal/transport/http.go | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/internal/domain/errors.go b/internal/domain/errors.go index 272a16a..507d592 100644 --- a/internal/domain/errors.go +++ b/internal/domain/errors.go @@ -4,4 +4,6 @@ import "errors" var ( ErrRequired = errors.New("required value") + ErrNotFound = errors.New("not found") + ErrNil = errors.New("nil data") ) diff --git a/internal/repository/inmem/port_store_inmem.go b/internal/repository/inmem/port_store_inmem.go index 2c821b2..a3bf7eb 100644 --- a/internal/repository/inmem/port_store_inmem.go +++ b/internal/repository/inmem/port_store_inmem.go @@ -2,7 +2,6 @@ package inmem import ( "context" - "errors" "fmt" "sync" "time" @@ -10,11 +9,6 @@ import ( "github.com/evt/port-api/internal/domain" ) -var ( - ErrNotFound = errors.New("not found") - ErrNil = errors.New("nil data") -) - type PortStore struct { data map[string]*Port mu sync.RWMutex @@ -32,7 +26,7 @@ func (s *PortStore) GetPort(_ context.Context, id string) (*domain.Port, error) storePort, exists := s.data[id] if !exists { - return nil, ErrNotFound + return nil, domain.ErrNotFound } domainPort, err := portStoreToDomain(storePort) @@ -52,7 +46,7 @@ func (s *PortStore) CountPorts(_ context.Context) (int, error) { func (s *PortStore) CreateOrUpdatePort(ctx context.Context, p *domain.Port) error { if p == nil { - return ErrNil + return domain.ErrNil } storePort := portDomainToStore(p) @@ -70,7 +64,7 @@ func (s *PortStore) CreateOrUpdatePort(ctx context.Context, p *domain.Port) erro func (s *PortStore) createPort(_ context.Context, storePort *Port) error { if storePort == nil { - return ErrNil + return domain.ErrNil } // set created and updated at @@ -84,13 +78,13 @@ func (s *PortStore) createPort(_ context.Context, storePort *Port) error { func (s *PortStore) updatePort(_ context.Context, p *Port) error { if p == nil { - return ErrNil + return domain.ErrNil } // check if port exists storePort, exists := s.data[p.ID] if !exists { - return ErrNotFound + return domain.ErrNotFound } storePortCopy := storePort.Copy() diff --git a/internal/repository/inmem/port_store_inmem_test.go b/internal/repository/inmem/port_store_inmem_test.go index b81836b..b0b30cb 100644 --- a/internal/repository/inmem/port_store_inmem_test.go +++ b/internal/repository/inmem/port_store_inmem_test.go @@ -55,7 +55,7 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { t.Run("nil port", func(t *testing.T) { t.Parallel() err := store.CreateOrUpdatePort(context.Background(), nil) - require.ErrorIs(t, err, inmem.ErrNil) + require.ErrorIs(t, err, domain.ErrNil) }) } diff --git a/internal/transport/http.go b/internal/transport/http.go index f8db9fa..fd15c76 100644 --- a/internal/transport/http.go +++ b/internal/transport/http.go @@ -7,7 +7,7 @@ import ( "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/common/server" - "github.com/evt/port-api/internal/repository/inmem" + "github.com/evt/port-api/internal/domain" ) // HttpServer is a HTTP server for ports @@ -37,7 +37,7 @@ func (h HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { func (h HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { port, err := h.app.Service.GetPort(r.Context(), r.URL.Query().Get("id")) if err != nil { - if errors.Is(err, inmem.ErrNotFound) { + if errors.Is(err, domain.ErrNotFound) { server.NotFound("port-not-found", err, w, r) return } From a7b0a9fbd2b3a794c8ee2d0c7b39e8e8931debdf Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 02:46:10 +0400 Subject: [PATCH 09/13] chore: fixed package name for a unit test --- internal/repository/inmem/port_store_inmem_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/repository/inmem/port_store_inmem_test.go b/internal/repository/inmem/port_store_inmem_test.go index b0b30cb..0caad3a 100644 --- a/internal/repository/inmem/port_store_inmem_test.go +++ b/internal/repository/inmem/port_store_inmem_test.go @@ -1,4 +1,4 @@ -package inmem_test +package inmem import ( "context" @@ -6,14 +6,13 @@ import ( "testing" "github.com/evt/port-api/internal/domain" - "github.com/evt/port-api/internal/repository/inmem" "github.com/google/uuid" "github.com/stretchr/testify/require" ) func TestPortStore_CreateOrUpdatePort(t *testing.T) { t.Parallel() - store := inmem.NewPortStore() + store := NewPortStore() t.Run("create port", func(t *testing.T) { t.Parallel() From 830e9e40fa7dca5dc6f124b4f914d420a2c970f9 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 02:48:29 +0400 Subject: [PATCH 10/13] chore: fixed package name for a unit test --- internal/domain/port_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/domain/port_test.go b/internal/domain/port_test.go index d296d91..0c7095c 100644 --- a/internal/domain/port_test.go +++ b/internal/domain/port_test.go @@ -1,9 +1,8 @@ -package domain_test +package domain import ( "testing" - "github.com/evt/port-api/internal/domain" "github.com/stretchr/testify/require" ) @@ -17,7 +16,7 @@ func TestNewPort(t *testing.T) { portCountry := "port country" t.Run("valid", func(t *testing.T) { - port, err := domain.NewPort(portID, portName, portCode, portCity, portCountry, + port, err := NewPort(portID, portName, portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.NoError(t, err) @@ -29,25 +28,25 @@ func TestNewPort(t *testing.T) { }) t.Run("missing port ID", func(t *testing.T) { - _, err := domain.NewPort("", portName, portCode, portCity, portCountry, + _, err := NewPort("", portName, portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port name", func(t *testing.T) { - _, err := domain.NewPort(portID, "", portCode, portCity, portCountry, + _, err := NewPort(portID, "", portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port city", func(t *testing.T) { - _, err := domain.NewPort(portID, portName, portCode, "", portCountry, + _, err := NewPort(portID, portName, portCode, "", portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port country", func(t *testing.T) { - _, err := domain.NewPort(portID, portName, portCode, portCity, "", + _, err := NewPort(portID, portName, portCode, portCity, "", nil, nil, nil, "", "", nil) require.Error(t, err) }) From bfcd23868333e44db4472951061db902092ca02b Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Thu, 15 Jun 2023 23:29:19 +0400 Subject: [PATCH 11/13] chore: removed application and injected port service into http server instead --- cmd/port-service/main.go | 6 +----- internal/app/app.go | 26 -------------------------- internal/transport/http.go | 21 ++++++++++++++------- internal/transport/http_e2e_test.go | 14 +++++--------- 4 files changed, 20 insertions(+), 47 deletions(-) delete mode 100644 internal/app/app.go diff --git a/cmd/port-service/main.go b/cmd/port-service/main.go index 7647b57..52a5353 100644 --- a/cmd/port-service/main.go +++ b/cmd/port-service/main.go @@ -9,7 +9,6 @@ import ( "syscall" "time" - "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/config" "github.com/evt/port-api/internal/repository/inmem" "github.com/evt/port-api/internal/services" @@ -34,11 +33,8 @@ func run() error { // create port service portService := services.NewPortService(portStoreRepo) - // create application - application := app.NewApplication(portService) - // create http server with application injected - httpServer := transport.NewHttpServer(application) + httpServer := transport.NewHttpServer(portService) // create http router router := mux.NewRouter() diff --git a/internal/app/app.go b/internal/app/app.go deleted file mode 100644 index a716494..0000000 --- a/internal/app/app.go +++ /dev/null @@ -1,26 +0,0 @@ -package app - -import ( - "context" - - "github.com/evt/port-api/internal/domain" -) - -// PortService is a port service -type PortService interface { - GetPort(ctx context.Context, id string) (*domain.Port, error) - CountPorts(ctx context.Context) (int, error) - CreateOrUpdatePort(ctx context.Context, port *domain.Port) error -} - -// Application is a port application -type Application struct { - Service PortService -} - -// NewApplication creates a new port application with the given service -func NewApplication(service PortService) Application { - return Application{ - Service: service, - } -} diff --git a/internal/transport/http.go b/internal/transport/http.go index fd15c76..f13a441 100644 --- a/internal/transport/http.go +++ b/internal/transport/http.go @@ -1,30 +1,37 @@ package transport import ( + "context" "errors" "log" "net/http" - "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/common/server" "github.com/evt/port-api/internal/domain" ) +// PortService is a port service +type PortService interface { + GetPort(ctx context.Context, id string) (*domain.Port, error) + CountPorts(ctx context.Context) (int, error) + CreateOrUpdatePort(ctx context.Context, port *domain.Port) error +} + // HttpServer is a HTTP server for ports type HttpServer struct { - app app.Application + service PortService } // NewHttpServer creates a new HTTP server for ports -func NewHttpServer(application app.Application) HttpServer { +func NewHttpServer(service PortService) HttpServer { return HttpServer{ - app: application, + service: service, } } // CountPorts returns total ports stored in DB func (h HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { - total, err := h.app.Service.CountPorts(r.Context()) + total, err := h.service.CountPorts(r.Context()) if err != nil { server.RespondWithError(err, w, r) return @@ -35,7 +42,7 @@ func (h HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { // GetPort returns a port by ID func (h HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { - port, err := h.app.Service.GetPort(r.Context(), r.URL.Query().Get("id")) + port, err := h.service.GetPort(r.Context(), r.URL.Query().Get("id")) if err != nil { if errors.Is(err, domain.ErrNotFound) { server.NotFound("port-not-found", err, w, r) @@ -99,7 +106,7 @@ func (h HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { server.BadRequest("port-to-domain", err, w, r) return } - if err := h.app.Service.CreateOrUpdatePort(r.Context(), p); err != nil { + if err := h.service.CreateOrUpdatePort(r.Context(), p); err != nil { server.RespondWithError(err, w, r) return } diff --git a/internal/transport/http_e2e_test.go b/internal/transport/http_e2e_test.go index 07ca372..df8703a 100644 --- a/internal/transport/http_e2e_test.go +++ b/internal/transport/http_e2e_test.go @@ -10,7 +10,6 @@ import ( "os" "testing" - "github.com/evt/port-api/internal/app" "github.com/evt/port-api/internal/repository/inmem" "github.com/evt/port-api/internal/services" "github.com/evt/port-api/internal/transport" @@ -20,8 +19,8 @@ import ( type HttpTestSuite struct { suite.Suite - app app.Application - httpServer transport.HttpServer + portService transport.PortService + httpServer transport.HttpServer } func NewHttpTestSuite() *HttpTestSuite { @@ -31,13 +30,10 @@ func NewHttpTestSuite() *HttpTestSuite { portStoreRepo := inmem.NewPortStore() // create port service - portService := services.NewPortService(portStoreRepo) - - // create application - suite.app = app.NewApplication(portService) + suite.portService = services.NewPortService(portStoreRepo) // create http server with application injected - suite.httpServer = transport.NewHttpServer(suite.app) + suite.httpServer = transport.NewHttpServer(suite.portService) return suite } @@ -74,7 +70,7 @@ func (suite *HttpTestSuite) TestUploadPorts() { require.Equal(suite.T(), portsResponse, data) // count ports in storage - storedPortsTotal, err := suite.app.Service.CountPorts(context.Background()) + storedPortsTotal, err := suite.portService.CountPorts(context.Background()) require.NoError(suite.T(), err) // compare number of ports in request and storage From b81bbac168a2907149d7fab65b8e47ae5a566610 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Fri, 16 Jun 2023 12:50:34 +0400 Subject: [PATCH 12/13] fix: handle potential nil panic --- internal/repository/inmem/port.go | 7 ++ .../repository/inmem/port_store_inmem_test.go | 9 +-- internal/repository/inmem/port_test.go | 79 +++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 internal/repository/inmem/port_test.go diff --git a/internal/repository/inmem/port.go b/internal/repository/inmem/port.go index febc1be..046bf3e 100644 --- a/internal/repository/inmem/port.go +++ b/internal/repository/inmem/port.go @@ -1,6 +1,7 @@ package inmem import ( + "errors" "time" "github.com/evt/port-api/internal/domain" @@ -24,6 +25,9 @@ type Port struct { } func (p *Port) Copy() *Port { + if p == nil { + return nil + } return &Port{ ID: p.ID, Name: p.Name, @@ -42,6 +46,9 @@ func (p *Port) Copy() *Port { } func portStoreToDomain(p *Port) (*domain.Port, error) { + if p == nil { + return nil, errors.New("store port is nil") + } return domain.NewPort( p.ID, p.Name, diff --git a/internal/repository/inmem/port_store_inmem_test.go b/internal/repository/inmem/port_store_inmem_test.go index 0caad3a..233eca0 100644 --- a/internal/repository/inmem/port_store_inmem_test.go +++ b/internal/repository/inmem/port_store_inmem_test.go @@ -2,7 +2,6 @@ package inmem import ( "context" - "fmt" "testing" "github.com/evt/port-api/internal/domain" @@ -17,7 +16,7 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { t.Run("create port", func(t *testing.T) { t.Parallel() - randomPort := newRandomPort(t) + randomPort := newRandomDomainPort(t) err := store.CreateOrUpdatePort(context.Background(), randomPort) require.NoError(t, err) @@ -30,7 +29,7 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { t.Run("update port", func(t *testing.T) { t.Parallel() - randomPort := newRandomPort(t) + randomPort := newRandomDomainPort(t) err := store.CreateOrUpdatePort(context.Background(), randomPort) require.NoError(t, err) @@ -48,7 +47,7 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { updatedPort, err := store.GetPort(context.Background(), randomPort.ID()) require.NoError(t, err) - fmt.Printf("updatedPort.Name = %s\n", updatedPort.Name()) + require.NotEqual(t, beforeUpdatePort.Name(), updatedPort.Name()) }) t.Run("nil port", func(t *testing.T) { @@ -58,7 +57,7 @@ func TestPortStore_CreateOrUpdatePort(t *testing.T) { }) } -func newRandomPort(t *testing.T) *domain.Port { +func newRandomDomainPort(t *testing.T) *domain.Port { t.Helper() randomID := uuid.New().String() port, err := domain.NewPort(randomID, randomID, randomID, randomID, randomID, diff --git a/internal/repository/inmem/port_test.go b/internal/repository/inmem/port_test.go new file mode 100644 index 0000000..3231919 --- /dev/null +++ b/internal/repository/inmem/port_test.go @@ -0,0 +1,79 @@ +package inmem + +import ( + "testing" + "time" + + "github.com/evt/port-api/internal/domain" + "github.com/stretchr/testify/require" +) + +func Test_portStoreToDomain(t *testing.T) { + type args struct { + p *Port + } + tests := []struct { + name string + args args + want *domain.Port + wantErr bool + }{ + { + name: "should return error when store port is nil", + args: args{ + p: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "should return domain port when store port is not nil", + args: args{ + p: newTestStorePort(t), + }, + want: newTestDomainPort(t), + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := portStoreToDomain(tt.args.p) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func newTestStorePort(t *testing.T) *Port { + t.Helper() + const testString = "test" + return &Port{ + ID: testString, + Name: testString, + Code: testString, + City: testString, + Country: testString, + Alias: []string{testString}, + Regions: []string{testString}, + Coordinates: []float64{1.0, 2.0}, + Province: testString, + Timezone: testString, + Unlocs: []string{testString}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +func newTestDomainPort(t *testing.T) *domain.Port { + t.Helper() + const testString = "test" + port, err := domain.NewPort(testString, testString, testString, testString, testString, + []string{testString}, []string{testString}, []float64{1.0, 2.0}, testString, testString, []string{testString}) + require.NoError(t, err) + return port +} From 38a344c37a2f30284650936ecf27914907168475 Mon Sep 17 00:00:00 2001 From: Eugene Toropov Date: Fri, 16 Jun 2023 12:59:41 +0400 Subject: [PATCH 13/13] chore: share test string between helpers --- internal/repository/inmem/port_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/repository/inmem/port_test.go b/internal/repository/inmem/port_test.go index 3231919..d4f8fec 100644 --- a/internal/repository/inmem/port_test.go +++ b/internal/repository/inmem/port_test.go @@ -49,9 +49,10 @@ func Test_portStoreToDomain(t *testing.T) { } } +const testString = "test" + func newTestStorePort(t *testing.T) *Port { t.Helper() - const testString = "test" return &Port{ ID: testString, Name: testString, @@ -71,7 +72,6 @@ func newTestStorePort(t *testing.T) *Port { func newTestDomainPort(t *testing.T) *domain.Port { t.Helper() - const testString = "test" port, err := domain.NewPort(testString, testString, testString, testString, testString, []string{testString}, []string{testString}, []float64{1.0, 2.0}, testString, testString, []string{testString}) require.NoError(t, err)