From 446ee6d64dcfe072a878f697feeb4959b18db226 Mon Sep 17 00:00:00 2001 From: thangchung Date: Tue, 6 Dec 2022 18:58:49 +0700 Subject: [PATCH 01/16] start re-factor code --- Makefile | 36 +++---- docker-compose-core.yaml | 34 ++++++ docker-compose-full.yaml | 137 ------------------------ docker-compose.yaml | 103 ++++++++++++++++++ docker/Dockerfile-web | 2 +- internal/counter/app/app.go | 4 +- internal/counter/grpc/counter_server.go | 16 +-- internal/counter/grpc/product_client.go | 10 +- pkg/rabbitmq/publisher/publisher.go | 2 +- 9 files changed, 167 insertions(+), 177 deletions(-) create mode 100755 docker-compose-core.yaml delete mode 100755 docker-compose-full.yaml diff --git a/Makefile b/Makefile index 1f36cfa..be8135d 100755 --- a/Makefile +++ b/Makefile @@ -1,16 +1,9 @@ include .env export -PRODUCT_BINARY_NAME=product.out -PROXY_BINARY_NAME=proxy.out - all: build test -build-product: - go build -tags migrate -o ./cmd/product/${PRODUCT_BINARY_NAME} github.com/thangchung/go-coffeeshop/cmd/product - -build-proxy: - go build -tags migrate -o ./cmd/proxy/${PROXY_BINARY_NAME} github.com/thangchung/go-coffeeshop/cmd/proxy +run: run-product run-counter run-barista run-kitchen run-proxy run-web run-product: cd cmd/product && go mod tidy && go mod download && \ @@ -42,30 +35,25 @@ run-web: CGO_ENABLED=0 go run github.com/thangchung/go-coffeeshop/cmd/web .PHONY: run-web -test: - go test -v main.go +compose-up: + docker-compose up --build +.PHONY: compose-up + +compose-down: + docker-compose down --remove-orphans -v +.PHONY: compose-down -package: +compose-build: docker-compose down --remove-orphans -v docker-compose build .PHONY: package -compose-up: ### Run docker-compose - docker-compose up --build -d postgres && docker-compose logs -f -.PHONY: compose-up - -compose-down: ### Down docker-compose - docker-compose down --remove-orphans -.PHONY: compose-down - -docker-rm-volume: ### remove docker volume - docker volume rm go-clean-template_pg-data -.PHONY: docker-rm-volume +test: + go test -v main.go linter-golangci: ### check by golangci linter golangci-lint run .PHONY: linter-golangci clean: - go clean - rm ${PRODUCT_BINARY_NAME} \ No newline at end of file + go clean \ No newline at end of file diff --git a/docker-compose-core.yaml b/docker-compose-core.yaml new file mode 100755 index 0000000..ee6e502 --- /dev/null +++ b/docker-compose-core.yaml @@ -0,0 +1,34 @@ +version: "3" + +services: + postgres: + image: postgres:14-alpine + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=P@ssw0rd + healthcheck: + test: ["CMD", "pg_isready"] + ports: + - "5432:5432" + networks: + - coffeeshop-network + + rabbitmq: + image: rabbitmq:3.11-management-alpine + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 30s + timeout: 30s + retries: 3 + ports: + - "5672:5672" + - "15672:15672" + networks: + - coffeeshop-network + +networks: + coffeeshop-network: diff --git a/docker-compose-full.yaml b/docker-compose-full.yaml deleted file mode 100755 index 8918471..0000000 --- a/docker-compose-full.yaml +++ /dev/null @@ -1,137 +0,0 @@ -version: "3" - -services: - postgres: - image: postgres:14-alpine - environment: - - POSTGRES_DB=postgres - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=P@ssw0rd - healthcheck: - test: ["CMD", "pg_isready"] - ports: - - "5432:5432" - networks: - - coffeeshop-network - - rabbitmq: - image: rabbitmq:3.11-management-alpine - environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest - healthcheck: - test: rabbitmq-diagnostics -q ping - interval: 30s - timeout: 30s - retries: 3 - ports: - - "5672:5672" - - "15672:15672" - networks: - - coffeeshop-network - - proxy: - build: - context: . - dockerfile: ./docker/Dockerfile-proxy - image: go-coffeeshop-proxy - environment: - APP_NAME: 'proxy-service in docker' - GRPC_PRODUCT_HOST: 'product' - GRPC_PRODUCT_PORT: 5001 - GRPC_COUNTER_HOST: 'counter' - GRPC_COUNTER_PORT: 5002 - ports: - - 5000:5000 - depends_on: - - product - - counter - networks: - - coffeeshop-network - - product: - build: - context: . - dockerfile: ./docker/Dockerfile-product - image: go-coffeeshop-product - environment: - APP_NAME: 'product-service in docker' - ports: - - 5001:5001 - networks: - - coffeeshop-network - - counter: - build: - context: . - dockerfile: ./docker/Dockerfile-counter - image: go-coffeeshop-counter - environment: - APP_NAME: 'counter-service in docker' - IN_DOCKER: "true" - PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ - PRODUCT_CLIENT_URL: product:5001 - ports: - - 5002:5002 - depends_on: - postgres: - condition: service_healthy - rabbitmq: - condition: service_healthy - networks: - - coffeeshop-network - - barista: - build: - context: . - dockerfile: ./docker/Dockerfile-barista - image: go-coffeeshop-barista - environment: - APP_NAME: 'barista-service in docker' - IN_DOCKER: "true" - PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ - depends_on: - postgres: - condition: service_healthy - rabbitmq: - condition: service_healthy - networks: - - coffeeshop-network - - kitchen: - build: - context: . - dockerfile: ./docker/Dockerfile-kitchen - image: go-coffeeshop-kitchen - environment: - APP_NAME: 'kitchen-service in docker' - IN_DOCKER: "true" - PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ - depends_on: - postgres: - condition: service_healthy - rabbitmq: - condition: service_healthy - networks: - - coffeeshop-network - - web: - build: - context: . - dockerfile: ./docker/Dockerfile-web - image: go-coffeeshop-web - environment: - REVERSE_PROXY_URL: http://localhost:5000 - WEB_PORT: 8888 - ports: - - 8080:8080 - depends_on: - - proxy - networks: - - coffeeshop-network - -networks: - coffeeshop-network: diff --git a/docker-compose.yaml b/docker-compose.yaml index ee6e502..12d70c9 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -29,6 +29,109 @@ services: - "15672:15672" networks: - coffeeshop-network + + proxy: + build: + context: . + dockerfile: ./docker/Dockerfile-proxy + image: go-coffeeshop-proxy + environment: + APP_NAME: 'proxy-service in docker' + GRPC_PRODUCT_HOST: 'product' + GRPC_PRODUCT_PORT: 5001 + GRPC_COUNTER_HOST: 'counter' + GRPC_COUNTER_PORT: 5002 + ports: + - 5000:5000 + depends_on: + - product + - counter + networks: + - coffeeshop-network + + product: + build: + context: . + dockerfile: ./docker/Dockerfile-product + image: go-coffeeshop-product + environment: + APP_NAME: 'product-service in docker' + ports: + - 5001:5001 + networks: + - coffeeshop-network + + counter: + build: + context: . + dockerfile: ./docker/Dockerfile-counter + image: go-coffeeshop-counter + environment: + APP_NAME: 'counter-service in docker' + IN_DOCKER: "true" + PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ + PRODUCT_CLIENT_URL: product:5001 + ports: + - 5002:5002 + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + networks: + - coffeeshop-network + + barista: + build: + context: . + dockerfile: ./docker/Dockerfile-barista + image: go-coffeeshop-barista + environment: + APP_NAME: 'barista-service in docker' + IN_DOCKER: "true" + PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + networks: + - coffeeshop-network + + kitchen: + build: + context: . + dockerfile: ./docker/Dockerfile-kitchen + image: go-coffeeshop-kitchen + environment: + APP_NAME: 'kitchen-service in docker' + IN_DOCKER: "true" + PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_healthy + networks: + - coffeeshop-network + + web: + build: + context: . + dockerfile: ./docker/Dockerfile-web + image: go-coffeeshop-web + environment: + REVERSE_PROXY_URL: http://localhost:5000 + WEB_PORT: 8888 + ports: + - 8888:8888 + depends_on: + - proxy + networks: + - coffeeshop-network networks: coffeeshop-network: diff --git a/docker/Dockerfile-web b/docker/Dockerfile-web index ddc3bde..38cd3db 100644 --- a/docker/Dockerfile-web +++ b/docker/Dockerfile-web @@ -15,7 +15,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ # Step 3: Final FROM scratch -EXPOSE 8080 +EXPOSE 8888 # GOPATH for scratch images is / COPY --from=builder /app/cmd/web/app/ / diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index ab27243..dd25c3b 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -112,7 +112,7 @@ func (a *App) Run() error { orderRepo := repo.NewOrderRepo(pg) // domain service - productDomainSvc := counterGrpc.NewProductDomainService(conn) + productDomainSvc := counterGrpc.NewGRPCProductClient(conn) // event handlers. a.handler = eventhandlers.NewBaristaOrderUpdatedEventHandler(orderRepo) @@ -154,7 +154,7 @@ func (a *App) Run() error { }() server := grpc.NewServer() - counterGrpc.NewCounterServiceServerGrpc( + counterGrpc.NewGRPCCounterServer( server, amqpConn, a.cfg, diff --git a/internal/counter/grpc/counter_server.go b/internal/counter/grpc/counter_server.go index 8c6d573..2ad633e 100644 --- a/internal/counter/grpc/counter_server.go +++ b/internal/counter/grpc/counter_server.go @@ -16,7 +16,7 @@ import ( "google.golang.org/grpc/reflection" ) -type CounterServiceServerImpl struct { +type counterGRPCServer struct { gen.UnimplementedCounterServiceServer logger *mylogger.Logger amqpConn *amqp.Connection @@ -27,7 +27,9 @@ type CounterServiceServerImpl struct { kitchenOrderPub publisher.Publisher } -func NewCounterServiceServerGrpc( +var _ gen.CounterServiceServer = (*counterGRPCServer)(nil) + +func NewGRPCCounterServer( grpcServer *grpc.Server, amqpConn *amqp.Connection, cfg *config.Config, @@ -37,7 +39,7 @@ func NewCounterServiceServerGrpc( baristaOrderPub publisher.Publisher, kitchenOrderPub publisher.Publisher, ) { - svc := CounterServiceServerImpl{ + svc := counterGRPCServer{ cfg: cfg, logger: log, amqpConn: amqpConn, @@ -52,7 +54,7 @@ func NewCounterServiceServerGrpc( reflection.Register(grpcServer) } -func (g *CounterServiceServerImpl) GetListOrderFulfillment( +func (g *counterGRPCServer) GetListOrderFulfillment( ctx context.Context, request *gen.GetListOrderFulfillmentRequest, ) (*gen.GetListOrderFulfillmentResponse, error) { @@ -62,7 +64,7 @@ func (g *CounterServiceServerImpl) GetListOrderFulfillment( entities, err := g.orderRepo.GetAll(ctx) if err != nil { - return nil, fmt.Errorf("CounterServiceServerImpl-GetListOrderFulfillment-g.orderRepo.GetAll: %w", err) + return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.orderRepo.GetAll: %w", err) } for _, entity := range entities { @@ -88,7 +90,7 @@ func (g *CounterServiceServerImpl) GetListOrderFulfillment( return &res, nil } -func (g *CounterServiceServerImpl) PlaceOrder( +func (g *counterGRPCServer) PlaceOrder( ctx context.Context, request *gen.PlaceOrderRequest, ) (*gen.PlaceOrderResponse, error) { @@ -97,7 +99,7 @@ func (g *CounterServiceServerImpl) PlaceOrder( // add order order, err := domain.CreateOrderFrom(ctx, request, g.productDomainSvc, g.baristaOrderPub, g.kitchenOrderPub) if err != nil { - return nil, errors.Wrap(err, "CounterServiceServerImpl-PlaceOrder-domain.CreateOrderFrom") + return nil, errors.Wrap(err, "counterGRPCServer-PlaceOrder-domain.CreateOrderFrom") } // save to database diff --git a/internal/counter/grpc/product_client.go b/internal/counter/grpc/product_client.go index b520b23..9137333 100644 --- a/internal/counter/grpc/product_client.go +++ b/internal/counter/grpc/product_client.go @@ -11,19 +11,19 @@ import ( "google.golang.org/grpc" ) -type productDomainService struct { +type productGRPCClient struct { conn *grpc.ClientConn } -var _ domain.ProductDomainService = (*productDomainService)(nil) +var _ domain.ProductDomainService = (*productGRPCClient)(nil) -func NewProductDomainService(conn *grpc.ClientConn) domain.ProductDomainService { - return &productDomainService{ +func NewGRPCProductClient(conn *grpc.ClientConn) domain.ProductDomainService { + return &productGRPCClient{ conn: conn, } } -func (p *productDomainService) GetItemsByType( +func (p *productGRPCClient) GetItemsByType( ctx context.Context, request *gen.PlaceOrderRequest, isBarista bool, diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index cacc1f7..77ae59f 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -79,7 +79,7 @@ func (p *Publisher) Publish(ctx context.Context, body []byte, contentType string MessageId: uuid.New().String(), Timestamp: time.Now(), Body: body, - Type: p.messageTypeName, //"barista.ordered", + Type: p.messageTypeName, }, ); err != nil { return errors.Wrap(err, "ch.Publish") From 72635f45137e4149726a5d041b4cf182b6627f5a Mon Sep 17 00:00:00 2001 From: thangchung Date: Sun, 18 Dec 2022 23:38:39 +0700 Subject: [PATCH 02/16] refactor product svc --- .devcontainer/devcontainer.json | 23 ++++-- cmd/barista/config.yml | 4 +- cmd/counter/config.yml | 4 +- cmd/kitchen/config.yml | 4 +- cmd/product/main.go | 19 +++-- go.mod | 2 +- internal/kitchen/app/app.go | 10 +-- internal/kitchen/domain/order.go | 24 ++++-- .../orders/eventhandlers/kitchen_ordered.go | 29 ++++++- internal/product/app/app.go | 24 +++--- internal/product/domain/interfaces.go | 6 +- internal/product/domain/models.go | 11 ++- internal/product/grpc/product_server.go | 66 ---------------- .../product/infras/grpc/product_server.go | 78 +++++++++++++++++++ .../repo/products_inmem.go} | 23 +++--- .../product/usecases/products/interfaces.go | 12 +++ internal/product/usecases/products/service.go | 41 ++++++++++ pkg/logger/logrus_adaptor.go | 77 ++++++++++++++++++ 18 files changed, 324 insertions(+), 133 deletions(-) delete mode 100644 internal/product/grpc/product_server.go create mode 100644 internal/product/infras/grpc/product_server.go rename internal/product/{features/products/repo/products_postgres.go => infras/repo/products_inmem.go} (73%) create mode 100644 internal/product/usecases/products/interfaces.go create mode 100644 internal/product/usecases/products/service.go create mode 100644 pkg/logger/logrus_adaptor.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4006086..3ce8ab5 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,7 @@ // new files getting created as root, but you may need to update the USER_UID // and USER_GID in .devcontainer/Dockerfile to match your user if not 1000. "-u", - "vscode", + "root", // Mount go mod cache "-v", "coffeeshop-gomodcache:/go/pkg", @@ -84,31 +84,38 @@ "maxStringLen": 2048 }, "apiVersion": 2 - } + }, + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/usr/bin/zsh" + } + }, + "terminal.integrated.defaultProfile.linux": "zsh" }, // Add the IDs of extensions you want installed when the container is created in the array below. "extensions": [ "golang.go", // optional: - "ms-azuretools.vscode-docker", - "ms-kubernetes-tools.vscode-kubernetes-tools", "mutantdino.resourcemonitor", "humao.rest-client", - "42crunch.vscode-openapi", "heaths.vscode-guid", "bungcip.better-toml", - "eamodio.gitlens", + "ms-vscode.makefile-tools", "casualjim.gotemplate", "davidanson.vscode-markdownlint", - "cweijan.vscode-mysql-client2", + "cweijan.vscode-database-client2", "bierner.markdown-mermaid", "hashicorp.hcl", "fredwangwang.vscode-hcl-format" + // "ms-azuretools.vscode-docker", + // "ms-kubernetes-tools.vscode-kubernetes-tools", + // "42crunch.vscode-openapi", + // "eamodio.gitlens", ], "postCreateCommand": "go version", "features": { "ghcr.io/devcontainers/features/go:1": { - "version": "1.19" + "version": "1.19.4" }, "ghcr.io/devcontainers/features/docker-in-docker:1": { "moby": false diff --git a/cmd/barista/config.yml b/cmd/barista/config.yml index cdc63fa..b3b0db2 100755 --- a/cmd/barista/config.yml +++ b/cmd/barista/config.yml @@ -8,10 +8,10 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@172.28.240.102:5432/postgres?sslmode=disable + url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable rabbit_mq: - url: amqp://guest:guest@172.28.240.102:5672/ + url: amqp://guest:guest@127.0.0.1:5672/ logger: log_level: 'debug' diff --git a/cmd/counter/config.yml b/cmd/counter/config.yml index 15e5a50..ae981bf 100755 --- a/cmd/counter/config.yml +++ b/cmd/counter/config.yml @@ -8,10 +8,10 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@172.28.240.102:5432/postgres?sslmode=disable + url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable rabbit_mq: - url: amqp://guest:guest@172.28.240.102:5672/ + url: amqp://guest:guest@127.0.0.1:5672/ product_client: url: 0.0.0.0:5001 diff --git a/cmd/kitchen/config.yml b/cmd/kitchen/config.yml index 4faf26b..d4f6856 100755 --- a/cmd/kitchen/config.yml +++ b/cmd/kitchen/config.yml @@ -8,10 +8,10 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@172.28.240.102:5432/postgres?sslmode=disable + url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable rabbit_mq: - url: amqp://guest:guest@172.28.240.102:5672/ + url: amqp://guest:guest@127.0.0.1:5672/ logger: log_level: 'debug' diff --git a/cmd/product/main.go b/cmd/product/main.go index ea31d2a..0323b6a 100755 --- a/cmd/product/main.go +++ b/cmd/product/main.go @@ -3,23 +3,30 @@ package main import ( "os" - "github.com/golang/glog" + "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/product/config" "github.com/thangchung/go-coffeeshop/internal/product/app" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) func main() { cfg, err := config.NewConfig() if err != nil { - glog.Fatal(err) + slog.Error("failed get config", err) } - mylog := mylogger.New(cfg.Level) + // set up logrus + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logger.ConvertLogLevel(cfg.Log.Level)) - a := app.New(mylog, cfg) + // integrate Logrus with the slog logger + slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) + + a := app.New(cfg) if err = a.Run(); err != nil { - glog.Fatal(err) + slog.Error("failed app run", err) os.Exit(1) } } diff --git a/go.mod b/go.mod index c5d70d8..d3ada43 100755 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/rabbitmq/amqp091-go v1.5.0 github.com/samber/lo v1.33.0 github.com/sirupsen/logrus v1.9.0 + golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a google.golang.org/grpc v1.50.1 google.golang.org/protobuf v1.28.1 @@ -45,7 +46,6 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/crypto v0.3.0 // indirect - golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 // indirect golang.org/x/net v0.2.0 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index bc49d83..bf818de 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -43,7 +43,7 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) - // PostgresDB + // postgresdb. pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { a.logger.Fatal("app - Run - postgres.NewPostgres: %s", err.Error()) @@ -54,7 +54,7 @@ func (a *App) Run() error { } defer pg.Close() - // rabbitmq + // rabbitmq. amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL, a.logger) if err != nil { cancel() @@ -79,13 +79,13 @@ func (a *App) Run() error { return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") } - // repository + // repository. orderRepo := repo.NewOrderRepo(pg) // event handlers. - a.handler = eventhandlers.NewKitchenOrderedEventHandler(orderRepo, counterOrderPub) + a.handler = eventhandlers.NewKitchenOrderedEventHandler(orderRepo, counterOrderPub, a.logger) - // consumers + // consumers. consumer, err := consumer.NewConsumer( amqpConn, a.logger, diff --git a/internal/kitchen/domain/order.go b/internal/kitchen/domain/order.go index da7b8ad..4432c41 100644 --- a/internal/kitchen/domain/order.go +++ b/internal/kitchen/domain/order.go @@ -4,15 +4,23 @@ import ( "time" "github.com/google/uuid" - "github.com/thangchung/go-coffeeshop/proto/gen" +) + +type ItemType int8 + +const ( + CakePop ItemType = iota + 6 + Croissant + Muffin + CroissantChocolate ) type KitchenOrder struct { - ID uuid.UUID `json:"id" db:"id"` - OrderID uuid.UUID `json:"orderId" db:"order_id"` - ItemName string `json:"itemName" db:"item_name"` - ItemType gen.ItemType `json:"itemType" db:"item_type"` - TimeUp time.Time `json:"timeUp" db:"time_up"` - Created time.Time `json:"created" db:"created"` - Updated time.Time `json:"updated" db:"updated"` + ID uuid.UUID `json:"id" db:"id"` + OrderID uuid.UUID `json:"orderId" db:"order_id"` + ItemName string `json:"itemName" db:"item_name"` + ItemType ItemType `json:"itemType" db:"item_type"` + TimeUp time.Time `json:"timeUp" db:"time_up"` + Created time.Time `json:"created" db:"created"` + Updated time.Time `json:"updated" db:"updated"` } diff --git a/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go b/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go index 8b362fc..a104eb7 100644 --- a/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go @@ -3,12 +3,12 @@ package eventhandlers import ( "context" "encoding/json" - "fmt" "time" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" "github.com/thangchung/go-coffeeshop/pkg/event" + mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "github.com/thangchung/go-coffeeshop/proto/gen" ) @@ -22,17 +22,23 @@ var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) type kitchenOrderedEventHandler struct { repo domain.OrderRepo counterPub *publisher.Publisher + logger *mylogger.Logger } -func NewKitchenOrderedEventHandler(repo domain.OrderRepo, counterPub *publisher.Publisher) KitchenOrderedEventHandler { +func NewKitchenOrderedEventHandler( + repo domain.OrderRepo, + counterPub *publisher.Publisher, + logger *mylogger.Logger, +) KitchenOrderedEventHandler { return &kitchenOrderedEventHandler{ repo: repo, counterPub: counterPub, + logger: logger, } } func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.KitchenOrdered) error { - fmt.Println(e) + h.logger.Info("kitchenOrderedEventHandler-Handle: %v", e) timeIn := time.Now() @@ -44,7 +50,7 @@ func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.Kitche err := h.repo.Create(ctx, &domain.KitchenOrder{ ID: e.ItemLineID, OrderID: e.OrderID, - ItemType: e.ItemType, + ItemType: convertToItemType(e.ItemType), ItemName: e.ItemType.String(), TimeUp: timeUp, Created: time.Now(), @@ -90,3 +96,18 @@ func calculateDelay(itemType gen.ItemType) time.Duration { return 3 * time.Second } } + +func convertToItemType(dto gen.ItemType) domain.ItemType { + switch dto { + case gen.ItemType_CROISSANT: + return domain.Croissant + case gen.ItemType_CROISSANT_CHOCOLATE: + return domain.CroissantChocolate + case gen.ItemType_CAKEPOP: + return domain.CakePop + case gen.ItemType_MUFFIN: + return domain.Muffin + default: + return domain.Croissant + } +} diff --git a/internal/product/app/app.go b/internal/product/app/app.go index cbe27c0..496e0be 100644 --- a/internal/product/app/app.go +++ b/internal/product/app/app.go @@ -6,22 +6,21 @@ import ( "net" "github.com/thangchung/go-coffeeshop/cmd/product/config" - productRepo "github.com/thangchung/go-coffeeshop/internal/product/features/products/repo" - productGrpc "github.com/thangchung/go-coffeeshop/internal/product/grpc" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" + productGrpc "github.com/thangchung/go-coffeeshop/internal/product/infras/grpc" + productRepo "github.com/thangchung/go-coffeeshop/internal/product/infras/repo" + productUC "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" + "golang.org/x/exp/slog" "google.golang.org/grpc" ) type App struct { - logger *mylogger.Logger cfg *config.Config network string address string } -func New(log *mylogger.Logger, cfg *config.Config) *App { +func New(cfg *config.Config) *App { return &App{ - logger: log, cfg: cfg, network: "tcp", address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), @@ -29,36 +28,39 @@ func New(log *mylogger.Logger, cfg *config.Config) *App { } func (a *App) Run() error { - a.logger.Info("Init %s %s\n", a.cfg.Name, a.cfg.Version) + slog.Info("init", "name", a.cfg.Name, "version", a.cfg.Version) ctx, _ := context.WithCancel(context.Background()) // Repository repo := productRepo.NewOrderRepo() + // UC + uc := productUC.NewService(repo) + // gRPC Server l, err := net.Listen(a.network, a.address) if err != nil { - a.logger.Fatal("app-Run-net.Listener: %s", err.Error()) + slog.Error("failed app-Run-net.Listener", err) return err } defer func() { if err := l.Close(); err != nil { - a.logger.Error("Failed to close %s %s: %v", a.network, a.address, err) + slog.Error("failed to close", err, "network", a.network, "address", a.address) } }() s := grpc.NewServer() - productGrpc.NewProductServiceServerGrpc(s, a.logger, repo) + productGrpc.NewProductGRPCServer(s, uc) go func() { defer s.GracefulStop() <-ctx.Done() }() - a.logger.Info("Start server at " + a.address + " ...") + slog.Info("start server", "address", a.address) return s.Serve(l) } diff --git a/internal/product/domain/interfaces.go b/internal/product/domain/interfaces.go index 8a4438c..98f641c 100644 --- a/internal/product/domain/interfaces.go +++ b/internal/product/domain/interfaces.go @@ -2,13 +2,11 @@ package domain import ( "context" - - "github.com/thangchung/go-coffeeshop/proto/gen" ) type ( ProductRepo interface { - GetAll(context.Context) ([]*gen.ItemTypeDto, error) - GetByTypes(context.Context, []string) ([]*gen.ItemDto, error) + GetAll(context.Context) ([]*ItemTypeDto, error) + GetByTypes(context.Context, []string) ([]*ItemDto, error) } ) diff --git a/internal/product/domain/models.go b/internal/product/domain/models.go index b4e441b..1e6ed64 100644 --- a/internal/product/domain/models.go +++ b/internal/product/domain/models.go @@ -1,6 +1,13 @@ package domain type ItemTypeDto struct { - Name string `json:"name"` - Type int `json:"type"` + Name string `json:"name"` + Type int `json:"type"` + Price float64 `json:"price"` + Image string `json:"image"` +} + +type ItemDto struct { + Price float64 `json:"price"` + Type int `json:"type"` } diff --git a/internal/product/grpc/product_server.go b/internal/product/grpc/product_server.go deleted file mode 100644 index 9576d78..0000000 --- a/internal/product/grpc/product_server.go +++ /dev/null @@ -1,66 +0,0 @@ -package grpc - -import ( - "context" - "strings" - - "github.com/pkg/errors" - "github.com/thangchung/go-coffeeshop/internal/product/domain" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" - "github.com/thangchung/go-coffeeshop/proto/gen" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type ProductServiceServerImpl struct { - gen.UnimplementedProductServiceServer - repo domain.ProductRepo - logger *mylogger.Logger -} - -func NewProductServiceServerGrpc( - grpcServer *grpc.Server, - log *mylogger.Logger, - repo domain.ProductRepo, -) { - svc := ProductServiceServerImpl{ - logger: log, - repo: repo, - } - - gen.RegisterProductServiceServer(grpcServer, &svc) - - reflection.Register(grpcServer) -} - -func (g *ProductServiceServerImpl) GetItemTypes(ctx context.Context, request *gen.GetItemTypesRequest) (*gen.GetItemTypesResponse, error) { - g.logger.Info("GET: GetItemTypes") - - res := gen.GetItemTypesResponse{} - - results, err := g.repo.GetAll(ctx) - if err != nil { - return nil, errors.Wrap(err, "ProductServiceServerImpl-g.repo.GetAll") - } - - res.ItemTypes = append(res.ItemTypes, results...) - - return &res, nil -} - -func (g *ProductServiceServerImpl) GetItemsByType(ctx context.Context, request *gen.GetItemsByTypeRequest) (*gen.GetItemsByTypeResponse, error) { - g.logger.Info("GET: GetItemsByType with %s", request.ItemTypes) - - res := gen.GetItemsByTypeResponse{} - - itemTypes := strings.Split(request.ItemTypes, ",") - - results, err := g.repo.GetByTypes(ctx, itemTypes) - if err != nil { - return nil, errors.Wrap(err, "ProductServiceServerImpl-g.repo.GetItemsByType") - } - - res.Items = append(res.Items, results...) - - return &res, nil -} diff --git a/internal/product/infras/grpc/product_server.go b/internal/product/infras/grpc/product_server.go new file mode 100644 index 0000000..b202f04 --- /dev/null +++ b/internal/product/infras/grpc/product_server.go @@ -0,0 +1,78 @@ +package grpc + +import ( + "context" + + "github.com/pkg/errors" + "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" + "github.com/thangchung/go-coffeeshop/proto/gen" + "golang.org/x/exp/slog" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +type productGRPCServer struct { + gen.UnimplementedProductServiceServer + uc products.UseCase +} + +func NewProductGRPCServer( + grpcServer *grpc.Server, + uc products.UseCase, +) { + svc := productGRPCServer{ + uc: uc, + } + + gen.RegisterProductServiceServer(grpcServer, &svc) + + reflection.Register(grpcServer) +} + +func (g *productGRPCServer) GetItemTypes( + ctx context.Context, + request *gen.GetItemTypesRequest, +) (*gen.GetItemTypesResponse, error) { + slog.Info("GET: GetItemTypes") + + res := gen.GetItemTypesResponse{} + + results, err := g.uc.GetItemTypes(ctx) + if err != nil { + return nil, errors.Wrap(err, "productGRPCServer-GetItemTypes") + } + + for _, item := range results { + res.ItemTypes = append(res.ItemTypes, &gen.ItemTypeDto{ + Name: item.Name, + Type: int32(item.Type), + Price: item.Price, + Image: item.Image, + }) + } + + return &res, nil +} + +func (g *productGRPCServer) GetItemsByType( + ctx context.Context, + request *gen.GetItemsByTypeRequest, +) (*gen.GetItemsByTypeResponse, error) { + slog.Info("GET: GetItemsByType", "itemTypes", request.ItemTypes) + + res := gen.GetItemsByTypeResponse{} + + results, err := g.uc.GetItemsByType(ctx, request.ItemTypes) + if err != nil { + return nil, errors.Wrap(err, "productGRPCServer-GetItemsByType") + } + + for _, item := range results { + res.Items = append(res.Items, &gen.ItemDto{ + Type: int32(item.Type), + Price: item.Price, + }) + } + + return &res, nil +} diff --git a/internal/product/features/products/repo/products_postgres.go b/internal/product/infras/repo/products_inmem.go similarity index 73% rename from internal/product/features/products/repo/products_postgres.go rename to internal/product/infras/repo/products_inmem.go index b0affe5..4a300f8 100644 --- a/internal/product/features/products/repo/products_postgres.go +++ b/internal/product/infras/repo/products_inmem.go @@ -4,18 +4,17 @@ import ( "context" "github.com/thangchung/go-coffeeshop/internal/product/domain" - "github.com/thangchung/go-coffeeshop/proto/gen" ) -var _ domain.ProductRepo = (*productRepo)(nil) +var _ domain.ProductRepo = (*productInMemRepo)(nil) -type productRepo struct { - itemTypes map[string]gen.ItemTypeDto +type productInMemRepo struct { + itemTypes map[string]*domain.ItemTypeDto } func NewOrderRepo() domain.ProductRepo { - return &productRepo{ - itemTypes: map[string]gen.ItemTypeDto{ + return &productInMemRepo{ + itemTypes: map[string]*domain.ItemTypeDto{ "CAPPUCCINO": { Name: "CAPPUCCINO", Type: 0, @@ -80,11 +79,11 @@ func NewOrderRepo() domain.ProductRepo { } } -func (p *productRepo) GetAll(ctx context.Context) ([]*gen.ItemTypeDto, error) { - results := make([]*gen.ItemTypeDto, 0) +func (p *productInMemRepo) GetAll(ctx context.Context) ([]*domain.ItemTypeDto, error) { + results := make([]*domain.ItemTypeDto, 0) for _, v := range p.itemTypes { - results = append(results, &gen.ItemTypeDto{ + results = append(results, &domain.ItemTypeDto{ Name: v.Name, Type: v.Type, Price: v.Price, @@ -95,13 +94,13 @@ func (p *productRepo) GetAll(ctx context.Context) ([]*gen.ItemTypeDto, error) { return results, nil } -func (p *productRepo) GetByTypes(ctx context.Context, itemTypes []string) ([]*gen.ItemDto, error) { - results := make([]*gen.ItemDto, 0) +func (p *productInMemRepo) GetByTypes(ctx context.Context, itemTypes []string) ([]*domain.ItemDto, error) { + results := make([]*domain.ItemDto, 0) for _, itemType := range itemTypes { item := p.itemTypes[itemType] if item.Name != "" { - results = append(results, &gen.ItemDto{ + results = append(results, &domain.ItemDto{ Price: item.Price, Type: item.Type, }) diff --git a/internal/product/usecases/products/interfaces.go b/internal/product/usecases/products/interfaces.go new file mode 100644 index 0000000..f71005c --- /dev/null +++ b/internal/product/usecases/products/interfaces.go @@ -0,0 +1,12 @@ +package products + +import ( + "context" + + "github.com/thangchung/go-coffeeshop/internal/product/domain" +) + +type UseCase interface { + GetItemTypes(context.Context) ([]*domain.ItemTypeDto, error) + GetItemsByType(context.Context, string) ([]*domain.ItemDto, error) +} diff --git a/internal/product/usecases/products/service.go b/internal/product/usecases/products/service.go new file mode 100644 index 0000000..0c51ee5 --- /dev/null +++ b/internal/product/usecases/products/service.go @@ -0,0 +1,41 @@ +package products + +import ( + "context" + "strings" + + "github.com/pkg/errors" + "github.com/thangchung/go-coffeeshop/internal/product/domain" +) + +type service struct { + repo domain.ProductRepo +} + +var _ UseCase = (*service)(nil) + +func NewService(repo domain.ProductRepo) UseCase { + return &service{ + repo: repo, + } +} + +func (s *service) GetItemTypes(ctx context.Context) ([]*domain.ItemTypeDto, error) { + results, err := s.repo.GetAll(ctx) + if err != nil { + return nil, errors.Wrap(err, "service.GetItemTypes") + } + + return results, nil +} + +func (s *service) GetItemsByType(ctx context.Context, itemTypes string) ([]*domain.ItemDto, error) { + types := strings.Split(itemTypes, ",") + + results, err := s.repo.GetByTypes(ctx, types) + if err != nil { + return nil, errors.Wrap(err, "service.GetItemsByType") + } + + return results, nil +} diff --git a/pkg/logger/logrus_adaptor.go b/pkg/logger/logrus_adaptor.go new file mode 100644 index 0000000..3f74166 --- /dev/null +++ b/pkg/logger/logrus_adaptor.go @@ -0,0 +1,77 @@ +package logger + +// ref: https://josephwoodward.co.uk/2022/11/slog-structured-logging-proposal + +import ( + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/exp/slog" +) + +type LogrusHandler struct { + logger *logrus.Logger +} + +func NewLogrusHandler(logger *logrus.Logger) *LogrusHandler { + return &LogrusHandler{ + logger: logger, + } +} + +func ConvertLogLevel(level string) logrus.Level { + var l logrus.Level + + switch strings.ToLower(level) { + case "error": + l = logrus.ErrorLevel + case "warm": + l = logrus.WarnLevel + case "info": + l = logrus.InfoLevel + case "debug": + l = logrus.DebugLevel + default: + l = logrus.InfoLevel + } + + return l +} + +func (h *LogrusHandler) Enabled(_ slog.Level) bool { + // support all logging levels + return true +} + +func (h *LogrusHandler) Handle(rec slog.Record) error { + fields := make(map[string]interface{}, rec.NumAttrs()) + + rec.Attrs(func(a slog.Attr) { + fields[a.Key] = a.Value.Any() + }) + + entry := h.logger.WithFields(fields) + + switch rec.Level { + case slog.DebugLevel: + entry.Debug(rec.Message) + case slog.InfoLevel.Level(): + entry.Info(rec.Message) + case slog.WarnLevel: + entry.Warn(rec.Message) + case slog.ErrorLevel: + entry.Error(rec.Message) + } + + return nil +} + +func (h *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + // not implemented for brevity + return h +} + +func (h *LogrusHandler) WithGroup(name string) slog.Handler { + // not implemented for brevity + return h +} From c591fbedeabeba8e4b669a5dfcafe3e185be1194 Mon Sep 17 00:00:00 2001 From: thangchung Date: Mon, 19 Dec 2022 17:11:50 +0700 Subject: [PATCH 03/16] refactor counter --- go.mod | 3 + go.sum | 6 +- internal/barista/app/app.go | 6 +- internal/barista/domain/order.go | 14 +- .../orders/eventhandlers/barista_ordered.go | 16 +- .../orders/repo/orders_postgres.go | 0 internal/counter/app/app.go | 32 +- internal/counter/domain/errors.go | 7 + internal/counter/domain/interfaces.go | 12 +- internal/counter/domain/line_item.go | 18 +- internal/counter/domain/model/results.go | 8 - internal/counter/domain/models.go | 45 +++ internal/counter/domain/order.go | 147 +++------ internal/counter/domain/results.go | 6 + .../eventhandlers/barista_order_updated.go | 63 ---- internal/counter/grpc/counter_server.go | 134 -------- internal/counter/grpc/product_client.go | 45 --- .../counter/infras/grpc/counter_server.go | 118 +++++++ .../counter/infras/grpc/product_client.go | 60 ++++ .../orders => infras}/repo/orders_postgres.go | 44 ++- .../counter/usecases/orders/event_handlers.go | 39 +++ .../counter/usecases/orders/interfaces.go | 19 ++ internal/counter/usecases/orders/service.go | 82 +++++ internal/kitchen/app/app.go | 6 +- internal/kitchen/domain/order.go | 29 +- .../orders/eventhandlers/kitchen_ordered.go | 31 +- .../orders/repo/orders_postgres.go | 0 internal/pkg/event/events.go | 62 ++++ internal/pkg/event/publisher.go | 38 +++ internal/pkg/shared_kernel/aggregate_root.go | 27 ++ internal/pkg/shared_kernel/entity.go | 15 + internal/pkg/shared_kernel/entity_test.go | 22 ++ internal/pkg/shared_kernel/enums.go | 67 ++++ pkg/event/events.go | 40 --- proto/counter.proto | 18 +- proto/gen/counter.pb.go | 298 ++++++++---------- third_party/OpenAPI/counter.swagger.json | 77 ++--- 37 files changed, 910 insertions(+), 744 deletions(-) rename internal/barista/{features => usecases}/orders/eventhandlers/barista_ordered.go (84%) rename internal/barista/{features => usecases}/orders/repo/orders_postgres.go (100%) create mode 100644 internal/counter/domain/errors.go delete mode 100644 internal/counter/domain/model/results.go create mode 100644 internal/counter/domain/models.go create mode 100644 internal/counter/domain/results.go delete mode 100644 internal/counter/features/orders/eventhandlers/barista_order_updated.go delete mode 100644 internal/counter/grpc/counter_server.go delete mode 100644 internal/counter/grpc/product_client.go create mode 100644 internal/counter/infras/grpc/counter_server.go create mode 100644 internal/counter/infras/grpc/product_client.go rename internal/counter/{features/orders => infras}/repo/orders_postgres.go (82%) create mode 100644 internal/counter/usecases/orders/event_handlers.go create mode 100644 internal/counter/usecases/orders/interfaces.go create mode 100644 internal/counter/usecases/orders/service.go rename internal/kitchen/{features => usecases}/orders/eventhandlers/kitchen_ordered.go (75%) rename internal/kitchen/{features => usecases}/orders/repo/orders_postgres.go (100%) create mode 100644 internal/pkg/event/events.go create mode 100644 internal/pkg/event/publisher.go create mode 100644 internal/pkg/shared_kernel/aggregate_root.go create mode 100644 internal/pkg/shared_kernel/entity.go create mode 100644 internal/pkg/shared_kernel/entity_test.go create mode 100644 internal/pkg/shared_kernel/enums.go delete mode 100644 pkg/event/events.go diff --git a/go.mod b/go.mod index d3ada43..6c2ec51 100755 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/rabbitmq/amqp091-go v1.5.0 github.com/samber/lo v1.33.0 github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.1 golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a google.golang.org/grpc v1.50.1 @@ -24,6 +25,7 @@ require ( require ( github.com/BurntSushi/toml v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -42,6 +44,7 @@ require ( github.com/lib/pq v1.10.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/atomic v1.10.0 // indirect diff --git a/go.sum b/go.sum index 55bd371..2148620 100755 --- a/go.sum +++ b/go.sum @@ -1076,8 +1076,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1086,8 +1087,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 1fc79e7..5eb14a5 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -11,9 +11,9 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" - "github.com/thangchung/go-coffeeshop/internal/barista/features/orders/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/barista/features/orders/repo" - "github.com/thangchung/go-coffeeshop/pkg/event" + "github.com/thangchung/go-coffeeshop/internal/barista/usecases/orders/eventhandlers" + "github.com/thangchung/go-coffeeshop/internal/barista/usecases/orders/repo" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" diff --git a/internal/barista/domain/order.go b/internal/barista/domain/order.go index 809d009..8a417b2 100644 --- a/internal/barista/domain/order.go +++ b/internal/barista/domain/order.go @@ -4,14 +4,14 @@ import ( "time" "github.com/google/uuid" - "github.com/thangchung/go-coffeeshop/proto/gen" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) type BaristaOrder struct { - ID uuid.UUID `json:"id" db:"id"` - ItemName string `json:"itemName" db:"item_name"` - ItemType gen.ItemType `json:"itemType" db:"item_type"` - TimeUp time.Time `json:"timeUp" db:"time_up"` - Created time.Time `json:"created" db:"created"` - Updated time.Time `json:"updated" db:"updated"` + ID uuid.UUID `json:"id" db:"id"` + ItemName string `json:"itemName" db:"item_name"` + ItemType shared.ItemType `json:"itemType" db:"item_type"` + TimeUp time.Time `json:"timeUp" db:"time_up"` + Created time.Time `json:"created" db:"created"` + Updated time.Time `json:"updated" db:"updated"` } diff --git a/internal/barista/features/orders/eventhandlers/barista_ordered.go b/internal/barista/usecases/orders/eventhandlers/barista_ordered.go similarity index 84% rename from internal/barista/features/orders/eventhandlers/barista_ordered.go rename to internal/barista/usecases/orders/eventhandlers/barista_ordered.go index b2074cb..10e21d9 100644 --- a/internal/barista/features/orders/eventhandlers/barista_ordered.go +++ b/internal/barista/usecases/orders/eventhandlers/barista_ordered.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/barista/domain" - "github.com/thangchung/go-coffeeshop/pkg/event" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" - "github.com/thangchung/go-coffeeshop/proto/gen" ) type BaristaOrderedEventHandler interface { @@ -75,17 +75,17 @@ func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e *event.Barist return nil } -func calculateDelay(itemType gen.ItemType) time.Duration { +func calculateDelay(itemType shared.ItemType) time.Duration { switch itemType { - case gen.ItemType_COFFEE_BLACK: + case shared.ItemTypeCoffeeBlack: return 5 * time.Second - case gen.ItemType_COFFEE_WITH_ROOM: + case shared.ItemTypeCoffeeWithRoom: return 5 * time.Second - case gen.ItemType_ESPRESSO: + case shared.ItemTypeEspresso: return 7 * time.Second - case gen.ItemType_ESPRESSO_DOUBLE: + case shared.ItemTypeEspressoDouble: return 7 * time.Second - case gen.ItemType_CAPPUCCINO: + case shared.ItemTypeCappuccino: return 10 * time.Second default: return 3 * time.Second diff --git a/internal/barista/features/orders/repo/orders_postgres.go b/internal/barista/usecases/orders/repo/orders_postgres.go similarity index 100% rename from internal/barista/features/orders/repo/orders_postgres.go rename to internal/barista/usecases/orders/repo/orders_postgres.go diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index dd25c3b..9c67937 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -9,11 +9,10 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/internal/counter/features/orders/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/counter/features/orders/repo" - counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/grpc" - "github.com/thangchung/go-coffeeshop/pkg/event" + counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" + "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" @@ -28,7 +27,7 @@ type App struct { cfg *config.Config network string address string - handler domain.BaristaOrderUpdatedEventHandler + handler orders.BaristaOrderUpdatedEventHandler } func New(log *mylogger.Logger, cfg *config.Config) *App { @@ -114,8 +113,20 @@ func (a *App) Run() error { // domain service productDomainSvc := counterGrpc.NewGRPCProductClient(conn) + // event publishers. + baristaEventPub := event.NewEventPublisher(*baristaOrderPub) + kitchenEventPub := event.NewEventPublisher(*kitchenOrderPub) + + // usecases. + uc := orders.NewUseCase( + orderRepo, + productDomainSvc, + baristaEventPub, + kitchenEventPub, + ) + // event handlers. - a.handler = eventhandlers.NewBaristaOrderUpdatedEventHandler(orderRepo) + a.handler = orders.NewBaristaOrderUpdatedEventHandler(orderRepo) // consumers consumer, err := rabConsumer.NewConsumer( @@ -156,13 +167,8 @@ func (a *App) Run() error { server := grpc.NewServer() counterGrpc.NewGRPCCounterServer( server, - amqpConn, a.cfg, - a.logger, - orderRepo, - productDomainSvc, - *baristaOrderPub, - *kitchenOrderPub, + uc, ) go func() { diff --git a/internal/counter/domain/errors.go b/internal/counter/domain/errors.go new file mode 100644 index 0000000..26814ed --- /dev/null +++ b/internal/counter/domain/errors.go @@ -0,0 +1,7 @@ +package domain + +import "github.com/pkg/errors" + +var ( + ErrItemNotFound = errors.New("item not found") +) diff --git a/internal/counter/domain/interfaces.go b/internal/counter/domain/interfaces.go index 984b992..01a924a 100644 --- a/internal/counter/domain/interfaces.go +++ b/internal/counter/domain/interfaces.go @@ -4,23 +4,17 @@ import ( "context" "github.com/google/uuid" - "github.com/thangchung/go-coffeeshop/pkg/event" - gen "github.com/thangchung/go-coffeeshop/proto/gen" ) type ( OrderRepo interface { GetAll(context.Context) ([]*Order, error) GetByID(context.Context, uuid.UUID) (*Order, error) - Create(context.Context, *gen.OrderDto) error - Update(context.Context, *gen.OrderDto) (*gen.OrderDto, error) + Create(context.Context, *Order) error + Update(context.Context, *Order) (*Order, error) } ProductDomainService interface { - GetItemsByType(context.Context, *gen.PlaceOrderRequest, bool) (*gen.GetItemsByTypeResponse, error) - } - - BaristaOrderUpdatedEventHandler interface { - Handle(context.Context, *event.BaristaOrderUpdated) error + GetItemsByType(context.Context, *PlaceOrderModel, bool) ([]*ItemModel, error) } ) diff --git a/internal/counter/domain/line_item.go b/internal/counter/domain/line_item.go index 7fdbd93..3da7418 100644 --- a/internal/counter/domain/line_item.go +++ b/internal/counter/domain/line_item.go @@ -2,20 +2,20 @@ package domain import ( "github.com/google/uuid" - gen "github.com/thangchung/go-coffeeshop/proto/gen" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) type LineItem struct { - ID uuid.UUID `json:"id" db:"id"` - ItemType gen.ItemType `json:"item_type" db:"item_type"` - Name string `json:"name" db:"name"` - Price float32 `json:"price" db:"price"` - ItemStatus gen.Status `json:"item_status" db:"item_status"` - IsBaristaOrder bool `json:"is_barista_order" db:"is_barista_order"` - OrderID uuid.UUID `json:"order_id" db:"order_id"` // shadow field + ID uuid.UUID `json:"id" db:"id"` + ItemType shared.ItemType `json:"item_type" db:"item_type"` + Name string `json:"name" db:"name"` + Price float32 `json:"price" db:"price"` + ItemStatus shared.Status `json:"item_status" db:"item_status"` + IsBaristaOrder bool `json:"is_barista_order" db:"is_barista_order"` + OrderID uuid.UUID `json:"order_id" db:"order_id"` // shadow field } -func NewLineItem(itemType gen.ItemType, name string, price float32, itemStatus gen.Status, isBarista bool) *LineItem { +func NewLineItem(itemType shared.ItemType, name string, price float32, itemStatus shared.Status, isBarista bool) *LineItem { return &LineItem{ ID: uuid.New(), ItemType: itemType, diff --git a/internal/counter/domain/model/results.go b/internal/counter/domain/model/results.go deleted file mode 100644 index 590a192..0000000 --- a/internal/counter/domain/model/results.go +++ /dev/null @@ -1,8 +0,0 @@ -package model - -import "github.com/thangchung/go-coffeeshop/internal/counter/domain" - -type OrderListResult struct { - Order *domain.Order `db:"o"` - LineItem *domain.LineItem `db:"l"` -} diff --git a/internal/counter/domain/models.go b/internal/counter/domain/models.go new file mode 100644 index 0000000..a9aaef8 --- /dev/null +++ b/internal/counter/domain/models.go @@ -0,0 +1,45 @@ +package domain + +import ( + "time" + + "github.com/google/uuid" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" +) + +type OrderModel struct { + ID uuid.UUID + OrderSource shared.OrderSource + LoyaltyMemberID uuid.UUID + OrderStatus shared.Status + Location shared.Location + LineItems []*LineItemModel +} + +type LineItemModel struct { + ID uuid.UUID + ItemType shared.ItemType + Name string + Price float64 + ItemStatus shared.Status + IsBaristaOrder bool +} + +type PlaceOrderModel struct { + CommandType shared.CommandType + OrderSource shared.OrderSource + Location shared.Location + LoyaltyMemberID uuid.UUID + BaristaItems []*OrderItemModel + KitchenItems []*OrderItemModel + Timestamp time.Time +} + +type OrderItemModel struct { + ItemType shared.ItemType +} + +type ItemModel struct { + ItemType shared.ItemType + Price float64 +} diff --git a/internal/counter/domain/order.go b/internal/counter/domain/order.go index d28643e..eb4284c 100644 --- a/internal/counter/domain/order.go +++ b/internal/counter/domain/order.go @@ -2,30 +2,28 @@ package domain import ( "context" - "encoding/json" "github.com/google/uuid" - "github.com/pkg/errors" "github.com/samber/lo" - events "github.com/thangchung/go-coffeeshop/pkg/event" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" - gen "github.com/thangchung/go-coffeeshop/proto/gen" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) type Order struct { - ID uuid.UUID `json:"id" db:"id"` - OrderSource gen.OrderSource `json:"order_source" db:"order_source"` - LoyaltyMemberID uuid.UUID `json:"loyalty_member_id" db:"loyalty_member_id"` - OrderStatus gen.Status `json:"order_status" db:"order_status"` - Location gen.Location `json:"location" db:"location"` + shared.AggregateRoot + ID uuid.UUID `json:"id" db:"id"` + OrderSource shared.OrderSource `json:"order_source" db:"order_source"` + LoyaltyMemberID uuid.UUID `json:"loyalty_member_id" db:"loyalty_member_id"` + OrderStatus shared.Status `json:"order_status" db:"order_status"` + Location shared.Location `json:"location" db:"location"` LineItems []*LineItem } func NewOrder( - orderSource gen.OrderSource, + orderSource shared.OrderSource, loyaltyMemberID uuid.UUID, - orderStatus gen.Status, - location gen.Location, + orderStatus shared.Status, + location shared.Location, ) *Order { return &Order{ ID: uuid.New(), @@ -38,17 +36,10 @@ func NewOrder( func CreateOrderFrom( ctx context.Context, - request *gen.PlaceOrderRequest, + request *PlaceOrderModel, productDomainSvc ProductDomainService, - baristaOrderPub publisher.Publisher, - kitchenOrderPub publisher.Publisher, ) (*Order, error) { - loyaltyMemberID, err := uuid.Parse(request.LoyaltyMemberId) - if err != nil { - return nil, err - } - - order := NewOrder(request.OrderSource, loyaltyMemberID, gen.Status_IN_PROGRESS, request.Location) + order := NewOrder(request.OrderSource, request.LoyaltyMemberID, shared.StatusInProcess, request.Location) numberOfBaristaItems := len(request.BaristaItems) > 0 numberOfKitchenItems := len(request.KitchenItems) > 0 @@ -59,23 +50,21 @@ func CreateOrderFrom( return nil, err } - lo.ForEach(request.BaristaItems, func(item *gen.CommandItem, index int) { - find, ok := lo.Find(itemTypesRes.Items, func(i *gen.ItemDto) bool { - return i.Type == int32(item.ItemType) + lo.ForEach(request.BaristaItems, func(item *OrderItemModel, _ int) { + find, ok := lo.Find(itemTypesRes, func(i *ItemModel) bool { + return i.ItemType == item.ItemType }) if ok { - lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), gen.Status_IN_PROGRESS, true) - - err = publishBaristaOrderEvent( - ctx, - order.ID, - lineItem.ID, - lineItem.ItemType, - baristaOrderPub, - kitchenOrderPub, - true, - ) + lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), shared.StatusInProcess, true) + + event := event.BaristaOrdered{ + OrderID: order.ID, + ItemLineID: lineItem.ID, + ItemType: item.ItemType, + } + + order.ApplyDomain(event) order.LineItems = append(order.LineItems, lineItem) } @@ -92,23 +81,21 @@ func CreateOrderFrom( return nil, err } - lo.ForEach(request.KitchenItems, func(item *gen.CommandItem, index int) { - find, ok := lo.Find(itemTypesRes.Items, func(i *gen.ItemDto) bool { - return i.Type == int32(item.ItemType) + lo.ForEach(request.KitchenItems, func(item *OrderItemModel, index int) { + find, ok := lo.Find(itemTypesRes, func(i *ItemModel) bool { + return i.ItemType == item.ItemType }) if ok { - lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), gen.Status_IN_PROGRESS, false) - - err = publishBaristaOrderEvent( - ctx, - order.ID, - lineItem.ID, - lineItem.ItemType, - baristaOrderPub, - kitchenOrderPub, - false, - ) + lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), shared.StatusInProcess, false) + + event := event.KitchenOrdered{ + OrderID: order.ID, + ItemLineID: lineItem.ID, + ItemType: item.ItemType, + } + + order.ApplyDomain(event) order.LineItems = append(order.LineItems, lineItem) } @@ -122,7 +109,7 @@ func CreateOrderFrom( return order, nil } -func (o *Order) Apply(event *events.BaristaOrderUpdated) error { +func (o *Order) Apply(event *event.BaristaOrderUpdated) error { if len(o.LineItems) == 0 { return nil // we dont do anything } @@ -132,71 +119,21 @@ func (o *Order) Apply(event *events.BaristaOrderUpdated) error { }) if !ok { - return errors.New("item not found") + return ErrItemNotFound } - o.LineItems[index].ItemStatus = gen.Status_FULFILLED + o.LineItems[index].ItemStatus = shared.StatusFulfilled if checkFulfilledStatus(o.LineItems) { - o.OrderStatus = gen.Status_FULFILLED + o.OrderStatus = shared.StatusFulfilled } return nil } -func publishBaristaOrderEvent( - ctx context.Context, - orderID uuid.UUID, - lineItemID uuid.UUID, - itemType gen.ItemType, - baristaOrderPub publisher.Publisher, - kitchenOrderPub publisher.Publisher, - isBarista bool, -) error { - if isBarista { - // todo: refactor to event domain dispatcher - event := events.BaristaOrdered{ - OrderID: orderID, - ItemLineID: lineItemID, - ItemType: itemType, - } - - eventBytes, err := json.Marshal(event) - if err != nil { - return errors.Wrap(err, "json.Marshal - events.BaristaOrdered") - } - - err = baristaOrderPub.Publish(ctx, eventBytes, "text/plain") - if err != nil { - return errors.Wrap(err, "orderPublisher - Publish") - } - - return nil - } else { - // todo: refactor to event domain dispatcher - event := events.KitchenOrdered{ - OrderID: orderID, - ItemLineID: lineItemID, - ItemType: itemType, - } - - eventBytes, err := json.Marshal(event) - if err != nil { - return errors.Wrap(err, "json.Marshal - events.BaristaOrdered") - } - - err = kitchenOrderPub.Publish(ctx, eventBytes, "text/plain") - if err != nil { - return errors.Wrap(err, "orderPublisher - Publish") - } - - return nil - } -} - func checkFulfilledStatus(lineItems []*LineItem) bool { for _, item := range lineItems { - if item.ItemStatus != gen.Status_FULFILLED { + if item.ItemStatus != shared.StatusFulfilled { return false } } diff --git a/internal/counter/domain/results.go b/internal/counter/domain/results.go new file mode 100644 index 0000000..641295c --- /dev/null +++ b/internal/counter/domain/results.go @@ -0,0 +1,6 @@ +package domain + +type OrderListResult struct { + Order *Order `db:"o"` + LineItem *LineItem `db:"l"` +} diff --git a/internal/counter/features/orders/eventhandlers/barista_order_updated.go b/internal/counter/features/orders/eventhandlers/barista_order_updated.go deleted file mode 100644 index 54d5831..0000000 --- a/internal/counter/features/orders/eventhandlers/barista_order_updated.go +++ /dev/null @@ -1,63 +0,0 @@ -package eventhandlers - -import ( - "context" - "fmt" - - "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/pkg/event" - "github.com/thangchung/go-coffeeshop/proto/gen" -) - -type baristaOrderUpdatedEventHandler struct { - orderRepo domain.OrderRepo -} - -var _ domain.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) - -func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.BaristaOrderUpdatedEventHandler { - return &baristaOrderUpdatedEventHandler{ - orderRepo: orderRepo, - } -} - -func (h *baristaOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.BaristaOrderUpdated) error { - order, err := h.orderRepo.GetByID(ctx, e.OrderID) - if err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) - } - - if err = order.Apply(e); err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) - } - - _, err = h.orderRepo.Update(ctx, ToDto(order)) - if err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.Update(ctx, ToDto(order)): %w", err) - } - - return nil -} - -func ToDto(order *domain.Order) *gen.OrderDto { - orderModel := &gen.OrderDto{ - Id: order.ID.String(), - Localtion: order.Location, - OrderSource: order.OrderSource, - OrderStatus: order.OrderStatus, - LoyaltyMemberId: order.LoyaltyMemberID.String(), - } - - for _, item := range order.LineItems { - orderModel.LineItems = append(orderModel.LineItems, &gen.LineItemDto{ - Id: item.ID.String(), - Name: item.Name, - Price: float64(item.Price), - ItemType: item.ItemType, - ItemStatus: item.ItemStatus, - IsBaristaOrder: item.IsBaristaOrder, - }) - } - - return orderModel -} diff --git a/internal/counter/grpc/counter_server.go b/internal/counter/grpc/counter_server.go deleted file mode 100644 index 2ad633e..0000000 --- a/internal/counter/grpc/counter_server.go +++ /dev/null @@ -1,134 +0,0 @@ -package grpc - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - amqp "github.com/rabbitmq/amqp091-go" - "github.com/samber/lo" - "github.com/thangchung/go-coffeeshop/cmd/counter/config" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" - gen "github.com/thangchung/go-coffeeshop/proto/gen" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type counterGRPCServer struct { - gen.UnimplementedCounterServiceServer - logger *mylogger.Logger - amqpConn *amqp.Connection - cfg *config.Config - productDomainSvc domain.ProductDomainService - orderRepo domain.OrderRepo - baristaOrderPub publisher.Publisher - kitchenOrderPub publisher.Publisher -} - -var _ gen.CounterServiceServer = (*counterGRPCServer)(nil) - -func NewGRPCCounterServer( - grpcServer *grpc.Server, - amqpConn *amqp.Connection, - cfg *config.Config, - log *mylogger.Logger, - orderRepo domain.OrderRepo, - productDomainSvc domain.ProductDomainService, - baristaOrderPub publisher.Publisher, - kitchenOrderPub publisher.Publisher, -) { - svc := counterGRPCServer{ - cfg: cfg, - logger: log, - amqpConn: amqpConn, - orderRepo: orderRepo, - productDomainSvc: productDomainSvc, - baristaOrderPub: baristaOrderPub, - kitchenOrderPub: kitchenOrderPub, - } - - gen.RegisterCounterServiceServer(grpcServer, &svc) - - reflection.Register(grpcServer) -} - -func (g *counterGRPCServer) GetListOrderFulfillment( - ctx context.Context, - request *gen.GetListOrderFulfillmentRequest, -) (*gen.GetListOrderFulfillmentResponse, error) { - g.logger.Info("GET: GetListOrderFulfillment") - - res := gen.GetListOrderFulfillmentResponse{} - - entities, err := g.orderRepo.GetAll(ctx) - if err != nil { - return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.orderRepo.GetAll: %w", err) - } - - for _, entity := range entities { - res.Orders = append(res.Orders, &gen.OrderDto{ - Id: entity.ID.String(), - OrderSource: entity.OrderSource, - OrderStatus: entity.OrderStatus, - Localtion: entity.Location, - LoyaltyMemberId: entity.LoyaltyMemberID.String(), - LineItems: lo.Map(entity.LineItems, func(item *domain.LineItem, _ int) *gen.LineItemDto { - return &gen.LineItemDto{ - Id: item.ID.String(), - ItemType: item.ItemType, - Name: item.Name, - Price: float64(item.Price), - ItemStatus: item.ItemStatus, - IsBaristaOrder: item.IsBaristaOrder, - } - }), - }) - } - - return &res, nil -} - -func (g *counterGRPCServer) PlaceOrder( - ctx context.Context, - request *gen.PlaceOrderRequest, -) (*gen.PlaceOrderResponse, error) { - g.logger.Info("POST: PlaceOrder") - - // add order - order, err := domain.CreateOrderFrom(ctx, request, g.productDomainSvc, g.baristaOrderPub, g.kitchenOrderPub) - if err != nil { - return nil, errors.Wrap(err, "counterGRPCServer-PlaceOrder-domain.CreateOrderFrom") - } - - // save to database - orderModel := &gen.OrderDto{ - Id: order.ID.String(), - Localtion: order.Location, - LoyaltyMemberId: order.LoyaltyMemberID.String(), - OrderSource: order.OrderSource, - OrderStatus: order.OrderStatus, - } - - for _, item := range order.LineItems { - orderModel.LineItems = append(orderModel.LineItems, &gen.LineItemDto{ - ItemType: item.ItemType, - Name: item.Name, - Price: float64(item.Price), - ItemStatus: item.ItemStatus, - IsBaristaOrder: item.IsBaristaOrder, - }) - } - - err = g.orderRepo.Create(ctx, orderModel) - if err != nil { - return nil, errors.Wrap(err, "PlaceOrder-g.orderCommand.Create") - } - - g.logger.Debug("order created: %v", *order) - - res := gen.PlaceOrderResponse{} - - return &res, nil -} diff --git a/internal/counter/grpc/product_client.go b/internal/counter/grpc/product_client.go deleted file mode 100644 index 9137333..0000000 --- a/internal/counter/grpc/product_client.go +++ /dev/null @@ -1,45 +0,0 @@ -package grpc - -import ( - "context" - "fmt" - "strings" - - "github.com/samber/lo" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" - gen "github.com/thangchung/go-coffeeshop/proto/gen" - "google.golang.org/grpc" -) - -type productGRPCClient struct { - conn *grpc.ClientConn -} - -var _ domain.ProductDomainService = (*productGRPCClient)(nil) - -func NewGRPCProductClient(conn *grpc.ClientConn) domain.ProductDomainService { - return &productGRPCClient{ - conn: conn, - } -} - -func (p *productGRPCClient) GetItemsByType( - ctx context.Context, - request *gen.PlaceOrderRequest, - isBarista bool, -) (*gen.GetItemsByTypeResponse, error) { - c := gen.NewProductServiceClient(p.conn) - - itemTypes := "" - if isBarista { - itemTypes = lo.Reduce(request.BaristaItems, func(agg string, item *gen.CommandItem, _ int) string { - return fmt.Sprintf("%s,%s", agg, item.ItemType) - }, "") - } else { - itemTypes = lo.Reduce(request.KitchenItems, func(agg string, item *gen.CommandItem, _ int) string { - return fmt.Sprintf("%s,%s", agg, item.ItemType) - }, "") - } - - return c.GetItemsByType(ctx, &gen.GetItemsByTypeRequest{ItemTypes: strings.TrimLeft(itemTypes, ",")}) -} diff --git a/internal/counter/infras/grpc/counter_server.go b/internal/counter/infras/grpc/counter_server.go new file mode 100644 index 0000000..1b178c2 --- /dev/null +++ b/internal/counter/infras/grpc/counter_server.go @@ -0,0 +1,118 @@ +package grpc + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/thangchung/go-coffeeshop/cmd/counter/config" + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + gen "github.com/thangchung/go-coffeeshop/proto/gen" + "golang.org/x/exp/slog" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +type counterGRPCServer struct { + gen.UnimplementedCounterServiceServer + cfg *config.Config + uc orders.UseCase +} + +var _ gen.CounterServiceServer = (*counterGRPCServer)(nil) + +func NewGRPCCounterServer( + grpcServer *grpc.Server, + cfg *config.Config, + uc orders.UseCase, +) { + svc := counterGRPCServer{ + cfg: cfg, + uc: uc, + } + + gen.RegisterCounterServiceServer(grpcServer, &svc) + + reflection.Register(grpcServer) +} + +func (g *counterGRPCServer) GetListOrderFulfillment( + ctx context.Context, + request *gen.GetListOrderFulfillmentRequest, +) (*gen.GetListOrderFulfillmentResponse, error) { + slog.Info("GET: GetListOrderFulfillment") + + res := gen.GetListOrderFulfillmentResponse{} + + entities, err := g.uc.GetListOrderFulfillment(ctx) + if err != nil { + return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.uc.GetListOrderFulfillment: %w", err) + } + + for _, entity := range entities { + res.Orders = append(res.Orders, &gen.OrderDto{ + Id: entity.ID.String(), + OrderSource: int32(entity.OrderSource), + OrderStatus: int32(entity.OrderStatus), + Localtion: int32(entity.Location), + LoyaltyMemberId: entity.LoyaltyMemberID.String(), + LineItems: lo.Map(entity.LineItems, func(item *domain.LineItem, _ int) *gen.LineItemDto { + return &gen.LineItemDto{ + Id: item.ID.String(), + ItemType: int32(item.ItemType), + Name: item.Name, + Price: float64(item.Price), + ItemStatus: int32(item.ItemStatus), + IsBaristaOrder: item.IsBaristaOrder, + } + }), + }) + } + + return &res, nil +} + +func (g *counterGRPCServer) PlaceOrder( + ctx context.Context, + request *gen.PlaceOrderRequest, +) (*gen.PlaceOrderResponse, error) { + slog.Info("POST: PlaceOrder") + + loyaltyMemberID, err := uuid.Parse(request.LoyaltyMemberId) + if err != nil { + return nil, errors.Wrap(err, "counterGRPCServer-uuid.Parse") + } + + model := domain.PlaceOrderModel{ + CommandType: shared.CommandType(request.CommandType), + OrderSource: shared.OrderSource(request.OrderSource), + Location: shared.Location(request.Location), + LoyaltyMemberID: loyaltyMemberID, + Timestamp: request.Timestamp.AsTime(), + } + + for _, barista := range request.BaristaItems { + model.BaristaItems = append(model.BaristaItems, &domain.OrderItemModel{ + ItemType: shared.ItemType(barista.ItemType), + }) + } + + for _, kitchen := range request.KitchenItems { + model.KitchenItems = append(model.KitchenItems, &domain.OrderItemModel{ + ItemType: shared.ItemType(kitchen.ItemType), + }) + } + + err = g.uc.PlaceOrder(ctx, &model) + if err != nil { + return nil, errors.Wrap(err, "counterGRPCServer-g.uc.PlaceOrder") + } + + res := gen.PlaceOrderResponse{} + + return &res, nil +} diff --git a/internal/counter/infras/grpc/product_client.go b/internal/counter/infras/grpc/product_client.go new file mode 100644 index 0000000..9043337 --- /dev/null +++ b/internal/counter/infras/grpc/product_client.go @@ -0,0 +1,60 @@ +package grpc + +import ( + "context" + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + gen "github.com/thangchung/go-coffeeshop/proto/gen" + "google.golang.org/grpc" +) + +type productGRPCClient struct { + conn *grpc.ClientConn +} + +var _ domain.ProductDomainService = (*productGRPCClient)(nil) + +func NewGRPCProductClient(conn *grpc.ClientConn) domain.ProductDomainService { + return &productGRPCClient{ + conn: conn, + } +} + +func (p *productGRPCClient) GetItemsByType( + ctx context.Context, + model *domain.PlaceOrderModel, + isBarista bool, +) ([]*domain.ItemModel, error) { + c := gen.NewProductServiceClient(p.conn) + + itemTypes := "" + if isBarista { + itemTypes = lo.Reduce(model.BaristaItems, func(agg string, item *domain.OrderItemModel, _ int) string { + return fmt.Sprintf("%s,%s", agg, item.ItemType.String()) + }, "") + } else { + itemTypes = lo.Reduce(model.KitchenItems, func(agg string, item *domain.OrderItemModel, _ int) string { + return fmt.Sprintf("%s,%s", agg, item.ItemType.String()) + }, "") + } + + res, err := c.GetItemsByType(ctx, &gen.GetItemsByTypeRequest{ItemTypes: strings.TrimLeft(itemTypes, ",")}) + if err != nil { + return nil, errors.Wrap(err, "productGRPCClient-c.GetItemsByType") + } + + results := make([]*domain.ItemModel, 0) + for _, item := range res.Items { + results = append(results, &domain.ItemModel{ + ItemType: shared.ItemType(item.Type), + Price: item.Price, + }) + } + + return results, nil +} diff --git a/internal/counter/features/orders/repo/orders_postgres.go b/internal/counter/infras/repo/orders_postgres.go similarity index 82% rename from internal/counter/features/orders/repo/orders_postgres.go rename to internal/counter/infras/repo/orders_postgres.go index d2a5cd1..c87ec4c 100644 --- a/internal/counter/features/orders/repo/orders_postgres.go +++ b/internal/counter/infras/repo/orders_postgres.go @@ -10,9 +10,7 @@ import ( "github.com/pkg/errors" "github.com/samber/lo" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/internal/counter/domain/model" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/proto/gen" ) const _defaultEntityCap = 64 @@ -81,18 +79,18 @@ func (d *orderRepo) GetAll(ctx context.Context) ([]*domain.Order, error) { } defer rows.Close() - var results []model.OrderListResult + var results []domain.OrderListResult if err := pgxscan.ScanAll(&results, rows); err != nil { return nil, fmt.Errorf("NewOrderRepo-GetAll-pgxscan.ScanAll: %w", err) } - uniqueResults := lo.UniqBy(results, func(x model.OrderListResult) string { + uniqueResults := lo.UniqBy(results, func(x domain.OrderListResult) string { return x.Order.ID.String() }) - orders := lo.Map(uniqueResults, func(x model.OrderListResult, _ int) *domain.Order { + orders := lo.Map(uniqueResults, func(x domain.OrderListResult, _ int) *domain.Order { return x.Order }) - lineItems := lo.Map(results, func(x model.OrderListResult, _ int) *domain.LineItem { + lineItems := lo.Map(results, func(x domain.OrderListResult, _ int) *domain.LineItem { return x.LineItem }) entities := make([]*domain.Order, 0, _defaultEntityCap) @@ -139,19 +137,19 @@ func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, e } defer rows.Close() - var results []model.OrderListResult + var results []domain.OrderListResult if err := pgxscan.ScanAll(&results, rows); err != nil { return nil, fmt.Errorf("NewOrderRepo-GetByID-pgxscan.ScanAll: %w", err) } - uniqueResults := lo.UniqBy(results, func(x model.OrderListResult) string { + uniqueResults := lo.UniqBy(results, func(x domain.OrderListResult) string { return x.Order.ID.String() }) - orders := lo.Map(uniqueResults, func(x model.OrderListResult, _ int) *domain.Order { + orders := lo.Map(uniqueResults, func(x domain.OrderListResult, _ int) *domain.Order { return x.Order }) - lineItems := lo.Map(results, func(x model.OrderListResult, _ int) *domain.LineItem { + lineItems := lo.Map(results, func(x domain.OrderListResult, _ int) *domain.LineItem { return x.LineItem }) @@ -180,7 +178,7 @@ func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, e return order, nil } -func (d *orderRepo) Create(ctx context.Context, orderModel *gen.OrderDto) error { +func (d *orderRepo) Create(ctx context.Context, order *domain.Order) error { tx, err := d.pg.Pool.Begin(ctx) if err != nil { return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") @@ -191,10 +189,10 @@ func (d *orderRepo) Create(ctx context.Context, orderModel *gen.OrderDto) error Insert(`"order".orders`). Columns("id", "order_source", "loyalty_member_id", "order_status", "updated"). Values( - orderModel.Id, - orderModel.OrderSource, - orderModel.LoyaltyMemberId, - orderModel.OrderStatus, + order.ID, + order.OrderSource, + order.LoyaltyMemberID, + order.OrderStatus, time.Now(), ). ToSql() @@ -208,7 +206,7 @@ func (d *orderRepo) Create(ctx context.Context, orderModel *gen.OrderDto) error } // continue to insert order items - for _, item := range orderModel.LineItems { + for _, item := range order.LineItems { sql, args, err = d.pg.Builder. Insert(`"order".line_items`). Columns("id", "item_type", "name", "price", "item_status", "is_barista_order", "order_id", "created", "updated"). @@ -219,7 +217,7 @@ func (d *orderRepo) Create(ctx context.Context, orderModel *gen.OrderDto) error item.Price, item.ItemStatus, item.IsBaristaOrder, - orderModel.Id, + order.ID, time.Now(), time.Now(), ). @@ -237,7 +235,7 @@ func (d *orderRepo) Create(ctx context.Context, orderModel *gen.OrderDto) error return tx.Commit(ctx) } -func (d *orderRepo) Update(ctx context.Context, orderModel *gen.OrderDto) (*gen.OrderDto, error) { +func (d *orderRepo) Update(ctx context.Context, order *domain.Order) (*domain.Order, error) { tx, err := d.pg.Pool.Begin(ctx) if err != nil { return nil, errors.Wrapf(err, "orderRepo-Update-d.pg.Pool.Begin(ctx)") @@ -246,9 +244,9 @@ func (d *orderRepo) Update(ctx context.Context, orderModel *gen.OrderDto) (*gen. // update order sql, args, err := d.pg.Builder. Update(`"order".orders`). - Set("order_status", orderModel.OrderStatus). + Set("order_status", order.OrderStatus). Set("updated", time.Now()). - Where("id = ?", orderModel.Id). + Where("id = ?", order.ID). ToSql() if err != nil { return nil, tx.Rollback(ctx) @@ -260,12 +258,12 @@ func (d *orderRepo) Update(ctx context.Context, orderModel *gen.OrderDto) (*gen. } // continue to update order items - for _, item := range orderModel.LineItems { + for _, item := range order.LineItems { sql, args, err = d.pg.Builder. Update(`"order".line_items`). Set("item_status", item.ItemStatus). Set("updated", time.Now()). - Where("id = ?", item.Id). + Where("id = ?", item.ID). ToSql() if err != nil { return nil, tx.Rollback(ctx) @@ -277,5 +275,5 @@ func (d *orderRepo) Update(ctx context.Context, orderModel *gen.OrderDto) (*gen. } } - return orderModel, tx.Commit(ctx) + return order, tx.Commit(ctx) } diff --git a/internal/counter/usecases/orders/event_handlers.go b/internal/counter/usecases/orders/event_handlers.go new file mode 100644 index 0000000..aa0894c --- /dev/null +++ b/internal/counter/usecases/orders/event_handlers.go @@ -0,0 +1,39 @@ +package orders + +import ( + "context" + "fmt" + + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type baristaOrderUpdatedEventHandler struct { + orderRepo domain.OrderRepo +} + +var _ BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) + +func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) BaristaOrderUpdatedEventHandler { + return &baristaOrderUpdatedEventHandler{ + orderRepo: orderRepo, + } +} + +func (h *baristaOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.BaristaOrderUpdated) error { + order, err := h.orderRepo.GetByID(ctx, e.OrderID) + if err != nil { + return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) + } + + if err = order.Apply(e); err != nil { + return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) + } + + _, err = h.orderRepo.Update(ctx, order) + if err != nil { + return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.Update(ctx, ToDto(order)): %w", err) + } + + return nil +} diff --git a/internal/counter/usecases/orders/interfaces.go b/internal/counter/usecases/orders/interfaces.go new file mode 100644 index 0000000..3521830 --- /dev/null +++ b/internal/counter/usecases/orders/interfaces.go @@ -0,0 +1,19 @@ +package orders + +import ( + "context" + + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type ( + UseCase interface { + GetListOrderFulfillment(context.Context) ([]*domain.Order, error) + PlaceOrder(context.Context, *domain.PlaceOrderModel) error + } + + BaristaOrderUpdatedEventHandler interface { + Handle(context.Context, *event.BaristaOrderUpdated) error + } +) diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go new file mode 100644 index 0000000..56847e1 --- /dev/null +++ b/internal/counter/usecases/orders/service.go @@ -0,0 +1,82 @@ +package orders + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + "golang.org/x/exp/slog" +) + +type usecase struct { + orderRepo domain.OrderRepo + productDomainSvc domain.ProductDomainService + baristaEventPub shared.EventPublisher + kitchenEventPub shared.EventPublisher +} + +var _ UseCase = (*usecase)(nil) + +func NewUseCase( + orderRepo domain.OrderRepo, + productDomainSvc domain.ProductDomainService, + baristaEventPub shared.EventPublisher, + kitchenEventPub shared.EventPublisher, +) UseCase { + return &usecase{ + orderRepo: orderRepo, + productDomainSvc: productDomainSvc, + baristaEventPub: baristaEventPub, + kitchenEventPub: kitchenEventPub, + } +} + +func (uc *usecase) GetListOrderFulfillment(ctx context.Context) ([]*domain.Order, error) { + entities, err := uc.orderRepo.GetAll(ctx) + if err != nil { + return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.orderRepo.GetAll: %w", err) + } + + return entities, nil +} + +func (uc *usecase) PlaceOrder(ctx context.Context, model *domain.PlaceOrderModel) error { + order, err := domain.CreateOrderFrom(ctx, model, uc.productDomainSvc) + if err != nil { + return errors.Wrap(err, "usecase-domain.CreateOrderFrom") + } + + err = uc.orderRepo.Create(ctx, order) + if err != nil { + return errors.Wrap(err, "usecase-uc.orderRepo.Create") + } + + slog.Debug("order created", "order", *order) + + baristaEvents := make([]shared.DomainEvent, 0) + kitchenEvents := make([]shared.DomainEvent, 0) + + for _, event := range order.DomainEvents() { + if event.Identity() == "BaristaOrdered" { + baristaEvents = append(baristaEvents, event) + } + + if event.Identity() == "KitchenOrdered" { + kitchenEvents = append(kitchenEvents, event) + } + } + + err = uc.baristaEventPub.Publish(ctx, baristaEvents) + if err != nil { + return errors.Wrap(err, "usecase-baristaEventPub.Publish") + } + + err = uc.kitchenEventPub.Publish(ctx, kitchenEvents) + if err != nil { + return errors.Wrap(err, "usecase-kitchenEventPub.Publish") + } + + return nil +} diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index bf818de..2e9f10a 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -11,9 +11,9 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" - "github.com/thangchung/go-coffeeshop/internal/kitchen/features/orders/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/kitchen/features/orders/repo" - "github.com/thangchung/go-coffeeshop/pkg/event" + "github.com/thangchung/go-coffeeshop/internal/kitchen/usecases/orders/eventhandlers" + "github.com/thangchung/go-coffeeshop/internal/kitchen/usecases/orders/repo" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" diff --git a/internal/kitchen/domain/order.go b/internal/kitchen/domain/order.go index 4432c41..4ee2082 100644 --- a/internal/kitchen/domain/order.go +++ b/internal/kitchen/domain/order.go @@ -4,23 +4,24 @@ import ( "time" "github.com/google/uuid" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) -type ItemType int8 +// type ItemType int8 -const ( - CakePop ItemType = iota + 6 - Croissant - Muffin - CroissantChocolate -) +// const ( +// CakePop ItemType = iota + 6 +// Croissant +// Muffin +// CroissantChocolate +// ) type KitchenOrder struct { - ID uuid.UUID `json:"id" db:"id"` - OrderID uuid.UUID `json:"orderId" db:"order_id"` - ItemName string `json:"itemName" db:"item_name"` - ItemType ItemType `json:"itemType" db:"item_type"` - TimeUp time.Time `json:"timeUp" db:"time_up"` - Created time.Time `json:"created" db:"created"` - Updated time.Time `json:"updated" db:"updated"` + ID uuid.UUID `json:"id" db:"id"` + OrderID uuid.UUID `json:"orderId" db:"order_id"` + ItemName string `json:"itemName" db:"item_name"` + ItemType shared.ItemType `json:"itemType" db:"item_type"` + TimeUp time.Time `json:"timeUp" db:"time_up"` + Created time.Time `json:"created" db:"created"` + Updated time.Time `json:"updated" db:"updated"` } diff --git a/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go b/internal/kitchen/usecases/orders/eventhandlers/kitchen_ordered.go similarity index 75% rename from internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go rename to internal/kitchen/usecases/orders/eventhandlers/kitchen_ordered.go index a104eb7..12cef26 100644 --- a/internal/kitchen/features/orders/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/usecases/orders/eventhandlers/kitchen_ordered.go @@ -7,10 +7,10 @@ import ( "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" - "github.com/thangchung/go-coffeeshop/pkg/event" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" - "github.com/thangchung/go-coffeeshop/proto/gen" ) type KitchenOrderedEventHandler interface { @@ -50,7 +50,7 @@ func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.Kitche err := h.repo.Create(ctx, &domain.KitchenOrder{ ID: e.ItemLineID, OrderID: e.OrderID, - ItemType: convertToItemType(e.ItemType), + ItemType: e.ItemType, ItemName: e.ItemType.String(), TimeUp: timeUp, Created: time.Now(), @@ -82,32 +82,17 @@ func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.Kitche return nil } -func calculateDelay(itemType gen.ItemType) time.Duration { +func calculateDelay(itemType shared.ItemType) time.Duration { switch itemType { - case gen.ItemType_CROISSANT: + case shared.ItemTypeCroissant: return 7 * time.Second - case gen.ItemType_CROISSANT_CHOCOLATE: + case shared.ItemTypeCroissantChocolate: return 7 * time.Second - case gen.ItemType_CAKEPOP: + case shared.ItemTypeCakePop: return 5 * time.Second - case gen.ItemType_MUFFIN: + case shared.ItemTypeMuffin: return 7 * time.Second default: return 3 * time.Second } } - -func convertToItemType(dto gen.ItemType) domain.ItemType { - switch dto { - case gen.ItemType_CROISSANT: - return domain.Croissant - case gen.ItemType_CROISSANT_CHOCOLATE: - return domain.CroissantChocolate - case gen.ItemType_CAKEPOP: - return domain.CakePop - case gen.ItemType_MUFFIN: - return domain.Muffin - default: - return domain.Croissant - } -} diff --git a/internal/kitchen/features/orders/repo/orders_postgres.go b/internal/kitchen/usecases/orders/repo/orders_postgres.go similarity index 100% rename from internal/kitchen/features/orders/repo/orders_postgres.go rename to internal/kitchen/usecases/orders/repo/orders_postgres.go diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go new file mode 100644 index 0000000..600df51 --- /dev/null +++ b/internal/pkg/event/events.go @@ -0,0 +1,62 @@ +package event + +import ( + "reflect" + "time" + + "github.com/google/uuid" + + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" +) + +type BaristaOrdered struct { + shared.DomainEvent + OrderID uuid.UUID `json:"orderId"` + ItemLineID uuid.UUID `json:"itemLineId"` + ItemType shared.ItemType `json:"itemType"` +} + +func (e BaristaOrdered) Identity() string { + return reflect.TypeOf(e).String() +} + +type KitchenOrdered struct { + shared.DomainEvent + OrderID uuid.UUID `json:"orderId"` + ItemLineID uuid.UUID `json:"itemLineId"` + ItemType shared.ItemType `json:"itemType"` +} + +func (e KitchenOrdered) Identity() string { + return reflect.TypeOf(e).String() +} + +type BaristaOrderUpdated struct { + shared.DomainEvent + OrderID uuid.UUID `json:"orderId"` + ItemLineID uuid.UUID `json:"itemLineId"` + Name string `json:"name"` + ItemType shared.ItemType `json:"itemType"` + TimeIn time.Time `json:"timeIn"` + MadeBy string `json:"madeBy"` + TimeUp time.Time `json:"timeUp"` +} + +func (e BaristaOrderUpdated) Identity() string { + return reflect.TypeOf(e).String() +} + +type KitchenOrderUpdated struct { + shared.DomainEvent + OrderID uuid.UUID `json:"orderId"` + ItemLineID uuid.UUID `json:"itemLineId"` + Name string `json:"name"` + ItemType shared.ItemType `json:"itemType"` + TimeIn time.Time `json:"timeIn"` + MadeBy string `json:"madeBy"` + TimeUp time.Time `json:"timeUp"` +} + +func (e KitchenOrderUpdated) Identity() string { + return reflect.TypeOf(e).String() +} diff --git a/internal/pkg/event/publisher.go b/internal/pkg/event/publisher.go new file mode 100644 index 0000000..e73283a --- /dev/null +++ b/internal/pkg/event/publisher.go @@ -0,0 +1,38 @@ +package event + +import ( + "context" + "encoding/json" + + "github.com/pkg/errors" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" +) + +type eventPublisherRabbitMQ struct { + pub publisher.Publisher +} + +var _ shared.EventPublisher = (*eventPublisherRabbitMQ)(nil) + +func NewEventPublisher(pub publisher.Publisher) shared.EventPublisher { + return &eventPublisherRabbitMQ{ + pub: pub, + } +} + +func (p *eventPublisherRabbitMQ) Publish(ctx context.Context, events []shared.DomainEvent) error { + for _, e := range events { + b, err := json.Marshal(e) + if err != nil { + return errors.Wrap(err, "eventPublisherRabbitMQ-json.Marshal") + } + + err = p.pub.Publish(ctx, b, "text/plain") + if err != nil { + return errors.Wrap(err, "eventPublisherRabbitMQ-pub.Publish") + } + } + + return nil +} diff --git a/internal/pkg/shared_kernel/aggregate_root.go b/internal/pkg/shared_kernel/aggregate_root.go new file mode 100644 index 0000000..a64dbd2 --- /dev/null +++ b/internal/pkg/shared_kernel/aggregate_root.go @@ -0,0 +1,27 @@ +package sharedkernel + +import ( + "context" + "time" +) + +type DomainEvent interface { + CreateAt() time.Time + Identity() string +} + +type EventPublisher interface { + Publish(context.Context, []DomainEvent) error +} + +type AggregateRoot struct { + domainEvents []DomainEvent +} + +func (ar *AggregateRoot) ApplyDomain(e DomainEvent) { + ar.domainEvents = append(ar.domainEvents, e) +} + +func (ar *AggregateRoot) DomainEvents() []DomainEvent { + return ar.domainEvents +} diff --git a/internal/pkg/shared_kernel/entity.go b/internal/pkg/shared_kernel/entity.go new file mode 100644 index 0000000..ad22fbd --- /dev/null +++ b/internal/pkg/shared_kernel/entity.go @@ -0,0 +1,15 @@ +package sharedkernel + +import "github.com/google/uuid" + +type ID = uuid.UUID + +func NewID() ID { + return uuid.New() +} + +func StringToID(s string) (ID, error) { + id, err := uuid.Parse(s) + + return id, err +} diff --git a/internal/pkg/shared_kernel/entity_test.go b/internal/pkg/shared_kernel/entity_test.go new file mode 100644 index 0000000..1651ec9 --- /dev/null +++ b/internal/pkg/shared_kernel/entity_test.go @@ -0,0 +1,22 @@ +package sharedkernel_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" +) + +func TestNewID(t *testing.T) { + t.Parallel() + + id := shared.NewID() + assert.NotNil(t, id) +} + +func TestStringToID(t *testing.T) { + t.Parallel() + + _, err := shared.StringToID("fd14c028-5f56-488a-8c29-3186fd62395c") + assert.Nil(t, err) +} diff --git a/internal/pkg/shared_kernel/enums.go b/internal/pkg/shared_kernel/enums.go new file mode 100644 index 0000000..ab00ef6 --- /dev/null +++ b/internal/pkg/shared_kernel/enums.go @@ -0,0 +1,67 @@ +package sharedkernel + +import "fmt" + +type OrderSource int8 + +const ( + OrderSourceCounter OrderSource = iota + OrderSourceWeb +) + +func (e OrderSource) String() string { + return fmt.Sprintf("%d", int(e)) +} + +type Status int8 + +const ( + StatusPlaced Status = iota + StatusInProcess + StatusFulfilled +) + +func (e Status) String() string { + return fmt.Sprintf("%d", int(e)) +} + +type Location int8 + +const ( + LocationAtlanta Location = iota + LocationCharlotte + LocationRaleigh +) + +func (e Location) String() string { + return fmt.Sprintf("%d", int(e)) +} + +type CommandType int8 + +const ( + CommandTypePlaceOrder CommandType = iota +) + +func (e CommandType) String() string { + return fmt.Sprintf("%d", int(e)) +} + +type ItemType int8 + +const ( + ItemTypeCappuccino ItemType = iota + ItemTypeCoffeeBlack + ItemTypeCoffeeWithRoom + ItemTypeEspresso + ItemTypeEspressoDouble + ItemTypeLatte + ItemTypeCakePop + ItemTypeCroissant + ItemTypeMuffin + ItemTypeCroissantChocolate +) + +func (e ItemType) String() string { + return fmt.Sprintf("%d", int(e)) +} diff --git a/pkg/event/events.go b/pkg/event/events.go deleted file mode 100644 index 6fbdca5..0000000 --- a/pkg/event/events.go +++ /dev/null @@ -1,40 +0,0 @@ -package event - -import ( - "time" - - "github.com/google/uuid" - gen "github.com/thangchung/go-coffeeshop/proto/gen" -) - -type BaristaOrdered struct { - OrderID uuid.UUID `json:"orderId"` - ItemLineID uuid.UUID `json:"itemLineId"` - ItemType gen.ItemType `json:"itemType"` -} - -type KitchenOrdered struct { - OrderID uuid.UUID `json:"orderId"` - ItemLineID uuid.UUID `json:"itemLineId"` - ItemType gen.ItemType `json:"itemType"` -} - -type BaristaOrderUpdated struct { - OrderID uuid.UUID `json:"orderId"` - ItemLineID uuid.UUID `json:"itemLineId"` - Name string `json:"name"` - ItemType gen.ItemType `json:"itemType"` - TimeIn time.Time `json:"timeIn"` - MadeBy string `json:"madeBy"` - TimeUp time.Time `json:"timeUp"` -} - -type KitchenOrderUpdated struct { - OrderID uuid.UUID `json:"orderId"` - ItemLineID uuid.UUID `json:"itemLineId"` - Name string `json:"name"` - ItemType gen.ItemType `json:"itemType"` - TimeIn time.Time `json:"timeIn"` - MadeBy string `json:"madeBy"` - TimeUp time.Time `json:"timeUp"` -} diff --git a/proto/counter.proto b/proto/counter.proto index 8f3dcc5..f0ea3df 100644 --- a/proto/counter.proto +++ b/proto/counter.proto @@ -40,26 +40,26 @@ message GetListOrderFulfillmentResponse { message OrderDto { string id = 1; - common.OrderSource order_source = 2; + int32 order_source = 2; string loyalty_member_id = 3; - common.Status order_status = 4; - common.Location localtion = 5; + int32 order_status = 4; + int32 localtion = 5; repeated LineItemDto line_items = 6; } message LineItemDto { string id = 1; - common.ItemType item_type = 2; + int32 item_type = 2; string name = 3; double price = 4; - common.Status item_status = 5; + int32 item_status = 5; bool is_barista_order = 6; } message PlaceOrderRequest { - common.CommandType command_type = 1; - common.OrderSource order_source = 2; - common.Location location = 3; + int32 command_type = 1; + int32 order_source = 2; + int32 location = 3; string loyalty_member_id = 4; repeated CommandItem barista_items = 5; repeated CommandItem kitchen_items = 6; @@ -68,5 +68,5 @@ message PlaceOrderRequest { message PlaceOrderResponse {} message CommandItem { - common.ItemType item_type = 1; + int32 item_type = 1; } \ No newline at end of file diff --git a/proto/gen/counter.pb.go b/proto/gen/counter.pb.go index e7298e4..a216a26 100644 --- a/proto/gen/counter.pb.go +++ b/proto/gen/counter.pb.go @@ -114,10 +114,10 @@ type OrderDto struct { unknownFields protoimpl.UnknownFields Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - OrderSource OrderSource `protobuf:"varint,2,opt,name=order_source,json=orderSource,proto3,enum=go.coffeeshop.proto.common.OrderSource" json:"order_source,omitempty"` + OrderSource int32 `protobuf:"varint,2,opt,name=order_source,json=orderSource,proto3" json:"order_source,omitempty"` LoyaltyMemberId string `protobuf:"bytes,3,opt,name=loyalty_member_id,json=loyaltyMemberId,proto3" json:"loyalty_member_id,omitempty"` - OrderStatus Status `protobuf:"varint,4,opt,name=order_status,json=orderStatus,proto3,enum=go.coffeeshop.proto.common.Status" json:"order_status,omitempty"` - Localtion Location `protobuf:"varint,5,opt,name=localtion,proto3,enum=go.coffeeshop.proto.common.Location" json:"localtion,omitempty"` + OrderStatus int32 `protobuf:"varint,4,opt,name=order_status,json=orderStatus,proto3" json:"order_status,omitempty"` + Localtion int32 `protobuf:"varint,5,opt,name=localtion,proto3" json:"localtion,omitempty"` LineItems []*LineItemDto `protobuf:"bytes,6,rep,name=line_items,json=lineItems,proto3" json:"line_items,omitempty"` } @@ -160,11 +160,11 @@ func (x *OrderDto) GetId() string { return "" } -func (x *OrderDto) GetOrderSource() OrderSource { +func (x *OrderDto) GetOrderSource() int32 { if x != nil { return x.OrderSource } - return OrderSource_COUNTER + return 0 } func (x *OrderDto) GetLoyaltyMemberId() string { @@ -174,18 +174,18 @@ func (x *OrderDto) GetLoyaltyMemberId() string { return "" } -func (x *OrderDto) GetOrderStatus() Status { +func (x *OrderDto) GetOrderStatus() int32 { if x != nil { return x.OrderStatus } - return Status_PLACED + return 0 } -func (x *OrderDto) GetLocaltion() Location { +func (x *OrderDto) GetLocaltion() int32 { if x != nil { return x.Localtion } - return Location_ATLANTA + return 0 } func (x *OrderDto) GetLineItems() []*LineItemDto { @@ -200,12 +200,12 @@ type LineItemDto struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - ItemType ItemType `protobuf:"varint,2,opt,name=item_type,json=itemType,proto3,enum=go.coffeeshop.proto.common.ItemType" json:"item_type,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Price float64 `protobuf:"fixed64,4,opt,name=price,proto3" json:"price,omitempty"` - ItemStatus Status `protobuf:"varint,5,opt,name=item_status,json=itemStatus,proto3,enum=go.coffeeshop.proto.common.Status" json:"item_status,omitempty"` - IsBaristaOrder bool `protobuf:"varint,6,opt,name=is_barista_order,json=isBaristaOrder,proto3" json:"is_barista_order,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ItemType int32 `protobuf:"varint,2,opt,name=item_type,json=itemType,proto3" json:"item_type,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Price float64 `protobuf:"fixed64,4,opt,name=price,proto3" json:"price,omitempty"` + ItemStatus int32 `protobuf:"varint,5,opt,name=item_status,json=itemStatus,proto3" json:"item_status,omitempty"` + IsBaristaOrder bool `protobuf:"varint,6,opt,name=is_barista_order,json=isBaristaOrder,proto3" json:"is_barista_order,omitempty"` } func (x *LineItemDto) Reset() { @@ -247,11 +247,11 @@ func (x *LineItemDto) GetId() string { return "" } -func (x *LineItemDto) GetItemType() ItemType { +func (x *LineItemDto) GetItemType() int32 { if x != nil { return x.ItemType } - return ItemType_CAPPUCCINO + return 0 } func (x *LineItemDto) GetName() string { @@ -268,11 +268,11 @@ func (x *LineItemDto) GetPrice() float64 { return 0 } -func (x *LineItemDto) GetItemStatus() Status { +func (x *LineItemDto) GetItemStatus() int32 { if x != nil { return x.ItemStatus } - return Status_PLACED + return 0 } func (x *LineItemDto) GetIsBaristaOrder() bool { @@ -287,9 +287,9 @@ type PlaceOrderRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CommandType CommandType `protobuf:"varint,1,opt,name=command_type,json=commandType,proto3,enum=go.coffeeshop.proto.common.CommandType" json:"command_type,omitempty"` - OrderSource OrderSource `protobuf:"varint,2,opt,name=order_source,json=orderSource,proto3,enum=go.coffeeshop.proto.common.OrderSource" json:"order_source,omitempty"` - Location Location `protobuf:"varint,3,opt,name=location,proto3,enum=go.coffeeshop.proto.common.Location" json:"location,omitempty"` + CommandType int32 `protobuf:"varint,1,opt,name=command_type,json=commandType,proto3" json:"command_type,omitempty"` + OrderSource int32 `protobuf:"varint,2,opt,name=order_source,json=orderSource,proto3" json:"order_source,omitempty"` + Location int32 `protobuf:"varint,3,opt,name=location,proto3" json:"location,omitempty"` LoyaltyMemberId string `protobuf:"bytes,4,opt,name=loyalty_member_id,json=loyaltyMemberId,proto3" json:"loyalty_member_id,omitempty"` BaristaItems []*CommandItem `protobuf:"bytes,5,rep,name=barista_items,json=baristaItems,proto3" json:"barista_items,omitempty"` KitchenItems []*CommandItem `protobuf:"bytes,6,rep,name=kitchen_items,json=kitchenItems,proto3" json:"kitchen_items,omitempty"` @@ -328,25 +328,25 @@ func (*PlaceOrderRequest) Descriptor() ([]byte, []int) { return file_counter_proto_rawDescGZIP(), []int{4} } -func (x *PlaceOrderRequest) GetCommandType() CommandType { +func (x *PlaceOrderRequest) GetCommandType() int32 { if x != nil { return x.CommandType } - return CommandType_PLACE_ORDER + return 0 } -func (x *PlaceOrderRequest) GetOrderSource() OrderSource { +func (x *PlaceOrderRequest) GetOrderSource() int32 { if x != nil { return x.OrderSource } - return OrderSource_COUNTER + return 0 } -func (x *PlaceOrderRequest) GetLocation() Location { +func (x *PlaceOrderRequest) GetLocation() int32 { if x != nil { return x.Location } - return Location_ATLANTA + return 0 } func (x *PlaceOrderRequest) GetLoyaltyMemberId() string { @@ -420,7 +420,7 @@ type CommandItem struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ItemType ItemType `protobuf:"varint,1,opt,name=item_type,json=itemType,proto3,enum=go.coffeeshop.proto.common.ItemType" json:"item_type,omitempty"` + ItemType int32 `protobuf:"varint,1,opt,name=item_type,json=itemType,proto3" json:"item_type,omitempty"` } func (x *CommandItem) Reset() { @@ -455,11 +455,11 @@ func (*CommandItem) Descriptor() ([]byte, []int) { return file_counter_proto_rawDescGZIP(), []int{6} } -func (x *CommandItem) GetItemType() ItemType { +func (x *CommandItem) GetItemType() int32 { if x != nil { return x.ItemType } - return ItemType_CAPPUCCINO + return 0 } var File_counter_proto protoreflect.FileDescriptor @@ -484,117 +484,95 @@ var file_counter_proto_rawDesc = []byte{ 0x0b, 0x32, 0x28, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x74, 0x6f, 0x52, 0x06, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x08, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x74, 0x6f, + 0x65, 0x72, 0x73, 0x22, 0xf6, 0x01, 0x0a, 0x08, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x74, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x4a, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, - 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, - 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, - 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x42, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, - 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, - 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, - 0x6d, 0x44, 0x74, 0x6f, 0x52, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x22, - 0xf9, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x74, 0x6f, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x41, 0x0a, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, - 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x49, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x0b, - 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x22, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x62, 0x61, 0x72, 0x69, 0x73, 0x74, 0x61, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x42, - 0x61, 0x72, 0x69, 0x73, 0x74, 0x61, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xf7, 0x03, 0x0a, 0x11, - 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, - 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4a, 0x0a, - 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, - 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0b, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x6c, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, + 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x5f, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x4a, 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, + 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x74, + 0x6f, 0x52, 0x09, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xaf, 0x01, 0x0a, + 0x0b, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x74, 0x6f, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x6d, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x62, 0x61, 0x72, 0x69, 0x73, + 0x74, 0x61, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x69, 0x73, 0x42, 0x61, 0x72, 0x69, 0x73, 0x74, 0x61, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0xff, + 0x02, 0x0a, 0x11, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, + 0x79, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x50, 0x0a, 0x0d, 0x62, 0x61, 0x72, 0x69, 0x73, 0x74, 0x61, 0x5f, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x2e, 0x63, + 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0c, 0x62, 0x61, 0x72, 0x69, 0x73, 0x74, 0x61, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x12, 0x50, 0x0a, 0x0d, 0x6b, 0x69, 0x74, 0x63, 0x68, 0x65, 0x6e, 0x5f, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6c, - 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x6f, 0x79, 0x61, 0x6c, 0x74, 0x79, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x50, 0x0a, 0x0d, 0x62, 0x61, 0x72, 0x69, 0x73, - 0x74, 0x61, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, + 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0c, 0x6b, 0x69, 0x74, 0x63, 0x68, 0x65, + 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x22, 0x14, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, + 0x70, 0x65, 0x32, 0xe2, 0x03, 0x0a, 0x0e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x84, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x73, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x46, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x3e, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, + 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x46, + 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x3f, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, + 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x46, + 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x68, 0x92, 0x41, 0x47, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x16, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x66, 0x75, 0x6c, 0x66, + 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x20, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, + 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0xc8, 0x01, 0x0a, + 0x0a, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x31, 0x2e, 0x67, 0x6f, + 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x6c, 0x61, + 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0c, 0x62, 0x61, 0x72, - 0x69, 0x73, 0x74, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x50, 0x0a, 0x0d, 0x6b, 0x69, 0x74, - 0x63, 0x68, 0x65, 0x6e, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, - 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0c, 0x6b, - 0x69, 0x74, 0x63, 0x68, 0x65, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x14, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x0a, 0x0b, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x41, 0x0a, 0x09, 0x69, 0x74, - 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, - 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x32, 0xe2, 0x03, - 0x0a, 0x0e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x84, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x46, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3e, 0x2e, 0x67, - 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, - 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x46, 0x75, 0x6c, 0x66, 0x69, 0x6c, - 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3f, 0x2e, 0x67, - 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, - 0x74, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x46, 0x75, 0x6c, 0x66, 0x69, 0x6c, - 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x68, 0x92, - 0x41, 0x47, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x16, 0x4c, 0x69, 0x73, 0x74, - 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, - 0x6e, 0x74, 0x1a, 0x25, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x66, - 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, - 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x75, 0x6c, 0x66, 0x69, 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, - 0x2d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0xc8, 0x01, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x63, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x31, 0x2e, 0x67, 0x6f, 0x2e, 0x63, 0x6f, 0x66, 0x66, - 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x63, - 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x6c, 0x61, 0x63, 0x65, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x92, - 0x41, 0x37, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x0e, 0x50, 0x6c, 0x61, 0x63, - 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, 0x1d, 0x50, 0x6c, 0x61, 0x63, - 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, - 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x73, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x74, 0x68, 0x61, 0x6e, 0x67, 0x63, 0x68, 0x75, 0x6e, 0x67, 0x2f, 0x67, 0x6f, 0x2d, 0x63, - 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x50, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x53, 0x92, 0x41, 0x37, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x0e, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, + 0x1d, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, + 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x68, 0x61, 0x6e, 0x67, 0x63, 0x68, 0x75, 0x6e, 0x67, + 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x6f, 0x66, 0x66, 0x65, 0x65, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -618,37 +596,23 @@ var file_counter_proto_goTypes = []interface{}{ (*PlaceOrderRequest)(nil), // 4: go.coffeeshop.proto.counterapi.PlaceOrderRequest (*PlaceOrderResponse)(nil), // 5: go.coffeeshop.proto.counterapi.PlaceOrderResponse (*CommandItem)(nil), // 6: go.coffeeshop.proto.counterapi.CommandItem - (OrderSource)(0), // 7: go.coffeeshop.proto.common.OrderSource - (Status)(0), // 8: go.coffeeshop.proto.common.Status - (Location)(0), // 9: go.coffeeshop.proto.common.Location - (ItemType)(0), // 10: go.coffeeshop.proto.common.ItemType - (CommandType)(0), // 11: go.coffeeshop.proto.common.CommandType - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp + (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp } var file_counter_proto_depIdxs = []int32{ - 2, // 0: go.coffeeshop.proto.counterapi.GetListOrderFulfillmentResponse.orders:type_name -> go.coffeeshop.proto.counterapi.OrderDto - 7, // 1: go.coffeeshop.proto.counterapi.OrderDto.order_source:type_name -> go.coffeeshop.proto.common.OrderSource - 8, // 2: go.coffeeshop.proto.counterapi.OrderDto.order_status:type_name -> go.coffeeshop.proto.common.Status - 9, // 3: go.coffeeshop.proto.counterapi.OrderDto.localtion:type_name -> go.coffeeshop.proto.common.Location - 3, // 4: go.coffeeshop.proto.counterapi.OrderDto.line_items:type_name -> go.coffeeshop.proto.counterapi.LineItemDto - 10, // 5: go.coffeeshop.proto.counterapi.LineItemDto.item_type:type_name -> go.coffeeshop.proto.common.ItemType - 8, // 6: go.coffeeshop.proto.counterapi.LineItemDto.item_status:type_name -> go.coffeeshop.proto.common.Status - 11, // 7: go.coffeeshop.proto.counterapi.PlaceOrderRequest.command_type:type_name -> go.coffeeshop.proto.common.CommandType - 7, // 8: go.coffeeshop.proto.counterapi.PlaceOrderRequest.order_source:type_name -> go.coffeeshop.proto.common.OrderSource - 9, // 9: go.coffeeshop.proto.counterapi.PlaceOrderRequest.location:type_name -> go.coffeeshop.proto.common.Location - 6, // 10: go.coffeeshop.proto.counterapi.PlaceOrderRequest.barista_items:type_name -> go.coffeeshop.proto.counterapi.CommandItem - 6, // 11: go.coffeeshop.proto.counterapi.PlaceOrderRequest.kitchen_items:type_name -> go.coffeeshop.proto.counterapi.CommandItem - 12, // 12: go.coffeeshop.proto.counterapi.PlaceOrderRequest.timestamp:type_name -> google.protobuf.Timestamp - 10, // 13: go.coffeeshop.proto.counterapi.CommandItem.item_type:type_name -> go.coffeeshop.proto.common.ItemType - 0, // 14: go.coffeeshop.proto.counterapi.CounterService.GetListOrderFulfillment:input_type -> go.coffeeshop.proto.counterapi.GetListOrderFulfillmentRequest - 4, // 15: go.coffeeshop.proto.counterapi.CounterService.PlaceOrder:input_type -> go.coffeeshop.proto.counterapi.PlaceOrderRequest - 1, // 16: go.coffeeshop.proto.counterapi.CounterService.GetListOrderFulfillment:output_type -> go.coffeeshop.proto.counterapi.GetListOrderFulfillmentResponse - 5, // 17: go.coffeeshop.proto.counterapi.CounterService.PlaceOrder:output_type -> go.coffeeshop.proto.counterapi.PlaceOrderResponse - 16, // [16:18] is the sub-list for method output_type - 14, // [14:16] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 2, // 0: go.coffeeshop.proto.counterapi.GetListOrderFulfillmentResponse.orders:type_name -> go.coffeeshop.proto.counterapi.OrderDto + 3, // 1: go.coffeeshop.proto.counterapi.OrderDto.line_items:type_name -> go.coffeeshop.proto.counterapi.LineItemDto + 6, // 2: go.coffeeshop.proto.counterapi.PlaceOrderRequest.barista_items:type_name -> go.coffeeshop.proto.counterapi.CommandItem + 6, // 3: go.coffeeshop.proto.counterapi.PlaceOrderRequest.kitchen_items:type_name -> go.coffeeshop.proto.counterapi.CommandItem + 7, // 4: go.coffeeshop.proto.counterapi.PlaceOrderRequest.timestamp:type_name -> google.protobuf.Timestamp + 0, // 5: go.coffeeshop.proto.counterapi.CounterService.GetListOrderFulfillment:input_type -> go.coffeeshop.proto.counterapi.GetListOrderFulfillmentRequest + 4, // 6: go.coffeeshop.proto.counterapi.CounterService.PlaceOrder:input_type -> go.coffeeshop.proto.counterapi.PlaceOrderRequest + 1, // 7: go.coffeeshop.proto.counterapi.CounterService.GetListOrderFulfillment:output_type -> go.coffeeshop.proto.counterapi.GetListOrderFulfillmentResponse + 5, // 8: go.coffeeshop.proto.counterapi.CounterService.PlaceOrder:output_type -> go.coffeeshop.proto.counterapi.PlaceOrderResponse + 7, // [7:9] is the sub-list for method output_type + 5, // [5:7] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_counter_proto_init() } diff --git a/third_party/OpenAPI/counter.swagger.json b/third_party/OpenAPI/counter.swagger.json index d5523b4..367b1d1 100644 --- a/third_party/OpenAPI/counter.swagger.json +++ b/third_party/OpenAPI/counter.swagger.json @@ -76,43 +76,12 @@ } }, "definitions": { - "commonCommandType": { - "type": "string", - "enum": [ - "PLACE_ORDER" - ], - "default": "PLACE_ORDER" - }, - "commonItemType": { - "type": "string", - "enum": [ - "CAPPUCCINO", - "COFFEE_BLACK", - "COFFEE_WITH_ROOM", - "ESPRESSO", - "ESPRESSO_DOUBLE", - "LATTE", - "CAKEPOP", - "CROISSANT", - "MUFFIN", - "CROISSANT_CHOCOLATE" - ], - "default": "CAPPUCCINO", - "title": "- CAPPUCCINO: Beverages\n - CAKEPOP: Food" - }, - "commonOrderSource": { - "type": "string", - "enum": [ - "COUNTER", - "WEB" - ], - "default": "COUNTER" - }, "counterapiCommandItem": { "type": "object", "properties": { "itemType": { - "$ref": "#/definitions/commonItemType" + "type": "integer", + "format": "int32" } } }, @@ -134,7 +103,8 @@ "type": "string" }, "itemType": { - "$ref": "#/definitions/commonItemType" + "type": "integer", + "format": "int32" }, "name": { "type": "string" @@ -144,7 +114,8 @@ "format": "double" }, "itemStatus": { - "$ref": "#/definitions/protocommonStatus" + "type": "integer", + "format": "int32" }, "isBaristaOrder": { "type": "boolean" @@ -158,16 +129,19 @@ "type": "string" }, "orderSource": { - "$ref": "#/definitions/commonOrderSource" + "type": "integer", + "format": "int32" }, "loyaltyMemberId": { "type": "string" }, "orderStatus": { - "$ref": "#/definitions/protocommonStatus" + "type": "integer", + "format": "int32" }, "localtion": { - "$ref": "#/definitions/protocommonLocation" + "type": "integer", + "format": "int32" }, "lineItems": { "type": "array", @@ -181,13 +155,16 @@ "type": "object", "properties": { "commandType": { - "$ref": "#/definitions/commonCommandType" + "type": "integer", + "format": "int32" }, "orderSource": { - "$ref": "#/definitions/commonOrderSource" + "type": "integer", + "format": "int32" }, "location": { - "$ref": "#/definitions/protocommonLocation" + "type": "integer", + "format": "int32" }, "loyaltyMemberId": { "type": "string" @@ -239,24 +216,6 @@ } }, "additionalProperties": {} - }, - "protocommonLocation": { - "type": "string", - "enum": [ - "ATLANTA", - "CHARLOTTE", - "RALEIGH" - ], - "default": "ATLANTA" - }, - "protocommonStatus": { - "type": "string", - "enum": [ - "PLACED", - "IN_PROGRESS", - "FULFILLED" - ], - "default": "PLACED" } } } From 5d26dbbcf5bea9b1e19ddfd2ca06dc9e9f278377 Mon Sep 17 00:00:00 2001 From: thangchung Date: Tue, 20 Dec 2022 16:55:54 +0700 Subject: [PATCH 04/16] fixed bugs --- client.http | 2 +- internal/pkg/event/events.go | 4 ++-- internal/pkg/shared_kernel/enums.go | 29 +++++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/client.http b/client.http index 3871c17..acbda1a 100644 --- a/client.http +++ b/client.http @@ -5,7 +5,7 @@ GET {{host}}/v1/api/item-types HTTP/1.1 content-type: application/json ### -GET {{host}}/v1/api/items-by-types/COFFEE_WITH_ROOM,MUFFIN HTTP/1.1 +GET {{host}}/v1/api/items-by-types/COFFEE_WITH_ROOM,MUFFIN,COFFEE_BLACK,CROISSANT_CHOCOLATE HTTP/1.1 content-type: application/json ### diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go index 600df51..b16f147 100644 --- a/internal/pkg/event/events.go +++ b/internal/pkg/event/events.go @@ -17,7 +17,7 @@ type BaristaOrdered struct { } func (e BaristaOrdered) Identity() string { - return reflect.TypeOf(e).String() + return "BaristaOrdered" } type KitchenOrdered struct { @@ -28,7 +28,7 @@ type KitchenOrdered struct { } func (e KitchenOrdered) Identity() string { - return reflect.TypeOf(e).String() + return "KitchenOrdered" } type BaristaOrderUpdated struct { diff --git a/internal/pkg/shared_kernel/enums.go b/internal/pkg/shared_kernel/enums.go index ab00ef6..0254f2c 100644 --- a/internal/pkg/shared_kernel/enums.go +++ b/internal/pkg/shared_kernel/enums.go @@ -1,6 +1,8 @@ package sharedkernel -import "fmt" +import ( + "fmt" +) type OrderSource int8 @@ -63,5 +65,28 @@ const ( ) func (e ItemType) String() string { - return fmt.Sprintf("%d", int(e)) + switch e { + case ItemTypeCappuccino: + return "CAPPUCCINO" + case ItemTypeCoffeeBlack: + return "COFFEE_BLACK" + case ItemTypeCoffeeWithRoom: + return "COFFEE_WITH_ROOM" + case ItemTypeEspresso: + return "ESPRESSO" + case ItemTypeEspressoDouble: + return "ESPRESSO_DOUBLE" + case ItemTypeLatte: + return "LATTE" + case ItemTypeCakePop: + return "CAKEPOP" + case ItemTypeCroissant: + return "CROISSANT" + case ItemTypeMuffin: + return "MUFFIN" + case ItemTypeCroissantChocolate: + return "CROISSANT_CHOCOLATE" + default: + return "CAPPUCCINO" + } } From 652dd1a36305dbb1b94ff66c2f86c48ad672f37f Mon Sep 17 00:00:00 2001 From: thangchung Date: Wed, 21 Dec 2022 00:57:27 +0700 Subject: [PATCH 05/16] refactor counter, barista, kitchen svc --- internal/barista/app/app.go | 6 +-- .../eventhandlers/barista_ordered.go | 0 .../orders => infras}/repo/orders_postgres.go | 0 internal/counter/app/app.go | 42 ++++++++++++---- internal/counter/domain/interfaces.go | 9 ++++ internal/counter/domain/order.go | 2 +- .../barista_order_updated.go} | 17 +++++-- .../eventhandlers/kitchen_order_updated.go | 48 +++++++++++++++++++ .../counter/usecases/orders/interfaces.go | 5 -- internal/kitchen/app/app.go | 6 +-- .../eventhandlers/kitchen_ordered.go | 0 .../orders => infras}/repo/orders_postgres.go | 0 internal/pkg/event/events.go | 20 ++++++-- 13 files changed, 126 insertions(+), 29 deletions(-) rename internal/barista/{usecases/orders => }/eventhandlers/barista_ordered.go (100%) rename internal/barista/{usecases/orders => infras}/repo/orders_postgres.go (100%) rename internal/counter/{usecases/orders/event_handlers.go => eventhandlers/barista_order_updated.go} (71%) create mode 100644 internal/counter/eventhandlers/kitchen_order_updated.go rename internal/kitchen/{usecases/orders => }/eventhandlers/kitchen_ordered.go (100%) rename internal/kitchen/{usecases/orders => infras}/repo/orders_postgres.go (100%) diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 5eb14a5..04a4fe2 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -11,8 +11,8 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" - "github.com/thangchung/go-coffeeshop/internal/barista/usecases/orders/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/barista/usecases/orders/repo" + "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" + "github.com/thangchung/go-coffeeshop/internal/barista/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -69,7 +69,7 @@ func (a *App) Run() error { a.logger, publisher.ExchangeName("counter-order-exchange"), publisher.BindingKey("counter-order-routing-key"), - publisher.MessageTypeName("counter-order-updated"), + publisher.MessageTypeName("barista-order-updated"), ) defer counterOrderPub.CloseChan() diff --git a/internal/barista/usecases/orders/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go similarity index 100% rename from internal/barista/usecases/orders/eventhandlers/barista_ordered.go rename to internal/barista/eventhandlers/barista_ordered.go diff --git a/internal/barista/usecases/orders/repo/orders_postgres.go b/internal/barista/infras/repo/orders_postgres.go similarity index 100% rename from internal/barista/usecases/orders/repo/orders_postgres.go rename to internal/barista/infras/repo/orders_postgres.go diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 9c67937..159e923 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -9,6 +9,8 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/counter/eventhandlers" counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" @@ -23,11 +25,12 @@ import ( ) type App struct { - logger *mylogger.Logger - cfg *config.Config - network string - address string - handler orders.BaristaOrderUpdatedEventHandler + logger *mylogger.Logger + cfg *config.Config + network string + address string + baristaHandler domain.BaristaOrderUpdatedEventHandler + kitchenHandler domain.KitchenOrderUpdatedEventHandler } func New(log *mylogger.Logger, cfg *config.Config) *App { @@ -126,7 +129,8 @@ func (a *App) Run() error { ) // event handlers. - a.handler = orders.NewBaristaOrderUpdatedEventHandler(orderRepo) + a.baristaHandler = eventhandlers.NewBaristaOrderUpdatedEventHandler(orderRepo) + a.kitchenHandler = eventhandlers.NewKitchenOrderUpdatedEventHandler(orderRepo) // consumers consumer, err := rabConsumer.NewConsumer( @@ -187,7 +191,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { c.logger.Info("received %s", delivery.Type) switch delivery.Type { - case "counter-order-updated": + case "barista-order-updated": var payload event.BaristaOrderUpdated err := json.Unmarshal(delivery.Body, &payload) @@ -195,7 +199,29 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { c.logger.LogError(err) } - err = c.handler.Handle(ctx, &payload) + err = c.baristaHandler.Handle(ctx, &payload) + + if err != nil { + if err = delivery.Reject(false); err != nil { + c.logger.Error("Err delivery.Reject: %v", err) + } + + c.logger.Error("Failed to process delivery: %v", err) + } else { + err = delivery.Ack(false) + if err != nil { + c.logger.Error("Failed to acknowledge delivery: %v", err) + } + } + case "kitchen-order-updated": + var payload event.KitchenOrderUpdated + err := json.Unmarshal(delivery.Body, &payload) + + if err != nil { + c.logger.LogError(err) + } + + err = c.kitchenHandler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { diff --git a/internal/counter/domain/interfaces.go b/internal/counter/domain/interfaces.go index 01a924a..743cecd 100644 --- a/internal/counter/domain/interfaces.go +++ b/internal/counter/domain/interfaces.go @@ -4,6 +4,7 @@ import ( "context" "github.com/google/uuid" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) type ( @@ -17,4 +18,12 @@ type ( ProductDomainService interface { GetItemsByType(context.Context, *PlaceOrderModel, bool) ([]*ItemModel, error) } + + BaristaOrderUpdatedEventHandler interface { + Handle(context.Context, *event.BaristaOrderUpdated) error + } + + KitchenOrderUpdatedEventHandler interface { + Handle(context.Context, *event.KitchenOrderUpdated) error + } ) diff --git a/internal/counter/domain/order.go b/internal/counter/domain/order.go index eb4284c..09525f8 100644 --- a/internal/counter/domain/order.go +++ b/internal/counter/domain/order.go @@ -109,7 +109,7 @@ func CreateOrderFrom( return order, nil } -func (o *Order) Apply(event *event.BaristaOrderUpdated) error { +func (o *Order) Apply(event *event.OrderUp) error { if len(o.LineItems) == 0 { return nil // we dont do anything } diff --git a/internal/counter/usecases/orders/event_handlers.go b/internal/counter/eventhandlers/barista_order_updated.go similarity index 71% rename from internal/counter/usecases/orders/event_handlers.go rename to internal/counter/eventhandlers/barista_order_updated.go index aa0894c..88c12fc 100644 --- a/internal/counter/usecases/orders/event_handlers.go +++ b/internal/counter/eventhandlers/barista_order_updated.go @@ -1,4 +1,4 @@ -package orders +package eventhandlers import ( "context" @@ -12,9 +12,9 @@ type baristaOrderUpdatedEventHandler struct { orderRepo domain.OrderRepo } -var _ BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) +var _ domain.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) -func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) BaristaOrderUpdatedEventHandler { +func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.BaristaOrderUpdatedEventHandler { return &baristaOrderUpdatedEventHandler{ orderRepo: orderRepo, } @@ -26,7 +26,16 @@ func (h *baristaOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.B return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) } - if err = order.Apply(e); err != nil { + orderUp := event.OrderUp{ + OrderID: e.OrderID, + ItemLineID: e.ItemLineID, + Name: e.Name, + ItemType: e.ItemType, + TimeUp: e.TimeUp, + MadeBy: e.MadeBy, + } + + if err = order.Apply(&orderUp); err != nil { return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) } diff --git a/internal/counter/eventhandlers/kitchen_order_updated.go b/internal/counter/eventhandlers/kitchen_order_updated.go new file mode 100644 index 0000000..2a4b470 --- /dev/null +++ b/internal/counter/eventhandlers/kitchen_order_updated.go @@ -0,0 +1,48 @@ +package eventhandlers + +import ( + "context" + "fmt" + + "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type kitchenOrderUpdatedEventHandler struct { + orderRepo domain.OrderRepo +} + +var _ domain.KitchenOrderUpdatedEventHandler = (*kitchenOrderUpdatedEventHandler)(nil) + +func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.KitchenOrderUpdatedEventHandler { + return &kitchenOrderUpdatedEventHandler{ + orderRepo: orderRepo, + } +} + +func (h *kitchenOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.KitchenOrderUpdated) error { + order, err := h.orderRepo.GetByID(ctx, e.OrderID) + if err != nil { + return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) + } + + orderUp := event.OrderUp{ + OrderID: e.OrderID, + ItemLineID: e.ItemLineID, + Name: e.Name, + ItemType: e.ItemType, + TimeUp: e.TimeUp, + MadeBy: e.MadeBy, + } + + if err = order.Apply(&orderUp); err != nil { + return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) + } + + _, err = h.orderRepo.Update(ctx, order) + if err != nil { + return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-h.orderRepo.Update(ctx, ToDto(order)): %w", err) + } + + return nil +} diff --git a/internal/counter/usecases/orders/interfaces.go b/internal/counter/usecases/orders/interfaces.go index 3521830..5f4c191 100644 --- a/internal/counter/usecases/orders/interfaces.go +++ b/internal/counter/usecases/orders/interfaces.go @@ -4,7 +4,6 @@ import ( "context" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) type ( @@ -12,8 +11,4 @@ type ( GetListOrderFulfillment(context.Context) ([]*domain.Order, error) PlaceOrder(context.Context, *domain.PlaceOrderModel) error } - - BaristaOrderUpdatedEventHandler interface { - Handle(context.Context, *event.BaristaOrderUpdated) error - } ) diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index 2e9f10a..4d0083e 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -11,8 +11,8 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" - "github.com/thangchung/go-coffeeshop/internal/kitchen/usecases/orders/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/kitchen/usecases/orders/repo" + "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" + "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -69,7 +69,7 @@ func (a *App) Run() error { a.logger, publisher.ExchangeName("counter-order-exchange"), publisher.BindingKey("counter-order-routing-key"), - publisher.MessageTypeName("counter-order-updated"), + publisher.MessageTypeName("kitchen-order-updated"), ) defer counterOrderPub.CloseChan() diff --git a/internal/kitchen/usecases/orders/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go similarity index 100% rename from internal/kitchen/usecases/orders/eventhandlers/kitchen_ordered.go rename to internal/kitchen/eventhandlers/kitchen_ordered.go diff --git a/internal/kitchen/usecases/orders/repo/orders_postgres.go b/internal/kitchen/infras/repo/orders_postgres.go similarity index 100% rename from internal/kitchen/usecases/orders/repo/orders_postgres.go rename to internal/kitchen/infras/repo/orders_postgres.go diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go index b16f147..84bd9e9 100644 --- a/internal/pkg/event/events.go +++ b/internal/pkg/event/events.go @@ -1,7 +1,6 @@ package event import ( - "reflect" "time" "github.com/google/uuid" @@ -32,7 +31,6 @@ func (e KitchenOrdered) Identity() string { } type BaristaOrderUpdated struct { - shared.DomainEvent OrderID uuid.UUID `json:"orderId"` ItemLineID uuid.UUID `json:"itemLineId"` Name string `json:"name"` @@ -43,11 +41,10 @@ type BaristaOrderUpdated struct { } func (e BaristaOrderUpdated) Identity() string { - return reflect.TypeOf(e).String() + return "BaristaOrderUpdated" } type KitchenOrderUpdated struct { - shared.DomainEvent OrderID uuid.UUID `json:"orderId"` ItemLineID uuid.UUID `json:"itemLineId"` Name string `json:"name"` @@ -58,5 +55,18 @@ type KitchenOrderUpdated struct { } func (e KitchenOrderUpdated) Identity() string { - return reflect.TypeOf(e).String() + return "KitchenOrderUpdated" +} + +type OrderUp struct { + OrderID uuid.UUID `json:"orderId"` + ItemLineID uuid.UUID `json:"itemLineId"` + Name string `json:"name"` + ItemType shared.ItemType `json:"itemType"` + TimeUp time.Time `json:"timeUp"` + MadeBy string `json:"madeBy"` +} + +func (e OrderUp) Identity() string { + return "OrderUp" } From d7b6fce6dc3a38631b1d15a2a62d7f3d40b58f9c Mon Sep 17 00:00:00 2001 From: thangchung Date: Wed, 21 Dec 2022 18:03:01 +0700 Subject: [PATCH 06/16] removed mylogger --- cmd/barista/main.go | 19 +++++-- cmd/counter/main.go | 19 +++++-- cmd/kitchen/main.go | 19 +++++-- cmd/proxy/main.go | 30 ++++++---- internal/barista/app/app.go | 42 +++++++------- internal/counter/app/app.go | 57 +++++++++---------- internal/kitchen/app/app.go | 44 +++++++------- .../kitchen/eventhandlers/kitchen_ordered.go | 7 +-- .../product/infras/grpc/product_server.go | 4 +- pkg/rabbitmq/consumer/consumer.go | 20 ++----- pkg/rabbitmq/publisher/publisher.go | 10 ++-- pkg/rabbitmq/rabbitmq.go | 12 ++-- 12 files changed, 143 insertions(+), 140 deletions(-) diff --git a/cmd/barista/main.go b/cmd/barista/main.go index 922bcd1..48ac68e 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -3,23 +3,30 @@ package main import ( "os" - "github.com/golang/glog" + "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/app" - mylog "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) func main() { cfg, err := config.NewConfig() if err != nil { - glog.Fatal(err) + slog.Error("failed get config", err) } - logger := mylog.New(cfg.Level) + // set up logrus + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logger.ConvertLogLevel(cfg.Log.Level)) - a := app.New(logger, cfg) + // integrate Logrus with the slog logger + slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) + + a := app.New(cfg) if err = a.Run(); err != nil { - glog.Fatal(err) + slog.Error("failed app run", err) os.Exit(1) } } diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 0e190dc..9bf0cac 100755 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -3,23 +3,30 @@ package main import ( "os" - "github.com/golang/glog" + "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) func main() { cfg, err := config.NewConfig() if err != nil { - glog.Fatal(err) + slog.Error("failed get config", err) } - mylog := mylogger.New(cfg.Level) + // set up logrus + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logger.ConvertLogLevel(cfg.Log.Level)) - a := app.New(mylog, cfg) + // integrate Logrus with the slog logger + slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) + + a := app.New(cfg) if err = a.Run(); err != nil { - glog.Fatal(err) + slog.Error("failed app run", err) os.Exit(1) } } diff --git a/cmd/kitchen/main.go b/cmd/kitchen/main.go index e30cb1e..e6b101e 100755 --- a/cmd/kitchen/main.go +++ b/cmd/kitchen/main.go @@ -3,23 +3,30 @@ package main import ( "os" - "github.com/golang/glog" + "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/app" - mylog "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) func main() { cfg, err := config.NewConfig() if err != nil { - glog.Fatal(err) + slog.Error("failed get config", err) } - logger := mylog.New(cfg.Level) + // set up logrus + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logger.ConvertLogLevel(cfg.Log.Level)) - a := app.New(logger, cfg) + // integrate Logrus with the slog logger + slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) + + a := app.New(cfg) if err = a.Run(); err != nil { - glog.Fatal(err) + slog.Error("failed app run", err) os.Exit(1) } } diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index bc9ab4e..a350256 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "net/http" + "os" "strings" "github.com/golang/glog" gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/proxy/config" - mylog "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/logger" gen "github.com/thangchung/go-coffeeshop/proto/gen" + "golang.org/x/exp/slog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -63,12 +66,12 @@ func preflightHandler(w http.ResponseWriter, r *http.Request) { methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"} w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ",")) - glog.Infof("preflight request for %s", r.URL.Path) + slog.Info("preflight request", "http_path", r.URL.Path) } func withLogger(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - glog.Infof("Run %s %s", r.Method, r.URL) + slog.Info("Run request", "http_method", r.Method, "http_url", r.URL) h.ServeHTTP(w, r) }) @@ -76,8 +79,8 @@ func withLogger(h http.Handler) http.Handler { func main() { ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancel(ctx) defer cancel() cfg, err := config.NewConfig() @@ -85,14 +88,19 @@ func main() { glog.Fatalf("Config error: %s", err) } - logger := mylog.New(cfg.Log.Level) - logger.Info("Init %s %s\n", cfg.Name, cfg.Version) + // set up logrus + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.SetOutput(os.Stdout) + logrus.SetLevel(logger.ConvertLogLevel(cfg.Log.Level)) + + // integrate Logrus with the slog logger + slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) mux := http.NewServeMux() gw, err := newGateway(ctx, cfg, nil) if err != nil { - logger.Fatal("%s", err) + slog.Error("failed to create a new gateway", err, err.Error()) } mux.Handle("/", gw) @@ -104,16 +112,16 @@ func main() { go func() { <-ctx.Done() - glog.Infof("Shutting down the http server") + slog.Info("shutting down the http server") if err := s.Shutdown(context.Background()); err != nil { - glog.Errorf("Failed to shutdown http server: %v", err) + slog.Error("Failed to shutdown http server: %v", err) } }() - glog.Infof("Starting listening at %s", fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)) + slog.Info("start listening...", "address", fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)) if err := s.ListenAndServe(); errors.Is(err, http.ErrServerClosed) { - glog.Errorf("Failed to listen and serve: %v", err) + slog.Error("failed to listen and serve", err, err.Error()) } } diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 04a4fe2..b455997 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -14,24 +14,22 @@ import ( "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/barista/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "golang.org/x/exp/slog" ) type App struct { - logger *mylogger.Logger cfg *config.Config network string address string handler eventhandlers.BaristaOrderedEventHandler } -func New(log *mylogger.Logger, cfg *config.Config) *App { +func New(cfg *config.Config) *App { return &App{ - logger: log, cfg: cfg, network: "tcp", address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), @@ -39,14 +37,14 @@ func New(log *mylogger.Logger, cfg *config.Config) *App { } func (a *App) Run() error { - a.logger.Info("Init %s %s\n", a.cfg.Name, a.cfg.Version) + slog.Info("init app", "name", a.cfg.Name, "version", a.cfg.Version) ctx, cancel := context.WithCancel(context.Background()) // PostgresDB pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { - a.logger.Fatal("app - Run - postgres.NewPostgres: %s", err.Error()) + slog.Error("failed to create a new Postgres", err, err.Error()) cancel() @@ -55,18 +53,17 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL, a.logger) + amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { cancel() - a.logger.Fatal("app - Run - rabbitmq.NewRabbitMQConn: %s", err.Error()) + slog.Error("failed to create a new RabbitMQConn", err, err.Error()) } defer amqpConn.Close() // publishers counterOrderPub, err := publisher.NewPublisher( amqpConn, - a.logger, publisher.ExchangeName("counter-order-exchange"), publisher.BindingKey("counter-order-routing-key"), publisher.MessageTypeName("barista-order-updated"), @@ -88,7 +85,6 @@ func (a *App) Run() error { // consumers consumer, err := consumer.NewConsumer( amqpConn, - a.logger, consumer.ExchangeName("barista-order-exchange"), consumer.QueueName("barista-order-queue"), consumer.BindingKey("barista-order-routing-key"), @@ -96,14 +92,14 @@ func (a *App) Run() error { ) if err != nil { - a.logger.Fatal("app - Run - consumer.NewOrderConsumer: %s", err.Error()) + slog.Error("failed to create a new OrderConsumer", err, err.Error()) cancel() } go func() { err := consumer.StartConsumer(a.worker) if err != nil { - a.logger.Error("StartConsumer: %v", err) + slog.Error("failed to start Consumer", err) cancel() } }() @@ -113,20 +109,20 @@ func (a *App) Run() error { select { case v := <-quit: - a.logger.Error("signal.Notify: %v", v) + slog.Info("signal.Notify", v) case done := <-ctx.Done(): - a.logger.Error("ctx.Done: %v", done) + slog.Info("ctx.Done", done) } - a.logger.Info("Start server at " + a.address + " ...") + slog.Info("start server...", "address", a.address) return nil } func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { - c.logger.Info("processDeliveries deliveryTag %v", delivery.DeliveryTag) - c.logger.Info("received %s", delivery.Type) + slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) + slog.Info("received", "delivery_type", delivery.Type) switch delivery.Type { case "barista-order-created": @@ -134,27 +130,27 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { err := json.Unmarshal(delivery.Body, &payload) if err != nil { - c.logger.LogError(err) + slog.Error("failed to Unmarshal", err, err.Error()) } err = c.handler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { - c.logger.Error("Err delivery.Reject: %v", err) + slog.Error("failed to delivery.Reject", err, err.Error()) } - c.logger.Error("Failed to process delivery: %v", err) + slog.Error("failed to process delivery", err, err.Error()) } else { err = delivery.Ack(false) if err != nil { - c.logger.Error("Failed to acknowledge delivery: %v", err) + slog.Error("failed to acknowledge delivery", err, err.Error()) } } default: - c.logger.Info("default") + slog.Info("default") } } - c.logger.Info("Deliveries channel closed") + slog.Info("Deliveries channel closed") } diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 159e923..030a66c 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -15,17 +15,16 @@ import ( "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" "github.com/thangchung/go-coffeeshop/internal/pkg/event" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" rabConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "golang.org/x/exp/slog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) type App struct { - logger *mylogger.Logger cfg *config.Config network string address string @@ -33,9 +32,8 @@ type App struct { kitchenHandler domain.KitchenOrderUpdatedEventHandler } -func New(log *mylogger.Logger, cfg *config.Config) *App { +func New(cfg *config.Config) *App { return &App{ - logger: log, cfg: cfg, network: "tcp", address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), @@ -43,14 +41,14 @@ func New(log *mylogger.Logger, cfg *config.Config) *App { } func (a *App) Run() error { - a.logger.Info("Init %s %s\n", a.cfg.Name, a.cfg.Version) + slog.Info("Init app", "name", a.cfg.Name, "version", a.cfg.Version) ctx, cancel := context.WithCancel(context.Background()) // PostgresDB pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { - a.logger.Fatal("app - Run - postgres.NewPostgres: %s", err.Error()) + slog.Error("failed to create new instance of postgres", err, err.Error()) cancel() @@ -59,9 +57,9 @@ func (a *App) Run() error { defer pg.Close() // RabbitMQ - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL, a.logger) + amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { - a.logger.Fatal("app - Run - rabbitmq.NewRabbitMQConn: %s", err.Error()) + slog.Error("failed to create a new RabbitMQConn", err, err.Error()) cancel() @@ -80,7 +78,6 @@ func (a *App) Run() error { baristaOrderPub, err := publisher.NewPublisher( amqpConn, - a.logger, publisher.ExchangeName("barista-order-exchange"), publisher.BindingKey("barista-order-routing-key"), publisher.MessageTypeName("barista-order-created"), @@ -95,7 +92,6 @@ func (a *App) Run() error { kitchenOrderPub, err := publisher.NewPublisher( amqpConn, - a.logger, publisher.ExchangeName("kitchen-order-exchange"), publisher.BindingKey("kitchen-order-routing-key"), publisher.MessageTypeName("kitchen-order-created"), @@ -108,7 +104,7 @@ func (a *App) Run() error { return errors.Wrap(err, "counterRabbitMQ-Kitchen-NewOrderPublisher") } - a.logger.Info("Order Publisher initialized") + slog.Info("Order Publisher initialized") // repository orderRepo := repo.NewOrderRepo(pg) @@ -135,7 +131,6 @@ func (a *App) Run() error { // consumers consumer, err := rabConsumer.NewConsumer( amqpConn, - a.logger, rabConsumer.ExchangeName("counter-order-exchange"), rabConsumer.QueueName("counter-order-queue"), rabConsumer.BindingKey("counter-order-routing-key"), @@ -143,13 +138,13 @@ func (a *App) Run() error { ) if err != nil { - a.logger.Fatal("app-Run-consumer.NewOrderConsumer: %s", err.Error()) + slog.Error("failed to create a new consumer", err, err.Error()) } go func() { err = consumer.StartConsumer(a.worker) if err != nil { - a.logger.Error("StartConsumer: %v", err) + slog.Error("failed to start consumer: %v", err) cancel() } }() @@ -157,14 +152,14 @@ func (a *App) Run() error { // gRPC Server l, err := net.Listen(a.network, a.address) if err != nil { - a.logger.Fatal("app-Run-net.Listener: %s", err.Error()) + slog.Error("failed to listen to address", err, err.Error(), "network", a.network, "address", a.address) return err } defer func() { if err := l.Close(); err != nil { - a.logger.Error("Failed to close %s %s: %v", a.network, a.address, err) + slog.Error("failed to close", err, "network", a.network, "address", a.address) } }() @@ -180,65 +175,65 @@ func (a *App) Run() error { <-ctx.Done() }() - a.logger.Info("Start server at " + a.address + " ...") + slog.Info("start server...", "address", a.address) return server.Serve(l) } func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { - c.logger.Info("processDeliveries deliveryTag %v", delivery.DeliveryTag) - c.logger.Info("received %s", delivery.Type) + slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) + slog.Info("received", "delivery_type", delivery.Type) switch delivery.Type { case "barista-order-updated": var payload event.BaristaOrderUpdated - err := json.Unmarshal(delivery.Body, &payload) + err := json.Unmarshal(delivery.Body, &payload) if err != nil { - c.logger.LogError(err) + slog.Error("failed to Unmarshal message", err) } err = c.baristaHandler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { - c.logger.Error("Err delivery.Reject: %v", err) + slog.Error("failed to delivery.Reject", err) } - c.logger.Error("Failed to process delivery: %v", err) + slog.Error("failed to process delivery", err) } else { err = delivery.Ack(false) if err != nil { - c.logger.Error("Failed to acknowledge delivery: %v", err) + slog.Error("failed to acknowledge delivery", err) } } case "kitchen-order-updated": var payload event.KitchenOrderUpdated - err := json.Unmarshal(delivery.Body, &payload) + err := json.Unmarshal(delivery.Body, &payload) if err != nil { - c.logger.LogError(err) + slog.Error("failed to Unmarshal message", err) } err = c.kitchenHandler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { - c.logger.Error("Err delivery.Reject: %v", err) + slog.Error("failed to delivery.Reject", err) } - c.logger.Error("Failed to process delivery: %v", err) + slog.Error("failed to process delivery", err) } else { err = delivery.Ack(false) if err != nil { - c.logger.Error("Failed to acknowledge delivery: %v", err) + slog.Error("failed to acknowledge delivery", err) } } default: - c.logger.Info("default") + slog.Info("default") } } - c.logger.Info("Deliveries channel closed") + slog.Info("Deliveries channel closed") } diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index 4d0083e..a25f49b 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -14,24 +14,22 @@ import ( "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "golang.org/x/exp/slog" ) type App struct { - logger *mylogger.Logger cfg *config.Config network string address string handler eventhandlers.KitchenOrderedEventHandler } -func New(log *mylogger.Logger, cfg *config.Config) *App { +func New(cfg *config.Config) *App { return &App{ - logger: log, cfg: cfg, network: "tcp", address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), @@ -39,14 +37,14 @@ func New(log *mylogger.Logger, cfg *config.Config) *App { } func (a *App) Run() error { - a.logger.Info("Init %s %s\n", a.cfg.Name, a.cfg.Version) + slog.Info("init app", "name", a.cfg.Name, "version", a.cfg.Version) ctx, cancel := context.WithCancel(context.Background()) // postgresdb. pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { - a.logger.Fatal("app - Run - postgres.NewPostgres: %s", err.Error()) + slog.Error("failed to create a new Postgres", err, err.Error()) cancel() @@ -55,18 +53,17 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL, a.logger) + amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { cancel() - a.logger.Fatal("App-Run-rabbitmq.NewRabbitMQConn: %s", err.Error()) + slog.Error("failed to create a new RabbitMQConn", err, err.Error()) } defer amqpConn.Close() // publishers counterOrderPub, err := publisher.NewPublisher( amqpConn, - a.logger, publisher.ExchangeName("counter-order-exchange"), publisher.BindingKey("counter-order-routing-key"), publisher.MessageTypeName("kitchen-order-updated"), @@ -83,12 +80,11 @@ func (a *App) Run() error { orderRepo := repo.NewOrderRepo(pg) // event handlers. - a.handler = eventhandlers.NewKitchenOrderedEventHandler(orderRepo, counterOrderPub, a.logger) + a.handler = eventhandlers.NewKitchenOrderedEventHandler(orderRepo, counterOrderPub) // consumers. consumer, err := consumer.NewConsumer( amqpConn, - a.logger, consumer.ExchangeName("kitchen-order-exchange"), consumer.QueueName("kitchen-order-queue"), consumer.BindingKey("kitchen-order-routing-key"), @@ -96,14 +92,14 @@ func (a *App) Run() error { ) if err != nil { - a.logger.Fatal("App-Run-consumer.NewOrderConsumer: %s", err.Error()) + slog.Error("failed to create a new OrderConsumer", err, err.Error()) cancel() } go func() { err := consumer.StartConsumer(a.worker) if err != nil { - a.logger.Error("StartConsumer: %v", err) + slog.Error("failed to start Consumer", err) cancel() } }() @@ -113,20 +109,20 @@ func (a *App) Run() error { select { case v := <-quit: - a.logger.Error("signal.Notify: %v", v) + slog.Info("signal.Notify", v) case done := <-ctx.Done(): - a.logger.Error("ctx.Done: %v", done) + slog.Info("ctx.Done", done) } - a.logger.Info("Start server at " + a.address + " ...") + slog.Info("start server...", "address", a.address) return nil } func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { - c.logger.Info("processDeliveries deliveryTag %v", delivery.DeliveryTag) - c.logger.Info("received %s", delivery.Type) + slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) + slog.Info("received", "delivery_type", delivery.Type) switch delivery.Type { case "kitchen-order-created": @@ -134,27 +130,27 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { err := json.Unmarshal(delivery.Body, &payload) if err != nil { - c.logger.LogError(err) + slog.Error("failed to Unmarshal message", err) } err = c.handler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { - c.logger.Error("Err delivery.Reject: %v", err) + slog.Error("failed to delivery.Reject", err, err.Error()) } - c.logger.Error("Failed to process delivery: %v", err) + slog.Error("failed to process delivery", err, err.Error()) } else { err = delivery.Ack(false) if err != nil { - c.logger.Error("Failed to acknowledge delivery: %v", err) + slog.Error("failed to acknowledge delivery", err, err.Error()) } } default: - c.logger.Info("default") + slog.Info("default") } } - c.logger.Info("Deliveries channel closed") + slog.Info("deliveries channel closed") } diff --git a/internal/kitchen/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go index 12cef26..3480210 100644 --- a/internal/kitchen/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/eventhandlers/kitchen_ordered.go @@ -9,8 +9,8 @@ import ( "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" "github.com/thangchung/go-coffeeshop/internal/pkg/event" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "golang.org/x/exp/slog" ) type KitchenOrderedEventHandler interface { @@ -22,23 +22,20 @@ var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) type kitchenOrderedEventHandler struct { repo domain.OrderRepo counterPub *publisher.Publisher - logger *mylogger.Logger } func NewKitchenOrderedEventHandler( repo domain.OrderRepo, counterPub *publisher.Publisher, - logger *mylogger.Logger, ) KitchenOrderedEventHandler { return &kitchenOrderedEventHandler{ repo: repo, counterPub: counterPub, - logger: logger, } } func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.KitchenOrdered) error { - h.logger.Info("kitchenOrderedEventHandler-Handle: %v", e) + slog.Info("kitchenOrderedEventHandler-Handle", "KitchenOrdered", e) timeIn := time.Now() diff --git a/internal/product/infras/grpc/product_server.go b/internal/product/infras/grpc/product_server.go index b202f04..1ca2732 100644 --- a/internal/product/infras/grpc/product_server.go +++ b/internal/product/infras/grpc/product_server.go @@ -33,7 +33,7 @@ func (g *productGRPCServer) GetItemTypes( ctx context.Context, request *gen.GetItemTypesRequest, ) (*gen.GetItemTypesResponse, error) { - slog.Info("GET: GetItemTypes") + slog.Info("gRPC client", "http_method", "GET", "http_name", "GetItemTypes") res := gen.GetItemTypesResponse{} @@ -58,7 +58,7 @@ func (g *productGRPCServer) GetItemsByType( ctx context.Context, request *gen.GetItemsByTypeRequest, ) (*gen.GetItemsByTypeResponse, error) { - slog.Info("GET: GetItemsByType", "itemTypes", request.ItemTypes) + slog.Info("gRPC client", "http_method", "GET", "http_name", "GetItemsByType", "item_types", request.ItemTypes) res := gen.GetItemsByTypeResponse{} diff --git a/pkg/rabbitmq/consumer/consumer.go b/pkg/rabbitmq/consumer/consumer.go index fe0d610..d12b939 100644 --- a/pkg/rabbitmq/consumer/consumer.go +++ b/pkg/rabbitmq/consumer/consumer.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" amqp "github.com/rabbitmq/amqp091-go" - log "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) const ( @@ -42,17 +42,14 @@ type Consumer struct { exchangeName, queueName, bindingKey, consumerTag string workerPoolSize int amqpConn *amqp.Connection - logger *log.Logger } func NewConsumer( amqpConn *amqp.Connection, - logger *log.Logger, opts ...Option, ) (*Consumer, error) { sub := &Consumer{ amqpConn: amqpConn, - logger: logger, exchangeName: _exchangeName, queueName: _queueName, bindingKey: _bindingKey, @@ -74,7 +71,7 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return nil, errors.Wrap(err, "Error amqpConn.Channel") } - c.logger.Info("Declaring exchange: %s", c.exchangeName) + slog.Info("declaring exchange", c.exchangeName) err = ch.ExchangeDeclare( c.exchangeName, _exchangeKind, @@ -101,13 +98,8 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return nil, errors.Wrap(err, "Error ch.QueueDeclare") } - c.logger.Info("Declared queue, binding it to exchange: Queue: %v, messagesCount: %v, "+ - "consumerCount: %v, exchange: %v, bindingKey: %v", - queue.Name, - queue.Messages, - queue.Consumers, - c.exchangeName, - c.bindingKey, + slog.Info("declared queue, binding it to exchange", "queue", queue.Name, "messages_count", queue.Messages, + "consumer_count", queue.Consumers, "exchange", c.exchangeName, "binding_key", c.bindingKey, ) err = ch.QueueBind( @@ -121,7 +113,7 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return nil, errors.Wrap(err, "Error ch.QueueBind") } - c.logger.Info("Queue bound to exchange, starting to consume from queue, consumerTag: %v", c.consumerTag) + slog.Info("queue bound to exchange, starting to consume from queue", "consumer_tag", c.consumerTag) err = ch.Qos( _prefetchCount, // prefetch count @@ -166,7 +158,7 @@ func (c *Consumer) StartConsumer(fn worker) error { } chanErr := <-ch.NotifyClose(make(chan *amqp.Error)) - c.logger.Error("ch.NotifyClose: %v", chanErr) + slog.Error("ch.NotifyClose", chanErr) <-forever return chanErr diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index 77ae59f..dc3b312 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -7,7 +7,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" amqp "github.com/rabbitmq/amqp091-go" - log "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) const ( @@ -24,10 +24,9 @@ type Publisher struct { messageTypeName string amqpChan *amqp.Channel amqpConn *amqp.Connection - logger *log.Logger } -func NewPublisher(amqpConn *amqp.Connection, logger *log.Logger, opts ...Option) (*Publisher, error) { +func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (*Publisher, error) { ch, err := amqpConn.Channel() if err != nil { panic(err) @@ -37,7 +36,6 @@ func NewPublisher(amqpConn *amqp.Connection, logger *log.Logger, opts ...Option) pub := &Publisher{ amqpConn: amqpConn, amqpChan: ch, - logger: logger, exchangeName: _exchangeName, bindingKey: _bindingKey, messageTypeName: _messageTypeName, @@ -53,7 +51,7 @@ func NewPublisher(amqpConn *amqp.Connection, logger *log.Logger, opts ...Option) // CloseChan Close messages chan. func (p *Publisher) CloseChan() { if err := p.amqpChan.Close(); err != nil { - p.logger.Error("Publisher CloseChan: %v", err) + slog.Error("failed to close chan", err) } } @@ -65,7 +63,7 @@ func (p *Publisher) Publish(ctx context.Context, body []byte, contentType string } defer ch.Close() - p.logger.Info("Publishing message Exchange: %s, RoutingKey: %s", p.exchangeName, p.bindingKey) + slog.Info("publish message", "exchange", p.exchangeName, "routing_key", p.bindingKey) if err := ch.PublishWithContext( ctx, diff --git a/pkg/rabbitmq/rabbitmq.go b/pkg/rabbitmq/rabbitmq.go index 8fc1da4..8fe6b4d 100644 --- a/pkg/rabbitmq/rabbitmq.go +++ b/pkg/rabbitmq/rabbitmq.go @@ -5,7 +5,7 @@ import ( "time" amqp "github.com/rabbitmq/amqp091-go" - mylogger "github.com/thangchung/go-coffeeshop/pkg/logger" + "golang.org/x/exp/slog" ) const ( @@ -15,7 +15,7 @@ const ( var ErrCannotConnectRabbitMQ = errors.New("cannot connect to rabbit") -func NewRabbitMQConn(rabbitMqURL string, logger *mylogger.Logger) (*amqp.Connection, error) { +func NewRabbitMQConn(rabbitMqURL string) (*amqp.Connection, error) { var ( amqpConn *amqp.Connection counts int64 @@ -24,7 +24,7 @@ func NewRabbitMQConn(rabbitMqURL string, logger *mylogger.Logger) (*amqp.Connect for { connection, err := amqp.Dial(rabbitMqURL) if err != nil { - logger.Error("RabbitMq at %s not ready...\n", rabbitMqURL) + slog.Error("failed to connect to RabbitMq...", err, rabbitMqURL) counts++ } else { amqpConn = connection @@ -33,18 +33,18 @@ func NewRabbitMQConn(rabbitMqURL string, logger *mylogger.Logger) (*amqp.Connect } if counts > _retryTimes { - logger.LogError(err) + slog.Error("failed to retry", err) return nil, ErrCannotConnectRabbitMQ } - logger.Info("Backing off for 2 seconds...") + slog.Info("Backing off for 2 seconds...") time.Sleep(_backOffSeconds * time.Second) continue } - logger.Info("Connected to RabbitMQ!") + slog.Info("Connected to RabbitMQ!") return amqpConn, nil } From ed78a814b2363fe81861d09d6b7feb1f6880ed25 Mon Sep 17 00:00:00 2001 From: thangchung Date: Fri, 23 Dec 2022 18:47:22 +0700 Subject: [PATCH 07/16] init, but not work when `go run` --- .devcontainer/Dockerfile-dev | 3 + .devcontainer/devcontainer.json | 1 + cmd/barista/main.go | 2 + go.mod | 2 +- go.sum | 3 +- internal/barista/app/app.go | 21 +- .../barista/eventhandlers/barista_ordered.go | 8 +- internal/barista/infras/postgresql/db.go | 31 ++ internal/barista/infras/postgresql/models.go | 21 ++ .../barista/infras/postgresql/query.sql.go | 58 ++++ .../barista/infras/postgresql/query/query.sql | 12 + .../infras/postgresql/schema/schema.sql | 14 + .../barista/infras/repo/orders_postgres.go | 80 +++-- internal/counter/infras/postgresql/db.go | 31 ++ internal/counter/infras/postgresql/models.go | 32 ++ .../counter/infras/postgresql/query.sql.go | 279 ++++++++++++++++++ .../counter/infras/postgresql/query/query.sql | 76 +++++ .../infras/postgresql/schema/schema.sql | 38 +++ internal/kitchen/infras/postgresql/db.go | 31 ++ internal/kitchen/infras/postgresql/models.go | 22 ++ .../kitchen/infras/postgresql/query.sql.go | 62 ++++ .../kitchen/infras/postgresql/query/query.sql | 13 + .../infras/postgresql/schema/schema.sql | 15 + pkg/postgres/postgres.go | 46 +++ pkg/rabbitmq/consumer/consumer.go | 2 +- sqlc.yaml | 26 ++ 26 files changed, 893 insertions(+), 36 deletions(-) create mode 100644 internal/barista/infras/postgresql/db.go create mode 100644 internal/barista/infras/postgresql/models.go create mode 100644 internal/barista/infras/postgresql/query.sql.go create mode 100644 internal/barista/infras/postgresql/query/query.sql create mode 100644 internal/barista/infras/postgresql/schema/schema.sql create mode 100644 internal/counter/infras/postgresql/db.go create mode 100644 internal/counter/infras/postgresql/models.go create mode 100644 internal/counter/infras/postgresql/query.sql.go create mode 100644 internal/counter/infras/postgresql/query/query.sql create mode 100644 internal/counter/infras/postgresql/schema/schema.sql create mode 100644 internal/kitchen/infras/postgresql/db.go create mode 100644 internal/kitchen/infras/postgresql/models.go create mode 100644 internal/kitchen/infras/postgresql/query.sql.go create mode 100644 internal/kitchen/infras/postgresql/query/query.sql create mode 100644 internal/kitchen/infras/postgresql/schema/schema.sql create mode 100644 sqlc.yaml diff --git a/.devcontainer/Dockerfile-dev b/.devcontainer/Dockerfile-dev index f5362c8..2e4faf2 100755 --- a/.devcontainer/Dockerfile-dev +++ b/.devcontainer/Dockerfile-dev @@ -20,3 +20,6 @@ RUN BIN="/usr/local/bin" \ && curl -L "https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz" | tar xvz \ && mv migrate "${BIN}/migrate" \ && chmod +x "${BIN}/migrate" + +# Install sqlc +RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3ce8ab5..a70f2b8 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,6 +22,7 @@ "--security-opt", "seccomp=unconfined" ], + "forwardPorts": [5000, 8888, 5432, 15672], // Use 'settings' to set *default* container specific settings.json values on container create. // You can edit these settings after create using File > Preferences > Settings > Remote. "settings": { diff --git a/cmd/barista/main.go b/cmd/barista/main.go index 48ac68e..9d47343 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -8,6 +8,8 @@ import ( "github.com/thangchung/go-coffeeshop/internal/barista/app" "github.com/thangchung/go-coffeeshop/pkg/logger" "golang.org/x/exp/slog" + + _ "github.com/lib/pq" ) func main() { diff --git a/go.mod b/go.mod index 6c2ec51..4752de1 100755 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/ilyakaznacheev/cleanenv v1.3.0 github.com/jackc/pgx/v4 v4.17.2 github.com/labstack/echo/v4 v4.9.0 + github.com/lib/pq v1.10.7 github.com/pkg/errors v0.9.1 github.com/rabbitmq/amqp091-go v1.5.0 github.com/samber/lo v1.33.0 @@ -41,7 +42,6 @@ require ( github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2148620..981f61f 100755 --- a/go.sum +++ b/go.sum @@ -808,8 +808,9 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index b455997..014f8d3 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -42,15 +42,25 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) // PostgresDB - pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) - if err != nil { - slog.Error("failed to create a new Postgres", err, err.Error()) + // pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) + // if err != nil { + // slog.Error("failed to create a new Postgres", err, err.Error()) + + // cancel() + // return err + // } + // defer pg.Close() + + pg, err := postgres.NewPostgreSQLDb(a.cfg.PG.URL) + if err != nil { cancel() + slog.Error("failed to create a new Postgres", err, err.Error()) + return err } - defer pg.Close() + defer pg.CloseDB() // rabbitmq amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) @@ -58,6 +68,8 @@ func (a *App) Run() error { cancel() slog.Error("failed to create a new RabbitMQConn", err, err.Error()) + + return err } defer amqpConn.Close() @@ -90,7 +102,6 @@ func (a *App) Run() error { consumer.BindingKey("barista-order-routing-key"), consumer.ConsumerTag("barista-order-consumer"), ) - if err != nil { slog.Error("failed to create a new OrderConsumer", err, err.Error()) cancel() diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index 10e21d9..d3fab87 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -3,7 +3,6 @@ package eventhandlers import ( "context" "encoding/json" - "fmt" "time" "github.com/pkg/errors" @@ -11,6 +10,7 @@ import ( "github.com/thangchung/go-coffeeshop/internal/pkg/event" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "golang.org/x/exp/slog" ) type BaristaOrderedEventHandler interface { @@ -32,19 +32,19 @@ func NewBaristaOrderedEventHandler(repo domain.OrderRepo, counterPub *publisher. } func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e *event.BaristaOrdered) error { - fmt.Println(e) + slog.Info("received event", "event.BaristaOrdered", *e) timeIn := time.Now() delay := calculateDelay(e.ItemType) - time.Sleep(delay) + // time.Sleep(delay) timeUp := time.Now().Add(delay) err := h.repo.Create(ctx, &domain.BaristaOrder{ ID: e.ItemLineID, - ItemType: e.ItemType, ItemName: e.ItemType.String(), + ItemType: e.ItemType, TimeUp: timeUp, Created: time.Now(), Updated: time.Now(), diff --git a/internal/barista/infras/postgresql/db.go b/internal/barista/infras/postgresql/db.go new file mode 100644 index 0000000..18c0d96 --- /dev/null +++ b/internal/barista/infras/postgresql/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/barista/infras/postgresql/models.go b/internal/barista/infras/postgresql/models.go new file mode 100644 index 0000000..8890ad6 --- /dev/null +++ b/internal/barista/infras/postgresql/models.go @@ -0,0 +1,21 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "database/sql" + "time" + + "github.com/google/uuid" +) + +type BaristaBaristaOrder struct { + ID uuid.UUID + ItemType int32 + ItemName string + TimeUp time.Time + Created time.Time + Updated sql.NullTime +} diff --git a/internal/barista/infras/postgresql/query.sql.go b/internal/barista/infras/postgresql/query.sql.go new file mode 100644 index 0000000..67c995c --- /dev/null +++ b/internal/barista/infras/postgresql/query.sql.go @@ -0,0 +1,58 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package postgresql + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" +) + +const createOrder = `-- name: CreateOrder :one + +INSERT INTO + "barista".barista_orders ( + id, + item_type, + item_name, + time_up, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, item_type, item_name, time_up, created, updated +` + +type CreateOrderParams struct { + ID uuid.UUID + ItemType int32 + ItemName string + TimeUp time.Time + Created time.Time + Updated sql.NullTime +} + +func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (BaristaBaristaOrder, error) { + row := q.db.QueryRowContext(ctx, createOrder, + arg.ID, + arg.ItemType, + arg.ItemName, + arg.TimeUp, + arg.Created, + arg.Updated, + ) + var i BaristaBaristaOrder + err := row.Scan( + &i.ID, + &i.ItemType, + &i.ItemName, + &i.TimeUp, + &i.Created, + &i.Updated, + ) + return i, err +} diff --git a/internal/barista/infras/postgresql/query/query.sql b/internal/barista/infras/postgresql/query/query.sql new file mode 100644 index 0000000..998ae35 --- /dev/null +++ b/internal/barista/infras/postgresql/query/query.sql @@ -0,0 +1,12 @@ +-- name: CreateOrder :one + +INSERT INTO + "barista".barista_orders ( + id, + item_type, + item_name, + time_up, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; \ No newline at end of file diff --git a/internal/barista/infras/postgresql/schema/schema.sql b/internal/barista/infras/postgresql/schema/schema.sql new file mode 100644 index 0000000..9e4dc82 --- /dev/null +++ b/internal/barista/infras/postgresql/schema/schema.sql @@ -0,0 +1,14 @@ +CREATE SCHEMA "barista"; + +CREATE TABLE + barista.barista_orders ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + item_type int4 NOT NULL, + item_name text NOT NULL, + time_up timestamptz NOT NULL, + created timestamptz NOT NULL DEFAULT now(), + updated timestamptz NULL, + CONSTRAINT pk_barista_orders PRIMARY KEY (id) + ); + +CREATE UNIQUE INDEX ix_barista_orders_id ON barista.barista_orders USING btree (id); \ No newline at end of file diff --git a/internal/barista/infras/repo/orders_postgres.go b/internal/barista/infras/repo/orders_postgres.go index 414acbf..48fd21f 100644 --- a/internal/barista/infras/repo/orders_postgres.go +++ b/internal/barista/infras/repo/orders_postgres.go @@ -2,10 +2,12 @@ package repo import ( "context" + "database/sql" - "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/barista/domain" + "github.com/thangchung/go-coffeeshop/internal/barista/infras/postgresql" "github.com/thangchung/go-coffeeshop/pkg/postgres" + "golang.org/x/exp/slog" ) var _ domain.OrderRepo = (*orderRepo)(nil) @@ -19,32 +21,62 @@ func NewOrderRepo(pg *postgres.Postgres) domain.OrderRepo { } func (d *orderRepo) Create(ctx context.Context, baristaOrder *domain.BaristaOrder) error { - tx, err := d.pg.Pool.Begin(ctx) - if err != nil { - return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") - } + slog.Info("create", "domain.BaristaOrder", *baristaOrder) + // tx, err := d.db.Begin() + // if err != nil { + // return err + // } + // defer tx.Rollback() - // insert order - sql, args, err := d.pg.Builder. - Insert(`"barista".barista_orders`). - Columns("id", "item_type", "item_name", "time_up", "created", "updated"). - Values( - baristaOrder.ID, - baristaOrder.ItemType, - baristaOrder.ItemName, - baristaOrder.TimeUp, - baristaOrder.Created, - baristaOrder.Updated, - ). - ToSql() - if err != nil { - return tx.Rollback(ctx) - } + queries := postgresql.New(d.pg.DB) + // qtx := queries.WithTx(tx) + + slog.Info("debug: itemType", "itemType", baristaOrder.ItemType) + slog.Info("debug: itemType", "itemType32", int32(baristaOrder.ItemType)) - _, err = d.pg.Pool.Exec(ctx, sql, args...) + _, err := queries.CreateOrder(ctx, postgresql.CreateOrderParams{ + ID: baristaOrder.ID, + ItemType: int32(baristaOrder.ItemType), + ItemName: baristaOrder.ItemName, + TimeUp: baristaOrder.TimeUp, + Created: baristaOrder.Created, + Updated: sql.NullTime{ + Time: baristaOrder.Updated, + Valid: true, + }, + }) if err != nil { - return tx.Rollback(ctx) + return err } - return tx.Commit(ctx) + return nil + + // tx, err := d.pg.Pool.Begin(ctx) + // if err != nil { + // return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") + // } + + // // insert order + // sql, args, err := d.pg.Builder. + // Insert(`"barista".barista_orders`). + // Columns("id", "item_type", "item_name", "time_up", "created", "updated"). + // Values( + // baristaOrder.ID, + // baristaOrder.ItemType, + // baristaOrder.ItemName, + // baristaOrder.TimeUp, + // baristaOrder.Created, + // baristaOrder.Updated, + // ). + // ToSql() + // if err != nil { + // return tx.Rollback(ctx) + // } + + // _, err = d.pg.Pool.Exec(ctx, sql, args...) + // if err != nil { + // return tx.Rollback(ctx) + // } + + // return tx.Commit(ctx) } diff --git a/internal/counter/infras/postgresql/db.go b/internal/counter/infras/postgresql/db.go new file mode 100644 index 0000000..18c0d96 --- /dev/null +++ b/internal/counter/infras/postgresql/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/counter/infras/postgresql/models.go b/internal/counter/infras/postgresql/models.go new file mode 100644 index 0000000..bc33165 --- /dev/null +++ b/internal/counter/infras/postgresql/models.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "database/sql" + "time" + + "github.com/google/uuid" +) + +type OrderLineItem struct { + ID uuid.UUID + ItemType int32 + Name string + Price string + ItemStatus int32 + IsBaristaOrder bool + OrderID uuid.NullUUID + Created time.Time + Updated sql.NullTime +} + +type OrderOrder struct { + ID uuid.UUID + OrderSource int32 + LoyaltyMemberID uuid.UUID + OrderStatus int32 + Updated sql.NullTime +} diff --git a/internal/counter/infras/postgresql/query.sql.go b/internal/counter/infras/postgresql/query.sql.go new file mode 100644 index 0000000..c950ca3 --- /dev/null +++ b/internal/counter/infras/postgresql/query.sql.go @@ -0,0 +1,279 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package postgresql + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" +) + +const createOrder = `-- name: CreateOrder :one + +INSERT INTO + "order".orders ( + id, + order_source, + loyalty_member_id, + order_status, + updated + ) +VALUES ($1, $2, $3, $4, $5) RETURNING id, order_source, loyalty_member_id, order_status, updated +` + +type CreateOrderParams struct { + ID uuid.UUID + OrderSource int32 + LoyaltyMemberID uuid.UUID + OrderStatus int32 + Updated sql.NullTime +} + +func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (OrderOrder, error) { + row := q.db.QueryRowContext(ctx, createOrder, + arg.ID, + arg.OrderSource, + arg.LoyaltyMemberID, + arg.OrderStatus, + arg.Updated, + ) + var i OrderOrder + err := row.Scan( + &i.ID, + &i.OrderSource, + &i.LoyaltyMemberID, + &i.OrderStatus, + &i.Updated, + ) + return i, err +} + +const getOrderByID = `-- name: GetOrderByID :many + +SELECT + o.id, + order_source, + loyalty_member_id, + order_status, + l.id as "line_item_id", + item_type, + name, + price, + item_status, + is_barista_order +FROM "order".orders o + LEFT JOIN "order".line_items l ON o.id = l.order_id +WHERE o.id = $1 +` + +type GetOrderByIDRow struct { + ID uuid.UUID + OrderSource int32 + LoyaltyMemberID uuid.UUID + OrderStatus int32 + LineItemID uuid.NullUUID + ItemType int32 + Name string + Price string + ItemStatus int32 + IsBaristaOrder bool +} + +func (q *Queries) GetOrderByID(ctx context.Context, id uuid.UUID) ([]GetOrderByIDRow, error) { + rows, err := q.db.QueryContext(ctx, getOrderByID, id) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetOrderByIDRow + for rows.Next() { + var i GetOrderByIDRow + if err := rows.Scan( + &i.ID, + &i.OrderSource, + &i.LoyaltyMemberID, + &i.OrderStatus, + &i.LineItemID, + &i.ItemType, + &i.Name, + &i.Price, + &i.ItemStatus, + &i.IsBaristaOrder, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertItemLine = `-- name: InsertItemLine :one + +INSERT INTO + "order".line_items ( + id, + item_type, + name, + price, + item_status, + is_barista_order, + order_id, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, item_type, name, price, item_status, is_barista_order, order_id, created, updated +` + +type InsertItemLineParams struct { + ID uuid.UUID + ItemType int32 + Name string + Price string + ItemStatus int32 + IsBaristaOrder bool + OrderID uuid.NullUUID + Created time.Time + Updated sql.NullTime +} + +func (q *Queries) InsertItemLine(ctx context.Context, arg InsertItemLineParams) (OrderLineItem, error) { + row := q.db.QueryRowContext(ctx, insertItemLine, + arg.ID, + arg.ItemType, + arg.Name, + arg.Price, + arg.ItemStatus, + arg.IsBaristaOrder, + arg.OrderID, + arg.Created, + arg.Updated, + ) + var i OrderLineItem + err := row.Scan( + &i.ID, + &i.ItemType, + &i.Name, + &i.Price, + &i.ItemStatus, + &i.IsBaristaOrder, + &i.OrderID, + &i.Created, + &i.Updated, + ) + return i, err +} + +const listOrders = `-- name: ListOrders :many + +SELECT + o.id, + order_source, + loyalty_member_id, + order_status, + l.id as "line_item_id", + item_type, + name, + price, + item_status, + is_barista_order +FROM "order".orders o + LEFT JOIN "order".line_items l ON o.id = l.order_id +` + +type ListOrdersRow struct { + ID uuid.UUID + OrderSource int32 + LoyaltyMemberID uuid.UUID + OrderStatus int32 + LineItemID uuid.NullUUID + ItemType int32 + Name string + Price string + ItemStatus int32 + IsBaristaOrder bool +} + +func (q *Queries) ListOrders(ctx context.Context) ([]ListOrdersRow, error) { + rows, err := q.db.QueryContext(ctx, listOrders) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListOrdersRow + for rows.Next() { + var i ListOrdersRow + if err := rows.Scan( + &i.ID, + &i.OrderSource, + &i.LoyaltyMemberID, + &i.OrderStatus, + &i.LineItemID, + &i.ItemType, + &i.Name, + &i.Price, + &i.ItemStatus, + &i.IsBaristaOrder, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateItemLine = `-- name: UpdateItemLine :exec + +UPDATE "order".line_items +SET + item_status = $2, + updated = $3 +WHERE id = $1 +` + +type UpdateItemLineParams struct { + ID uuid.UUID + ItemStatus int32 + Updated sql.NullTime +} + +func (q *Queries) UpdateItemLine(ctx context.Context, arg UpdateItemLineParams) error { + _, err := q.db.ExecContext(ctx, updateItemLine, arg.ID, arg.ItemStatus, arg.Updated) + return err +} + +const updateOrder = `-- name: UpdateOrder :exec + +UPDATE "order".orders +SET + order_status = $2, + updated = $3 +WHERE id = $1 +` + +type UpdateOrderParams struct { + ID uuid.UUID + OrderStatus int32 + Updated sql.NullTime +} + +func (q *Queries) UpdateOrder(ctx context.Context, arg UpdateOrderParams) error { + _, err := q.db.ExecContext(ctx, updateOrder, arg.ID, arg.OrderStatus, arg.Updated) + return err +} diff --git a/internal/counter/infras/postgresql/query/query.sql b/internal/counter/infras/postgresql/query/query.sql new file mode 100644 index 0000000..27c7695 --- /dev/null +++ b/internal/counter/infras/postgresql/query/query.sql @@ -0,0 +1,76 @@ +-- name: ListOrders :many + +SELECT + o.id, + order_source, + loyalty_member_id, + order_status, + l.id as "line_item_id", + item_type, + name, + price, + item_status, + is_barista_order +FROM "order".orders o + LEFT JOIN "order".line_items l ON o.id = l.order_id; + +-- name: GetOrderByID :many + +SELECT + o.id, + order_source, + loyalty_member_id, + order_status, + l.id as "line_item_id", + item_type, + name, + price, + item_status, + is_barista_order +FROM "order".orders o + LEFT JOIN "order".line_items l ON o.id = l.order_id +WHERE o.id = $1; + +-- name: CreateOrder :one + +INSERT INTO + "order".orders ( + id, + order_source, + loyalty_member_id, + order_status, + updated + ) +VALUES ($1, $2, $3, $4, $5) RETURNING *; + +-- name: InsertItemLine :one + +INSERT INTO + "order".line_items ( + id, + item_type, + name, + price, + item_status, + is_barista_order, + order_id, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; + +-- name: UpdateOrder :exec + +UPDATE "order".orders +SET + order_status = $2, + updated = $3 +WHERE id = $1; + +-- name: UpdateItemLine :exec + +UPDATE "order".line_items +SET + item_status = $2, + updated = $3 +WHERE id = $1; \ No newline at end of file diff --git a/internal/counter/infras/postgresql/schema/schema.sql b/internal/counter/infras/postgresql/schema/schema.sql new file mode 100644 index 0000000..81b05c2 --- /dev/null +++ b/internal/counter/infras/postgresql/schema/schema.sql @@ -0,0 +1,38 @@ +CREATE SCHEMA "order"; + +CREATE TABLE + "order".orders ( + id uuid NOT NULL DEFAULT (uuid_generate_v4()), + order_source integer NOT NULL, + loyalty_member_id uuid NOT NULL, + order_status integer NOT NULL, + updated timestamp + with + time zone NULL, + CONSTRAINT pk_orders PRIMARY KEY (id) + ); + +CREATE TABLE + "order".line_items ( + id uuid NOT NULL DEFAULT (uuid_generate_v4()), + item_type integer NOT NULL, + name text NOT NULL, + price numeric NOT NULL, + item_status integer NOT NULL, + is_barista_order boolean NOT NULL, + order_id uuid NULL, + created timestamp + with + time zone NOT NULL DEFAULT (now()), + updated timestamp + with + time zone NULL, + CONSTRAINT pk_line_items PRIMARY KEY (id), + CONSTRAINT fk_line_items_orders_order_temp_id FOREIGN KEY (order_id) REFERENCES "order".orders (id) + ); + +CREATE UNIQUE INDEX ix_line_items_id ON "order".line_items (id); + +CREATE INDEX ix_line_items_order_id ON "order".line_items (order_id); + +CREATE UNIQUE INDEX ix_orders_id ON "order".orders (id); \ No newline at end of file diff --git a/internal/kitchen/infras/postgresql/db.go b/internal/kitchen/infras/postgresql/db.go new file mode 100644 index 0000000..18c0d96 --- /dev/null +++ b/internal/kitchen/infras/postgresql/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/kitchen/infras/postgresql/models.go b/internal/kitchen/infras/postgresql/models.go new file mode 100644 index 0000000..b4a2caa --- /dev/null +++ b/internal/kitchen/infras/postgresql/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package postgresql + +import ( + "database/sql" + "time" + + "github.com/google/uuid" +) + +type KitchenKitchenOrder struct { + ID uuid.UUID + OrderID uuid.UUID + ItemType int32 + ItemName string + TimeUp time.Time + Created time.Time + Updated sql.NullTime +} diff --git a/internal/kitchen/infras/postgresql/query.sql.go b/internal/kitchen/infras/postgresql/query.sql.go new file mode 100644 index 0000000..f8036ae --- /dev/null +++ b/internal/kitchen/infras/postgresql/query.sql.go @@ -0,0 +1,62 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package postgresql + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" +) + +const createOrder = `-- name: CreateOrder :one + +INSERT INTO + "kitchen".kitchen_orders ( + id, + order_id, + item_type, + item_name, + time_up, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, order_id, item_type, item_name, time_up, created, updated +` + +type CreateOrderParams struct { + ID uuid.UUID + OrderID uuid.UUID + ItemType int32 + ItemName string + TimeUp time.Time + Created time.Time + Updated sql.NullTime +} + +func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (KitchenKitchenOrder, error) { + row := q.db.QueryRowContext(ctx, createOrder, + arg.ID, + arg.OrderID, + arg.ItemType, + arg.ItemName, + arg.TimeUp, + arg.Created, + arg.Updated, + ) + var i KitchenKitchenOrder + err := row.Scan( + &i.ID, + &i.OrderID, + &i.ItemType, + &i.ItemName, + &i.TimeUp, + &i.Created, + &i.Updated, + ) + return i, err +} diff --git a/internal/kitchen/infras/postgresql/query/query.sql b/internal/kitchen/infras/postgresql/query/query.sql new file mode 100644 index 0000000..273539d --- /dev/null +++ b/internal/kitchen/infras/postgresql/query/query.sql @@ -0,0 +1,13 @@ +-- name: CreateOrder :one + +INSERT INTO + "kitchen".kitchen_orders ( + id, + order_id, + item_type, + item_name, + time_up, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *; \ No newline at end of file diff --git a/internal/kitchen/infras/postgresql/schema/schema.sql b/internal/kitchen/infras/postgresql/schema/schema.sql new file mode 100644 index 0000000..dde17bc --- /dev/null +++ b/internal/kitchen/infras/postgresql/schema/schema.sql @@ -0,0 +1,15 @@ +CREATE SCHEMA "kitchen"; + +CREATE TABLE + kitchen.kitchen_orders ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + order_id uuid NOT NULL, + item_type int4 NOT NULL, + item_name text NOT NULL, + time_up timestamptz NOT NULL, + created timestamptz NOT NULL DEFAULT now(), + updated timestamptz NULL, + CONSTRAINT pk_kitchen_orders PRIMARY KEY (id) + ); + +CREATE UNIQUE INDEX ix_kitchen_orders_id ON kitchen.kitchen_orders USING btree (id); \ No newline at end of file diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 428e761..733cec7 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -2,12 +2,15 @@ package postgres import ( "context" + "database/sql" "fmt" "log" "time" "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v4/pgxpool" + + _ "github.com/lib/pq" ) const ( @@ -23,6 +26,43 @@ type Postgres struct { Builder squirrel.StatementBuilderType Pool *pgxpool.Pool + DB *sql.DB +} + +func NewPostgreSQLDb(url string, opts ...Option) (*Postgres, error) { + pg := &Postgres{ + maxPoolSize: _defaultMaxPoolSize, + connAttempts: _defaultConnAttempts, + connTimeout: _defaultConnTimeout, + } + + for _, opt := range opts { + opt(pg) + } + + // var err error + + // for pg.connAttempts > 0 { + // pg.DB, err = sql.Open("postgres", url) + // if err != nil { + // break + // } + + // log.Printf("Postgres is trying to connect, attempts left: %d", pg.connAttempts) + + // time.Sleep(pg.connTimeout) + + // pg.connAttempts-- + // } + + var err error + + pg.DB, err = sql.Open("postgres", url) + if err != nil { + return nil, err + } + + return pg, nil } func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { @@ -65,6 +105,12 @@ func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { return pg, nil } +func (p *Postgres) CloseDB() { + if p.DB != nil { + p.DB.Close() + } +} + func (p *Postgres) Close() { if p.Pool != nil { p.Pool.Close() diff --git a/pkg/rabbitmq/consumer/consumer.go b/pkg/rabbitmq/consumer/consumer.go index d12b939..c227e52 100644 --- a/pkg/rabbitmq/consumer/consumer.go +++ b/pkg/rabbitmq/consumer/consumer.go @@ -71,7 +71,7 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return nil, errors.Wrap(err, "Error amqpConn.Channel") } - slog.Info("declaring exchange", c.exchangeName) + slog.Info("declaring exchange", "exchange_name", c.exchangeName) err = ch.ExchangeDeclare( c.exchangeName, _exchangeKind, diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..6fe1179 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,26 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "internal/counter/infras/postgresql/query/query.sql" + schema: "internal/counter/infras/postgresql/schema/schema.sql" + gen: + go: + package: "postgresql" + out: "internal/counter/infras/postgresql" + + - engine: "postgresql" + queries: "internal/kitchen/infras/postgresql/query/query.sql" + schema: "internal/kitchen/infras/postgresql/schema/schema.sql" + gen: + go: + package: "postgresql" + out: "internal/kitchen/infras/postgresql" + + - engine: "postgresql" + queries: "internal/barista/infras/postgresql/query/" + schema: "internal/barista/infras/postgresql/schema/" + gen: + go: + package: "postgresql" + out: "internal/barista/infras/postgresql" + sql_package: "pgx/v5" \ No newline at end of file From 13f257b2c990d4c0b2bc7540166e95d5cadfd4ab Mon Sep 17 00:00:00 2001 From: thangchung Date: Fri, 23 Dec 2022 19:53:13 +0700 Subject: [PATCH 08/16] minor fixed --- .devcontainer/devcontainer.json | 1 + pkg/postgres/postgres.go | 2 +- pkg/rabbitmq/consumer/consumer.go | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3ce8ab5..a70f2b8 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,6 +22,7 @@ "--security-opt", "seccomp=unconfined" ], + "forwardPorts": [5000, 8888, 5432, 15672], // Use 'settings' to set *default* container specific settings.json values on container create. // You can edit these settings after create using File > Preferences > Settings > Remote. "settings": { diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 428e761..7975039 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -12,7 +12,7 @@ import ( const ( _defaultMaxPoolSize = 1 - _defaultConnAttempts = 5 + _defaultConnAttempts = 3 _defaultConnTimeout = time.Second ) diff --git a/pkg/rabbitmq/consumer/consumer.go b/pkg/rabbitmq/consumer/consumer.go index d12b939..f240731 100644 --- a/pkg/rabbitmq/consumer/consumer.go +++ b/pkg/rabbitmq/consumer/consumer.go @@ -71,7 +71,7 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return nil, errors.Wrap(err, "Error amqpConn.Channel") } - slog.Info("declaring exchange", c.exchangeName) + slog.Info("declaring exchange", "exchange_name", c.exchangeName) err = ch.ExchangeDeclare( c.exchangeName, _exchangeKind, @@ -121,7 +121,7 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { _prefetchGlobal, // global ) if err != nil { - return nil, errors.Wrap(err, "Error ch.Qos") + return nil, errors.Wrap(err, "Error ch.Qos") } return ch, nil From 3077da165d44d26c6e32a2288a56814dc645865e Mon Sep 17 00:00:00 2001 From: thangchung Date: Sat, 24 Dec 2022 07:58:54 +0000 Subject: [PATCH 09/16] fixed barista code for sqlc --- .devcontainer/Dockerfile-dev | 5 +- cmd/barista/config.yml | 4 +- cmd/barista/config/config.go | 6 +- cmd/proxy/main.go | 6 +- db/migrations/000001_init_counterdb.up.sql | 23 +++-- db/migrations/000002_init_baristadb.up.sql | 23 +++-- db/migrations/000003_init_kitchendb.up.sql | 23 +++-- go.mod | 1 + go.sum | 2 + internal/barista/app/app.go | 24 +++--- internal/barista/domain/interfaces.go | 11 --- internal/barista/domain/order.go | 51 +++++++++++ .../barista/eventhandlers/barista_ordered.go | 84 +++++++------------ internal/barista/infras/postgresql/models.go | 12 +-- .../barista/infras/postgresql/query.sql.go | 14 ++-- .../barista/infras/postgresql/query/query.sql | 2 +- .../infras/postgresql/schema/schema.sql | 14 ---- .../barista/infras/repo/orders_postgres.go | 82 ------------------ internal/counter/app/app.go | 8 +- .../infras/postgresql/schema/schema.sql | 38 --------- internal/kitchen/app/app.go | 12 +-- .../kitchen/infras/postgresql/query.sql.go | 2 +- .../kitchen/infras/postgresql/query/query.sql | 2 +- .../infras/postgresql/schema/schema.sql | 15 ---- internal/pkg/event/events.go | 1 + internal/pkg/shared_kernel/enums.go | 39 ++++----- pkg/logger/logrus_adaptor.go | 4 +- pkg/postgres/postgres.go | 33 ++++---- sqlc.yaml | 10 ++- tools/tools.go | 11 ++- 30 files changed, 226 insertions(+), 336 deletions(-) delete mode 100644 internal/barista/domain/interfaces.go delete mode 100644 internal/barista/infras/postgresql/schema/schema.sql delete mode 100644 internal/barista/infras/repo/orders_postgres.go delete mode 100644 internal/counter/infras/postgresql/schema/schema.sql delete mode 100644 internal/kitchen/infras/postgresql/schema/schema.sql diff --git a/.devcontainer/Dockerfile-dev b/.devcontainer/Dockerfile-dev index 2e4faf2..8520c39 100755 --- a/.devcontainer/Dockerfile-dev +++ b/.devcontainer/Dockerfile-dev @@ -22,4 +22,7 @@ RUN BIN="/usr/local/bin" \ && chmod +x "${BIN}/migrate" # Install sqlc -RUN go install github.com/kyleconroy/sqlc/cmd/sqlc@latest +RUN BIN="/usr/local/bin" \ + && curl -L "https://github.com/kyleconroy/sqlc/releases/download/v1.16.0/sqlc_1.16.0_linux_amd64.tar.gz" | tar xvz \ + && mv sqlc "${BIN}/sqlc" \ + && chmod +x "${BIN}/sqlc" diff --git a/cmd/barista/config.yml b/cmd/barista/config.yml index b3b0db2..f389fb0 100755 --- a/cmd/barista/config.yml +++ b/cmd/barista/config.yml @@ -8,9 +8,9 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable + dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable -rabbit_mq: +rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ logger: diff --git a/cmd/barista/config/config.go b/cmd/barista/config/config.go index 9d999d7..adaa7b2 100755 --- a/cmd/barista/config/config.go +++ b/cmd/barista/config/config.go @@ -15,12 +15,12 @@ type ( configs.HTTP `yaml:"http"` configs.Log `yaml:"logger"` PG `yaml:"postgres"` - RabbitMQ `yaml:"rabbit_mq"` + RabbitMQ `yaml:"rabbitmq"` } PG struct { PoolMax int `env-required:"true" yaml:"pool_max" env:"PG_POOL_MAX"` - URL string `env-required:"true" yaml:"url" env:"PG_URL"` + DsnURL string `env-required:"true" yaml:"dsn_url" env:"PG_DSN_URL"` } RabbitMQ struct { @@ -37,7 +37,7 @@ func NewConfig() (*Config, error) { } // debug - fmt.Println(dir) + fmt.Println("config path: " + dir) err = cleanenv.ReadConfig(dir+"/config.yml", cfg) if err != nil { diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index a350256..02fe2ce 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -100,7 +100,7 @@ func main() { gw, err := newGateway(ctx, cfg, nil) if err != nil { - slog.Error("failed to create a new gateway", err, err.Error()) + slog.Error("failed to create a new gateway", err) } mux.Handle("/", gw) @@ -115,13 +115,13 @@ func main() { slog.Info("shutting down the http server") if err := s.Shutdown(context.Background()); err != nil { - slog.Error("Failed to shutdown http server: %v", err) + slog.Error("failed to shutdown http server", err) } }() slog.Info("start listening...", "address", fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)) if err := s.ListenAndServe(); errors.Is(err, http.ErrServerClosed) { - slog.Error("failed to listen and serve", err, err.Error()) + slog.Error("failed to listen and serve", err) } } diff --git a/db/migrations/000001_init_counterdb.up.sql b/db/migrations/000001_init_counterdb.up.sql index 1dd7ddf..bd45fc6 100644 --- a/db/migrations/000001_init_counterdb.up.sql +++ b/db/migrations/000001_init_counterdb.up.sql @@ -1,15 +1,22 @@ START TRANSACTION; -DO $EF$ BEGIN IF NOT EXISTS( - SELECT 1 - FROM pg_namespace - WHERE - nspname = 'order' - ) THEN CREATE SCHEMA "order"; +-- DO $$ BEGIN IF NOT EXISTS( -END IF; +-- SELECT 1 -END $EF$; +-- FROM pg_namespace + +-- WHERE + +-- nspname = 'order' + +-- ) THEN CREATE SCHEMA "order"; + +-- END IF; + +-- END $$; + +CREATE SCHEMA IF NOT EXISTS "order"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/db/migrations/000002_init_baristadb.up.sql b/db/migrations/000002_init_baristadb.up.sql index 0b6fd29..b3a6d9a 100644 --- a/db/migrations/000002_init_baristadb.up.sql +++ b/db/migrations/000002_init_baristadb.up.sql @@ -1,15 +1,22 @@ START TRANSACTION; -DO $EF$ BEGIN IF NOT EXISTS( - SELECT 1 - FROM pg_namespace - WHERE - nspname = 'barista' - ) THEN CREATE SCHEMA barista; +-- DO $$ BEGIN IF NOT EXISTS( -END IF; +-- SELECT 1 -END $EF$; +-- FROM pg_namespace + +-- WHERE + +-- nspname = 'barista' + +-- ) THEN CREATE SCHEMA barista; + +-- END IF; + +-- END $$; + +CREATE SCHEMA IF NOT EXISTS "barista"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/db/migrations/000003_init_kitchendb.up.sql b/db/migrations/000003_init_kitchendb.up.sql index 5feb7a0..7d79889 100644 --- a/db/migrations/000003_init_kitchendb.up.sql +++ b/db/migrations/000003_init_kitchendb.up.sql @@ -1,15 +1,22 @@ START TRANSACTION; -DO $EF$ BEGIN IF NOT EXISTS( - SELECT 1 - FROM pg_namespace - WHERE - nspname = 'kitchen' - ) THEN CREATE SCHEMA kitchen; +-- DO $$ BEGIN IF NOT EXISTS( -END IF; +-- SELECT 1 -END $EF$; +-- FROM pg_namespace + +-- WHERE + +-- nspname = 'kitchen' + +-- ) THEN CREATE SCHEMA "kitchen"; + +-- END IF; + +-- END $$; + +CREATE SCHEMA IF NOT EXISTS "kitchen"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; diff --git a/go.mod b/go.mod index 4752de1..bc2e90a 100755 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 github.com/ilyakaznacheev/cleanenv v1.3.0 github.com/jackc/pgx/v4 v4.17.2 + github.com/kyleconroy/sqlc v1.16.0 github.com/labstack/echo/v4 v4.9.0 github.com/lib/pq v1.10.7 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 981f61f..2b8bb9c 100755 --- a/go.sum +++ b/go.sum @@ -794,6 +794,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/kyleconroy/sqlc v1.16.0 h1:PE5xrrnUiV5T2b97sLWKHgpBPQoPo/N1K/gWU/GFwaE= +github.com/kyleconroy/sqlc v1.16.0/go.mod h1:m+cX/UyBRnKP58lFfUsq+0gw87UUw9AmxwqU/AaQeDA= github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 014f8d3..78510ec 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -12,7 +12,6 @@ import ( "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/barista/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" @@ -52,11 +51,11 @@ func (a *App) Run() error { // } // defer pg.Close() - pg, err := postgres.NewPostgreSQLDb(a.cfg.PG.URL) + pg, err := postgres.NewPostgreSQLDb(a.cfg.PG.DsnURL) if err != nil { cancel() - slog.Error("failed to create a new Postgres", err, err.Error()) + slog.Error("failed to create a new Postgres", err) return err } @@ -67,7 +66,7 @@ func (a *App) Run() error { if err != nil { cancel() - slog.Error("failed to create a new RabbitMQConn", err, err.Error()) + slog.Error("failed to create a new RabbitMQConn", err) return err } @@ -88,11 +87,8 @@ func (a *App) Run() error { return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") } - // repository - orderRepo := repo.NewOrderRepo(pg) - // event handlers. - a.handler = eventhandlers.NewBaristaOrderedEventHandler(orderRepo, counterOrderPub) + a.handler = eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) // consumers consumer, err := consumer.NewConsumer( @@ -103,7 +99,7 @@ func (a *App) Run() error { consumer.ConsumerTag("barista-order-consumer"), ) if err != nil { - slog.Error("failed to create a new OrderConsumer", err, err.Error()) + slog.Error("failed to create a new OrderConsumer", err) cancel() } @@ -141,21 +137,21 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { err := json.Unmarshal(delivery.Body, &payload) if err != nil { - slog.Error("failed to Unmarshal", err, err.Error()) + slog.Error("failed to Unmarshal", err) } - err = c.handler.Handle(ctx, &payload) + err = c.handler.Handle(ctx, payload) if err != nil { if err = delivery.Reject(false); err != nil { - slog.Error("failed to delivery.Reject", err, err.Error()) + slog.Error("failed to delivery.Reject", err) } - slog.Error("failed to process delivery", err, err.Error()) + slog.Error("failed to process delivery", err) } else { err = delivery.Ack(false) if err != nil { - slog.Error("failed to acknowledge delivery", err, err.Error()) + slog.Error("failed to acknowledge delivery", err) } } default: diff --git a/internal/barista/domain/interfaces.go b/internal/barista/domain/interfaces.go deleted file mode 100644 index b7b56ca..0000000 --- a/internal/barista/domain/interfaces.go +++ /dev/null @@ -1,11 +0,0 @@ -package domain - -import ( - "context" -) - -type ( - OrderRepo interface { - Create(context.Context, *BaristaOrder) error - } -) diff --git a/internal/barista/domain/order.go b/internal/barista/domain/order.go index 8a417b2..2e3ba3f 100644 --- a/internal/barista/domain/order.go +++ b/internal/barista/domain/order.go @@ -4,10 +4,12 @@ import ( "time" "github.com/google/uuid" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) type BaristaOrder struct { + shared.AggregateRoot ID uuid.UUID `json:"id" db:"id"` ItemName string `json:"itemName" db:"item_name"` ItemType shared.ItemType `json:"itemType" db:"item_type"` @@ -15,3 +17,52 @@ type BaristaOrder struct { Created time.Time `json:"created" db:"created"` Updated time.Time `json:"updated" db:"updated"` } + +func NewBaristaOrder(e event.BaristaOrdered) BaristaOrder { + timeIn := time.Now() + + delay := calculateDelay(e.ItemType) + time.Sleep(delay) // simulate the delay when makes the drink + + timeUp := time.Now().Add(delay) + + order := BaristaOrder{ + ID: e.ItemLineID, + ItemName: e.ItemType.String(), + ItemType: e.ItemType, + TimeUp: timeUp, + Created: time.Now(), + Updated: time.Now(), + } + + orderUpdateEvent := event.BaristaOrderUpdated{ + OrderID: e.OrderID, + ItemLineID: e.ItemLineID, + Name: e.ItemType.String(), + ItemType: e.ItemType, + MadeBy: "teesee", + TimeIn: timeIn, + TimeUp: timeUp, + } + + order.ApplyDomain(orderUpdateEvent) + + return order +} + +func calculateDelay(itemType shared.ItemType) time.Duration { + switch itemType { + case shared.ItemTypeCoffeeBlack: + return 5 * time.Second + case shared.ItemTypeCoffeeWithRoom: + return 5 * time.Second + case shared.ItemTypeEspresso: + return 7 * time.Second + case shared.ItemTypeEspressoDouble: + return 7 * time.Second + case shared.ItemTypeCappuccino: + return 10 * time.Second + default: + return 3 * time.Second + } +} diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index d3fab87..1c933ce 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -2,92 +2,70 @@ package eventhandlers import ( "context" + "database/sql" "encoding/json" - "time" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/barista/domain" + "github.com/thangchung/go-coffeeshop/internal/barista/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" - shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) type BaristaOrderedEventHandler interface { - Handle(context.Context, *event.BaristaOrdered) error + Handle(context.Context, event.BaristaOrdered) error } var _ BaristaOrderedEventHandler = (*baristaOrderedEventHandler)(nil) type baristaOrderedEventHandler struct { - repo domain.OrderRepo + pg *postgres.Postgres counterPub *publisher.Publisher } -func NewBaristaOrderedEventHandler(repo domain.OrderRepo, counterPub *publisher.Publisher) BaristaOrderedEventHandler { +func NewBaristaOrderedEventHandler(pg *postgres.Postgres, counterPub *publisher.Publisher) BaristaOrderedEventHandler { return &baristaOrderedEventHandler{ - repo: repo, + pg: pg, counterPub: counterPub, } } -func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e *event.BaristaOrdered) error { - slog.Info("received event", "event.BaristaOrdered", *e) +func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e event.BaristaOrdered) error { + slog.Info("received event", "event.BaristaOrdered", e) - timeIn := time.Now() + order := domain.NewBaristaOrder(e) - delay := calculateDelay(e.ItemType) - // time.Sleep(delay) + querier := postgresql.New(h.pg.DB) - timeUp := time.Now().Add(delay) - - err := h.repo.Create(ctx, &domain.BaristaOrder{ - ID: e.ItemLineID, - ItemName: e.ItemType.String(), - ItemType: e.ItemType, - TimeUp: timeUp, - Created: time.Now(), - Updated: time.Now(), + _, err := querier.CreateOrder(ctx, postgresql.CreateOrderParams{ + ID: order.ID, + ItemType: int32(order.ItemType), + ItemName: order.ItemName, + TimeUp: order.TimeUp, + Created: order.Created, + Updated: sql.NullTime{ + Time: order.Updated, + Valid: true, + }, }) if err != nil { - return errors.Wrap(err, "baristaOrderedEventHandler-h.repo.Create") - } + slog.Info("failed to call to repo", "error", err) - message := event.BaristaOrderUpdated{ - OrderID: e.OrderID, - ItemLineID: e.ItemLineID, - Name: e.ItemType.String(), - ItemType: e.ItemType, - MadeBy: "teesee", - TimeIn: timeIn, - TimeUp: timeUp, + return errors.Wrap(err, "baristaOrderedEventHandler-querier.CreateOrder") } - eventBytes, err := json.Marshal(message) - if err != nil { - return errors.Wrap(err, "json.Marshal - events.BaristaOrderUpdated") - } + for _, event := range order.DomainEvents() { + eventBytes, err := json.Marshal(event) + if err != nil { + return errors.Wrap(err, "json.Marshal[event]") + } - if err := h.counterPub.Publish(ctx, eventBytes, "text/plain"); err != nil { - return errors.Wrap(err, "BaristaOrderedEventHandler - Publish") + if err := h.counterPub.Publish(ctx, eventBytes, "text/plain"); err != nil { + return errors.Wrap(err, "counterPub.Publish") + } } return nil } - -func calculateDelay(itemType shared.ItemType) time.Duration { - switch itemType { - case shared.ItemTypeCoffeeBlack: - return 5 * time.Second - case shared.ItemTypeCoffeeWithRoom: - return 5 * time.Second - case shared.ItemTypeEspresso: - return 7 * time.Second - case shared.ItemTypeEspressoDouble: - return 7 * time.Second - case shared.ItemTypeCappuccino: - return 10 * time.Second - default: - return 3 * time.Second - } -} diff --git a/internal/barista/infras/postgresql/models.go b/internal/barista/infras/postgresql/models.go index 8890ad6..705d534 100644 --- a/internal/barista/infras/postgresql/models.go +++ b/internal/barista/infras/postgresql/models.go @@ -12,10 +12,10 @@ import ( ) type BaristaBaristaOrder struct { - ID uuid.UUID - ItemType int32 - ItemName string - TimeUp time.Time - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + ItemType int32 `json:"item_type"` + ItemName string `json:"item_name"` + TimeUp time.Time `json:"time_up"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } diff --git a/internal/barista/infras/postgresql/query.sql.go b/internal/barista/infras/postgresql/query.sql.go index 67c995c..e6989d0 100644 --- a/internal/barista/infras/postgresql/query.sql.go +++ b/internal/barista/infras/postgresql/query.sql.go @@ -16,7 +16,7 @@ import ( const createOrder = `-- name: CreateOrder :one INSERT INTO - "barista".barista_orders ( + barista.barista_orders ( id, item_type, item_name, @@ -28,12 +28,12 @@ VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, item_type, item_name, time_up, cre ` type CreateOrderParams struct { - ID uuid.UUID - ItemType int32 - ItemName string - TimeUp time.Time - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + ItemType int32 `json:"item_type"` + ItemName string `json:"item_name"` + TimeUp time.Time `json:"time_up"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (BaristaBaristaOrder, error) { diff --git a/internal/barista/infras/postgresql/query/query.sql b/internal/barista/infras/postgresql/query/query.sql index 998ae35..6e10613 100644 --- a/internal/barista/infras/postgresql/query/query.sql +++ b/internal/barista/infras/postgresql/query/query.sql @@ -1,7 +1,7 @@ -- name: CreateOrder :one INSERT INTO - "barista".barista_orders ( + barista.barista_orders ( id, item_type, item_name, diff --git a/internal/barista/infras/postgresql/schema/schema.sql b/internal/barista/infras/postgresql/schema/schema.sql deleted file mode 100644 index 9e4dc82..0000000 --- a/internal/barista/infras/postgresql/schema/schema.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE SCHEMA "barista"; - -CREATE TABLE - barista.barista_orders ( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - item_type int4 NOT NULL, - item_name text NOT NULL, - time_up timestamptz NOT NULL, - created timestamptz NOT NULL DEFAULT now(), - updated timestamptz NULL, - CONSTRAINT pk_barista_orders PRIMARY KEY (id) - ); - -CREATE UNIQUE INDEX ix_barista_orders_id ON barista.barista_orders USING btree (id); \ No newline at end of file diff --git a/internal/barista/infras/repo/orders_postgres.go b/internal/barista/infras/repo/orders_postgres.go deleted file mode 100644 index 48fd21f..0000000 --- a/internal/barista/infras/repo/orders_postgres.go +++ /dev/null @@ -1,82 +0,0 @@ -package repo - -import ( - "context" - "database/sql" - - "github.com/thangchung/go-coffeeshop/internal/barista/domain" - "github.com/thangchung/go-coffeeshop/internal/barista/infras/postgresql" - "github.com/thangchung/go-coffeeshop/pkg/postgres" - "golang.org/x/exp/slog" -) - -var _ domain.OrderRepo = (*orderRepo)(nil) - -type orderRepo struct { - pg *postgres.Postgres -} - -func NewOrderRepo(pg *postgres.Postgres) domain.OrderRepo { - return &orderRepo{pg: pg} -} - -func (d *orderRepo) Create(ctx context.Context, baristaOrder *domain.BaristaOrder) error { - slog.Info("create", "domain.BaristaOrder", *baristaOrder) - // tx, err := d.db.Begin() - // if err != nil { - // return err - // } - // defer tx.Rollback() - - queries := postgresql.New(d.pg.DB) - // qtx := queries.WithTx(tx) - - slog.Info("debug: itemType", "itemType", baristaOrder.ItemType) - slog.Info("debug: itemType", "itemType32", int32(baristaOrder.ItemType)) - - _, err := queries.CreateOrder(ctx, postgresql.CreateOrderParams{ - ID: baristaOrder.ID, - ItemType: int32(baristaOrder.ItemType), - ItemName: baristaOrder.ItemName, - TimeUp: baristaOrder.TimeUp, - Created: baristaOrder.Created, - Updated: sql.NullTime{ - Time: baristaOrder.Updated, - Valid: true, - }, - }) - if err != nil { - return err - } - - return nil - - // tx, err := d.pg.Pool.Begin(ctx) - // if err != nil { - // return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") - // } - - // // insert order - // sql, args, err := d.pg.Builder. - // Insert(`"barista".barista_orders`). - // Columns("id", "item_type", "item_name", "time_up", "created", "updated"). - // Values( - // baristaOrder.ID, - // baristaOrder.ItemType, - // baristaOrder.ItemName, - // baristaOrder.TimeUp, - // baristaOrder.Created, - // baristaOrder.Updated, - // ). - // ToSql() - // if err != nil { - // return tx.Rollback(ctx) - // } - - // _, err = d.pg.Pool.Exec(ctx, sql, args...) - // if err != nil { - // return tx.Rollback(ctx) - // } - - // return tx.Commit(ctx) -} diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 030a66c..2261e20 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -48,7 +48,7 @@ func (a *App) Run() error { // PostgresDB pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { - slog.Error("failed to create new instance of postgres", err, err.Error()) + slog.Error("failed to create new instance of postgres", err) cancel() @@ -59,7 +59,7 @@ func (a *App) Run() error { // RabbitMQ amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { - slog.Error("failed to create a new RabbitMQConn", err, err.Error()) + slog.Error("failed to create a new RabbitMQConn", err) cancel() @@ -138,7 +138,7 @@ func (a *App) Run() error { ) if err != nil { - slog.Error("failed to create a new consumer", err, err.Error()) + slog.Error("failed to create a new consumer", err) } go func() { @@ -152,7 +152,7 @@ func (a *App) Run() error { // gRPC Server l, err := net.Listen(a.network, a.address) if err != nil { - slog.Error("failed to listen to address", err, err.Error(), "network", a.network, "address", a.address) + slog.Error("failed to listen to address", err, "network", a.network, "address", a.address) return err } diff --git a/internal/counter/infras/postgresql/schema/schema.sql b/internal/counter/infras/postgresql/schema/schema.sql deleted file mode 100644 index 81b05c2..0000000 --- a/internal/counter/infras/postgresql/schema/schema.sql +++ /dev/null @@ -1,38 +0,0 @@ -CREATE SCHEMA "order"; - -CREATE TABLE - "order".orders ( - id uuid NOT NULL DEFAULT (uuid_generate_v4()), - order_source integer NOT NULL, - loyalty_member_id uuid NOT NULL, - order_status integer NOT NULL, - updated timestamp - with - time zone NULL, - CONSTRAINT pk_orders PRIMARY KEY (id) - ); - -CREATE TABLE - "order".line_items ( - id uuid NOT NULL DEFAULT (uuid_generate_v4()), - item_type integer NOT NULL, - name text NOT NULL, - price numeric NOT NULL, - item_status integer NOT NULL, - is_barista_order boolean NOT NULL, - order_id uuid NULL, - created timestamp - with - time zone NOT NULL DEFAULT (now()), - updated timestamp - with - time zone NULL, - CONSTRAINT pk_line_items PRIMARY KEY (id), - CONSTRAINT fk_line_items_orders_order_temp_id FOREIGN KEY (order_id) REFERENCES "order".orders (id) - ); - -CREATE UNIQUE INDEX ix_line_items_id ON "order".line_items (id); - -CREATE INDEX ix_line_items_order_id ON "order".line_items (order_id); - -CREATE UNIQUE INDEX ix_orders_id ON "order".orders (id); \ No newline at end of file diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index a25f49b..51960aa 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -44,7 +44,7 @@ func (a *App) Run() error { // postgresdb. pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) if err != nil { - slog.Error("failed to create a new Postgres", err, err.Error()) + slog.Error("failed to create a new Postgres", err) cancel() @@ -57,7 +57,7 @@ func (a *App) Run() error { if err != nil { cancel() - slog.Error("failed to create a new RabbitMQConn", err, err.Error()) + slog.Error("failed to create a new RabbitMQConn", err) } defer amqpConn.Close() @@ -92,7 +92,7 @@ func (a *App) Run() error { ) if err != nil { - slog.Error("failed to create a new OrderConsumer", err, err.Error()) + slog.Error("failed to create a new OrderConsumer", err) cancel() } @@ -137,14 +137,14 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { if err != nil { if err = delivery.Reject(false); err != nil { - slog.Error("failed to delivery.Reject", err, err.Error()) + slog.Error("failed to delivery.Reject", err) } - slog.Error("failed to process delivery", err, err.Error()) + slog.Error("failed to process delivery", err) } else { err = delivery.Ack(false) if err != nil { - slog.Error("failed to acknowledge delivery", err, err.Error()) + slog.Error("failed to acknowledge delivery", err) } } default: diff --git a/internal/kitchen/infras/postgresql/query.sql.go b/internal/kitchen/infras/postgresql/query.sql.go index f8036ae..2c82e00 100644 --- a/internal/kitchen/infras/postgresql/query.sql.go +++ b/internal/kitchen/infras/postgresql/query.sql.go @@ -16,7 +16,7 @@ import ( const createOrder = `-- name: CreateOrder :one INSERT INTO - "kitchen".kitchen_orders ( + kitchen.kitchen_orders ( id, order_id, item_type, diff --git a/internal/kitchen/infras/postgresql/query/query.sql b/internal/kitchen/infras/postgresql/query/query.sql index 273539d..1520e9b 100644 --- a/internal/kitchen/infras/postgresql/query/query.sql +++ b/internal/kitchen/infras/postgresql/query/query.sql @@ -1,7 +1,7 @@ -- name: CreateOrder :one INSERT INTO - "kitchen".kitchen_orders ( + kitchen.kitchen_orders ( id, order_id, item_type, diff --git a/internal/kitchen/infras/postgresql/schema/schema.sql b/internal/kitchen/infras/postgresql/schema/schema.sql deleted file mode 100644 index dde17bc..0000000 --- a/internal/kitchen/infras/postgresql/schema/schema.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE SCHEMA "kitchen"; - -CREATE TABLE - kitchen.kitchen_orders ( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - order_id uuid NOT NULL, - item_type int4 NOT NULL, - item_name text NOT NULL, - time_up timestamptz NOT NULL, - created timestamptz NOT NULL DEFAULT now(), - updated timestamptz NULL, - CONSTRAINT pk_kitchen_orders PRIMARY KEY (id) - ); - -CREATE UNIQUE INDEX ix_kitchen_orders_id ON kitchen.kitchen_orders USING btree (id); \ No newline at end of file diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go index 84bd9e9..f137d55 100644 --- a/internal/pkg/event/events.go +++ b/internal/pkg/event/events.go @@ -31,6 +31,7 @@ func (e KitchenOrdered) Identity() string { } type BaristaOrderUpdated struct { + shared.DomainEvent OrderID uuid.UUID `json:"orderId"` ItemLineID uuid.UUID `json:"itemLineId"` Name string `json:"name"` diff --git a/internal/pkg/shared_kernel/enums.go b/internal/pkg/shared_kernel/enums.go index 0254f2c..37a5d1f 100644 --- a/internal/pkg/shared_kernel/enums.go +++ b/internal/pkg/shared_kernel/enums.go @@ -49,7 +49,7 @@ func (e CommandType) String() string { return fmt.Sprintf("%d", int(e)) } -type ItemType int8 +type ItemType int32 const ( ItemTypeCappuccino ItemType = iota @@ -65,28 +65,17 @@ const ( ) func (e ItemType) String() string { - switch e { - case ItemTypeCappuccino: - return "CAPPUCCINO" - case ItemTypeCoffeeBlack: - return "COFFEE_BLACK" - case ItemTypeCoffeeWithRoom: - return "COFFEE_WITH_ROOM" - case ItemTypeEspresso: - return "ESPRESSO" - case ItemTypeEspressoDouble: - return "ESPRESSO_DOUBLE" - case ItemTypeLatte: - return "LATTE" - case ItemTypeCakePop: - return "CAKEPOP" - case ItemTypeCroissant: - return "CROISSANT" - case ItemTypeMuffin: - return "MUFFIN" - case ItemTypeCroissantChocolate: - return "CROISSANT_CHOCOLATE" - default: - return "CAPPUCCINO" - } + return []string{ + "CAPPUCCINO", + "COFFEE_BLACK", + "COFFEE_WITH_ROOM", + "ESPRESSO", + "ESPRESSO_DOUBLE", + "LATTE", + "CAKEPOP", + "CROISSANT", + "MUFFIN", + "CROISSANT_CHOCOLATE", + "CAPPUCCINO", + }[e] } diff --git a/pkg/logger/logrus_adaptor.go b/pkg/logger/logrus_adaptor.go index 3f74166..f11d225 100644 --- a/pkg/logger/logrus_adaptor.go +++ b/pkg/logger/logrus_adaptor.go @@ -1,6 +1,8 @@ package logger -// ref: https://josephwoodward.co.uk/2022/11/slog-structured-logging-proposal +// refs: +// https://josephwoodward.co.uk/2022/11/slog-structured-logging-proposal +// https://thedevelopercafe.com/articles/logging-in-go-with-slog-a7bb489755c2 import ( "strings" diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 733cec7..1994bf6 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -9,6 +9,7 @@ import ( "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v4/pgxpool" + "golang.org/x/exp/slog" _ "github.com/lib/pq" ) @@ -30,6 +31,8 @@ type Postgres struct { } func NewPostgreSQLDb(url string, opts ...Option) (*Postgres, error) { + slog.Info("CONN", "connect string", url) + pg := &Postgres{ maxPoolSize: _defaultMaxPoolSize, connAttempts: _defaultConnAttempts, @@ -40,28 +43,26 @@ func NewPostgreSQLDb(url string, opts ...Option) (*Postgres, error) { opt(pg) } - // var err error + var err error + for pg.connAttempts > 0 { + pg.DB, err = sql.Open("postgres", url) + if err != nil { + break + } - // for pg.connAttempts > 0 { - // pg.DB, err = sql.Open("postgres", url) - // if err != nil { - // break - // } + log.Printf("Postgres is trying to connect, attempts left: %d", pg.connAttempts) - // log.Printf("Postgres is trying to connect, attempts left: %d", pg.connAttempts) + time.Sleep(pg.connTimeout) - // time.Sleep(pg.connTimeout) + pg.connAttempts-- + } - // pg.connAttempts-- + // slog.Info("CONN", "connect string", url) + // pg.DB, err = sql.Open("postgres", url) + // if err != nil { + // return nil, err // } - var err error - - pg.DB, err = sql.Open("postgres", url) - if err != nil { - return nil, err - } - return pg, nil } diff --git a/sqlc.yaml b/sqlc.yaml index 6fe1179..cee88b3 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -2,7 +2,7 @@ version: "2" sql: - engine: "postgresql" queries: "internal/counter/infras/postgresql/query/query.sql" - schema: "internal/counter/infras/postgresql/schema/schema.sql" + schema: "db/migrations/000001_init_counterdb.up.sql" gen: go: package: "postgresql" @@ -10,7 +10,7 @@ sql: - engine: "postgresql" queries: "internal/kitchen/infras/postgresql/query/query.sql" - schema: "internal/kitchen/infras/postgresql/schema/schema.sql" + schema: "db/migrations/000003_init_kitchendb.up.sql" gen: go: package: "postgresql" @@ -18,9 +18,11 @@ sql: - engine: "postgresql" queries: "internal/barista/infras/postgresql/query/" - schema: "internal/barista/infras/postgresql/schema/" + schema: "db/migrations/000002_init_baristadb.up.sql" gen: go: package: "postgresql" out: "internal/barista/infras/postgresql" - sql_package: "pgx/v5" \ No newline at end of file + sql_package: "pgx/v5" + emit_json_tags: true + emit_prepared_queries: false \ No newline at end of file diff --git a/tools/tools.go b/tools/tools.go index 828489f..eb8267e 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,8 +1,11 @@ +//go:build tools // +build tools package tools -_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway" -_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" -_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" -_ "google.golang.org/protobuf/cmd/protoc-gen-go" +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" + _ "github.com/kyleconroy/sqlc" + _ "google.golang.org/protobuf/cmd/protoc-gen-go" +) From dd51d0f53d443a699d0308a028d2192d9847fcb7 Mon Sep 17 00:00:00 2001 From: thangchung Date: Sun, 25 Dec 2022 16:25:54 +0000 Subject: [PATCH 10/16] applied sqlc for for kitchen, counter --- cmd/counter/config.yml | 4 +- cmd/counter/config/config.go | 6 +- cmd/kitchen/config.yml | 4 +- cmd/kitchen/config/config.go | 4 +- go.mod | 1 - go.sum | 7 - internal/barista/app/app.go | 18 +- internal/barista/domain/order.go | 16 +- .../barista/eventhandlers/barista_ordered.go | 16 +- internal/barista/eventhandlers/interfaces.go | 11 + internal/counter/app/app.go | 35 +-- internal/counter/domain/interfaces.go | 9 - internal/counter/domain/line_item.go | 14 +- internal/counter/domain/models.go | 18 -- internal/counter/domain/order.go | 18 +- internal/counter/domain/results.go | 6 - .../handlers}/barista_order_updated.go | 15 +- .../handlers}/kitchen_order_updated.go | 15 +- internal/counter/events/interfaces.go | 17 ++ .../counter/infras/postgresql/query.sql.go | 138 ++++----- .../counter/infras/postgresql/query/query.sql | 4 +- .../counter/infras/repo/orders_postgres.go | 266 ++++++++---------- internal/kitchen/app/app.go | 26 +- internal/kitchen/domain/order.go | 71 ++++- internal/kitchen/eventhandlers/interfaces.go | 11 + .../kitchen/eventhandlers/kitchen_ordered.go | 90 +++--- .../kitchen/infras/repo/orders_postgres.go | 51 ---- internal/pkg/event/events.go | 1 + internal/pkg/shared_kernel/aggregate_root.go | 14 - internal/pkg/shared_kernel/interfaces.go | 17 ++ pkg/postgres/options.go | 6 - pkg/postgres/postgres.go | 61 +--- 32 files changed, 435 insertions(+), 555 deletions(-) create mode 100644 internal/barista/eventhandlers/interfaces.go delete mode 100644 internal/counter/domain/results.go rename internal/counter/{eventhandlers => events/handlers}/barista_order_updated.go (65%) rename internal/counter/{eventhandlers => events/handlers}/kitchen_order_updated.go (65%) create mode 100644 internal/counter/events/interfaces.go create mode 100644 internal/kitchen/eventhandlers/interfaces.go delete mode 100644 internal/kitchen/infras/repo/orders_postgres.go create mode 100644 internal/pkg/shared_kernel/interfaces.go diff --git a/cmd/counter/config.yml b/cmd/counter/config.yml index ae981bf..47b1384 100755 --- a/cmd/counter/config.yml +++ b/cmd/counter/config.yml @@ -8,9 +8,9 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable + dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable -rabbit_mq: +rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ product_client: diff --git a/cmd/counter/config/config.go b/cmd/counter/config/config.go index 0acfb77..8b767af 100755 --- a/cmd/counter/config/config.go +++ b/cmd/counter/config/config.go @@ -15,13 +15,13 @@ type ( configs.HTTP `yaml:"http"` configs.Log `yaml:"logger"` PG `yaml:"postgres"` - RabbitMQ `yaml:"rabbit_mq"` + RabbitMQ `yaml:"rabbitmq"` ProductClient `yaml:"product_client"` } PG struct { PoolMax int `env-required:"true" yaml:"pool_max" env:"PG_POOL_MAX"` - URL string `env-required:"true" yaml:"url" env:"PG_URL"` + DsnURL string `env-required:"true" yaml:"dsn_url" env:"PG_DSN_URL"` } RabbitMQ struct { @@ -42,7 +42,7 @@ func NewConfig() (*Config, error) { } // debug - fmt.Println(dir) + fmt.Println("config path: " + dir) err = cleanenv.ReadConfig(dir+"/config.yml", cfg) if err != nil { diff --git a/cmd/kitchen/config.yml b/cmd/kitchen/config.yml index d4f6856..e2dedf0 100755 --- a/cmd/kitchen/config.yml +++ b/cmd/kitchen/config.yml @@ -8,9 +8,9 @@ http: postgres: pool_max: 2 - url: postgres://postgres:P@ssw0rd@127.0.0.1:5432/postgres?sslmode=disable + dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable -rabbit_mq: +rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ logger: diff --git a/cmd/kitchen/config/config.go b/cmd/kitchen/config/config.go index 9d999d7..600d978 100755 --- a/cmd/kitchen/config/config.go +++ b/cmd/kitchen/config/config.go @@ -15,12 +15,12 @@ type ( configs.HTTP `yaml:"http"` configs.Log `yaml:"logger"` PG `yaml:"postgres"` - RabbitMQ `yaml:"rabbit_mq"` + RabbitMQ `yaml:"rabbitmq"` } PG struct { PoolMax int `env-required:"true" yaml:"pool_max" env:"PG_POOL_MAX"` - URL string `env-required:"true" yaml:"url" env:"PG_URL"` + DsnURL string `env-required:"true" yaml:"dsn_url" env:"PG_DSN_URL"` } RabbitMQ struct { diff --git a/go.mod b/go.mod index bc2e90a..29b9c40 100755 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.19 require ( github.com/Masterminds/squirrel v1.5.3 - github.com/georgysavva/scany v1.2.1 github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang/glog v1.0.0 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 2b8bb9c..25227a6 100755 --- a/go.sum +++ b/go.sum @@ -213,8 +213,6 @@ github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= -github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= -github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -412,8 +410,6 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/georgysavva/scany v1.2.1 h1:91PAMBpwBtDjvn46TaLQmuVhxpAG6p6sjQaU4zPHPSM= -github.com/georgysavva/scany v1.2.1/go.mod h1:vGBpL5XRLOocMFFa55pj0P04DrL3I7qKVRL49K6Eu5o= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -490,8 +486,6 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1080,7 +1074,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 78510ec..3c64a90 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -40,18 +40,8 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) - // PostgresDB - // pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) - // if err != nil { - // slog.Error("failed to create a new Postgres", err, err.Error()) - - // cancel() - - // return err - // } - // defer pg.Close() - - pg, err := postgres.NewPostgreSQLDb(a.cfg.PG.DsnURL) + // postgresdb. + pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) if err != nil { cancel() @@ -59,9 +49,9 @@ func (a *App) Run() error { return err } - defer pg.CloseDB() + defer pg.Close() - // rabbitmq + // rabbitmq. amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { cancel() diff --git a/internal/barista/domain/order.go b/internal/barista/domain/order.go index 2e3ba3f..9915f05 100644 --- a/internal/barista/domain/order.go +++ b/internal/barista/domain/order.go @@ -10,12 +10,12 @@ import ( type BaristaOrder struct { shared.AggregateRoot - ID uuid.UUID `json:"id" db:"id"` - ItemName string `json:"itemName" db:"item_name"` - ItemType shared.ItemType `json:"itemType" db:"item_type"` - TimeUp time.Time `json:"timeUp" db:"time_up"` - Created time.Time `json:"created" db:"created"` - Updated time.Time `json:"updated" db:"updated"` + ID uuid.UUID + ItemName string + ItemType shared.ItemType + TimeUp time.Time + Created time.Time + Updated time.Time } func NewBaristaOrder(e event.BaristaOrdered) BaristaOrder { @@ -35,7 +35,7 @@ func NewBaristaOrder(e event.BaristaOrdered) BaristaOrder { Updated: time.Now(), } - orderUpdateEvent := event.BaristaOrderUpdated{ + orderUpdatedEvent := event.BaristaOrderUpdated{ OrderID: e.OrderID, ItemLineID: e.ItemLineID, Name: e.ItemType.String(), @@ -45,7 +45,7 @@ func NewBaristaOrder(e event.BaristaOrdered) BaristaOrder { TimeUp: timeUp, } - order.ApplyDomain(orderUpdateEvent) + order.ApplyDomain(orderUpdatedEvent) return order } diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index 1c933ce..39f39a3 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -14,10 +14,6 @@ import ( "golang.org/x/exp/slog" ) -type BaristaOrderedEventHandler interface { - Handle(context.Context, event.BaristaOrdered) error -} - var _ BaristaOrderedEventHandler = (*baristaOrderedEventHandler)(nil) type baristaOrderedEventHandler struct { @@ -39,7 +35,14 @@ func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e event.Barista querier := postgresql.New(h.pg.DB) - _, err := querier.CreateOrder(ctx, postgresql.CreateOrderParams{ + tx, err := h.pg.DB.Begin() + if err != nil { + return errors.Wrap(err, "baristaOrderedEventHandler.Handle") + } + + qtx := querier.WithTx(tx) + + _, err = qtx.CreateOrder(ctx, postgresql.CreateOrderParams{ ID: order.ID, ItemType: int32(order.ItemType), ItemName: order.ItemName, @@ -56,6 +59,7 @@ func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e event.Barista return errors.Wrap(err, "baristaOrderedEventHandler-querier.CreateOrder") } + // todo: it might cause dual-write problem, but we accept it temporary for _, event := range order.DomainEvents() { eventBytes, err := json.Marshal(event) if err != nil { @@ -67,5 +71,5 @@ func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e event.Barista } } - return nil + return tx.Commit() } diff --git a/internal/barista/eventhandlers/interfaces.go b/internal/barista/eventhandlers/interfaces.go new file mode 100644 index 0000000..a0f71ef --- /dev/null +++ b/internal/barista/eventhandlers/interfaces.go @@ -0,0 +1,11 @@ +package eventhandlers + +import ( + "context" + + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type BaristaOrderedEventHandler interface { + Handle(context.Context, event.BaristaOrdered) error +} diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 2261e20..ac26c9a 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -9,12 +9,13 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/internal/counter/eventhandlers" + "github.com/thangchung/go-coffeeshop/internal/counter/events" + "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" + pkgevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" + sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" rabConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" @@ -28,8 +29,8 @@ type App struct { cfg *config.Config network string address string - baristaHandler domain.BaristaOrderUpdatedEventHandler - kitchenHandler domain.KitchenOrderUpdatedEventHandler + baristaHandler events.BaristaOrderUpdatedEventHandler + kitchenHandler events.KitchenOrderUpdatedEventHandler } func New(cfg *config.Config) *App { @@ -45,18 +46,18 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) - // PostgresDB - pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) + // postgresdb. + pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) if err != nil { - slog.Error("failed to create new instance of postgres", err) - cancel() + slog.Error("failed to create a new Postgres", err) + return err } defer pg.Close() - // RabbitMQ + // rabbitmq. amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { slog.Error("failed to create a new RabbitMQConn", err) @@ -67,7 +68,7 @@ func (a *App) Run() error { } defer amqpConn.Close() - // gRPC Client + // gRPC Client. conn, err := grpc.Dial(a.cfg.ProductClient.URL, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { cancel() @@ -113,8 +114,8 @@ func (a *App) Run() error { productDomainSvc := counterGrpc.NewGRPCProductClient(conn) // event publishers. - baristaEventPub := event.NewEventPublisher(*baristaOrderPub) - kitchenEventPub := event.NewEventPublisher(*kitchenOrderPub) + baristaEventPub := pkgevents.NewEventPublisher(*baristaOrderPub) + kitchenEventPub := pkgevents.NewEventPublisher(*kitchenOrderPub) // usecases. uc := orders.NewUseCase( @@ -125,8 +126,8 @@ func (a *App) Run() error { ) // event handlers. - a.baristaHandler = eventhandlers.NewBaristaOrderUpdatedEventHandler(orderRepo) - a.kitchenHandler = eventhandlers.NewKitchenOrderUpdatedEventHandler(orderRepo) + a.baristaHandler = handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) + a.kitchenHandler = handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) // consumers consumer, err := rabConsumer.NewConsumer( @@ -187,7 +188,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { switch delivery.Type { case "barista-order-updated": - var payload event.BaristaOrderUpdated + var payload sharedevents.BaristaOrderUpdated err := json.Unmarshal(delivery.Body, &payload) if err != nil { @@ -209,7 +210,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { } } case "kitchen-order-updated": - var payload event.KitchenOrderUpdated + var payload sharedevents.KitchenOrderUpdated err := json.Unmarshal(delivery.Body, &payload) if err != nil { diff --git a/internal/counter/domain/interfaces.go b/internal/counter/domain/interfaces.go index 743cecd..01a924a 100644 --- a/internal/counter/domain/interfaces.go +++ b/internal/counter/domain/interfaces.go @@ -4,7 +4,6 @@ import ( "context" "github.com/google/uuid" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) type ( @@ -18,12 +17,4 @@ type ( ProductDomainService interface { GetItemsByType(context.Context, *PlaceOrderModel, bool) ([]*ItemModel, error) } - - BaristaOrderUpdatedEventHandler interface { - Handle(context.Context, *event.BaristaOrderUpdated) error - } - - KitchenOrderUpdatedEventHandler interface { - Handle(context.Context, *event.KitchenOrderUpdated) error - } ) diff --git a/internal/counter/domain/line_item.go b/internal/counter/domain/line_item.go index 3da7418..006039f 100644 --- a/internal/counter/domain/line_item.go +++ b/internal/counter/domain/line_item.go @@ -6,13 +6,13 @@ import ( ) type LineItem struct { - ID uuid.UUID `json:"id" db:"id"` - ItemType shared.ItemType `json:"item_type" db:"item_type"` - Name string `json:"name" db:"name"` - Price float32 `json:"price" db:"price"` - ItemStatus shared.Status `json:"item_status" db:"item_status"` - IsBaristaOrder bool `json:"is_barista_order" db:"is_barista_order"` - OrderID uuid.UUID `json:"order_id" db:"order_id"` // shadow field + ID uuid.UUID + ItemType shared.ItemType + Name string + Price float32 + ItemStatus shared.Status + IsBaristaOrder bool + OrderID uuid.UUID // shadow field } func NewLineItem(itemType shared.ItemType, name string, price float32, itemStatus shared.Status, isBarista bool) *LineItem { diff --git a/internal/counter/domain/models.go b/internal/counter/domain/models.go index a9aaef8..92bdbd9 100644 --- a/internal/counter/domain/models.go +++ b/internal/counter/domain/models.go @@ -7,24 +7,6 @@ import ( shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) -type OrderModel struct { - ID uuid.UUID - OrderSource shared.OrderSource - LoyaltyMemberID uuid.UUID - OrderStatus shared.Status - Location shared.Location - LineItems []*LineItemModel -} - -type LineItemModel struct { - ID uuid.UUID - ItemType shared.ItemType - Name string - Price float64 - ItemStatus shared.Status - IsBaristaOrder bool -} - type PlaceOrderModel struct { CommandType shared.CommandType OrderSource shared.OrderSource diff --git a/internal/counter/domain/order.go b/internal/counter/domain/order.go index 09525f8..960e858 100644 --- a/internal/counter/domain/order.go +++ b/internal/counter/domain/order.go @@ -5,17 +5,17 @@ import ( "github.com/google/uuid" "github.com/samber/lo" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" + events "github.com/thangchung/go-coffeeshop/internal/pkg/event" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) type Order struct { shared.AggregateRoot - ID uuid.UUID `json:"id" db:"id"` - OrderSource shared.OrderSource `json:"order_source" db:"order_source"` - LoyaltyMemberID uuid.UUID `json:"loyalty_member_id" db:"loyalty_member_id"` - OrderStatus shared.Status `json:"order_status" db:"order_status"` - Location shared.Location `json:"location" db:"location"` + ID uuid.UUID + OrderSource shared.OrderSource + LoyaltyMemberID uuid.UUID + OrderStatus shared.Status + Location shared.Location LineItems []*LineItem } @@ -58,7 +58,7 @@ func CreateOrderFrom( if ok { lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), shared.StatusInProcess, true) - event := event.BaristaOrdered{ + event := events.BaristaOrdered{ OrderID: order.ID, ItemLineID: lineItem.ID, ItemType: item.ItemType, @@ -89,7 +89,7 @@ func CreateOrderFrom( if ok { lineItem := NewLineItem(item.ItemType, item.ItemType.String(), float32(find.Price), shared.StatusInProcess, false) - event := event.KitchenOrdered{ + event := events.KitchenOrdered{ OrderID: order.ID, ItemLineID: lineItem.ID, ItemType: item.ItemType, @@ -109,7 +109,7 @@ func CreateOrderFrom( return order, nil } -func (o *Order) Apply(event *event.OrderUp) error { +func (o *Order) Apply(event *events.OrderUp) error { if len(o.LineItems) == 0 { return nil // we dont do anything } diff --git a/internal/counter/domain/results.go b/internal/counter/domain/results.go deleted file mode 100644 index 641295c..0000000 --- a/internal/counter/domain/results.go +++ /dev/null @@ -1,6 +0,0 @@ -package domain - -type OrderListResult struct { - Order *Order `db:"o"` - LineItem *LineItem `db:"l"` -} diff --git a/internal/counter/eventhandlers/barista_order_updated.go b/internal/counter/events/handlers/barista_order_updated.go similarity index 65% rename from internal/counter/eventhandlers/barista_order_updated.go rename to internal/counter/events/handlers/barista_order_updated.go index 88c12fc..4094e6e 100644 --- a/internal/counter/eventhandlers/barista_order_updated.go +++ b/internal/counter/events/handlers/barista_order_updated.go @@ -1,10 +1,11 @@ -package eventhandlers +package handlers import ( "context" - "fmt" + "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/counter/events" "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) @@ -12,9 +13,9 @@ type baristaOrderUpdatedEventHandler struct { orderRepo domain.OrderRepo } -var _ domain.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) +var _ events.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) -func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.BaristaOrderUpdatedEventHandler { +func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.BaristaOrderUpdatedEventHandler { return &baristaOrderUpdatedEventHandler{ orderRepo: orderRepo, } @@ -23,7 +24,7 @@ func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.Baris func (h *baristaOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.BaristaOrderUpdated) error { order, err := h.orderRepo.GetByID(ctx, e.OrderID) if err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) + return errors.Wrap(err, "orderRepo.GetByID") } orderUp := event.OrderUp{ @@ -36,12 +37,12 @@ func (h *baristaOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.B } if err = order.Apply(&orderUp); err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) + return errors.Wrap(err, "order.Apply") } _, err = h.orderRepo.Update(ctx, order) if err != nil { - return fmt.Errorf("NewBaristaOrderUpdatedEventHandler-Handle-h.orderRepo.Update(ctx, ToDto(order)): %w", err) + return errors.Wrap(err, "orderRepo.Update") } return nil diff --git a/internal/counter/eventhandlers/kitchen_order_updated.go b/internal/counter/events/handlers/kitchen_order_updated.go similarity index 65% rename from internal/counter/eventhandlers/kitchen_order_updated.go rename to internal/counter/events/handlers/kitchen_order_updated.go index 2a4b470..4f7bea8 100644 --- a/internal/counter/eventhandlers/kitchen_order_updated.go +++ b/internal/counter/events/handlers/kitchen_order_updated.go @@ -1,10 +1,11 @@ -package eventhandlers +package handlers import ( "context" - "fmt" + "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/counter/events" "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) @@ -12,9 +13,9 @@ type kitchenOrderUpdatedEventHandler struct { orderRepo domain.OrderRepo } -var _ domain.KitchenOrderUpdatedEventHandler = (*kitchenOrderUpdatedEventHandler)(nil) +var _ events.KitchenOrderUpdatedEventHandler = (*kitchenOrderUpdatedEventHandler)(nil) -func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.KitchenOrderUpdatedEventHandler { +func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.KitchenOrderUpdatedEventHandler { return &kitchenOrderUpdatedEventHandler{ orderRepo: orderRepo, } @@ -23,7 +24,7 @@ func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) domain.Kitch func (h *kitchenOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.KitchenOrderUpdated) error { order, err := h.orderRepo.GetByID(ctx, e.OrderID) if err != nil { - return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-h.orderRepo.GetOrderByID(ctx, e.OrderID): %w", err) + return errors.Wrap(err, "orderRepo.GetOrderByID") } orderUp := event.OrderUp{ @@ -36,12 +37,12 @@ func (h *kitchenOrderUpdatedEventHandler) Handle(ctx context.Context, e *event.K } if err = order.Apply(&orderUp); err != nil { - return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-order.Apply(e): %w", err) + return errors.Wrap(err, "order.Apply") } _, err = h.orderRepo.Update(ctx, order) if err != nil { - return fmt.Errorf("NewKitchenOrderUpdatedEventHandler-Handle-h.orderRepo.Update(ctx, ToDto(order)): %w", err) + return errors.Wrap(err, "orderRepo.Update") } return nil diff --git a/internal/counter/events/interfaces.go b/internal/counter/events/interfaces.go new file mode 100644 index 0000000..b084198 --- /dev/null +++ b/internal/counter/events/interfaces.go @@ -0,0 +1,17 @@ +package events + +import ( + "context" + + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type ( + BaristaOrderUpdatedEventHandler interface { + Handle(context.Context, *event.BaristaOrderUpdated) error + } + + KitchenOrderUpdatedEventHandler interface { + Handle(context.Context, *event.KitchenOrderUpdated) error + } +) diff --git a/internal/counter/infras/postgresql/query.sql.go b/internal/counter/infras/postgresql/query.sql.go index c950ca3..9708333 100644 --- a/internal/counter/infras/postgresql/query.sql.go +++ b/internal/counter/infras/postgresql/query.sql.go @@ -53,7 +53,7 @@ func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (Order return i, err } -const getOrderByID = `-- name: GetOrderByID :many +const getAll = `-- name: GetAll :many SELECT o.id, @@ -68,10 +68,9 @@ SELECT is_barista_order FROM "order".orders o LEFT JOIN "order".line_items l ON o.id = l.order_id -WHERE o.id = $1 ` -type GetOrderByIDRow struct { +type GetAllRow struct { ID uuid.UUID OrderSource int32 LoyaltyMemberID uuid.UUID @@ -84,15 +83,15 @@ type GetOrderByIDRow struct { IsBaristaOrder bool } -func (q *Queries) GetOrderByID(ctx context.Context, id uuid.UUID) ([]GetOrderByIDRow, error) { - rows, err := q.db.QueryContext(ctx, getOrderByID, id) +func (q *Queries) GetAll(ctx context.Context) ([]GetAllRow, error) { + rows, err := q.db.QueryContext(ctx, getAll) if err != nil { return nil, err } defer rows.Close() - var items []GetOrderByIDRow + var items []GetAllRow for rows.Next() { - var i GetOrderByIDRow + var i GetAllRow if err := rows.Scan( &i.ID, &i.OrderSource, @@ -118,63 +117,7 @@ func (q *Queries) GetOrderByID(ctx context.Context, id uuid.UUID) ([]GetOrderByI return items, nil } -const insertItemLine = `-- name: InsertItemLine :one - -INSERT INTO - "order".line_items ( - id, - item_type, - name, - price, - item_status, - is_barista_order, - order_id, - created, - updated - ) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, item_type, name, price, item_status, is_barista_order, order_id, created, updated -` - -type InsertItemLineParams struct { - ID uuid.UUID - ItemType int32 - Name string - Price string - ItemStatus int32 - IsBaristaOrder bool - OrderID uuid.NullUUID - Created time.Time - Updated sql.NullTime -} - -func (q *Queries) InsertItemLine(ctx context.Context, arg InsertItemLineParams) (OrderLineItem, error) { - row := q.db.QueryRowContext(ctx, insertItemLine, - arg.ID, - arg.ItemType, - arg.Name, - arg.Price, - arg.ItemStatus, - arg.IsBaristaOrder, - arg.OrderID, - arg.Created, - arg.Updated, - ) - var i OrderLineItem - err := row.Scan( - &i.ID, - &i.ItemType, - &i.Name, - &i.Price, - &i.ItemStatus, - &i.IsBaristaOrder, - &i.OrderID, - &i.Created, - &i.Updated, - ) - return i, err -} - -const listOrders = `-- name: ListOrders :many +const getByID = `-- name: GetByID :many SELECT o.id, @@ -189,9 +132,10 @@ SELECT is_barista_order FROM "order".orders o LEFT JOIN "order".line_items l ON o.id = l.order_id +WHERE o.id = $1 ` -type ListOrdersRow struct { +type GetByIDRow struct { ID uuid.UUID OrderSource int32 LoyaltyMemberID uuid.UUID @@ -204,15 +148,15 @@ type ListOrdersRow struct { IsBaristaOrder bool } -func (q *Queries) ListOrders(ctx context.Context) ([]ListOrdersRow, error) { - rows, err := q.db.QueryContext(ctx, listOrders) +func (q *Queries) GetByID(ctx context.Context, id uuid.UUID) ([]GetByIDRow, error) { + rows, err := q.db.QueryContext(ctx, getByID, id) if err != nil { return nil, err } defer rows.Close() - var items []ListOrdersRow + var items []GetByIDRow for rows.Next() { - var i ListOrdersRow + var i GetByIDRow if err := rows.Scan( &i.ID, &i.OrderSource, @@ -238,6 +182,62 @@ func (q *Queries) ListOrders(ctx context.Context) ([]ListOrdersRow, error) { return items, nil } +const insertItemLine = `-- name: InsertItemLine :one + +INSERT INTO + "order".line_items ( + id, + item_type, + name, + price, + item_status, + is_barista_order, + order_id, + created, + updated + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, item_type, name, price, item_status, is_barista_order, order_id, created, updated +` + +type InsertItemLineParams struct { + ID uuid.UUID + ItemType int32 + Name string + Price string + ItemStatus int32 + IsBaristaOrder bool + OrderID uuid.NullUUID + Created time.Time + Updated sql.NullTime +} + +func (q *Queries) InsertItemLine(ctx context.Context, arg InsertItemLineParams) (OrderLineItem, error) { + row := q.db.QueryRowContext(ctx, insertItemLine, + arg.ID, + arg.ItemType, + arg.Name, + arg.Price, + arg.ItemStatus, + arg.IsBaristaOrder, + arg.OrderID, + arg.Created, + arg.Updated, + ) + var i OrderLineItem + err := row.Scan( + &i.ID, + &i.ItemType, + &i.Name, + &i.Price, + &i.ItemStatus, + &i.IsBaristaOrder, + &i.OrderID, + &i.Created, + &i.Updated, + ) + return i, err +} + const updateItemLine = `-- name: UpdateItemLine :exec UPDATE "order".line_items diff --git a/internal/counter/infras/postgresql/query/query.sql b/internal/counter/infras/postgresql/query/query.sql index 27c7695..73d8278 100644 --- a/internal/counter/infras/postgresql/query/query.sql +++ b/internal/counter/infras/postgresql/query/query.sql @@ -1,4 +1,4 @@ --- name: ListOrders :many +-- name: GetAll :many SELECT o.id, @@ -14,7 +14,7 @@ SELECT FROM "order".orders o LEFT JOIN "order".line_items l ON o.id = l.order_id; --- name: GetOrderByID :many +-- name: GetByID :many SELECT o.id, diff --git a/internal/counter/infras/repo/orders_postgres.go b/internal/counter/infras/repo/orders_postgres.go index c87ec4c..be7948b 100644 --- a/internal/counter/infras/repo/orders_postgres.go +++ b/internal/counter/infras/repo/orders_postgres.go @@ -2,14 +2,17 @@ package repo import ( "context" + "database/sql" "fmt" + "strconv" "time" - "github.com/georgysavva/scany/pgxscan" "github.com/google/uuid" "github.com/pkg/errors" "github.com/samber/lo" "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/internal/counter/infras/postgresql" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" "github.com/thangchung/go-coffeeshop/pkg/postgres" ) @@ -25,73 +28,41 @@ func NewOrderRepo(pg *postgres.Postgres) domain.OrderRepo { return &orderRepo{pg: pg} } -func (d *orderRepo) getAllFunc() (string, error) { - sql, _, err := d.pg.Builder. - Select(` - o.id as "o.id", - order_source as "o.order_source", - loyalty_member_id as "o.loyalty_member_id", - order_status as "o.order_status", - l.id as "l.id", - item_type as "l.item_type", - name as "l.name", - price as "l.price", - item_status as "l.item_status", - is_barista_order as "l.is_barista_order", - o.id as "l.order_id" - `). - From(`"order".orders o`).Join(`"order".line_items l ON o.id = l.order_id`). - Limit(_defaultEntityCap). - ToSql() - - return sql, err -} - -func (d *orderRepo) getByIDFunc(id uuid.UUID) (string, []interface{}, error) { - return d.pg.Builder. - Select(` - o.id as "o.id", - order_source as "o.order_source", - loyalty_member_id as "o.loyalty_member_id", - order_status as "o.order_status", - l.id as "l.id", - item_type as "l.item_type", - name as "l.name", - price as "l.price", - item_status as "l.item_status", - is_barista_order as "l.is_barista_order", - o.id as "l.order_id" - `). - From(`"order".orders o`).Join(`"order".line_items l ON o.id = l.order_id`). - Where("o.id = ?", id). - ToSql() -} - func (d *orderRepo) GetAll(ctx context.Context) ([]*domain.Order, error) { - sql, err := d.getAllFunc() - if err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetAll-r.Builder: %w", err) - } + querier := postgresql.New(d.pg.DB) - rows, err := d.pg.Pool.Query(ctx, sql) + results, err := querier.GetAll(ctx) if err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetAll-r.Pool.Query: %w", err) - } - defer rows.Close() - - var results []domain.OrderListResult - if err := pgxscan.ScanAll(&results, rows); err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetAll-pgxscan.ScanAll: %w", err) + return nil, errors.Wrap(err, "querier.GetAll") } - uniqueResults := lo.UniqBy(results, func(x domain.OrderListResult) string { - return x.Order.ID.String() + uniqueResults := lo.UniqBy(results, func(x postgresql.GetAllRow) string { + return x.ID.String() }) - orders := lo.Map(uniqueResults, func(x domain.OrderListResult, _ int) *domain.Order { - return x.Order + orders := lo.Map(uniqueResults, func(x postgresql.GetAllRow, _ int) *domain.Order { + return &domain.Order{ + ID: x.ID, + OrderSource: shared.OrderSource(x.OrderSource), + LoyaltyMemberID: x.LoyaltyMemberID, + OrderStatus: shared.Status(x.OrderStatus), + } }) - lineItems := lo.Map(results, func(x domain.OrderListResult, _ int) *domain.LineItem { - return x.LineItem + lineItems := lo.Map(results, func(x postgresql.GetAllRow, _ int) *domain.LineItem { + priceX, err := strconv.ParseFloat(x.Price, 32) + if err != nil { + return nil + } + price := float32(priceX) + + return &domain.LineItem{ + ID: x.LineItemID.UUID, + ItemType: shared.ItemType(x.ItemType), + Name: x.Name, + Price: price, + ItemStatus: shared.Status(x.ItemStatus), + IsBaristaOrder: x.IsBaristaOrder, + OrderID: x.ID, + } }) entities := make([]*domain.Order, 0, _defaultEntityCap) @@ -126,31 +97,41 @@ func (d *orderRepo) GetAll(ctx context.Context) ([]*domain.Order, error) { } func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, error) { - sql, args, err := d.getByIDFunc(id) - if err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetByID-r.Builder: %w", err) - } + querier := postgresql.New(d.pg.DB) - rows, err := d.pg.Pool.Query(ctx, sql, args...) + results, err := querier.GetByID(ctx, id) if err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetByID-r.Pool.Query: %w", err) + return nil, errors.Wrap(err, "querier.GetAll") } - defer rows.Close() - var results []domain.OrderListResult - if err := pgxscan.ScanAll(&results, rows); err != nil { - return nil, fmt.Errorf("NewOrderRepo-GetByID-pgxscan.ScanAll: %w", err) - } - - uniqueResults := lo.UniqBy(results, func(x domain.OrderListResult) string { - return x.Order.ID.String() + uniqueResults := lo.UniqBy(results, func(x postgresql.GetByIDRow) string { + return x.ID.String() }) - orders := lo.Map(uniqueResults, func(x domain.OrderListResult, _ int) *domain.Order { - return x.Order + orders := lo.Map(uniqueResults, func(x postgresql.GetByIDRow, _ int) *domain.Order { + return &domain.Order{ + ID: x.ID, + OrderSource: shared.OrderSource(x.OrderSource), + LoyaltyMemberID: x.LoyaltyMemberID, + OrderStatus: shared.Status(x.OrderStatus), + } }) - lineItems := lo.Map(results, func(x domain.OrderListResult, _ int) *domain.LineItem { - return x.LineItem + lineItems := lo.Map(results, func(x postgresql.GetByIDRow, _ int) *domain.LineItem { + priceX, err := strconv.ParseFloat(x.Price, 32) + if err != nil { + return nil + } + price := float32(priceX) + + return &domain.LineItem{ + ID: x.LineItemID.UUID, + ItemType: shared.ItemType(x.ItemType), + Name: x.Name, + Price: price, + ItemStatus: shared.Status(x.ItemStatus), + IsBaristaOrder: x.IsBaristaOrder, + OrderID: x.ID, + } }) if len(orders) == 0 { @@ -179,101 +160,94 @@ func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, e } func (d *orderRepo) Create(ctx context.Context, order *domain.Order) error { - tx, err := d.pg.Pool.Begin(ctx) - if err != nil { - return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") - } + querier := postgresql.New(d.pg.DB) - // insert order - sql, args, err := d.pg.Builder. - Insert(`"order".orders`). - Columns("id", "order_source", "loyalty_member_id", "order_status", "updated"). - Values( - order.ID, - order.OrderSource, - order.LoyaltyMemberID, - order.OrderStatus, - time.Now(), - ). - ToSql() + tx, err := d.pg.DB.Begin() if err != nil { - return tx.Rollback(ctx) + return errors.Wrap(err, "baristaOrderedEventHandler.Handle") } - _, err = d.pg.Pool.Exec(ctx, sql, args...) + qtx := querier.WithTx(tx) + + _, err = qtx.CreateOrder(ctx, postgresql.CreateOrderParams{ + ID: order.ID, + OrderSource: int32(order.OrderSource), + LoyaltyMemberID: order.LoyaltyMemberID, + OrderStatus: int32(order.OrderStatus), + Updated: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + }) if err != nil { - return tx.Rollback(ctx) + return errors.Wrap(err, "qtx.CreateOrder(ctx, postgresql.CreateOrderParams{})") } // continue to insert order items for _, item := range order.LineItems { - sql, args, err = d.pg.Builder. - Insert(`"order".line_items`). - Columns("id", "item_type", "name", "price", "item_status", "is_barista_order", "order_id", "created", "updated"). - Values( - uuid.New(), - item.ItemType, - item.Name, - item.Price, - item.ItemStatus, - item.IsBaristaOrder, - order.ID, - time.Now(), - time.Now(), - ). - ToSql() - if err != nil { - return tx.Rollback(ctx) - } + _, err = qtx.InsertItemLine(ctx, postgresql.InsertItemLineParams{ + ID: item.ID, + ItemType: int32(item.ItemType), + Name: item.Name, + Price: fmt.Sprintf("%f", item.Price), + ItemStatus: int32(item.ItemStatus), + IsBaristaOrder: item.IsBaristaOrder, + OrderID: uuid.NullUUID{ + UUID: order.ID, + Valid: true, + }, + Created: time.Now(), + Updated: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + }) - _, err = d.pg.Pool.Exec(ctx, sql, args...) if err != nil { - return tx.Rollback(ctx) + return errors.Wrap(err, "qtx.InsertItemLine(ctx, postgresql.InsertItemLineParams{})") } } - return tx.Commit(ctx) + return tx.Commit() } func (d *orderRepo) Update(ctx context.Context, order *domain.Order) (*domain.Order, error) { - tx, err := d.pg.Pool.Begin(ctx) - if err != nil { - return nil, errors.Wrapf(err, "orderRepo-Update-d.pg.Pool.Begin(ctx)") - } + querier := postgresql.New(d.pg.DB) - // update order - sql, args, err := d.pg.Builder. - Update(`"order".orders`). - Set("order_status", order.OrderStatus). - Set("updated", time.Now()). - Where("id = ?", order.ID). - ToSql() + tx, err := d.pg.DB.Begin() if err != nil { - return nil, tx.Rollback(ctx) + return nil, errors.Wrap(err, "baristaOrderedEventHandler.Handle") } - _, err = d.pg.Pool.Exec(ctx, sql, args...) + qtx := querier.WithTx(tx) + + err = qtx.UpdateOrder(ctx, postgresql.UpdateOrderParams{ + ID: order.ID, + OrderStatus: int32(order.OrderStatus), + Updated: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + }) if err != nil { - return nil, tx.Rollback(ctx) + return nil, errors.Wrap(err, "qtx.UpdateOrder(ctx, postgresql.UpdateOrderParams{})") } - // continue to update order items + // continue to insert order items for _, item := range order.LineItems { - sql, args, err = d.pg.Builder. - Update(`"order".line_items`). - Set("item_status", item.ItemStatus). - Set("updated", time.Now()). - Where("id = ?", item.ID). - ToSql() - if err != nil { - return nil, tx.Rollback(ctx) - } + err = qtx.UpdateItemLine(ctx, postgresql.UpdateItemLineParams{ + ID: item.ID, + ItemStatus: int32(item.ItemStatus), + Updated: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + }) - _, err = d.pg.Pool.Exec(ctx, sql, args...) if err != nil { - return nil, tx.Rollback(ctx) + return nil, errors.Wrap(err, "qtx.UpdateItemLine(ctx, postgresql.UpdateItemLineParams{})") } } - return order, tx.Commit(ctx) + return nil, tx.Commit() } diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index 51960aa..cd42c3a 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -12,11 +12,10 @@ import ( "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" - "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/repo" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgconsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) @@ -42,12 +41,12 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) // postgresdb. - pg, err := postgres.NewPostgresDB(a.cfg.PG.URL, postgres.MaxPoolSize(a.cfg.PG.PoolMax)) + pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) if err != nil { - slog.Error("failed to create a new Postgres", err) - cancel() + slog.Error("failed to create a new Postgres", err) + return err } defer pg.Close() @@ -76,19 +75,16 @@ func (a *App) Run() error { return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") } - // repository. - orderRepo := repo.NewOrderRepo(pg) - // event handlers. - a.handler = eventhandlers.NewKitchenOrderedEventHandler(orderRepo, counterOrderPub) + a.handler = eventhandlers.NewKitchenOrderedEventHandler(pg, counterOrderPub) // consumers. - consumer, err := consumer.NewConsumer( + consumer, err := pkgconsumer.NewConsumer( amqpConn, - consumer.ExchangeName("kitchen-order-exchange"), - consumer.QueueName("kitchen-order-queue"), - consumer.BindingKey("kitchen-order-routing-key"), - consumer.ConsumerTag("kitchen-order-consumer"), + pkgconsumer.ExchangeName("kitchen-order-exchange"), + pkgconsumer.QueueName("kitchen-order-queue"), + pkgconsumer.BindingKey("kitchen-order-routing-key"), + pkgconsumer.ConsumerTag("kitchen-order-consumer"), ) if err != nil { @@ -133,7 +129,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { slog.Error("failed to Unmarshal message", err) } - err = c.handler.Handle(ctx, &payload) + err = c.handler.Handle(ctx, payload) if err != nil { if err = delivery.Reject(false); err != nil { diff --git a/internal/kitchen/domain/order.go b/internal/kitchen/domain/order.go index 4ee2082..54c61d8 100644 --- a/internal/kitchen/domain/order.go +++ b/internal/kitchen/domain/order.go @@ -4,24 +4,65 @@ import ( "time" "github.com/google/uuid" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" ) -// type ItemType int8 +type KitchenOrder struct { + shared.AggregateRoot + ID uuid.UUID + OrderID uuid.UUID + ItemName string + ItemType shared.ItemType + TimeUp time.Time + Created time.Time + Updated time.Time +} -// const ( -// CakePop ItemType = iota + 6 -// Croissant -// Muffin -// CroissantChocolate -// ) +func NewKitchenOrder(e event.KitchenOrdered) KitchenOrder { + timeIn := time.Now() -type KitchenOrder struct { - ID uuid.UUID `json:"id" db:"id"` - OrderID uuid.UUID `json:"orderId" db:"order_id"` - ItemName string `json:"itemName" db:"item_name"` - ItemType shared.ItemType `json:"itemType" db:"item_type"` - TimeUp time.Time `json:"timeUp" db:"time_up"` - Created time.Time `json:"created" db:"created"` - Updated time.Time `json:"updated" db:"updated"` + delay := calculateDelay(e.ItemType) + time.Sleep(delay) // simulate the delay when makes the drink + + timeUp := time.Now().Add(delay) + + order := KitchenOrder{ + ID: e.ItemLineID, + OrderID: e.OrderID, + ItemName: e.ItemType.String(), + ItemType: e.ItemType, + TimeUp: timeUp, + Created: time.Now(), + Updated: time.Now(), + } + + orderUpdatedEvent := event.KitchenOrderUpdated{ + OrderID: e.OrderID, + ItemLineID: e.ItemLineID, + Name: e.ItemType.String(), + ItemType: e.ItemType, + MadeBy: "teesee", + TimeIn: timeIn, + TimeUp: timeUp, + } + + order.ApplyDomain(orderUpdatedEvent) + + return order +} + +func calculateDelay(itemType shared.ItemType) time.Duration { + switch itemType { + case shared.ItemTypeCroissant: + return 7 * time.Second + case shared.ItemTypeCroissantChocolate: + return 7 * time.Second + case shared.ItemTypeCakePop: + return 5 * time.Second + case shared.ItemTypeMuffin: + return 7 * time.Second + default: + return 3 * time.Second + } } diff --git a/internal/kitchen/eventhandlers/interfaces.go b/internal/kitchen/eventhandlers/interfaces.go new file mode 100644 index 0000000..19fff5c --- /dev/null +++ b/internal/kitchen/eventhandlers/interfaces.go @@ -0,0 +1,11 @@ +package eventhandlers + +import ( + "context" + + "github.com/thangchung/go-coffeeshop/internal/pkg/event" +) + +type KitchenOrderedEventHandler interface { + Handle(context.Context, event.KitchenOrdered) error +} diff --git a/internal/kitchen/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go index 3480210..f787fa8 100644 --- a/internal/kitchen/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/eventhandlers/kitchen_ordered.go @@ -2,94 +2,78 @@ package eventhandlers import ( "context" + "database/sql" "encoding/json" - "time" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" + "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" - shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) -type KitchenOrderedEventHandler interface { - Handle(context.Context, *event.KitchenOrdered) error -} - var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) type kitchenOrderedEventHandler struct { - repo domain.OrderRepo + pg *postgres.Postgres counterPub *publisher.Publisher } func NewKitchenOrderedEventHandler( - repo domain.OrderRepo, + pg *postgres.Postgres, counterPub *publisher.Publisher, ) KitchenOrderedEventHandler { return &kitchenOrderedEventHandler{ - repo: repo, + pg: pg, counterPub: counterPub, } } -func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e *event.KitchenOrdered) error { +func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e event.KitchenOrdered) error { slog.Info("kitchenOrderedEventHandler-Handle", "KitchenOrdered", e) - timeIn := time.Now() - - delay := calculateDelay(e.ItemType) - time.Sleep(delay) + order := domain.NewKitchenOrder(e) - timeUp := time.Now().Add(delay) + querier := postgresql.New(h.pg.DB) - err := h.repo.Create(ctx, &domain.KitchenOrder{ - ID: e.ItemLineID, - OrderID: e.OrderID, - ItemType: e.ItemType, - ItemName: e.ItemType.String(), - TimeUp: timeUp, - Created: time.Now(), - Updated: time.Now(), - }) + tx, err := h.pg.DB.Begin() if err != nil { - return errors.Wrap(err, "kitchenOrderedEventHandler-h.repo.Create") + return errors.Wrap(err, "kitchenOrderedEventHandler.Handle") } - message := event.KitchenOrderUpdated{ - OrderID: e.OrderID, - ItemLineID: e.ItemLineID, - Name: e.ItemType.String(), - ItemType: e.ItemType, - MadeBy: "teesee", - TimeIn: timeIn, - TimeUp: timeUp, - } + qtx := querier.WithTx(tx) - eventBytes, err := json.Marshal(message) + _, err = qtx.CreateOrder(ctx, postgresql.CreateOrderParams{ + ID: order.ID, + OrderID: e.OrderID, + ItemType: int32(order.ItemType), + ItemName: order.ItemName, + TimeUp: order.TimeUp, + Created: order.Created, + Updated: sql.NullTime{ + Time: order.Updated, + Valid: true, + }, + }) if err != nil { - return errors.Wrap(err, "json.Marshal-events.KitchenOrderUpdated") - } + slog.Info("failed to call to repo", "error", err) - if err := h.counterPub.Publish(ctx, eventBytes, "text/plain"); err != nil { - return errors.Wrap(err, "KitchenOrderedEventHandler-Publish") + return errors.Wrap(err, "kitchenOrderedEventHandler-querier.CreateOrder") } - return nil -} + // todo: it might cause dual-write problem, but we accept it temporary + for _, event := range order.DomainEvents() { + eventBytes, err := json.Marshal(event) + if err != nil { + return errors.Wrap(err, "json.Marshal[event]") + } -func calculateDelay(itemType shared.ItemType) time.Duration { - switch itemType { - case shared.ItemTypeCroissant: - return 7 * time.Second - case shared.ItemTypeCroissantChocolate: - return 7 * time.Second - case shared.ItemTypeCakePop: - return 5 * time.Second - case shared.ItemTypeMuffin: - return 7 * time.Second - default: - return 3 * time.Second + if err := h.counterPub.Publish(ctx, eventBytes, "text/plain"); err != nil { + return errors.Wrap(err, "counterPub.Publish") + } } + + return tx.Commit() } diff --git a/internal/kitchen/infras/repo/orders_postgres.go b/internal/kitchen/infras/repo/orders_postgres.go deleted file mode 100644 index 573fcda..0000000 --- a/internal/kitchen/infras/repo/orders_postgres.go +++ /dev/null @@ -1,51 +0,0 @@ -package repo - -import ( - "context" - - "github.com/pkg/errors" - "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" - "github.com/thangchung/go-coffeeshop/pkg/postgres" -) - -var _ domain.OrderRepo = (*orderRepo)(nil) - -type orderRepo struct { - pg *postgres.Postgres -} - -func NewOrderRepo(pg *postgres.Postgres) domain.OrderRepo { - return &orderRepo{pg: pg} -} - -func (d *orderRepo) Create(ctx context.Context, kitchenOrder *domain.KitchenOrder) error { - tx, err := d.pg.Pool.Begin(ctx) - if err != nil { - return errors.Wrapf(err, "orderRepo-Create-d.pg.Pool.Begin(ctx)") - } - - // insert order - sql, args, err := d.pg.Builder. - Insert(`"kitchen".kitchen_orders`). - Columns("id", "order_id", "item_type", "item_name", "time_up", "created", "updated"). - Values( - kitchenOrder.ID, - kitchenOrder.OrderID, - kitchenOrder.ItemType, - kitchenOrder.ItemName, - kitchenOrder.TimeUp, - kitchenOrder.Created, - kitchenOrder.Updated, - ). - ToSql() - if err != nil { - return tx.Rollback(ctx) - } - - _, err = d.pg.Pool.Exec(ctx, sql, args...) - if err != nil { - return tx.Rollback(ctx) - } - - return tx.Commit(ctx) -} diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go index f137d55..befc69c 100644 --- a/internal/pkg/event/events.go +++ b/internal/pkg/event/events.go @@ -46,6 +46,7 @@ func (e BaristaOrderUpdated) Identity() string { } type KitchenOrderUpdated struct { + shared.DomainEvent OrderID uuid.UUID `json:"orderId"` ItemLineID uuid.UUID `json:"itemLineId"` Name string `json:"name"` diff --git a/internal/pkg/shared_kernel/aggregate_root.go b/internal/pkg/shared_kernel/aggregate_root.go index a64dbd2..4a24bbf 100644 --- a/internal/pkg/shared_kernel/aggregate_root.go +++ b/internal/pkg/shared_kernel/aggregate_root.go @@ -1,19 +1,5 @@ package sharedkernel -import ( - "context" - "time" -) - -type DomainEvent interface { - CreateAt() time.Time - Identity() string -} - -type EventPublisher interface { - Publish(context.Context, []DomainEvent) error -} - type AggregateRoot struct { domainEvents []DomainEvent } diff --git a/internal/pkg/shared_kernel/interfaces.go b/internal/pkg/shared_kernel/interfaces.go new file mode 100644 index 0000000..89e9233 --- /dev/null +++ b/internal/pkg/shared_kernel/interfaces.go @@ -0,0 +1,17 @@ +package sharedkernel + +import ( + "context" + "time" +) + +type ( + DomainEvent interface { + CreateAt() time.Time + Identity() string + } + + EventPublisher interface { + Publish(context.Context, []DomainEvent) error + } +) diff --git a/pkg/postgres/options.go b/pkg/postgres/options.go index 220a377..d99af8f 100644 --- a/pkg/postgres/options.go +++ b/pkg/postgres/options.go @@ -4,12 +4,6 @@ import "time" type Option func(*Postgres) -func MaxPoolSize(size int) Option { - return func(p *Postgres) { - p.maxPoolSize = size - } -} - func ConnAttempts(attempts int) Option { return func(p *Postgres) { p.connAttempts = attempts diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 311f3e0..041e1a1 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -1,9 +1,7 @@ package postgres import ( - "context" "database/sql" - "fmt" "log" "time" @@ -15,13 +13,11 @@ import ( ) const ( - _defaultMaxPoolSize = 1 _defaultConnAttempts = 3 _defaultConnTimeout = time.Second ) type Postgres struct { - maxPoolSize int connAttempts int connTimeout time.Duration @@ -30,11 +26,10 @@ type Postgres struct { DB *sql.DB } -func NewPostgreSQLDb(url string, opts ...Option) (*Postgres, error) { +func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { slog.Info("CONN", "connect string", url) pg := &Postgres{ - maxPoolSize: _defaultMaxPoolSize, connAttempts: _defaultConnAttempts, connTimeout: _defaultConnTimeout, } @@ -57,63 +52,11 @@ func NewPostgreSQLDb(url string, opts ...Option) (*Postgres, error) { pg.connAttempts-- } - // slog.Info("CONN", "connect string", url) - // pg.DB, err = sql.Open("postgres", url) - // if err != nil { - // return nil, err - // } - - return pg, nil -} - -func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { - pg := &Postgres{ - maxPoolSize: _defaultMaxPoolSize, - connAttempts: _defaultConnAttempts, - connTimeout: _defaultConnTimeout, - } - - for _, opt := range opts { - opt(pg) - } - - pg.Builder = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) - - poolConfig, err := pgxpool.ParseConfig(url) - if err != nil { - return nil, fmt.Errorf("postgres - NewPostgres - pgxpool.ParseConfig: %w", err) - } - - poolConfig.MaxConns = int32(pg.maxPoolSize) - - for pg.connAttempts > 0 { - pg.Pool, err = pgxpool.ConnectConfig(context.Background(), poolConfig) - if err != nil { - break - } - - log.Printf("Postgres is trying to connect, attempts left: %d", pg.connAttempts) - - time.Sleep(pg.connTimeout) - - pg.connAttempts-- - } - - if err != nil { - return nil, fmt.Errorf("postgres - NewPostgres - connAttempts == 0: %w", err) - } - return pg, nil } -func (p *Postgres) CloseDB() { +func (p *Postgres) Close() { if p.DB != nil { p.DB.Close() } } - -func (p *Postgres) Close() { - if p.Pool != nil { - p.Pool.Close() - } -} From 17e845122048dc8deb668978f6e5138e497fb8c8 Mon Sep 17 00:00:00 2001 From: thangchung Date: Sun, 25 Dec 2022 23:38:05 +0700 Subject: [PATCH 11/16] clean up --- README.md | 19 ++--- go.mod | 12 --- go.sum | 42 ---------- internal/counter/infras/postgresql/models.go | 28 +++---- .../counter/infras/postgresql/query.sql.go | 80 +++++++++---------- internal/kitchen/infras/postgresql/models.go | 14 ++-- .../kitchen/infras/postgresql/query.sql.go | 14 ++-- pkg/postgres/postgres.go | 6 +- sqlc.yaml | 6 +- 9 files changed, 82 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index fa9fa2d..c774bd3 100755 --- a/README.md +++ b/README.md @@ -8,17 +8,18 @@ Other version can be found at: - [.NET CoffeeShop with Modular Monolith approach](https://github.com/thangchung/coffeeshop-modular) ## Technical stack + - Backend building blocks - [grpc-ecosystem/grpc-gateway/v2](https://github.com/grpc-ecosystem/grpc-gateway) - [labstack/echo/v4](https://github.com/labstack/echo) - [rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) - - [jackc/pgx/v4](https://github.com/jackc/pgx) - - [Masterminds/squirrel](https://github.com/Masterminds/squirrel) - - [georgysavva/scany](https://github.com/georgysavva/scany) + - [kyleconroy/sqlc](https://github.com/kyleconroy/sqlc) + - [pq](github.com/lib/pq) - [golang-migrate/migrate/v4](https://github.com/golang-migrate/migrate) - Utils - [ilyakaznacheev/cleanenv](https://github.com/ilyakaznacheev/cleanenv) - - [sirupsen/logrus](https://github.com/sirupsen/logrus) + - golang.org/x/exp/slog + - [sirupsen/logrus](https://github.com/sirupsen/logrus) - [samber/lo](https://github.com/samber/lo) - golang/glog - google/uuid @@ -33,7 +34,7 @@ Other version can be found at: ## CoffeeShop - Choreography Saga -![](docs/coffeeshop.svg) +![coffeeshop](docs/coffeeshop.svg) ## Services @@ -51,8 +52,8 @@ No. | Service | URI Jump into `.devcontainer`, then ```bash -> docker-compose -f docker-compose-full.yaml build -> docker-compose -f docker-compose-full.yaml up +> docker-compose build +> docker-compose up ``` From `vscode` => Press F1 => Type `Simple Browser View` => Choose it and enter [http://localhost:8080](http://localhost:8080). @@ -74,7 +75,7 @@ Enjoy!!! ## HashiCorp stack deployment -![](docs/coffeeshop_hashicorp.svg) +![coffeeshop_hashicorp](docs/coffeeshop_hashicorp.svg) The details of how to run it can be find at [deployment with Nomad, Consult Connect and Vault](build/README.md). @@ -88,7 +89,7 @@ The details of how to run it can be find at [deployment with Nomad, Consult Conn ## Roadmap -- Enhance project structure with DDD patterns +- Enhance project structure with DDD patterns: DONE - Add testing - Add and integrate with observability libs and tools - Add user identity management (authentication and authorization) diff --git a/go.mod b/go.mod index 29b9c40..54ffee3 100755 --- a/go.mod +++ b/go.mod @@ -3,13 +3,11 @@ module github.com/thangchung/go-coffeeshop go 1.19 require ( - github.com/Masterminds/squirrel v1.5.3 github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang/glog v1.0.0 github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 github.com/ilyakaznacheev/cleanenv v1.3.0 - github.com/jackc/pgx/v4 v4.17.2 github.com/kyleconroy/sqlc v1.16.0 github.com/labstack/echo/v4 v4.9.0 github.com/lib/pq v1.10.7 @@ -30,18 +28,8 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.13.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.12.0 // indirect - github.com/jackc/puddle v1.3.0 // indirect github.com/joho/godotenv v1.4.0 // indirect github.com/labstack/gommon v0.4.0 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 25227a6..fa7f246 100755 --- a/go.sum +++ b/go.sum @@ -77,9 +77,6 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -210,7 +207,6 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -487,7 +483,6 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= @@ -664,7 +659,6 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= @@ -673,18 +667,9 @@ github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5 github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= @@ -694,11 +679,7 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1: github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= @@ -707,9 +688,6 @@ github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4 github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= @@ -717,16 +695,11 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -794,17 +767,12 @@ github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITV github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= @@ -1029,7 +997,6 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -1181,7 +1148,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= @@ -1190,13 +1156,11 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1218,14 +1182,10 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1339,7 +1299,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1569,7 +1528,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/internal/counter/infras/postgresql/models.go b/internal/counter/infras/postgresql/models.go index bc33165..0a2ef4b 100644 --- a/internal/counter/infras/postgresql/models.go +++ b/internal/counter/infras/postgresql/models.go @@ -12,21 +12,21 @@ import ( ) type OrderLineItem struct { - ID uuid.UUID - ItemType int32 - Name string - Price string - ItemStatus int32 - IsBaristaOrder bool - OrderID uuid.NullUUID - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + ItemType int32 `json:"item_type"` + Name string `json:"name"` + Price string `json:"price"` + ItemStatus int32 `json:"item_status"` + IsBaristaOrder bool `json:"is_barista_order"` + OrderID uuid.NullUUID `json:"order_id"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } type OrderOrder struct { - ID uuid.UUID - OrderSource int32 - LoyaltyMemberID uuid.UUID - OrderStatus int32 - Updated sql.NullTime + ID uuid.UUID `json:"id"` + OrderSource int32 `json:"order_source"` + LoyaltyMemberID uuid.UUID `json:"loyalty_member_id"` + OrderStatus int32 `json:"order_status"` + Updated sql.NullTime `json:"updated"` } diff --git a/internal/counter/infras/postgresql/query.sql.go b/internal/counter/infras/postgresql/query.sql.go index 9708333..87ee894 100644 --- a/internal/counter/infras/postgresql/query.sql.go +++ b/internal/counter/infras/postgresql/query.sql.go @@ -27,11 +27,11 @@ VALUES ($1, $2, $3, $4, $5) RETURNING id, order_source, loyalty_member_id, order ` type CreateOrderParams struct { - ID uuid.UUID - OrderSource int32 - LoyaltyMemberID uuid.UUID - OrderStatus int32 - Updated sql.NullTime + ID uuid.UUID `json:"id"` + OrderSource int32 `json:"order_source"` + LoyaltyMemberID uuid.UUID `json:"loyalty_member_id"` + OrderStatus int32 `json:"order_status"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (OrderOrder, error) { @@ -71,16 +71,16 @@ FROM "order".orders o ` type GetAllRow struct { - ID uuid.UUID - OrderSource int32 - LoyaltyMemberID uuid.UUID - OrderStatus int32 - LineItemID uuid.NullUUID - ItemType int32 - Name string - Price string - ItemStatus int32 - IsBaristaOrder bool + ID uuid.UUID `json:"id"` + OrderSource int32 `json:"order_source"` + LoyaltyMemberID uuid.UUID `json:"loyalty_member_id"` + OrderStatus int32 `json:"order_status"` + LineItemID uuid.NullUUID `json:"line_item_id"` + ItemType int32 `json:"item_type"` + Name string `json:"name"` + Price string `json:"price"` + ItemStatus int32 `json:"item_status"` + IsBaristaOrder bool `json:"is_barista_order"` } func (q *Queries) GetAll(ctx context.Context) ([]GetAllRow, error) { @@ -136,16 +136,16 @@ WHERE o.id = $1 ` type GetByIDRow struct { - ID uuid.UUID - OrderSource int32 - LoyaltyMemberID uuid.UUID - OrderStatus int32 - LineItemID uuid.NullUUID - ItemType int32 - Name string - Price string - ItemStatus int32 - IsBaristaOrder bool + ID uuid.UUID `json:"id"` + OrderSource int32 `json:"order_source"` + LoyaltyMemberID uuid.UUID `json:"loyalty_member_id"` + OrderStatus int32 `json:"order_status"` + LineItemID uuid.NullUUID `json:"line_item_id"` + ItemType int32 `json:"item_type"` + Name string `json:"name"` + Price string `json:"price"` + ItemStatus int32 `json:"item_status"` + IsBaristaOrder bool `json:"is_barista_order"` } func (q *Queries) GetByID(ctx context.Context, id uuid.UUID) ([]GetByIDRow, error) { @@ -200,15 +200,15 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, item_type, name, price ` type InsertItemLineParams struct { - ID uuid.UUID - ItemType int32 - Name string - Price string - ItemStatus int32 - IsBaristaOrder bool - OrderID uuid.NullUUID - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + ItemType int32 `json:"item_type"` + Name string `json:"name"` + Price string `json:"price"` + ItemStatus int32 `json:"item_status"` + IsBaristaOrder bool `json:"is_barista_order"` + OrderID uuid.NullUUID `json:"order_id"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) InsertItemLine(ctx context.Context, arg InsertItemLineParams) (OrderLineItem, error) { @@ -248,9 +248,9 @@ WHERE id = $1 ` type UpdateItemLineParams struct { - ID uuid.UUID - ItemStatus int32 - Updated sql.NullTime + ID uuid.UUID `json:"id"` + ItemStatus int32 `json:"item_status"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) UpdateItemLine(ctx context.Context, arg UpdateItemLineParams) error { @@ -268,9 +268,9 @@ WHERE id = $1 ` type UpdateOrderParams struct { - ID uuid.UUID - OrderStatus int32 - Updated sql.NullTime + ID uuid.UUID `json:"id"` + OrderStatus int32 `json:"order_status"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) UpdateOrder(ctx context.Context, arg UpdateOrderParams) error { diff --git a/internal/kitchen/infras/postgresql/models.go b/internal/kitchen/infras/postgresql/models.go index b4a2caa..e60d4c8 100644 --- a/internal/kitchen/infras/postgresql/models.go +++ b/internal/kitchen/infras/postgresql/models.go @@ -12,11 +12,11 @@ import ( ) type KitchenKitchenOrder struct { - ID uuid.UUID - OrderID uuid.UUID - ItemType int32 - ItemName string - TimeUp time.Time - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + OrderID uuid.UUID `json:"order_id"` + ItemType int32 `json:"item_type"` + ItemName string `json:"item_name"` + TimeUp time.Time `json:"time_up"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } diff --git a/internal/kitchen/infras/postgresql/query.sql.go b/internal/kitchen/infras/postgresql/query.sql.go index 2c82e00..5a55ce1 100644 --- a/internal/kitchen/infras/postgresql/query.sql.go +++ b/internal/kitchen/infras/postgresql/query.sql.go @@ -29,13 +29,13 @@ VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, order_id, item_type, item_name ` type CreateOrderParams struct { - ID uuid.UUID - OrderID uuid.UUID - ItemType int32 - ItemName string - TimeUp time.Time - Created time.Time - Updated sql.NullTime + ID uuid.UUID `json:"id"` + OrderID uuid.UUID `json:"order_id"` + ItemType int32 `json:"item_type"` + ItemName string `json:"item_name"` + TimeUp time.Time `json:"time_up"` + Created time.Time `json:"created"` + Updated sql.NullTime `json:"updated"` } func (q *Queries) CreateOrder(ctx context.Context, arg CreateOrderParams) (KitchenKitchenOrder, error) { diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 041e1a1..6ee6397 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -5,8 +5,6 @@ import ( "log" "time" - "github.com/Masterminds/squirrel" - "github.com/jackc/pgx/v4/pgxpool" "golang.org/x/exp/slog" _ "github.com/lib/pq" @@ -21,9 +19,7 @@ type Postgres struct { connAttempts int connTimeout time.Duration - Builder squirrel.StatementBuilderType - Pool *pgxpool.Pool - DB *sql.DB + DB *sql.DB } func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { diff --git a/sqlc.yaml b/sqlc.yaml index cee88b3..82985bb 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -7,6 +7,7 @@ sql: go: package: "postgresql" out: "internal/counter/infras/postgresql" + emit_json_tags: true - engine: "postgresql" queries: "internal/kitchen/infras/postgresql/query/query.sql" @@ -15,6 +16,7 @@ sql: go: package: "postgresql" out: "internal/kitchen/infras/postgresql" + emit_json_tags: true - engine: "postgresql" queries: "internal/barista/infras/postgresql/query/" @@ -23,6 +25,4 @@ sql: go: package: "postgresql" out: "internal/barista/infras/postgresql" - sql_package: "pgx/v5" - emit_json_tags: true - emit_prepared_queries: false \ No newline at end of file + emit_json_tags: true \ No newline at end of file From bc76ef4fc8dcdd97dd85c512cfc29ef7f3cd2e2f Mon Sep 17 00:00:00 2001 From: thangchung Date: Mon, 26 Dec 2022 21:02:25 +0700 Subject: [PATCH 12/16] init add wire for barista --- .devcontainer/Dockerfile-dev | 6 - .devcontainer/devcontainer.json | 4 +- .golangci.yml | 4 +- README.md | 4 +- cmd/barista/main.go | 15 ++- go.mod | 2 + go.sum | 7 ++ internal/barista/app/app.go | 105 +++++++++++------- internal/barista/app/wire.go | 24 ++++ internal/barista/app/wire_gen.go | 24 ++++ internal/barista/domain/order.go | 2 +- .../barista/eventhandlers/barista_ordered.go | 9 +- internal/counter/app/app.go | 9 +- internal/counter/usecases/orders/service.go | 39 ++++--- internal/kitchen/app/app.go | 4 +- internal/kitchen/domain/order.go | 2 +- .../kitchen/eventhandlers/kitchen_ordered.go | 6 +- internal/pkg/event/events.go | 6 +- internal/pkg/event/publisher.go | 38 ------- internal/pkg/shared_kernel/interfaces.go | 5 - pkg/logger/logger.go | 70 ------------ pkg/postgres/postgres.go | 4 +- pkg/rabbitmq/interfaces.go | 8 ++ pkg/rabbitmq/publisher/options.go | 8 +- pkg/rabbitmq/publisher/publisher.go | 30 ++++- pkg/rabbitmq/rabbitmq.go | 2 +- 26 files changed, 218 insertions(+), 219 deletions(-) create mode 100644 internal/barista/app/wire.go create mode 100644 internal/barista/app/wire_gen.go delete mode 100644 internal/pkg/event/publisher.go delete mode 100644 pkg/logger/logger.go create mode 100644 pkg/rabbitmq/interfaces.go diff --git a/.devcontainer/Dockerfile-dev b/.devcontainer/Dockerfile-dev index 8520c39..f5362c8 100755 --- a/.devcontainer/Dockerfile-dev +++ b/.devcontainer/Dockerfile-dev @@ -20,9 +20,3 @@ RUN BIN="/usr/local/bin" \ && curl -L "https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz" | tar xvz \ && mv migrate "${BIN}/migrate" \ && chmod +x "${BIN}/migrate" - -# Install sqlc -RUN BIN="/usr/local/bin" \ - && curl -L "https://github.com/kyleconroy/sqlc/releases/download/v1.16.0/sqlc_1.16.0_linux_amd64.tar.gz" | tar xvz \ - && mv sqlc "${BIN}/sqlc" \ - && chmod +x "${BIN}/sqlc" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a70f2b8..896fdbe 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,6 +22,8 @@ "--security-opt", "seccomp=unconfined" ], + "remoteUser": "root", + "containerUser": "root", "forwardPorts": [5000, 8888, 5432, 15672], // Use 'settings' to set *default* container specific settings.json values on container create. // You can edit these settings after create using File > Preferences > Settings > Remote. @@ -113,7 +115,7 @@ // "42crunch.vscode-openapi", // "eamodio.gitlens", ], - "postCreateCommand": "go version", + "postCreateCommand": "go version && go install github.com/google/wire/cmd/wire@latest && go install github.com/kyleconroy/sqlc/cmd/sqlc@latest", "features": { "ghcr.io/devcontainers/features/go:1": { "version": "1.19.4" diff --git a/.golangci.yml b/.golangci.yml index 0074676..4438982 100755 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,10 +12,10 @@ linters-settings: check-generated: false default-signifies-exhaustive: false funlen: - lines: 65 + lines: 100 statements: 40 gocognit: - min-complexity: 15 + min-complexity: 20 gocyclo: min-complexity: 10 goconst: diff --git a/README.md b/README.md index c774bd3..86fac8e 100755 --- a/README.md +++ b/README.md @@ -17,10 +17,12 @@ Other version can be found at: - [pq](github.com/lib/pq) - [golang-migrate/migrate/v4](https://github.com/golang-migrate/migrate) - Utils + - [google/wire](github.com/google/wire) - [ilyakaznacheev/cleanenv](https://github.com/ilyakaznacheev/cleanenv) - golang.org/x/exp/slog - [sirupsen/logrus](https://github.com/sirupsen/logrus) - [samber/lo](https://github.com/samber/lo) + - [automaxprocs/maxprocs](go.uber.org/automaxprocs/maxprocs) - golang/glog - google/uuid - google.golang.org/genproto @@ -89,7 +91,7 @@ The details of how to run it can be find at [deployment with Nomad, Consult Conn ## Roadmap -- Enhance project structure with DDD patterns: DONE +- ✅ Enhance project structure with DDD patterns - Add testing - Add and integrate with observability libs and tools - Add user identity management (authentication and authorization) diff --git a/cmd/barista/main.go b/cmd/barista/main.go index 9d47343..e6066af 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -1,18 +1,28 @@ package main import ( + "context" "os" "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/app" "github.com/thangchung/go-coffeeshop/pkg/logger" + "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/slog" _ "github.com/lib/pq" ) func main() { + // set GOMAXPROCS + _, err := maxprocs.Set() + if err != nil { + slog.Error("failed set max procs", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cfg, err := config.NewConfig() if err != nil { slog.Error("failed get config", err) @@ -26,9 +36,8 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a := app.New(cfg) - if err = a.Run(); err != nil { + if err = app.Run(ctx, cancel, cfg); err != nil { slog.Error("failed app run", err) - os.Exit(1) + cancel() } } diff --git a/go.mod b/go.mod index 54ffee3..fc3dd71 100755 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.15.2 github.com/golang/glog v1.0.0 github.com/google/uuid v1.3.0 + github.com/google/wire v0.5.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 github.com/ilyakaznacheev/cleanenv v1.3.0 github.com/kyleconroy/sqlc v1.16.0 @@ -16,6 +17,7 @@ require ( github.com/samber/lo v1.33.0 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.1 + go.uber.org/automaxprocs v1.5.1 golang.org/x/exp v0.0.0-20221026153819-32f3d567a233 google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a google.golang.org/grpc v1.50.1 diff --git a/go.sum b/go.sum index fa7f246..9669ec0 100755 --- a/go.sum +++ b/go.sum @@ -583,12 +583,15 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -937,6 +940,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -1152,6 +1156,8 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= +go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= @@ -1501,6 +1507,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index 3c64a90..f8dac9a 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -10,38 +10,22 @@ import ( "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) -type App struct { - cfg *config.Config - network string - address string - handler eventhandlers.BaristaOrderedEventHandler -} - -func New(cfg *config.Config) *App { - return &App{ - cfg: cfg, - network: "tcp", - address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), - } -} - -func (a *App) Run() error { - slog.Info("init app", "name", a.cfg.Name, "version", a.cfg.Version) - - ctx, cancel := context.WithCancel(context.Background()) +func Run(ctx context.Context, cancel context.CancelFunc, cfg *config.Config) error { + slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) // postgresdb. - pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) + pg, err := postgres.NewPostgresDB(cfg.PG.DsnURL) if err != nil { cancel() @@ -52,7 +36,7 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) + amqpConn, err := rabbitmq.NewRabbitMQConn(cfg.RabbitMQ.URL) if err != nil { cancel() @@ -62,12 +46,12 @@ func (a *App) Run() error { } defer amqpConn.Close() - // publishers - counterOrderPub, err := publisher.NewPublisher( + // publishers. + counterOrderPub, err := pkgPublisher.NewPublisher( amqpConn, - publisher.ExchangeName("counter-order-exchange"), - publisher.BindingKey("counter-order-routing-key"), - publisher.MessageTypeName("barista-order-updated"), + pkgPublisher.ExchangeName("counter-order-exchange"), + pkgPublisher.BindingKey("counter-order-routing-key"), + pkgPublisher.MessageTypeName("barista-order-updated"), ) defer counterOrderPub.CloseChan() @@ -77,24 +61,32 @@ func (a *App) Run() error { return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") } - // event handlers. - a.handler = eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) - - // consumers - consumer, err := consumer.NewConsumer( + // consumers. + consumer, err := pkgConsumer.NewConsumer( amqpConn, - consumer.ExchangeName("barista-order-exchange"), - consumer.QueueName("barista-order-queue"), - consumer.BindingKey("barista-order-routing-key"), - consumer.ConsumerTag("barista-order-consumer"), + pkgConsumer.ExchangeName("barista-order-exchange"), + pkgConsumer.QueueName("barista-order-queue"), + pkgConsumer.BindingKey("barista-order-routing-key"), + pkgConsumer.ConsumerTag("barista-order-consumer"), ) if err != nil { slog.Error("failed to create a new OrderConsumer", err) cancel() } + a, err := InitApp(cfg, pg, amqpConn, counterOrderPub, consumer) + if err != nil { + slog.Error("failed init app", err) + cancel() + } + + // event handlers. + a.handler = eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) + + slog.Info("🌏 start server...", "address", fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) + go func() { - err := consumer.StartConsumer(a.worker) + err := a.consumer.StartConsumer(a.worker) if err != nil { slog.Error("failed to start Consumer", err) cancel() @@ -111,11 +103,42 @@ func (a *App) Run() error { slog.Info("ctx.Done", done) } - slog.Info("start server...", "address", a.address) - return nil } +type App struct { + cfg *config.Config + + pg *postgres.Postgres + amqpConn *amqp.Connection + + counterOrderPub rabbitmq.EventPublisher + consumer *pkgConsumer.Consumer + + handler eventhandlers.BaristaOrderedEventHandler +} + +func New( + cfg *config.Config, + pg *postgres.Postgres, + amqpConn *amqp.Connection, + counterOrderPub rabbitmq.EventPublisher, + consumer *pkgConsumer.Consumer, + handler eventhandlers.BaristaOrderedEventHandler, +) *App { + return &App{ + cfg: cfg, + + pg: pg, + amqpConn: amqpConn, + + counterOrderPub: counterOrderPub, + consumer: consumer, + + handler: handler, + } +} + func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) @@ -124,8 +147,8 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { switch delivery.Type { case "barista-order-created": var payload event.BaristaOrdered - err := json.Unmarshal(delivery.Body, &payload) + err := json.Unmarshal(delivery.Body, &payload) if err != nil { slog.Error("failed to Unmarshal", err) } diff --git a/internal/barista/app/wire.go b/internal/barista/app/wire.go new file mode 100644 index 0000000..d6ad842 --- /dev/null +++ b/internal/barista/app/wire.go @@ -0,0 +1,24 @@ +//go:build wireinject +// +build wireinject + +package app + +import ( + "github.com/google/wire" + amqp "github.com/rabbitmq/amqp091-go" + "github.com/thangchung/go-coffeeshop/cmd/barista/config" + "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" +) + +func InitApp( + cfg *config.Config, + pg *postgres.Postgres, + amqpConn *amqp.Connection, + counterOrderPub rabbitmq.EventPublisher, + consumer *pkgConsumer.Consumer, +) (*App, error) { + panic(wire.Build(New, eventhandlers.BaristaOrderedEventHandlerSet)) +} diff --git a/internal/barista/app/wire_gen.go b/internal/barista/app/wire_gen.go new file mode 100644 index 0000000..06051bb --- /dev/null +++ b/internal/barista/app/wire_gen.go @@ -0,0 +1,24 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package app + +import ( + "github.com/rabbitmq/amqp091-go" + "github.com/thangchung/go-coffeeshop/cmd/barista/config" + "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" +) + +// Injectors from wire.go: + +func InitApp(cfg *config.Config, pg *postgres.Postgres, amqpConn *amqp091.Connection, counterOrderPub rabbitmq.EventPublisher, consumer2 *consumer.Consumer) (*App, error) { + baristaOrderedEventHandler := eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) + app := New(cfg, pg, amqpConn, counterOrderPub, consumer2, baristaOrderedEventHandler) + return app, nil +} diff --git a/internal/barista/domain/order.go b/internal/barista/domain/order.go index 9915f05..8740f5c 100644 --- a/internal/barista/domain/order.go +++ b/internal/barista/domain/order.go @@ -45,7 +45,7 @@ func NewBaristaOrder(e event.BaristaOrdered) BaristaOrder { TimeUp: timeUp, } - order.ApplyDomain(orderUpdatedEvent) + order.ApplyDomain(&orderUpdatedEvent) return order } diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index 39f39a3..c47763c 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -5,23 +5,26 @@ import ( "database/sql" "encoding/json" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/barista/domain" "github.com/thangchung/go-coffeeshop/internal/barista/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" ) +var BaristaOrderedEventHandlerSet = wire.NewSet(NewBaristaOrderedEventHandler) + var _ BaristaOrderedEventHandler = (*baristaOrderedEventHandler)(nil) type baristaOrderedEventHandler struct { pg *postgres.Postgres - counterPub *publisher.Publisher + counterPub rabbitmq.EventPublisher } -func NewBaristaOrderedEventHandler(pg *postgres.Postgres, counterPub *publisher.Publisher) BaristaOrderedEventHandler { +func NewBaristaOrderedEventHandler(pg *postgres.Postgres, counterPub rabbitmq.EventPublisher) BaristaOrderedEventHandler { return &baristaOrderedEventHandler{ pg: pg, counterPub: counterPub, diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index ac26c9a..1f8a549 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -14,7 +14,6 @@ import ( counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" - pkgevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" @@ -113,16 +112,12 @@ func (a *App) Run() error { // domain service productDomainSvc := counterGrpc.NewGRPCProductClient(conn) - // event publishers. - baristaEventPub := pkgevents.NewEventPublisher(*baristaOrderPub) - kitchenEventPub := pkgevents.NewEventPublisher(*kitchenOrderPub) - // usecases. uc := orders.NewUseCase( orderRepo, productDomainSvc, - baristaEventPub, - kitchenEventPub, + baristaOrderPub, + kitchenOrderPub, ) // event handlers. diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go index 56847e1..472a843 100644 --- a/internal/counter/usecases/orders/service.go +++ b/internal/counter/usecases/orders/service.go @@ -2,19 +2,20 @@ package orders import ( "context" + "encoding/json" "fmt" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" ) type usecase struct { orderRepo domain.OrderRepo productDomainSvc domain.ProductDomainService - baristaEventPub shared.EventPublisher - kitchenEventPub shared.EventPublisher + baristaEventPub rabbitmq.EventPublisher + kitchenEventPub rabbitmq.EventPublisher } var _ UseCase = (*usecase)(nil) @@ -22,8 +23,8 @@ var _ UseCase = (*usecase)(nil) func NewUseCase( orderRepo domain.OrderRepo, productDomainSvc domain.ProductDomainService, - baristaEventPub shared.EventPublisher, - kitchenEventPub shared.EventPublisher, + baristaEventPub rabbitmq.EventPublisher, + kitchenEventPub rabbitmq.EventPublisher, ) UseCase { return &usecase{ orderRepo: orderRepo, @@ -55,27 +56,25 @@ func (uc *usecase) PlaceOrder(ctx context.Context, model *domain.PlaceOrderModel slog.Debug("order created", "order", *order) - baristaEvents := make([]shared.DomainEvent, 0) - kitchenEvents := make([]shared.DomainEvent, 0) - + // todo: it might cause dual-write problem, but we accept it temporary for _, event := range order.DomainEvents() { if event.Identity() == "BaristaOrdered" { - baristaEvents = append(baristaEvents, event) - } + eventBytes, err := json.Marshal(event) + if err != nil { + return errors.Wrap(err, "json.Marshal[event]") + } - if event.Identity() == "KitchenOrdered" { - kitchenEvents = append(kitchenEvents, event) + uc.baristaEventPub.Publish(ctx, eventBytes, "text/plain") } - } - err = uc.baristaEventPub.Publish(ctx, baristaEvents) - if err != nil { - return errors.Wrap(err, "usecase-baristaEventPub.Publish") - } + if event.Identity() == "KitchenOrdered" { + eventBytes, err := json.Marshal(event) + if err != nil { + return errors.Wrap(err, "json.Marshal[event]") + } - err = uc.kitchenEventPub.Publish(ctx, kitchenEvents) - if err != nil { - return errors.Wrap(err, "usecase-kitchenEventPub.Publish") + uc.kitchenEventPub.Publish(ctx, eventBytes, "text/plain") + } } return nil diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index cd42c3a..78c22f2 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -14,7 +14,7 @@ import ( "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgRabbitMQ "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgconsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" @@ -52,7 +52,7 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) + amqpConn, err := pkgRabbitMQ.NewRabbitMQConn(a.cfg.RabbitMQ.URL) if err != nil { cancel() diff --git a/internal/kitchen/domain/order.go b/internal/kitchen/domain/order.go index 54c61d8..c76bd6c 100644 --- a/internal/kitchen/domain/order.go +++ b/internal/kitchen/domain/order.go @@ -47,7 +47,7 @@ func NewKitchenOrder(e event.KitchenOrdered) KitchenOrder { TimeUp: timeUp, } - order.ApplyDomain(orderUpdatedEvent) + order.ApplyDomain(&orderUpdatedEvent) return order } diff --git a/internal/kitchen/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go index f787fa8..3ee522b 100644 --- a/internal/kitchen/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/eventhandlers/kitchen_ordered.go @@ -10,7 +10,7 @@ import ( "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" ) @@ -18,12 +18,12 @@ var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) type kitchenOrderedEventHandler struct { pg *postgres.Postgres - counterPub *publisher.Publisher + counterPub rabbitmq.EventPublisher } func NewKitchenOrderedEventHandler( pg *postgres.Postgres, - counterPub *publisher.Publisher, + counterPub rabbitmq.EventPublisher, ) KitchenOrderedEventHandler { return &kitchenOrderedEventHandler{ pg: pg, diff --git a/internal/pkg/event/events.go b/internal/pkg/event/events.go index befc69c..35a1178 100644 --- a/internal/pkg/event/events.go +++ b/internal/pkg/event/events.go @@ -41,7 +41,7 @@ type BaristaOrderUpdated struct { TimeUp time.Time `json:"timeUp"` } -func (e BaristaOrderUpdated) Identity() string { +func (e *BaristaOrderUpdated) Identity() string { return "BaristaOrderUpdated" } @@ -56,7 +56,7 @@ type KitchenOrderUpdated struct { TimeUp time.Time `json:"timeUp"` } -func (e KitchenOrderUpdated) Identity() string { +func (e *KitchenOrderUpdated) Identity() string { return "KitchenOrderUpdated" } @@ -69,6 +69,6 @@ type OrderUp struct { MadeBy string `json:"madeBy"` } -func (e OrderUp) Identity() string { +func (e *OrderUp) Identity() string { return "OrderUp" } diff --git a/internal/pkg/event/publisher.go b/internal/pkg/event/publisher.go deleted file mode 100644 index e73283a..0000000 --- a/internal/pkg/event/publisher.go +++ /dev/null @@ -1,38 +0,0 @@ -package event - -import ( - "context" - "encoding/json" - - "github.com/pkg/errors" - shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" -) - -type eventPublisherRabbitMQ struct { - pub publisher.Publisher -} - -var _ shared.EventPublisher = (*eventPublisherRabbitMQ)(nil) - -func NewEventPublisher(pub publisher.Publisher) shared.EventPublisher { - return &eventPublisherRabbitMQ{ - pub: pub, - } -} - -func (p *eventPublisherRabbitMQ) Publish(ctx context.Context, events []shared.DomainEvent) error { - for _, e := range events { - b, err := json.Marshal(e) - if err != nil { - return errors.Wrap(err, "eventPublisherRabbitMQ-json.Marshal") - } - - err = p.pub.Publish(ctx, b, "text/plain") - if err != nil { - return errors.Wrap(err, "eventPublisherRabbitMQ-pub.Publish") - } - } - - return nil -} diff --git a/internal/pkg/shared_kernel/interfaces.go b/internal/pkg/shared_kernel/interfaces.go index 89e9233..3993b96 100644 --- a/internal/pkg/shared_kernel/interfaces.go +++ b/internal/pkg/shared_kernel/interfaces.go @@ -1,7 +1,6 @@ package sharedkernel import ( - "context" "time" ) @@ -10,8 +9,4 @@ type ( CreateAt() time.Time Identity() string } - - EventPublisher interface { - Publish(context.Context, []DomainEvent) error - } ) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go deleted file mode 100644 index 70517d6..0000000 --- a/pkg/logger/logger.go +++ /dev/null @@ -1,70 +0,0 @@ -package logger - -import ( - "os" - "strings" - - "github.com/sirupsen/logrus" -) - -type Interface interface { - Debug(message string, args ...interface{}) - Info(message string, args ...interface{}) - Warn(message string, args ...interface{}) - Error(message string, args ...interface{}) - LogError(err error) - Fatal(message string, args ...interface{}) -} - -type Logger struct { - logger *logrus.Entry -} - -var _ Interface = (*Logger)(nil) - -func New(level string) *Logger { - var l logrus.Level - - switch strings.ToLower(level) { - case "error": - l = logrus.ErrorLevel - case "warm": - l = logrus.WarnLevel - case "info": - l = logrus.InfoLevel - case "debug": - l = logrus.DebugLevel - default: - l = logrus.InfoLevel - } - - logger := logrus.NewEntry(logrus.StandardLogger()) - logger.Logger.SetLevel(l) - - return &Logger{logger: logger} -} - -func (l *Logger) Info(message string, args ...interface{}) { - l.logger.Infof(message, args...) -} - -func (l *Logger) Debug(message string, args ...interface{}) { - l.logger.Debugf(message, args...) -} - -func (l *Logger) Warn(message string, args ...interface{}) { - l.logger.Warnf(message, args...) -} - -func (l *Logger) Error(message string, args ...interface{}) { - l.logger.Errorf(message, args...) -} - -func (l *Logger) LogError(err error) { - l.logger.Error(err) -} - -func (l *Logger) Fatal(message string, args ...interface{}) { - l.logger.Fatalf(message, args...) - os.Exit(1) -} diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 6ee6397..52f2e69 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -6,8 +6,6 @@ import ( "time" "golang.org/x/exp/slog" - - _ "github.com/lib/pq" ) const ( @@ -48,6 +46,8 @@ func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { pg.connAttempts-- } + slog.Info("📰 connected to postgresdb 🎉") + return pg, nil } diff --git a/pkg/rabbitmq/interfaces.go b/pkg/rabbitmq/interfaces.go new file mode 100644 index 0000000..0e9d923 --- /dev/null +++ b/pkg/rabbitmq/interfaces.go @@ -0,0 +1,8 @@ +package rabbitmq + +import "context" + +type EventPublisher interface { + Publish(context.Context, []byte, string) error + CloseChan() +} diff --git a/pkg/rabbitmq/publisher/options.go b/pkg/rabbitmq/publisher/options.go index 665820d..61d9cda 100644 --- a/pkg/rabbitmq/publisher/options.go +++ b/pkg/rabbitmq/publisher/options.go @@ -1,21 +1,21 @@ package publisher -type Option func(*Publisher) +type Option func(*publisher) func ExchangeName(exchangeName string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.exchangeName = exchangeName } } func BindingKey(bindingKey string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.bindingKey = bindingKey } } func MessageTypeName(messageTypeName string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.messageTypeName = messageTypeName } } diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index dc3b312..b8a2137 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -2,11 +2,13 @@ package publisher import ( "context" + "encoding/json" "time" "github.com/google/uuid" "github.com/pkg/errors" amqp "github.com/rabbitmq/amqp091-go" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" ) @@ -19,21 +21,23 @@ const ( _messageTypeName = "ordered" ) -type Publisher struct { +type publisher struct { exchangeName, bindingKey string messageTypeName string amqpChan *amqp.Channel amqpConn *amqp.Connection } -func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (*Publisher, error) { +var _ rabbitmq.EventPublisher = (*publisher)(nil) + +func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (rabbitmq.EventPublisher, error) { ch, err := amqpConn.Channel() if err != nil { panic(err) } defer ch.Close() - pub := &Publisher{ + pub := &publisher{ amqpConn: amqpConn, amqpChan: ch, exchangeName: _exchangeName, @@ -49,14 +53,30 @@ func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (*Publisher, error) } // CloseChan Close messages chan. -func (p *Publisher) CloseChan() { +func (p *publisher) CloseChan() { if err := p.amqpChan.Close(); err != nil { slog.Error("failed to close chan", err) } } +func (p *publisher) PublishEvents(ctx context.Context, events []any) error { + for _, e := range events { + b, err := json.Marshal(e) + if err != nil { + return errors.Wrap(err, "publisher-json.Marshal") + } + + err = p.Publish(ctx, b, "text/plain") + if err != nil { + return errors.Wrap(err, "publisher-pub.Publish") + } + } + + return nil +} + // Publish message. -func (p *Publisher) Publish(ctx context.Context, body []byte, contentType string) error { +func (p *publisher) Publish(ctx context.Context, body []byte, contentType string) error { ch, err := p.amqpConn.Channel() if err != nil { return errors.Wrap(err, "CreateChannel") diff --git a/pkg/rabbitmq/rabbitmq.go b/pkg/rabbitmq/rabbitmq.go index 8fe6b4d..e50d13e 100644 --- a/pkg/rabbitmq/rabbitmq.go +++ b/pkg/rabbitmq/rabbitmq.go @@ -44,7 +44,7 @@ func NewRabbitMQConn(rabbitMqURL string) (*amqp.Connection, error) { continue } - slog.Info("Connected to RabbitMQ!") + slog.Info("📫 connected to rabbitmq 🎉") return amqpConn, nil } From 1aaa4f5d6ad6b388a60ba97571d7259304f842ca Mon Sep 17 00:00:00 2001 From: thangchung Date: Tue, 27 Dec 2022 13:44:02 +0700 Subject: [PATCH 13/16] refactor to DI --- .golangci.yml | 6 +- Makefile | 18 ++- cmd/barista/main.go | 52 +++++++- internal/barista/app/app.go | 119 +++--------------- internal/barista/app/wire.go | 10 +- internal/barista/app/wire_gen.go | 24 +++- .../barista/eventhandlers/barista_ordered.go | 13 +- internal/counter/app/app.go | 64 +++++----- .../counter/infras/repo/orders_postgres.go | 18 +-- internal/counter/usecases/orders/service.go | 10 +- internal/kitchen/app/app.go | 36 +++--- .../kitchen/eventhandlers/kitchen_ordered.go | 15 +-- pkg/postgres/interfaces.go | 9 ++ pkg/postgres/options.go | 6 +- pkg/postgres/postgres.go | 39 ++++-- pkg/rabbitmq/consumer/consumer.go | 102 +++++++-------- pkg/rabbitmq/consumer/interfaces.go | 14 +++ pkg/rabbitmq/consumer/options.go | 12 +- pkg/rabbitmq/{ => publisher}/interfaces.go | 7 +- pkg/rabbitmq/publisher/publisher.go | 16 ++- pkg/rabbitmq/rabbitmq.go | 9 +- 21 files changed, 325 insertions(+), 274 deletions(-) create mode 100644 pkg/postgres/interfaces.go create mode 100644 pkg/rabbitmq/consumer/interfaces.go rename pkg/rabbitmq/{ => publisher}/interfaces.go (55%) diff --git a/.golangci.yml b/.golangci.yml index 4438982..b1091c3 100755 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,11 +13,11 @@ linters-settings: default-signifies-exhaustive: false funlen: lines: 100 - statements: 40 + statements: 50 gocognit: - min-complexity: 20 + min-complexity: 40 gocyclo: - min-complexity: 10 + min-complexity: 15 goconst: min-len: 2 min-occurrences: 2 diff --git a/Makefile b/Makefile index be8135d..f4a677d 100755 --- a/Makefile +++ b/Makefile @@ -35,13 +35,23 @@ run-web: CGO_ENABLED=0 go run github.com/thangchung/go-coffeeshop/cmd/web .PHONY: run-web -compose-up: +compose-start: docker-compose up --build -.PHONY: compose-up +.PHONY: compose-start -compose-down: +compose-stop: docker-compose down --remove-orphans -v -.PHONY: compose-down +.PHONY: compose-stop + +compose-core: compose-core-stop compose-core-start + +compose-core-start: + docker-compose -f docker-compose-core.yaml up --build +.PHONY: compose-core-start + +compose-core-stop: + docker-compose -f docker-compose-core.yaml down --remove-orphans -v +.PHONY: compose-core-stop compose-build: docker-compose down --remove-orphans -v diff --git a/cmd/barista/main.go b/cmd/barista/main.go index e6066af..9ec25b2 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -2,15 +2,23 @@ package main import ( "context" + "fmt" "os" + "os/signal" + "syscall" "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/app" "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/slog" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + _ "github.com/lib/pq" ) @@ -28,6 +36,8 @@ func main() { slog.Error("failed get config", err) } + slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) + // set up logrus logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetOutput(os.Stdout) @@ -36,8 +46,46 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - if err = app.Run(ctx, cancel, cfg); err != nil { - slog.Error("failed app run", err) + a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) + if err != nil { + slog.Error("failed init app", err) cancel() } + + defer a.AMQPConn.Close() + defer a.Pg.Close() + + a.CounterOrderPub.Configure( + pkgPublisher.ExchangeName("counter-order-exchange"), + pkgPublisher.BindingKey("counter-order-routing-key"), + pkgPublisher.MessageTypeName("barista-order-updated"), + ) + defer a.CounterOrderPub.CloseChan() + + a.Consumer.Configure( + pkgConsumer.ExchangeName("barista-order-exchange"), + pkgConsumer.QueueName("barista-order-queue"), + pkgConsumer.BindingKey("barista-order-routing-key"), + pkgConsumer.ConsumerTag("barista-order-consumer"), + ) + + slog.Info("🌏 start server...", "address", fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) + + go func() { + err := a.Consumer.StartConsumer(a.Worker) + if err != nil { + slog.Error("failed to start Consumer", err) + cancel() + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + + select { + case v := <-quit: + slog.Info("signal.Notify", v) + case done := <-ctx.Done(): + slog.Info("ctx.Done", done) + } } diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index f8dac9a..cc24da2 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -3,143 +3,52 @@ package app import ( "context" "encoding/json" - "fmt" - "os" - "os/signal" - "syscall" - "github.com/pkg/errors" "github.com/rabbitmq/amqp091-go" amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) -func Run(ctx context.Context, cancel context.CancelFunc, cfg *config.Config) error { - slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) - - // postgresdb. - pg, err := postgres.NewPostgresDB(cfg.PG.DsnURL) - if err != nil { - cancel() - - slog.Error("failed to create a new Postgres", err) - - return err - } - defer pg.Close() - - // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(cfg.RabbitMQ.URL) - if err != nil { - cancel() - - slog.Error("failed to create a new RabbitMQConn", err) - - return err - } - defer amqpConn.Close() - - // publishers. - counterOrderPub, err := pkgPublisher.NewPublisher( - amqpConn, - pkgPublisher.ExchangeName("counter-order-exchange"), - pkgPublisher.BindingKey("counter-order-routing-key"), - pkgPublisher.MessageTypeName("barista-order-updated"), - ) - defer counterOrderPub.CloseChan() - - if err != nil { - cancel() - - return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") - } - - // consumers. - consumer, err := pkgConsumer.NewConsumer( - amqpConn, - pkgConsumer.ExchangeName("barista-order-exchange"), - pkgConsumer.QueueName("barista-order-queue"), - pkgConsumer.BindingKey("barista-order-routing-key"), - pkgConsumer.ConsumerTag("barista-order-consumer"), - ) - if err != nil { - slog.Error("failed to create a new OrderConsumer", err) - cancel() - } - - a, err := InitApp(cfg, pg, amqpConn, counterOrderPub, consumer) - if err != nil { - slog.Error("failed init app", err) - cancel() - } - - // event handlers. - a.handler = eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) - - slog.Info("🌏 start server...", "address", fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) - - go func() { - err := a.consumer.StartConsumer(a.worker) - if err != nil { - slog.Error("failed to start Consumer", err) - cancel() - } - }() - - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt, syscall.SIGTERM) - - select { - case v := <-quit: - slog.Info("signal.Notify", v) - case done := <-ctx.Done(): - slog.Info("ctx.Done", done) - } - - return nil -} - type App struct { - cfg *config.Config + Cfg *config.Config - pg *postgres.Postgres - amqpConn *amqp.Connection + Pg postgres.DBEngine + AMQPConn *amqp.Connection - counterOrderPub rabbitmq.EventPublisher - consumer *pkgConsumer.Consumer + CounterOrderPub pkgPublisher.EventPublisher + Consumer pkgConsumer.EventConsumer handler eventhandlers.BaristaOrderedEventHandler } func New( cfg *config.Config, - pg *postgres.Postgres, + pg postgres.DBEngine, amqpConn *amqp.Connection, - counterOrderPub rabbitmq.EventPublisher, - consumer *pkgConsumer.Consumer, + counterOrderPub pkgPublisher.EventPublisher, + consumer pkgConsumer.EventConsumer, handler eventhandlers.BaristaOrderedEventHandler, ) *App { return &App{ - cfg: cfg, + Cfg: cfg, - pg: pg, - amqpConn: amqpConn, + Pg: pg, + AMQPConn: amqpConn, - counterOrderPub: counterOrderPub, - consumer: consumer, + CounterOrderPub: counterOrderPub, + Consumer: consumer, handler: handler, } } -func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { +func (c *App) Worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) slog.Info("received", "delivery_type", delivery.Type) diff --git a/internal/barista/app/wire.go b/internal/barista/app/wire.go index d6ad842..899008e 100644 --- a/internal/barista/app/wire.go +++ b/internal/barista/app/wire.go @@ -5,20 +5,18 @@ package app import ( "github.com/google/wire" - amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" ) func InitApp( cfg *config.Config, - pg *postgres.Postgres, - amqpConn *amqp.Connection, - counterOrderPub rabbitmq.EventPublisher, - consumer *pkgConsumer.Consumer, + dbConnStr postgres.DBConnString, + rabbitMQConnStr rabbitmq.RabbitMQConnStr, ) (*App, error) { - panic(wire.Build(New, eventhandlers.BaristaOrderedEventHandlerSet)) + panic(wire.Build(New, postgres.DBEngineSet, rabbitmq.RabbitMQSet, pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, eventhandlers.BaristaOrderedEventHandlerSet)) } diff --git a/internal/barista/app/wire_gen.go b/internal/barista/app/wire_gen.go index 06051bb..dcb390e 100644 --- a/internal/barista/app/wire_gen.go +++ b/internal/barista/app/wire_gen.go @@ -7,18 +7,34 @@ package app import ( - "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" ) // Injectors from wire.go: -func InitApp(cfg *config.Config, pg *postgres.Postgres, amqpConn *amqp091.Connection, counterOrderPub rabbitmq.EventPublisher, consumer2 *consumer.Consumer) (*App, error) { - baristaOrderedEventHandler := eventhandlers.NewBaristaOrderedEventHandler(pg, counterOrderPub) - app := New(cfg, pg, amqpConn, counterOrderPub, consumer2, baristaOrderedEventHandler) +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, error) { + dbEngine, err := postgres.NewPostgresDB(dbConnStr) + if err != nil { + return nil, err + } + connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + if err != nil { + return nil, err + } + eventPublisher, err := publisher.NewPublisher(connection) + if err != nil { + return nil, err + } + eventConsumer, err := consumer.NewConsumer(connection) + if err != nil { + return nil, err + } + baristaOrderedEventHandler := eventhandlers.NewBaristaOrderedEventHandler(dbEngine, eventPublisher) + app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, baristaOrderedEventHandler) return app, nil } diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index c47763c..b6805af 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -11,7 +11,7 @@ import ( "github.com/thangchung/go-coffeeshop/internal/barista/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) @@ -20,11 +20,11 @@ var BaristaOrderedEventHandlerSet = wire.NewSet(NewBaristaOrderedEventHandler) var _ BaristaOrderedEventHandler = (*baristaOrderedEventHandler)(nil) type baristaOrderedEventHandler struct { - pg *postgres.Postgres - counterPub rabbitmq.EventPublisher + pg postgres.DBEngine + counterPub publisher.EventPublisher } -func NewBaristaOrderedEventHandler(pg *postgres.Postgres, counterPub rabbitmq.EventPublisher) BaristaOrderedEventHandler { +func NewBaristaOrderedEventHandler(pg postgres.DBEngine, counterPub publisher.EventPublisher) BaristaOrderedEventHandler { return &baristaOrderedEventHandler{ pg: pg, counterPub: counterPub, @@ -36,9 +36,10 @@ func (h *baristaOrderedEventHandler) Handle(ctx context.Context, e event.Barista order := domain.NewBaristaOrder(e) - querier := postgresql.New(h.pg.DB) + db := h.pg.GetDB() + querier := postgresql.New(db) - tx, err := h.pg.DB.Begin() + tx, err := db.Begin() if err != nil { return errors.Wrap(err, "baristaOrderedEventHandler.Handle") } diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 1f8a549..2809b24 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -17,8 +17,8 @@ import ( sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" - rabConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -46,7 +46,7 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) // postgresdb. - pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) + pg, err := postgres.NewPostgresDB(postgres.DBConnString(a.cfg.PG.DsnURL)) if err != nil { cancel() @@ -57,7 +57,7 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(a.cfg.RabbitMQ.URL) + amqpConn, err := rabbitmq.NewRabbitMQConn(rabbitmq.RabbitMQConnStr(a.cfg.RabbitMQ.URL)) if err != nil { slog.Error("failed to create a new RabbitMQConn", err) @@ -76,40 +76,44 @@ func (a *App) Run() error { } defer conn.Close() - baristaOrderPub, err := publisher.NewPublisher( + baristaOrderPub, err := pkgPublisher.NewPublisher( amqpConn, - publisher.ExchangeName("barista-order-exchange"), - publisher.BindingKey("barista-order-routing-key"), - publisher.MessageTypeName("barista-order-created"), ) - defer baristaOrderPub.CloseChan() - if err != nil { cancel() return errors.Wrap(err, "counterRabbitMQ-Barista-NewOrderPublisher") } + defer baristaOrderPub.CloseChan() - kitchenOrderPub, err := publisher.NewPublisher( - amqpConn, - publisher.ExchangeName("kitchen-order-exchange"), - publisher.BindingKey("kitchen-order-routing-key"), - publisher.MessageTypeName("kitchen-order-created"), + baristaOrderPub.Configure( + pkgPublisher.ExchangeName("barista-order-exchange"), + pkgPublisher.BindingKey("barista-order-routing-key"), + pkgPublisher.MessageTypeName("barista-order-created"), ) - defer kitchenOrderPub.CloseChan() + kitchenOrderPub, err := pkgPublisher.NewPublisher( + amqpConn, + ) if err != nil { cancel() return errors.Wrap(err, "counterRabbitMQ-Kitchen-NewOrderPublisher") } + defer kitchenOrderPub.CloseChan() + + kitchenOrderPub.Configure( + pkgPublisher.ExchangeName("kitchen-order-exchange"), + pkgPublisher.BindingKey("kitchen-order-routing-key"), + pkgPublisher.MessageTypeName("kitchen-order-created"), + ) slog.Info("Order Publisher initialized") - // repository + // repository. orderRepo := repo.NewOrderRepo(pg) - // domain service + // domain service. productDomainSvc := counterGrpc.NewGRPCProductClient(conn) // usecases. @@ -124,19 +128,21 @@ func (a *App) Run() error { a.baristaHandler = handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) a.kitchenHandler = handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) - // consumers - consumer, err := rabConsumer.NewConsumer( + // consumers. + consumer, err := pkgConsumer.NewConsumer( amqpConn, - rabConsumer.ExchangeName("counter-order-exchange"), - rabConsumer.QueueName("counter-order-queue"), - rabConsumer.BindingKey("counter-order-routing-key"), - rabConsumer.ConsumerTag("counter-order-consumer"), ) - if err != nil { slog.Error("failed to create a new consumer", err) } + consumer.Configure( + pkgConsumer.ExchangeName("counter-order-exchange"), + pkgConsumer.QueueName("counter-order-queue"), + pkgConsumer.BindingKey("counter-order-routing-key"), + pkgConsumer.ConsumerTag("counter-order-consumer"), + ) + go func() { err = consumer.StartConsumer(a.worker) if err != nil { @@ -145,7 +151,7 @@ func (a *App) Run() error { } }() - // gRPC Server + // gRPC Server. l, err := net.Listen(a.network, a.address) if err != nil { slog.Error("failed to listen to address", err, "network", a.network, "address", a.address) @@ -176,7 +182,7 @@ func (a *App) Run() error { return server.Serve(l) } -func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { +func (a *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { for delivery := range messages { slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) slog.Info("received", "delivery_type", delivery.Type) @@ -190,7 +196,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { slog.Error("failed to Unmarshal message", err) } - err = c.baristaHandler.Handle(ctx, &payload) + err = a.baristaHandler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { @@ -212,7 +218,7 @@ func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { slog.Error("failed to Unmarshal message", err) } - err = c.kitchenHandler.Handle(ctx, &payload) + err = a.kitchenHandler.Handle(ctx, &payload) if err != nil { if err = delivery.Reject(false); err != nil { diff --git a/internal/counter/infras/repo/orders_postgres.go b/internal/counter/infras/repo/orders_postgres.go index be7948b..131c2da 100644 --- a/internal/counter/infras/repo/orders_postgres.go +++ b/internal/counter/infras/repo/orders_postgres.go @@ -19,17 +19,17 @@ import ( const _defaultEntityCap = 64 type orderRepo struct { - pg *postgres.Postgres + pg postgres.DBEngine } var _ domain.OrderRepo = (*orderRepo)(nil) -func NewOrderRepo(pg *postgres.Postgres) domain.OrderRepo { +func NewOrderRepo(pg postgres.DBEngine) domain.OrderRepo { return &orderRepo{pg: pg} } func (d *orderRepo) GetAll(ctx context.Context) ([]*domain.Order, error) { - querier := postgresql.New(d.pg.DB) + querier := postgresql.New(d.pg.GetDB()) results, err := querier.GetAll(ctx) if err != nil { @@ -97,7 +97,7 @@ func (d *orderRepo) GetAll(ctx context.Context) ([]*domain.Order, error) { } func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, error) { - querier := postgresql.New(d.pg.DB) + querier := postgresql.New(d.pg.GetDB()) results, err := querier.GetByID(ctx, id) if err != nil { @@ -160,9 +160,10 @@ func (d *orderRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.Order, e } func (d *orderRepo) Create(ctx context.Context, order *domain.Order) error { - querier := postgresql.New(d.pg.DB) + db := d.pg.GetDB() + querier := postgresql.New(db) - tx, err := d.pg.DB.Begin() + tx, err := db.Begin() if err != nil { return errors.Wrap(err, "baristaOrderedEventHandler.Handle") } @@ -212,9 +213,10 @@ func (d *orderRepo) Create(ctx context.Context, order *domain.Order) error { } func (d *orderRepo) Update(ctx context.Context, order *domain.Order) (*domain.Order, error) { - querier := postgresql.New(d.pg.DB) + db := d.pg.GetDB() + querier := postgresql.New(db) - tx, err := d.pg.DB.Begin() + tx, err := db.Begin() if err != nil { return nil, errors.Wrap(err, "baristaOrderedEventHandler.Handle") } diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go index 472a843..c346da8 100644 --- a/internal/counter/usecases/orders/service.go +++ b/internal/counter/usecases/orders/service.go @@ -7,15 +7,15 @@ import ( "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) type usecase struct { orderRepo domain.OrderRepo productDomainSvc domain.ProductDomainService - baristaEventPub rabbitmq.EventPublisher - kitchenEventPub rabbitmq.EventPublisher + baristaEventPub pkgPublisher.EventPublisher + kitchenEventPub pkgPublisher.EventPublisher } var _ UseCase = (*usecase)(nil) @@ -23,8 +23,8 @@ var _ UseCase = (*usecase)(nil) func NewUseCase( orderRepo domain.OrderRepo, productDomainSvc domain.ProductDomainService, - baristaEventPub rabbitmq.EventPublisher, - kitchenEventPub rabbitmq.EventPublisher, + baristaEventPub pkgPublisher.EventPublisher, + kitchenEventPub pkgPublisher.EventPublisher, ) UseCase { return &usecase{ orderRepo: orderRepo, diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index 78c22f2..a6497b6 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -15,8 +15,8 @@ import ( "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" pkgRabbitMQ "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" - pkgconsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) @@ -41,7 +41,7 @@ func (a *App) Run() error { ctx, cancel := context.WithCancel(context.Background()) // postgresdb. - pg, err := postgres.NewPostgresDB(a.cfg.PG.DsnURL) + pg, err := postgres.NewPostgresDB(postgres.DBConnString(a.cfg.PG.DsnURL)) if err != nil { cancel() @@ -52,7 +52,7 @@ func (a *App) Run() error { defer pg.Close() // rabbitmq. - amqpConn, err := pkgRabbitMQ.NewRabbitMQConn(a.cfg.RabbitMQ.URL) + amqpConn, err := pkgRabbitMQ.NewRabbitMQConn(pkgRabbitMQ.RabbitMQConnStr(a.cfg.RabbitMQ.URL)) if err != nil { cancel() @@ -61,37 +61,41 @@ func (a *App) Run() error { defer amqpConn.Close() // publishers - counterOrderPub, err := publisher.NewPublisher( + counterOrderPub, err := pkgPublisher.NewPublisher( amqpConn, - publisher.ExchangeName("counter-order-exchange"), - publisher.BindingKey("counter-order-routing-key"), - publisher.MessageTypeName("kitchen-order-updated"), ) - defer counterOrderPub.CloseChan() - if err != nil { cancel() return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") } + defer counterOrderPub.CloseChan() + + counterOrderPub.Configure( + pkgPublisher.ExchangeName("counter-order-exchange"), + pkgPublisher.BindingKey("counter-order-routing-key"), + pkgPublisher.MessageTypeName("kitchen-order-updated"), + ) // event handlers. a.handler = eventhandlers.NewKitchenOrderedEventHandler(pg, counterOrderPub) // consumers. - consumer, err := pkgconsumer.NewConsumer( + consumer, err := pkgConsumer.NewConsumer( amqpConn, - pkgconsumer.ExchangeName("kitchen-order-exchange"), - pkgconsumer.QueueName("kitchen-order-queue"), - pkgconsumer.BindingKey("kitchen-order-routing-key"), - pkgconsumer.ConsumerTag("kitchen-order-consumer"), ) - if err != nil { slog.Error("failed to create a new OrderConsumer", err) cancel() } + consumer.Configure( + pkgConsumer.ExchangeName("kitchen-order-exchange"), + pkgConsumer.QueueName("kitchen-order-queue"), + pkgConsumer.BindingKey("kitchen-order-routing-key"), + pkgConsumer.ConsumerTag("kitchen-order-consumer"), + ) + go func() { err := consumer.StartConsumer(a.worker) if err != nil { diff --git a/internal/kitchen/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go index 3ee522b..e17f267 100644 --- a/internal/kitchen/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/eventhandlers/kitchen_ordered.go @@ -10,20 +10,20 @@ import ( "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/postgresql" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) type kitchenOrderedEventHandler struct { - pg *postgres.Postgres - counterPub rabbitmq.EventPublisher + pg postgres.DBEngine + counterPub pkgPublisher.EventPublisher } func NewKitchenOrderedEventHandler( - pg *postgres.Postgres, - counterPub rabbitmq.EventPublisher, + pg postgres.DBEngine, + counterPub pkgPublisher.EventPublisher, ) KitchenOrderedEventHandler { return &kitchenOrderedEventHandler{ pg: pg, @@ -36,9 +36,10 @@ func (h *kitchenOrderedEventHandler) Handle(ctx context.Context, e event.Kitchen order := domain.NewKitchenOrder(e) - querier := postgresql.New(h.pg.DB) + db := h.pg.GetDB() + querier := postgresql.New(db) - tx, err := h.pg.DB.Begin() + tx, err := db.Begin() if err != nil { return errors.Wrap(err, "kitchenOrderedEventHandler.Handle") } diff --git a/pkg/postgres/interfaces.go b/pkg/postgres/interfaces.go new file mode 100644 index 0000000..3d7fd8f --- /dev/null +++ b/pkg/postgres/interfaces.go @@ -0,0 +1,9 @@ +package postgres + +import "database/sql" + +type DBEngine interface { + GetDB() *sql.DB + Configure(...Option) DBEngine + Close() +} diff --git a/pkg/postgres/options.go b/pkg/postgres/options.go index d99af8f..0907da4 100644 --- a/pkg/postgres/options.go +++ b/pkg/postgres/options.go @@ -2,16 +2,16 @@ package postgres import "time" -type Option func(*Postgres) +type Option func(*postgres) func ConnAttempts(attempts int) Option { - return func(p *Postgres) { + return func(p *postgres) { p.connAttempts = attempts } } func ConnTimeout(timeout time.Duration) Option { - return func(p *Postgres) { + return func(p *postgres) { p.connTimeout = timeout } } diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 52f2e69..29d0af9 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -5,6 +5,7 @@ import ( "log" "time" + "github.com/google/wire" "golang.org/x/exp/slog" ) @@ -13,28 +14,30 @@ const ( _defaultConnTimeout = time.Second ) -type Postgres struct { +type DBConnString string + +type postgres struct { connAttempts int connTimeout time.Duration - DB *sql.DB + db *sql.DB } -func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { +var _ DBEngine = (*postgres)(nil) + +var DBEngineSet = wire.NewSet(NewPostgresDB) + +func NewPostgresDB(url DBConnString) (DBEngine, error) { slog.Info("CONN", "connect string", url) - pg := &Postgres{ + pg := &postgres{ connAttempts: _defaultConnAttempts, connTimeout: _defaultConnTimeout, } - for _, opt := range opts { - opt(pg) - } - var err error for pg.connAttempts > 0 { - pg.DB, err = sql.Open("postgres", url) + pg.db, err = sql.Open("postgres", string(url)) if err != nil { break } @@ -51,8 +54,20 @@ func NewPostgresDB(url string, opts ...Option) (*Postgres, error) { return pg, nil } -func (p *Postgres) Close() { - if p.DB != nil { - p.DB.Close() +func (p *postgres) Configure(opts ...Option) DBEngine { + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *postgres) GetDB() *sql.DB { + return p.db +} + +func (p *postgres) Close() { + if p.db != nil { + p.db.Close() } } diff --git a/pkg/rabbitmq/consumer/consumer.go b/pkg/rabbitmq/consumer/consumer.go index f240731..ff93f92 100644 --- a/pkg/rabbitmq/consumer/consumer.go +++ b/pkg/rabbitmq/consumer/consumer.go @@ -3,6 +3,7 @@ package consumer import ( "context" + "github.com/google/wire" "github.com/pkg/errors" amqp "github.com/rabbitmq/amqp091-go" "golang.org/x/exp/slog" @@ -20,7 +21,7 @@ const ( _queueExclusive = false _queueNoWait = false - _prefetchCount = 1 + _prefetchCount = 5 _prefetchSize = 0 _prefetchGlobal = false @@ -36,19 +37,18 @@ const ( _workerPoolSize = 24 ) -type worker func(ctx context.Context, messages <-chan amqp.Delivery) - -type Consumer struct { +type consumer struct { exchangeName, queueName, bindingKey, consumerTag string workerPoolSize int amqpConn *amqp.Connection } -func NewConsumer( - amqpConn *amqp.Connection, - opts ...Option, -) (*Consumer, error) { - sub := &Consumer{ +var _ EventConsumer = (*consumer)(nil) + +var EventConsumerSet = wire.NewSet(NewConsumer) + +func NewConsumer(amqpConn *amqp.Connection) (EventConsumer, error) { + sub := &consumer{ amqpConn: amqpConn, exchangeName: _exchangeName, queueName: _queueName, @@ -57,15 +57,56 @@ func NewConsumer( workerPoolSize: _workerPoolSize, } + return sub, nil +} + +func (c *consumer) Configure(opts ...Option) EventConsumer { for _, opt := range opts { - opt(sub) + opt(c) } - return sub, nil + return c +} + +// StartConsumer Start new rabbitmq consumer. +func (c *consumer) StartConsumer(fn worker) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ch, err := c.createChannel() + if err != nil { + return errors.Wrap(err, "CreateChannel") + } + defer ch.Close() + + deliveries, err := ch.Consume( + c.queueName, + c.consumerTag, + _consumeAutoAck, + _consumeExclusive, + _consumeNoLocal, + _consumeNoWait, + nil, + ) + if err != nil { + return errors.Wrap(err, "Consume") + } + + forever := make(chan bool) + + for i := 0; i < c.workerPoolSize; i++ { + go fn(ctx, deliveries) + } + + chanErr := <-ch.NotifyClose(make(chan *amqp.Error)) + slog.Error("ch.NotifyClose", chanErr) + <-forever + + return chanErr } // CreateChannel Consume messages. -func (c *Consumer) CreateChannel() (*amqp.Channel, error) { +func (c *consumer) createChannel() (*amqp.Channel, error) { ch, err := c.amqpConn.Channel() if err != nil { return nil, errors.Wrap(err, "Error amqpConn.Channel") @@ -126,40 +167,3 @@ func (c *Consumer) CreateChannel() (*amqp.Channel, error) { return ch, nil } - -// StartConsumer Start new rabbitmq consumer. -func (c *Consumer) StartConsumer(fn worker) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - ch, err := c.CreateChannel() - if err != nil { - return errors.Wrap(err, "CreateChannel") - } - defer ch.Close() - - deliveries, err := ch.Consume( - c.queueName, - c.consumerTag, - _consumeAutoAck, - _consumeExclusive, - _consumeNoLocal, - _consumeNoWait, - nil, - ) - if err != nil { - return errors.Wrap(err, "Consume") - } - - forever := make(chan bool) - - for i := 0; i < c.workerPoolSize; i++ { - go fn(ctx, deliveries) - } - - chanErr := <-ch.NotifyClose(make(chan *amqp.Error)) - slog.Error("ch.NotifyClose", chanErr) - <-forever - - return chanErr -} diff --git a/pkg/rabbitmq/consumer/interfaces.go b/pkg/rabbitmq/consumer/interfaces.go new file mode 100644 index 0000000..bc10073 --- /dev/null +++ b/pkg/rabbitmq/consumer/interfaces.go @@ -0,0 +1,14 @@ +package consumer + +import ( + "context" + + amqp "github.com/rabbitmq/amqp091-go" +) + +type worker func(ctx context.Context, messages <-chan amqp.Delivery) + +type EventConsumer interface { + Configure(...Option) EventConsumer + StartConsumer(fn worker) error +} diff --git a/pkg/rabbitmq/consumer/options.go b/pkg/rabbitmq/consumer/options.go index ed04d9e..2589ae6 100644 --- a/pkg/rabbitmq/consumer/options.go +++ b/pkg/rabbitmq/consumer/options.go @@ -1,33 +1,33 @@ package consumer -type Option func(*Consumer) +type Option func(*consumer) func ExchangeName(exchangeName string) Option { - return func(p *Consumer) { + return func(p *consumer) { p.exchangeName = exchangeName } } func QueueName(queueName string) Option { - return func(p *Consumer) { + return func(p *consumer) { p.queueName = queueName } } func BindingKey(bindingKey string) Option { - return func(p *Consumer) { + return func(p *consumer) { p.bindingKey = bindingKey } } func ConsumerTag(consumerTag string) Option { - return func(p *Consumer) { + return func(p *consumer) { p.consumerTag = consumerTag } } func WorkerPoolSize(workerPoolSize int) Option { - return func(p *Consumer) { + return func(p *consumer) { p.workerPoolSize = workerPoolSize } } diff --git a/pkg/rabbitmq/interfaces.go b/pkg/rabbitmq/publisher/interfaces.go similarity index 55% rename from pkg/rabbitmq/interfaces.go rename to pkg/rabbitmq/publisher/interfaces.go index 0e9d923..4884180 100644 --- a/pkg/rabbitmq/interfaces.go +++ b/pkg/rabbitmq/publisher/interfaces.go @@ -1,8 +1,11 @@ -package rabbitmq +package publisher -import "context" +import ( + "context" +) type EventPublisher interface { + Configure(...Option) EventPublisher Publish(context.Context, []byte, string) error CloseChan() } diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index b8a2137..cc46a49 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -6,9 +6,9 @@ import ( "time" "github.com/google/uuid" + "github.com/google/wire" "github.com/pkg/errors" amqp "github.com/rabbitmq/amqp091-go" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" ) @@ -28,9 +28,11 @@ type publisher struct { amqpConn *amqp.Connection } -var _ rabbitmq.EventPublisher = (*publisher)(nil) +var _ EventPublisher = (*publisher)(nil) -func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (rabbitmq.EventPublisher, error) { +var EventPublisherSet = wire.NewSet(NewPublisher) + +func NewPublisher(amqpConn *amqp.Connection) (EventPublisher, error) { ch, err := amqpConn.Channel() if err != nil { panic(err) @@ -45,11 +47,15 @@ func NewPublisher(amqpConn *amqp.Connection, opts ...Option) (rabbitmq.EventPubl messageTypeName: _messageTypeName, } + return pub, nil +} + +func (p *publisher) Configure(opts ...Option) EventPublisher { for _, opt := range opts { - opt(pub) + opt(p) } - return pub, nil + return p } // CloseChan Close messages chan. diff --git a/pkg/rabbitmq/rabbitmq.go b/pkg/rabbitmq/rabbitmq.go index e50d13e..e68e24e 100644 --- a/pkg/rabbitmq/rabbitmq.go +++ b/pkg/rabbitmq/rabbitmq.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/google/wire" amqp "github.com/rabbitmq/amqp091-go" "golang.org/x/exp/slog" ) @@ -13,16 +14,20 @@ const ( _backOffSeconds = 2 ) +type RabbitMQConnStr string + var ErrCannotConnectRabbitMQ = errors.New("cannot connect to rabbit") -func NewRabbitMQConn(rabbitMqURL string) (*amqp.Connection, error) { +var RabbitMQSet = wire.NewSet(NewRabbitMQConn) + +func NewRabbitMQConn(rabbitMqURL RabbitMQConnStr) (*amqp.Connection, error) { var ( amqpConn *amqp.Connection counts int64 ) for { - connection, err := amqp.Dial(rabbitMqURL) + connection, err := amqp.Dial(string(rabbitMqURL)) if err != nil { slog.Error("failed to connect to RabbitMq...", err, rabbitMqURL) counts++ From b8834bbc51e2039d84b6e865f64764fc1a8db5aa Mon Sep 17 00:00:00 2001 From: thangchung Date: Wed, 28 Dec 2022 01:24:11 +0700 Subject: [PATCH 14/16] refactor (not work) --- Makefile | 36 +++- README.md | 29 ++- cmd/barista/main.go | 6 +- cmd/counter/main.go | 109 +++++++++- cmd/kitchen/main.go | 67 +++++- cmd/product/main.go | 66 +++++- docker-compose.yaml | 3 + internal/barista/app/app.go | 10 +- internal/barista/app/wire.go | 9 +- .../barista/eventhandlers/barista_ordered.go | 4 +- internal/counter/app/app.go | 201 ++++-------------- .../router/counter_grpc_server.go} | 15 +- internal/counter/app/wire.go | 52 +++++ internal/counter/app/wire_gen.go | 64 ++++++ .../events/handlers/barista_order_updated.go | 3 + .../events/handlers/kitchen_order_updated.go | 3 + .../counter/infras/grpc/product_client.go | 14 +- .../counter/infras/repo/orders_postgres.go | 3 + internal/counter/usecases/orders/service.go | 16 +- internal/kitchen/app/app.go | 121 +++-------- internal/kitchen/app/wire.go | 29 +++ internal/kitchen/app/wire_gen.go | 40 ++++ .../kitchen/eventhandlers/kitchen_ordered.go | 7 +- internal/pkg/event/publishers.go | 7 + internal/product/app/app.go | 65 ++---- .../router/product_grpc_server.go} | 11 +- internal/product/app/wire.go | 25 +++ internal/product/app/wire_gen.go | 25 +++ .../product/infras/repo/products_inmem.go | 3 + internal/product/usecases/products/service.go | 7 +- 30 files changed, 681 insertions(+), 369 deletions(-) rename internal/counter/{infras/grpc/counter_server.go => app/router/counter_grpc_server.go} (90%) create mode 100644 internal/counter/app/wire.go create mode 100644 internal/counter/app/wire_gen.go create mode 100644 internal/kitchen/app/wire.go create mode 100644 internal/kitchen/app/wire_gen.go create mode 100644 internal/pkg/event/publishers.go rename internal/product/{infras/grpc/product_server.go => app/router/product_grpc_server.go} (89%) create mode 100644 internal/product/app/wire.go create mode 100644 internal/product/app/wire_gen.go diff --git a/Makefile b/Makefile index f4a677d..5bf9dee 100755 --- a/Makefile +++ b/Makefile @@ -35,28 +35,42 @@ run-web: CGO_ENABLED=0 go run github.com/thangchung/go-coffeeshop/cmd/web .PHONY: run-web -compose-start: +docker-compose: docker-compose-stop docker-compose-start +.PHONY: docker-compose + +docker-compose-start: docker-compose up --build -.PHONY: compose-start +.PHONY: docker-compose-start -compose-stop: +docker-compose-stop: docker-compose down --remove-orphans -v -.PHONY: compose-stop +.PHONY: docker-compose-stop -compose-core: compose-core-stop compose-core-start +docker-compose-core: docker-compose-core-stop docker-compose-core-start -compose-core-start: +docker-compose-core-start: docker-compose -f docker-compose-core.yaml up --build -.PHONY: compose-core-start +.PHONY: docker-compose-core-start -compose-core-stop: +docker-compose-core-stop: docker-compose -f docker-compose-core.yaml down --remove-orphans -v -.PHONY: compose-core-stop +.PHONY: docker-compose-core-stop -compose-build: +docker-compose-build: docker-compose down --remove-orphans -v docker-compose build -.PHONY: package +.PHONY: docker-compose-build + +wire: + cd internal/barista/app && wire && cd - && \ + cd internal/counter/app && wire && cd - && \ + cd internal/barista/app && wire && cd - && \ + cd internal/product/app && wire && cd - +.PHONY: wire + +sqlc: + sqlc generate +.PHONY: sqlc test: go test -v main.go diff --git a/README.md b/README.md index 86fac8e..80f37b6 100755 --- a/README.md +++ b/README.md @@ -45,20 +45,19 @@ No. | Service | URI 1 | grpc-gateway | [http://localhost:5000](http://localhost:5000) 2 | product service | [http://localhost:5001](http://localhost:5001) 3 | counter service | [http://localhost:5002](http://localhost:5002) -4 | barista service | [http://localhost:5003](http://localhost:5003) -5 | kitchen service | [http://localhost:5004](http://localhost:5004) -6 | web | [http://localhost:8080](http://localhost:8080) +4 | barista service | +5 | kitchen service | +6 | web | [http://localhost:8888](http://localhost:8888) ## Starting project Jump into `.devcontainer`, then ```bash -> docker-compose build -> docker-compose up +> make compose ``` -From `vscode` => Press F1 => Type `Simple Browser View` => Choose it and enter [http://localhost:8080](http://localhost:8080). +From `vscode` => Press F1 => Type `Simple Browser View` => Choose it and enter [http://localhost:8888](http://localhost:8888). Enjoy!!! ## Screenshots @@ -81,11 +80,25 @@ Enjoy!!! The details of how to run it can be find at [deployment with Nomad, Consult Connect and Vault](build/README.md). -## Debug Apps +## Development + +### Generate dependency injection instances with wire + +```bash +> make wire +``` + +### Generate code with sqlc + +```bash +> make sqlc +``` + +### Debug Apps [Debug golang app in monorepo](https://github.com/thangchung/go-coffeeshop/wiki/Golang#debug-app-in-monorepo) -## Trouble shooting +### Trouble shooting [Development project trouble shooting](https://github.com/thangchung/go-coffeeshop/wiki#trouble-shooting) diff --git a/cmd/barista/main.go b/cmd/barista/main.go index 9ec25b2..cf75677 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -52,15 +52,15 @@ func main() { cancel() } - defer a.AMQPConn.Close() - defer a.Pg.Close() + // defer a.AMQPConn.Close() + // defer a.PG.Close() a.CounterOrderPub.Configure( pkgPublisher.ExchangeName("counter-order-exchange"), pkgPublisher.BindingKey("counter-order-routing-key"), pkgPublisher.MessageTypeName("barista-order-updated"), ) - defer a.CounterOrderPub.CloseChan() + // defer a.CounterOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("barista-order-exchange"), diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 9bf0cac..06aa0be 100755 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -1,21 +1,44 @@ package main import ( + "context" + "fmt" + "net" "os" + "os/signal" + "syscall" "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app" "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "golang.org/x/exp/slog" + "google.golang.org/grpc" + + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + + _ "github.com/lib/pq" ) func main() { + // set GOMAXPROCS + // _, err := maxprocs.Set() + // if err != nil { + // slog.Error("failed set max procs", err) + // } + + ctx, _ := context.WithCancel(context.Background()) + cfg, err := config.NewConfig() if err != nil { slog.Error("failed get config", err) } + slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) + // set up logrus logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetOutput(os.Stdout) @@ -24,9 +47,87 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a := app.New(cfg) - if err = a.Run(); err != nil { - slog.Error("failed app run", err) - os.Exit(1) + server := grpc.NewServer() + + go func() { + defer server.GracefulStop() + <-ctx.Done() + }() + + a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL), server) + if err != nil { + slog.Error("failed init app", err) + // cancel() + <-ctx.Done() + } + + defer a.AMQPConn.Close() + defer a.PG.Close() + + a.BaristaOrderPub.Configure( + pkgPublisher.ExchangeName("barista-order-exchange"), + pkgPublisher.BindingKey("barista-order-routing-key"), + pkgPublisher.MessageTypeName("barista-order-created"), + ) + defer a.BaristaOrderPub.CloseChan() + + a.KitchenOrderPub.Configure( + pkgPublisher.ExchangeName("kitchen-order-exchange"), + pkgPublisher.BindingKey("kitchen-order-routing-key"), + pkgPublisher.MessageTypeName("kitchen-order-created"), + ) + defer a.KitchenOrderPub.CloseChan() + + a.Consumer.Configure( + pkgConsumer.ExchangeName("counter-order-exchange"), + pkgConsumer.QueueName("counter-order-queue"), + pkgConsumer.BindingKey("counter-order-routing-key"), + pkgConsumer.ConsumerTag("counter-order-consumer"), + ) + + go func() { + err1 := a.Consumer.StartConsumer(a.Worker) + if err1 != nil { + slog.Error("failed to start Consumer", err1) + // cancel() + <-ctx.Done() + } + }() + + // gRPC Server. + address := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) + network := "tcp" + + l, err := net.Listen(network, address) + if err != nil { + slog.Error("failed to listen to address", err, "network", network, "address", address) + // cancel() + <-ctx.Done() + } + + slog.Info("🌏 start server...", "address", address) + + defer func() { + if err1 := l.Close(); err != nil { + slog.Error("failed to close", err1, "network", network, "address", address) + <-ctx.Done() + } + }() + + err = server.Serve(l) + if err != nil { + slog.Error("failed start gRPC server", err, "network", network, "address", address) + // cancel() + <-ctx.Done() + } + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + + select { + case v := <-quit: + slog.Info("signal.Notify", v) + case done := <-ctx.Done(): + slog.Info("ctx.Done", done) } } diff --git a/cmd/kitchen/main.go b/cmd/kitchen/main.go index e6b101e..39a5a66 100755 --- a/cmd/kitchen/main.go +++ b/cmd/kitchen/main.go @@ -1,21 +1,43 @@ package main import ( + "context" + "fmt" "os" + "os/signal" + "syscall" "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/app" "github.com/thangchung/go-coffeeshop/pkg/logger" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/slog" + + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + + _ "github.com/lib/pq" ) func main() { + // set GOMAXPROCS + _, err := maxprocs.Set() + if err != nil { + slog.Error("failed set max procs", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cfg, err := config.NewConfig() if err != nil { slog.Error("failed get config", err) } + slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) + // set up logrus logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetOutput(os.Stdout) @@ -24,9 +46,46 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a := app.New(cfg) - if err = a.Run(); err != nil { - slog.Error("failed app run", err) - os.Exit(1) + a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) + if err != nil { + slog.Error("failed init app", err) + cancel() + } + + // defer a.AMQPConn.Close() + // defer a.PG.Close() + + a.CounterOrderPub.Configure( + pkgPublisher.ExchangeName("counter-order-exchange"), + pkgPublisher.BindingKey("counter-order-routing-key"), + pkgPublisher.MessageTypeName("kitchen-order-updated"), + ) + // defer a.CounterOrderPub.CloseChan() + + a.Consumer.Configure( + pkgConsumer.ExchangeName("kitchen-order-exchange"), + pkgConsumer.QueueName("kitchen-order-queue"), + pkgConsumer.BindingKey("kitchen-order-routing-key"), + pkgConsumer.ConsumerTag("kitchen-order-consumer"), + ) + + slog.Info("🌏 start server...", "address", fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)) + + go func() { + err := a.Consumer.StartConsumer(a.Worker) + if err != nil { + slog.Error("failed to start Consumer", err) + cancel() + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + + select { + case v := <-quit: + slog.Info("signal.Notify", v) + case done := <-ctx.Done(): + slog.Info("ctx.Done", done) } } diff --git a/cmd/product/main.go b/cmd/product/main.go index 0323b6a..83fb185 100755 --- a/cmd/product/main.go +++ b/cmd/product/main.go @@ -1,21 +1,38 @@ package main import ( + "context" + "fmt" + "net" "os" + "os/signal" + "syscall" "github.com/sirupsen/logrus" "github.com/thangchung/go-coffeeshop/cmd/product/config" "github.com/thangchung/go-coffeeshop/internal/product/app" "github.com/thangchung/go-coffeeshop/pkg/logger" + "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/slog" + "google.golang.org/grpc" ) func main() { + // set GOMAXPROCS + _, err := maxprocs.Set() + if err != nil { + slog.Error("failed set max procs", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + cfg, err := config.NewConfig() if err != nil { slog.Error("failed get config", err) } + slog.Info("⚡ init app", "name", cfg.Name, "version", cfg.Version) + // set up logrus logrus.SetFormatter(&logrus.JSONFormatter{}) logrus.SetOutput(os.Stdout) @@ -24,9 +41,50 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a := app.New(cfg) - if err = a.Run(); err != nil { - slog.Error("failed app run", err) - os.Exit(1) + server := grpc.NewServer() + + go func() { + defer server.GracefulStop() + <-ctx.Done() + }() + + _, err = app.InitApp(cfg, server) + if err != nil { + slog.Error("failed init app", err) + cancel() + } + + // gRPC Server. + address := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) + network := "tcp" + + l, err := net.Listen(network, address) + if err != nil { + slog.Error("failed to listen to address", err, "network", network, "address", address) + cancel() + } + + slog.Info("🌏 start server...", "address", address) + + defer func() { + if err1 := l.Close(); err != nil { + slog.Error("failed to close", err1, "network", network, "address", address) + } + }() + + err = server.Serve(l) + if err != nil { + slog.Error("failed start gRPC server", err, "network", network, "address", address) + cancel() + } + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + + select { + case v := <-quit: + slog.Info("signal.Notify", v) + case done := <-ctx.Done(): + slog.Info("ctx.Done", done) } } diff --git a/docker-compose.yaml b/docker-compose.yaml index 12d70c9..960a751 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,6 +70,7 @@ services: APP_NAME: 'counter-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ PRODUCT_CLIENT_URL: product:5001 ports: @@ -91,6 +92,7 @@ services: APP_NAME: 'barista-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ depends_on: postgres: @@ -109,6 +111,7 @@ services: APP_NAME: 'kitchen-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres + PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ depends_on: postgres: diff --git a/internal/barista/app/app.go b/internal/barista/app/app.go index cc24da2..3f84172 100644 --- a/internal/barista/app/app.go +++ b/internal/barista/app/app.go @@ -16,9 +16,8 @@ import ( ) type App struct { - Cfg *config.Config - - Pg postgres.DBEngine + Cfg *config.Config + PG postgres.DBEngine AMQPConn *amqp.Connection CounterOrderPub pkgPublisher.EventPublisher @@ -36,9 +35,8 @@ func New( handler eventhandlers.BaristaOrderedEventHandler, ) *App { return &App{ - Cfg: cfg, - - Pg: pg, + Cfg: cfg, + PG: pg, AMQPConn: amqpConn, CounterOrderPub: counterOrderPub, diff --git a/internal/barista/app/wire.go b/internal/barista/app/wire.go index 899008e..1f3349f 100644 --- a/internal/barista/app/wire.go +++ b/internal/barista/app/wire.go @@ -18,5 +18,12 @@ func InitApp( dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, ) (*App, error) { - panic(wire.Build(New, postgres.DBEngineSet, rabbitmq.RabbitMQSet, pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, eventhandlers.BaristaOrderedEventHandlerSet)) + panic(wire.Build( + New, + postgres.DBEngineSet, + rabbitmq.RabbitMQSet, + pkgPublisher.EventPublisherSet, + pkgConsumer.EventConsumerSet, + eventhandlers.BaristaOrderedEventHandlerSet, + )) } diff --git a/internal/barista/eventhandlers/barista_ordered.go b/internal/barista/eventhandlers/barista_ordered.go index b6805af..fc6882b 100644 --- a/internal/barista/eventhandlers/barista_ordered.go +++ b/internal/barista/eventhandlers/barista_ordered.go @@ -15,10 +15,10 @@ import ( "golang.org/x/exp/slog" ) -var BaristaOrderedEventHandlerSet = wire.NewSet(NewBaristaOrderedEventHandler) - var _ BaristaOrderedEventHandler = (*baristaOrderedEventHandler)(nil) +var BaristaOrderedEventHandlerSet = wire.NewSet(NewBaristaOrderedEventHandler) + type baristaOrderedEventHandler struct { pg postgres.DBEngine counterPub publisher.EventPublisher diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 2809b24..a9e67ac 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -3,186 +3,69 @@ package app import ( "context" "encoding/json" - "fmt" - "net" - "github.com/pkg/errors" - "github.com/rabbitmq/amqp091-go" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" + "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" - "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" - counterGrpc "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" - "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" - "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + ordersUC "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" - pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "github.com/thangchung/go-coffeeshop/proto/gen" "golang.org/x/exp/slog" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type App struct { - cfg *config.Config - network string - address string + Cfg *config.Config + PG postgres.DBEngine + AMQPConn *amqp.Connection + + BaristaOrderPub ordersUC.BaristaEventPublisher + KitchenOrderPub ordersUC.KitchenEventPublisher + Consumer pkgConsumer.EventConsumer + ProductDomainSvc domain.ProductDomainService + UC ordersUC.UseCase + CounterGRPCServer gen.CounterServiceServer + baristaHandler events.BaristaOrderUpdatedEventHandler kitchenHandler events.KitchenOrderUpdatedEventHandler } -func New(cfg *config.Config) *App { +func New( + cfg *config.Config, + pg postgres.DBEngine, + amqpConn *amqp.Connection, + + baristaOrderPub ordersUC.BaristaEventPublisher, + kitchenOrderPub ordersUC.KitchenEventPublisher, + consumer pkgConsumer.EventConsumer, + productDomainSvc domain.ProductDomainService, + uc ordersUC.UseCase, + counterGRPCServer gen.CounterServiceServer, + + baristaHandler events.BaristaOrderUpdatedEventHandler, + kitchenHandler events.KitchenOrderUpdatedEventHandler, +) *App { return &App{ - cfg: cfg, - network: "tcp", - address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), - } -} - -func (a *App) Run() error { - slog.Info("Init app", "name", a.cfg.Name, "version", a.cfg.Version) - - ctx, cancel := context.WithCancel(context.Background()) - - // postgresdb. - pg, err := postgres.NewPostgresDB(postgres.DBConnString(a.cfg.PG.DsnURL)) - if err != nil { - cancel() - - slog.Error("failed to create a new Postgres", err) - - return err - } - defer pg.Close() - - // rabbitmq. - amqpConn, err := rabbitmq.NewRabbitMQConn(rabbitmq.RabbitMQConnStr(a.cfg.RabbitMQ.URL)) - if err != nil { - slog.Error("failed to create a new RabbitMQConn", err) - - cancel() - - return err - } - defer amqpConn.Close() - - // gRPC Client. - conn, err := grpc.Dial(a.cfg.ProductClient.URL, grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - cancel() - - return err - } - defer conn.Close() - - baristaOrderPub, err := pkgPublisher.NewPublisher( - amqpConn, - ) - if err != nil { - cancel() - - return errors.Wrap(err, "counterRabbitMQ-Barista-NewOrderPublisher") - } - defer baristaOrderPub.CloseChan() - - baristaOrderPub.Configure( - pkgPublisher.ExchangeName("barista-order-exchange"), - pkgPublisher.BindingKey("barista-order-routing-key"), - pkgPublisher.MessageTypeName("barista-order-created"), - ) - - kitchenOrderPub, err := pkgPublisher.NewPublisher( - amqpConn, - ) - if err != nil { - cancel() - - return errors.Wrap(err, "counterRabbitMQ-Kitchen-NewOrderPublisher") - } - defer kitchenOrderPub.CloseChan() - - kitchenOrderPub.Configure( - pkgPublisher.ExchangeName("kitchen-order-exchange"), - pkgPublisher.BindingKey("kitchen-order-routing-key"), - pkgPublisher.MessageTypeName("kitchen-order-created"), - ) - - slog.Info("Order Publisher initialized") - - // repository. - orderRepo := repo.NewOrderRepo(pg) - - // domain service. - productDomainSvc := counterGrpc.NewGRPCProductClient(conn) - - // usecases. - uc := orders.NewUseCase( - orderRepo, - productDomainSvc, - baristaOrderPub, - kitchenOrderPub, - ) - - // event handlers. - a.baristaHandler = handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) - a.kitchenHandler = handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) - - // consumers. - consumer, err := pkgConsumer.NewConsumer( - amqpConn, - ) - if err != nil { - slog.Error("failed to create a new consumer", err) - } + Cfg: cfg, - consumer.Configure( - pkgConsumer.ExchangeName("counter-order-exchange"), - pkgConsumer.QueueName("counter-order-queue"), - pkgConsumer.BindingKey("counter-order-routing-key"), - pkgConsumer.ConsumerTag("counter-order-consumer"), - ) - - go func() { - err = consumer.StartConsumer(a.worker) - if err != nil { - slog.Error("failed to start consumer: %v", err) - cancel() - } - }() + PG: pg, + AMQPConn: amqpConn, - // gRPC Server. - l, err := net.Listen(a.network, a.address) - if err != nil { - slog.Error("failed to listen to address", err, "network", a.network, "address", a.address) + BaristaOrderPub: baristaOrderPub, + KitchenOrderPub: kitchenOrderPub, + Consumer: consumer, + ProductDomainSvc: productDomainSvc, + UC: uc, + CounterGRPCServer: counterGRPCServer, - return err + baristaHandler: baristaHandler, + kitchenHandler: kitchenHandler, } - - defer func() { - if err := l.Close(); err != nil { - slog.Error("failed to close", err, "network", a.network, "address", a.address) - } - }() - - server := grpc.NewServer() - counterGrpc.NewGRPCCounterServer( - server, - a.cfg, - uc, - ) - - go func() { - defer server.GracefulStop() - <-ctx.Done() - }() - - slog.Info("start server...", "address", a.address) - - return server.Serve(l) } -func (a *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { +func (a *App) Worker(ctx context.Context, messages <-chan amqp.Delivery) { for delivery := range messages { slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) slog.Info("received", "delivery_type", delivery.Type) diff --git a/internal/counter/infras/grpc/counter_server.go b/internal/counter/app/router/counter_grpc_server.go similarity index 90% rename from internal/counter/infras/grpc/counter_server.go rename to internal/counter/app/router/counter_grpc_server.go index 1b178c2..ba6f9b3 100644 --- a/internal/counter/infras/grpc/counter_server.go +++ b/internal/counter/app/router/counter_grpc_server.go @@ -1,10 +1,11 @@ -package grpc +package router import ( "context" "fmt" "github.com/google/uuid" + "github.com/google/wire" "github.com/pkg/errors" "github.com/samber/lo" "github.com/thangchung/go-coffeeshop/cmd/counter/config" @@ -25,11 +26,13 @@ type counterGRPCServer struct { var _ gen.CounterServiceServer = (*counterGRPCServer)(nil) +var CounterGRPCServerSet = wire.NewSet(NewGRPCCounterServer) + func NewGRPCCounterServer( grpcServer *grpc.Server, cfg *config.Config, uc orders.UseCase, -) { +) gen.CounterServiceServer { svc := counterGRPCServer{ cfg: cfg, uc: uc, @@ -38,6 +41,8 @@ func NewGRPCCounterServer( gen.RegisterCounterServiceServer(grpcServer, &svc) reflection.Register(grpcServer) + + return &svc } func (g *counterGRPCServer) GetListOrderFulfillment( @@ -50,7 +55,7 @@ func (g *counterGRPCServer) GetListOrderFulfillment( entities, err := g.uc.GetListOrderFulfillment(ctx) if err != nil { - return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.uc.GetListOrderFulfillment: %w", err) + return nil, fmt.Errorf("uc.GetListOrderFulfillment: %w", err) } for _, entity := range entities { @@ -84,7 +89,7 @@ func (g *counterGRPCServer) PlaceOrder( loyaltyMemberID, err := uuid.Parse(request.LoyaltyMemberId) if err != nil { - return nil, errors.Wrap(err, "counterGRPCServer-uuid.Parse") + return nil, errors.Wrap(err, "uuid.Parse") } model := domain.PlaceOrderModel{ @@ -109,7 +114,7 @@ func (g *counterGRPCServer) PlaceOrder( err = g.uc.PlaceOrder(ctx, &model) if err != nil { - return nil, errors.Wrap(err, "counterGRPCServer-g.uc.PlaceOrder") + return nil, errors.Wrap(err, "uc.PlaceOrder") } res := gen.PlaceOrderResponse{} diff --git a/internal/counter/app/wire.go b/internal/counter/app/wire.go new file mode 100644 index 0000000..8855e00 --- /dev/null +++ b/internal/counter/app/wire.go @@ -0,0 +1,52 @@ +//go:build wireinject +// +build wireinject + +package app + +import ( + "github.com/google/wire" + "github.com/rabbitmq/amqp091-go" + "github.com/thangchung/go-coffeeshop/cmd/counter/config" + "github.com/thangchung/go-coffeeshop/internal/counter/app/router" + "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" + infrasGRPC "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" + "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" + ordersUC "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "google.golang.org/grpc" +) + +func baristaEventPublisher(amqpConn *amqp091.Connection) ordersUC.BaristaEventPublisher { + pub, _ := pkgPublisher.NewPublisher(amqpConn) + return (ordersUC.BaristaEventPublisher)(pub) +} + +func kitchenEventPublisher(amqpConn *amqp091.Connection) ordersUC.KitchenEventPublisher { + pub, _ := pkgPublisher.NewPublisher(amqpConn) + return (ordersUC.KitchenEventPublisher)(pub) +} + +func InitApp( + cfg *config.Config, + dbConnStr postgres.DBConnString, + rabbitMQConnStr rabbitmq.RabbitMQConnStr, + grpcServer *grpc.Server, +) (*App, error) { + panic(wire.Build( + New, + postgres.DBEngineSet, + rabbitmq.RabbitMQSet, + pkgConsumer.EventConsumerSet, + infrasGRPC.ProductGRPCClientSet, + router.CounterGRPCServerSet, + repo.RepositorySet, + ordersUC.UseCaseSet, + baristaEventPublisher, + kitchenEventPublisher, + handlers.BaristaOrderUpdatedEventHandlerSet, + handlers.KitchenOrderUpdatedEventHandlerSet, + )) +} diff --git a/internal/counter/app/wire_gen.go b/internal/counter/app/wire_gen.go new file mode 100644 index 0000000..4f114de --- /dev/null +++ b/internal/counter/app/wire_gen.go @@ -0,0 +1,64 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package app + +import ( + "github.com/rabbitmq/amqp091-go" + "github.com/thangchung/go-coffeeshop/cmd/counter/config" + "github.com/thangchung/go-coffeeshop/internal/counter/app/router" + "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" + grpc2 "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" + "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "google.golang.org/grpc" +) + +// Injectors from wire.go: + +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, grpcServer *grpc.Server) (*App, error) { + dbEngine, err := postgres.NewPostgresDB(dbConnStr) + if err != nil { + return nil, err + } + connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + if err != nil { + return nil, err + } + ordersBaristaEventPublisher := baristaEventPublisher(connection) + ordersKitchenEventPublisher := kitchenEventPublisher(connection) + eventConsumer, err := consumer.NewConsumer(connection) + if err != nil { + return nil, err + } + productDomainService, err := grpc2.NewGRPCProductClient(cfg) + if err != nil { + return nil, err + } + orderRepo := repo.NewOrderRepo(dbEngine) + useCase := orders.NewUseCase(orderRepo, productDomainService, ordersBaristaEventPublisher, ordersKitchenEventPublisher) + counterServiceServer := router.NewGRPCCounterServer(grpcServer, cfg, useCase) + baristaOrderUpdatedEventHandler := handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) + kitchenOrderUpdatedEventHandler := handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) + app := New(cfg, dbEngine, connection, ordersBaristaEventPublisher, ordersKitchenEventPublisher, eventConsumer, productDomainService, useCase, counterServiceServer, baristaOrderUpdatedEventHandler, kitchenOrderUpdatedEventHandler) + return app, nil +} + +// wire.go: + +func baristaEventPublisher(amqpConn *amqp091.Connection) orders.BaristaEventPublisher { + pub, _ := publisher.NewPublisher(amqpConn) + return (orders.BaristaEventPublisher)(pub) +} + +func kitchenEventPublisher(amqpConn *amqp091.Connection) orders.KitchenEventPublisher { + pub, _ := publisher.NewPublisher(amqpConn) + return (orders.KitchenEventPublisher)(pub) +} diff --git a/internal/counter/events/handlers/barista_order_updated.go b/internal/counter/events/handlers/barista_order_updated.go index 4094e6e..aa1f72e 100644 --- a/internal/counter/events/handlers/barista_order_updated.go +++ b/internal/counter/events/handlers/barista_order_updated.go @@ -3,6 +3,7 @@ package handlers import ( "context" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" @@ -15,6 +16,8 @@ type baristaOrderUpdatedEventHandler struct { var _ events.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) +var BaristaOrderUpdatedEventHandlerSet = wire.NewSet(NewBaristaOrderUpdatedEventHandler) + func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.BaristaOrderUpdatedEventHandler { return &baristaOrderUpdatedEventHandler{ orderRepo: orderRepo, diff --git a/internal/counter/events/handlers/kitchen_order_updated.go b/internal/counter/events/handlers/kitchen_order_updated.go index 4f7bea8..1295930 100644 --- a/internal/counter/events/handlers/kitchen_order_updated.go +++ b/internal/counter/events/handlers/kitchen_order_updated.go @@ -3,6 +3,7 @@ package handlers import ( "context" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" @@ -15,6 +16,8 @@ type kitchenOrderUpdatedEventHandler struct { var _ events.KitchenOrderUpdatedEventHandler = (*kitchenOrderUpdatedEventHandler)(nil) +var KitchenOrderUpdatedEventHandlerSet = wire.NewSet(NewKitchenOrderUpdatedEventHandler) + func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.KitchenOrderUpdatedEventHandler { return &kitchenOrderUpdatedEventHandler{ orderRepo: orderRepo, diff --git a/internal/counter/infras/grpc/product_client.go b/internal/counter/infras/grpc/product_client.go index 9043337..69e9c85 100644 --- a/internal/counter/infras/grpc/product_client.go +++ b/internal/counter/infras/grpc/product_client.go @@ -5,12 +5,15 @@ import ( "fmt" "strings" + "github.com/google/wire" "github.com/pkg/errors" "github.com/samber/lo" + "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/domain" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" gen "github.com/thangchung/go-coffeeshop/proto/gen" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) type productGRPCClient struct { @@ -19,10 +22,17 @@ type productGRPCClient struct { var _ domain.ProductDomainService = (*productGRPCClient)(nil) -func NewGRPCProductClient(conn *grpc.ClientConn) domain.ProductDomainService { +var ProductGRPCClientSet = wire.NewSet(NewGRPCProductClient) + +func NewGRPCProductClient(cfg *config.Config) (domain.ProductDomainService, error) { + conn, err := grpc.Dial(cfg.ProductClient.URL, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + return &productGRPCClient{ conn: conn, - } + }, nil } func (p *productGRPCClient) GetItemsByType( diff --git a/internal/counter/infras/repo/orders_postgres.go b/internal/counter/infras/repo/orders_postgres.go index 131c2da..79bd11c 100644 --- a/internal/counter/infras/repo/orders_postgres.go +++ b/internal/counter/infras/repo/orders_postgres.go @@ -8,6 +8,7 @@ import ( "time" "github.com/google/uuid" + "github.com/google/wire" "github.com/pkg/errors" "github.com/samber/lo" "github.com/thangchung/go-coffeeshop/internal/counter/domain" @@ -24,6 +25,8 @@ type orderRepo struct { var _ domain.OrderRepo = (*orderRepo)(nil) +var RepositorySet = wire.NewSet(NewOrderRepo) + func NewOrderRepo(pg postgres.DBEngine) domain.OrderRepo { return &orderRepo{pg: pg} } diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go index c346da8..939bdb6 100644 --- a/internal/counter/usecases/orders/service.go +++ b/internal/counter/usecases/orders/service.go @@ -5,26 +5,34 @@ import ( "encoding/json" "fmt" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) +type ( + BaristaEventPublisher pkgPublisher.EventPublisher + KitchenEventPublisher pkgPublisher.EventPublisher +) + type usecase struct { orderRepo domain.OrderRepo productDomainSvc domain.ProductDomainService - baristaEventPub pkgPublisher.EventPublisher - kitchenEventPub pkgPublisher.EventPublisher + baristaEventPub BaristaEventPublisher + kitchenEventPub KitchenEventPublisher } var _ UseCase = (*usecase)(nil) +var UseCaseSet = wire.NewSet(NewUseCase) + func NewUseCase( orderRepo domain.OrderRepo, productDomainSvc domain.ProductDomainService, - baristaEventPub pkgPublisher.EventPublisher, - kitchenEventPub pkgPublisher.EventPublisher, + baristaEventPub BaristaEventPublisher, + kitchenEventPub KitchenEventPublisher, ) UseCase { return &usecase{ orderRepo: orderRepo, diff --git a/internal/kitchen/app/app.go b/internal/kitchen/app/app.go index a6497b6..7ebc581 100644 --- a/internal/kitchen/app/app.go +++ b/internal/kitchen/app/app.go @@ -3,123 +3,50 @@ package app import ( "context" "encoding/json" - "fmt" - "os" - "os/signal" - "syscall" - "github.com/pkg/errors" - "github.com/rabbitmq/amqp091-go" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" - pkgRabbitMQ "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "golang.org/x/exp/slog" ) type App struct { - cfg *config.Config - network string - address string - handler eventhandlers.KitchenOrderedEventHandler -} - -func New(cfg *config.Config) *App { - return &App{ - cfg: cfg, - network: "tcp", - address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), - } -} - -func (a *App) Run() error { - slog.Info("init app", "name", a.cfg.Name, "version", a.cfg.Version) - - ctx, cancel := context.WithCancel(context.Background()) - - // postgresdb. - pg, err := postgres.NewPostgresDB(postgres.DBConnString(a.cfg.PG.DsnURL)) - if err != nil { - cancel() - - slog.Error("failed to create a new Postgres", err) - - return err - } - defer pg.Close() - - // rabbitmq. - amqpConn, err := pkgRabbitMQ.NewRabbitMQConn(pkgRabbitMQ.RabbitMQConnStr(a.cfg.RabbitMQ.URL)) - if err != nil { - cancel() + Cfg *config.Config - slog.Error("failed to create a new RabbitMQConn", err) - } - defer amqpConn.Close() + PG postgres.DBEngine + AMQPConn *amqp.Connection - // publishers - counterOrderPub, err := pkgPublisher.NewPublisher( - amqpConn, - ) - if err != nil { - cancel() + CounterOrderPub pkgPublisher.EventPublisher + Consumer pkgConsumer.EventConsumer - return errors.Wrap(err, "publisher-Counter-NewOrderPublisher") - } - defer counterOrderPub.CloseChan() - - counterOrderPub.Configure( - pkgPublisher.ExchangeName("counter-order-exchange"), - pkgPublisher.BindingKey("counter-order-routing-key"), - pkgPublisher.MessageTypeName("kitchen-order-updated"), - ) - - // event handlers. - a.handler = eventhandlers.NewKitchenOrderedEventHandler(pg, counterOrderPub) - - // consumers. - consumer, err := pkgConsumer.NewConsumer( - amqpConn, - ) - if err != nil { - slog.Error("failed to create a new OrderConsumer", err) - cancel() - } + handler eventhandlers.KitchenOrderedEventHandler +} - consumer.Configure( - pkgConsumer.ExchangeName("kitchen-order-exchange"), - pkgConsumer.QueueName("kitchen-order-queue"), - pkgConsumer.BindingKey("kitchen-order-routing-key"), - pkgConsumer.ConsumerTag("kitchen-order-consumer"), - ) - - go func() { - err := consumer.StartConsumer(a.worker) - if err != nil { - slog.Error("failed to start Consumer", err) - cancel() - } - }() +func New( + cfg *config.Config, + pg postgres.DBEngine, + amqpConn *amqp.Connection, + counterOrderPub pkgPublisher.EventPublisher, + consumer pkgConsumer.EventConsumer, + handler eventhandlers.KitchenOrderedEventHandler, +) *App { + return &App{ + Cfg: cfg, + PG: pg, + AMQPConn: amqpConn, - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt, syscall.SIGTERM) + CounterOrderPub: counterOrderPub, + Consumer: consumer, - select { - case v := <-quit: - slog.Info("signal.Notify", v) - case done := <-ctx.Done(): - slog.Info("ctx.Done", done) + handler: handler, } - - slog.Info("start server...", "address", a.address) - - return nil } -func (c *App) worker(ctx context.Context, messages <-chan amqp091.Delivery) { +func (c *App) Worker(ctx context.Context, messages <-chan amqp.Delivery) { for delivery := range messages { slog.Info("processDeliveries", "delivery_tag", delivery.DeliveryTag) slog.Info("received", "delivery_type", delivery.Type) diff --git a/internal/kitchen/app/wire.go b/internal/kitchen/app/wire.go new file mode 100644 index 0000000..4c7bf5d --- /dev/null +++ b/internal/kitchen/app/wire.go @@ -0,0 +1,29 @@ +//go:build wireinject +// +build wireinject + +package app + +import ( + "github.com/google/wire" + "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" + "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" +) + +func InitApp( + cfg *config.Config, + dbConnStr postgres.DBConnString, + rabbitMQConnStr rabbitmq.RabbitMQConnStr, +) (*App, error) { + panic(wire.Build( + New, + postgres.DBEngineSet, + rabbitmq.RabbitMQSet, + pkgPublisher.EventPublisherSet, + pkgConsumer.EventConsumerSet, + eventhandlers.KitchenOrderedEventHandlerSet, + )) +} diff --git a/internal/kitchen/app/wire_gen.go b/internal/kitchen/app/wire_gen.go new file mode 100644 index 0000000..0905115 --- /dev/null +++ b/internal/kitchen/app/wire_gen.go @@ -0,0 +1,40 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package app + +import ( + "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" + "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" + "github.com/thangchung/go-coffeeshop/pkg/postgres" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" +) + +// Injectors from wire.go: + +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, error) { + dbEngine, err := postgres.NewPostgresDB(dbConnStr) + if err != nil { + return nil, err + } + connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + if err != nil { + return nil, err + } + eventPublisher, err := publisher.NewPublisher(connection) + if err != nil { + return nil, err + } + eventConsumer, err := consumer.NewConsumer(connection) + if err != nil { + return nil, err + } + kitchenOrderedEventHandler := eventhandlers.NewKitchenOrderedEventHandler(dbEngine, eventPublisher) + app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, kitchenOrderedEventHandler) + return app, nil +} diff --git a/internal/kitchen/eventhandlers/kitchen_ordered.go b/internal/kitchen/eventhandlers/kitchen_ordered.go index e17f267..4d4432e 100644 --- a/internal/kitchen/eventhandlers/kitchen_ordered.go +++ b/internal/kitchen/eventhandlers/kitchen_ordered.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/kitchen/domain" "github.com/thangchung/go-coffeeshop/internal/kitchen/infras/postgresql" @@ -14,13 +15,15 @@ import ( "golang.org/x/exp/slog" ) -var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) - type kitchenOrderedEventHandler struct { pg postgres.DBEngine counterPub pkgPublisher.EventPublisher } +var _ KitchenOrderedEventHandler = (*kitchenOrderedEventHandler)(nil) + +var KitchenOrderedEventHandlerSet = wire.NewSet(NewKitchenOrderedEventHandler) + func NewKitchenOrderedEventHandler( pg postgres.DBEngine, counterPub pkgPublisher.EventPublisher, diff --git a/internal/pkg/event/publishers.go b/internal/pkg/event/publishers.go new file mode 100644 index 0000000..6f8e914 --- /dev/null +++ b/internal/pkg/event/publishers.go @@ -0,0 +1,7 @@ +package event + +type BaristaEventPublisher struct { +} + +func (p *BaristaEventPublisher) abc() { +} diff --git a/internal/product/app/app.go b/internal/product/app/app.go index 496e0be..91f5e7d 100644 --- a/internal/product/app/app.go +++ b/internal/product/app/app.go @@ -1,66 +1,25 @@ package app import ( - "context" - "fmt" - "net" - "github.com/thangchung/go-coffeeshop/cmd/product/config" - productGrpc "github.com/thangchung/go-coffeeshop/internal/product/infras/grpc" - productRepo "github.com/thangchung/go-coffeeshop/internal/product/infras/repo" productUC "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" - "golang.org/x/exp/slog" - "google.golang.org/grpc" + "github.com/thangchung/go-coffeeshop/proto/gen" ) type App struct { - cfg *config.Config - network string - address string + Cfg *config.Config + UC productUC.UseCase + ProductGRPCServer gen.ProductServiceServer } -func New(cfg *config.Config) *App { +func New( + cfg *config.Config, + uc productUC.UseCase, + productGRPCServer gen.ProductServiceServer, +) *App { return &App{ - cfg: cfg, - network: "tcp", - address: fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port), - } -} - -func (a *App) Run() error { - slog.Info("init", "name", a.cfg.Name, "version", a.cfg.Version) - - ctx, _ := context.WithCancel(context.Background()) - - // Repository - repo := productRepo.NewOrderRepo() - - // UC - uc := productUC.NewService(repo) - - // gRPC Server - l, err := net.Listen(a.network, a.address) - if err != nil { - slog.Error("failed app-Run-net.Listener", err) - - return err + Cfg: cfg, + UC: uc, + ProductGRPCServer: productGRPCServer, } - - defer func() { - if err := l.Close(); err != nil { - slog.Error("failed to close", err, "network", a.network, "address", a.address) - } - }() - - s := grpc.NewServer() - productGrpc.NewProductGRPCServer(s, uc) - - go func() { - defer s.GracefulStop() - <-ctx.Done() - }() - - slog.Info("start server", "address", a.address) - - return s.Serve(l) } diff --git a/internal/product/infras/grpc/product_server.go b/internal/product/app/router/product_grpc_server.go similarity index 89% rename from internal/product/infras/grpc/product_server.go rename to internal/product/app/router/product_grpc_server.go index 1ca2732..91b409c 100644 --- a/internal/product/infras/grpc/product_server.go +++ b/internal/product/app/router/product_grpc_server.go @@ -1,8 +1,9 @@ -package grpc +package router import ( "context" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" "github.com/thangchung/go-coffeeshop/proto/gen" @@ -11,6 +12,10 @@ import ( "google.golang.org/grpc/reflection" ) +var _ gen.ProductServiceServer = (*productGRPCServer)(nil) + +var ProductGRPCServerSet = wire.NewSet(NewProductGRPCServer) + type productGRPCServer struct { gen.UnimplementedProductServiceServer uc products.UseCase @@ -19,7 +24,7 @@ type productGRPCServer struct { func NewProductGRPCServer( grpcServer *grpc.Server, uc products.UseCase, -) { +) gen.ProductServiceServer { svc := productGRPCServer{ uc: uc, } @@ -27,6 +32,8 @@ func NewProductGRPCServer( gen.RegisterProductServiceServer(grpcServer, &svc) reflection.Register(grpcServer) + + return &svc } func (g *productGRPCServer) GetItemTypes( diff --git a/internal/product/app/wire.go b/internal/product/app/wire.go new file mode 100644 index 0000000..3c0e16f --- /dev/null +++ b/internal/product/app/wire.go @@ -0,0 +1,25 @@ +//go:build wireinject +// +build wireinject + +package app + +import ( + "github.com/google/wire" + "github.com/thangchung/go-coffeeshop/cmd/product/config" + "github.com/thangchung/go-coffeeshop/internal/product/app/router" + "github.com/thangchung/go-coffeeshop/internal/product/infras/repo" + productsUC "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" + "google.golang.org/grpc" +) + +func InitApp( + cfg *config.Config, + grpcServer *grpc.Server, +) (*App, error) { + panic(wire.Build( + New, + router.ProductGRPCServerSet, + repo.RepositorySet, + productsUC.UseCaseSet, + )) +} diff --git a/internal/product/app/wire_gen.go b/internal/product/app/wire_gen.go new file mode 100644 index 0000000..150a713 --- /dev/null +++ b/internal/product/app/wire_gen.go @@ -0,0 +1,25 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package app + +import ( + "github.com/thangchung/go-coffeeshop/cmd/product/config" + "github.com/thangchung/go-coffeeshop/internal/product/app/router" + "github.com/thangchung/go-coffeeshop/internal/product/infras/repo" + "github.com/thangchung/go-coffeeshop/internal/product/usecases/products" + "google.golang.org/grpc" +) + +// Injectors from wire.go: + +func InitApp(cfg *config.Config, grpcServer *grpc.Server) (*App, error) { + productRepo := repo.NewOrderRepo() + useCase := products.NewService(productRepo) + productServiceServer := router.NewProductGRPCServer(grpcServer, useCase) + app := New(cfg, useCase, productServiceServer) + return app, nil +} diff --git a/internal/product/infras/repo/products_inmem.go b/internal/product/infras/repo/products_inmem.go index 4a300f8..0f786c0 100644 --- a/internal/product/infras/repo/products_inmem.go +++ b/internal/product/infras/repo/products_inmem.go @@ -3,11 +3,14 @@ package repo import ( "context" + "github.com/google/wire" "github.com/thangchung/go-coffeeshop/internal/product/domain" ) var _ domain.ProductRepo = (*productInMemRepo)(nil) +var RepositorySet = wire.NewSet(NewOrderRepo) + type productInMemRepo struct { itemTypes map[string]*domain.ItemTypeDto } diff --git a/internal/product/usecases/products/service.go b/internal/product/usecases/products/service.go index 0c51ee5..d72007f 100644 --- a/internal/product/usecases/products/service.go +++ b/internal/product/usecases/products/service.go @@ -4,16 +4,19 @@ import ( "context" "strings" + "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/product/domain" ) +var _ UseCase = (*service)(nil) + +var UseCaseSet = wire.NewSet(NewService) + type service struct { repo domain.ProductRepo } -var _ UseCase = (*service)(nil) - func NewService(repo domain.ProductRepo) UseCase { return &service{ repo: repo, From 5e2806613c44bcd9a5de179ce55d5a5bfc7d5fda Mon Sep 17 00:00:00 2001 From: thangchung Date: Wed, 28 Dec 2022 12:25:23 +0700 Subject: [PATCH 15/16] add more codes --- README.md | 8 +- cmd/barista/config.yml | 2 +- cmd/barista/main.go | 6 +- cmd/counter/config.yml | 2 +- cmd/counter/main.go | 97 +++++++++++---------- cmd/kitchen/config.yml | 2 +- cmd/kitchen/main.go | 6 +- docker-compose.yaml | 6 +- internal/counter/app/app.go | 34 +++++--- internal/counter/app/wire.go | 18 ++-- internal/counter/app/wire_gen.go | 26 ++---- internal/counter/usecases/orders/service.go | 15 ++-- internal/pkg/event/publishers.go | 66 +++++++++++++- pkg/rabbitmq/publisher/options.go | 8 +- pkg/rabbitmq/publisher/publisher.go | 14 +-- 15 files changed, 181 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 80f37b6..c80fc3f 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # go-coffeeshop -A coffee shop application with event-driven microservices has been written in Golang. Nomad, Consul Connect, Vault, and Terraform for deployment +A event-driven microservices coffee shop application has been written in Golang, and deployment using Nomad, Consul Connect, Vault, and Terraform. -Other version can be found at: +Other versions can be found at: - [.NET CoffeeShop with Microservices approach](https://github.com/thangchung/coffeeshop-on-nomad) - [.NET CoffeeShop with Modular Monolith approach](https://github.com/thangchung/coffeeshop-modular) @@ -45,8 +45,8 @@ No. | Service | URI 1 | grpc-gateway | [http://localhost:5000](http://localhost:5000) 2 | product service | [http://localhost:5001](http://localhost:5001) 3 | counter service | [http://localhost:5002](http://localhost:5002) -4 | barista service | -5 | kitchen service | +4 | barista service | +5 | kitchen service | 6 | web | [http://localhost:8888](http://localhost:8888) ## Starting project diff --git a/cmd/barista/config.yml b/cmd/barista/config.yml index f389fb0..76906e2 100755 --- a/cmd/barista/config.yml +++ b/cmd/barista/config.yml @@ -8,7 +8,7 @@ http: postgres: pool_max: 2 - dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + dsn_url: host=127.0.0.1 user=postgres password=P@ssw0rd dbname=postgres sslmode=disable rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ diff --git a/cmd/barista/main.go b/cmd/barista/main.go index cf75677..28c4b54 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -52,15 +52,15 @@ func main() { cancel() } - // defer a.AMQPConn.Close() - // defer a.PG.Close() + defer a.AMQPConn.Close() + defer a.PG.Close() a.CounterOrderPub.Configure( pkgPublisher.ExchangeName("counter-order-exchange"), pkgPublisher.BindingKey("counter-order-routing-key"), pkgPublisher.MessageTypeName("barista-order-updated"), ) - // defer a.CounterOrderPub.CloseChan() + defer a.CounterOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("barista-order-exchange"), diff --git a/cmd/counter/config.yml b/cmd/counter/config.yml index 47b1384..071c035 100755 --- a/cmd/counter/config.yml +++ b/cmd/counter/config.yml @@ -8,7 +8,7 @@ http: postgres: pool_max: 2 - dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + dsn_url: host=127.0.0.1 user=postgres password=P@ssw0rd dbname=postgres sslmode=disable rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 06aa0be..5d73955 100755 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -14,6 +14,7 @@ import ( "github.com/thangchung/go-coffeeshop/pkg/logger" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" + "go.uber.org/automaxprocs/maxprocs" "golang.org/x/exp/slog" "google.golang.org/grpc" @@ -25,12 +26,12 @@ import ( func main() { // set GOMAXPROCS - // _, err := maxprocs.Set() - // if err != nil { - // slog.Error("failed set max procs", err) - // } + _, err := maxprocs.Set() + if err != nil { + slog.Error("failed set max procs", err) + } - ctx, _ := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(context.Background()) cfg, err := config.NewConfig() if err != nil { @@ -54,45 +55,7 @@ func main() { <-ctx.Done() }() - a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL), server) - if err != nil { - slog.Error("failed init app", err) - // cancel() - <-ctx.Done() - } - - defer a.AMQPConn.Close() - defer a.PG.Close() - - a.BaristaOrderPub.Configure( - pkgPublisher.ExchangeName("barista-order-exchange"), - pkgPublisher.BindingKey("barista-order-routing-key"), - pkgPublisher.MessageTypeName("barista-order-created"), - ) - defer a.BaristaOrderPub.CloseChan() - - a.KitchenOrderPub.Configure( - pkgPublisher.ExchangeName("kitchen-order-exchange"), - pkgPublisher.BindingKey("kitchen-order-routing-key"), - pkgPublisher.MessageTypeName("kitchen-order-created"), - ) - defer a.KitchenOrderPub.CloseChan() - - a.Consumer.Configure( - pkgConsumer.ExchangeName("counter-order-exchange"), - pkgConsumer.QueueName("counter-order-queue"), - pkgConsumer.BindingKey("counter-order-routing-key"), - pkgConsumer.ConsumerTag("counter-order-consumer"), - ) - - go func() { - err1 := a.Consumer.StartConsumer(a.Worker) - if err1 != nil { - slog.Error("failed to start Consumer", err1) - // cancel() - <-ctx.Done() - } - }() + prepareApp(ctx, cancel, cfg, server) // gRPC Server. address := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) @@ -101,7 +64,7 @@ func main() { l, err := net.Listen(network, address) if err != nil { slog.Error("failed to listen to address", err, "network", network, "address", address) - // cancel() + cancel() <-ctx.Done() } @@ -117,7 +80,7 @@ func main() { err = server.Serve(l) if err != nil { slog.Error("failed start gRPC server", err, "network", network, "address", address) - // cancel() + cancel() <-ctx.Done() } @@ -131,3 +94,45 @@ func main() { slog.Info("ctx.Done", done) } } + +func prepareApp(ctx context.Context, cancel context.CancelFunc, cfg *config.Config, server *grpc.Server) { + a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL), server) + if err != nil { + slog.Error("failed init app", err) + cancel() + <-ctx.Done() + } + + // defer a.AMQPConn.Close() + // defer a.PG.Close() + + a.BaristaOrderPub.Configure( + pkgPublisher.ExchangeName("barista-order-exchange"), + pkgPublisher.BindingKey("barista-order-routing-key"), + pkgPublisher.MessageTypeName("barista-order-created"), + ) + // defer a.BaristaOrderPub.CloseChan() + + a.KitchenOrderPub.Configure( + pkgPublisher.ExchangeName("kitchen-order-exchange"), + pkgPublisher.BindingKey("kitchen-order-routing-key"), + pkgPublisher.MessageTypeName("kitchen-order-created"), + ) + // defer a.KitchenOrderPub.CloseChan() + + a.Consumer.Configure( + pkgConsumer.ExchangeName("counter-order-exchange"), + pkgConsumer.QueueName("counter-order-queue"), + pkgConsumer.BindingKey("counter-order-routing-key"), + pkgConsumer.ConsumerTag("counter-order-consumer"), + ) + + go func() { + err1 := a.Consumer.StartConsumer(a.Worker) + if err1 != nil { + slog.Error("failed to start Consumer", err1) + cancel() + <-ctx.Done() + } + }() +} diff --git a/cmd/kitchen/config.yml b/cmd/kitchen/config.yml index e2dedf0..9576b77 100755 --- a/cmd/kitchen/config.yml +++ b/cmd/kitchen/config.yml @@ -8,7 +8,7 @@ http: postgres: pool_max: 2 - dsn_url: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + dsn_url: host=127.0.0.1 user=postgres password=P@ssw0rd dbname=postgres sslmode=disable rabbitmq: url: amqp://guest:guest@127.0.0.1:5672/ diff --git a/cmd/kitchen/main.go b/cmd/kitchen/main.go index 39a5a66..029feaa 100755 --- a/cmd/kitchen/main.go +++ b/cmd/kitchen/main.go @@ -52,15 +52,15 @@ func main() { cancel() } - // defer a.AMQPConn.Close() - // defer a.PG.Close() + defer a.AMQPConn.Close() + defer a.PG.Close() a.CounterOrderPub.Configure( pkgPublisher.ExchangeName("counter-order-exchange"), pkgPublisher.BindingKey("counter-order-routing-key"), pkgPublisher.MessageTypeName("kitchen-order-updated"), ) - // defer a.CounterOrderPub.CloseChan() + defer a.CounterOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("kitchen-order-exchange"), diff --git a/docker-compose.yaml b/docker-compose.yaml index 960a751..c6f8e62 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,7 +70,7 @@ services: APP_NAME: 'counter-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + PG_DSN_URL: host=postgres user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ PRODUCT_CLIENT_URL: product:5001 ports: @@ -92,7 +92,7 @@ services: APP_NAME: 'barista-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + PG_DSN_URL: host=postgres user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ depends_on: postgres: @@ -111,7 +111,7 @@ services: APP_NAME: 'kitchen-service in docker' IN_DOCKER: "true" PG_URL: postgres://postgres:P@ssw0rd@postgres:5432/postgres - PG_DSN_URL: user=postgres password=P@ssw0rd dbname=postgres sslmode=disable + PG_DSN_URL: host=postgres user=postgres password=P@ssw0rd dbname=postgres sslmode=disable RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ depends_on: postgres: diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index a9e67ac..4d36352 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -12,18 +12,21 @@ import ( sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" + pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" "github.com/thangchung/go-coffeeshop/proto/gen" "golang.org/x/exp/slog" ) type App struct { - Cfg *config.Config - PG postgres.DBEngine - AMQPConn *amqp.Connection + Cfg *config.Config + PG postgres.DBEngine + AMQPConn *amqp.Connection + Publisher pkgPublisher.EventPublisher + Consumer pkgConsumer.EventConsumer + + BaristaOrderPub sharedevents.BaristaEventPublisher + KitchenOrderPub sharedevents.KitchenEventPublisher - BaristaOrderPub ordersUC.BaristaEventPublisher - KitchenOrderPub ordersUC.KitchenEventPublisher - Consumer pkgConsumer.EventConsumer ProductDomainSvc domain.ProductDomainService UC ordersUC.UseCase CounterGRPCServer gen.CounterServiceServer @@ -36,10 +39,11 @@ func New( cfg *config.Config, pg postgres.DBEngine, amqpConn *amqp.Connection, - - baristaOrderPub ordersUC.BaristaEventPublisher, - kitchenOrderPub ordersUC.KitchenEventPublisher, + publisher pkgPublisher.EventPublisher, consumer pkgConsumer.EventConsumer, + + baristaOrderPub sharedevents.BaristaEventPublisher, + kitchenOrderPub sharedevents.KitchenEventPublisher, productDomainSvc domain.ProductDomainService, uc ordersUC.UseCase, counterGRPCServer gen.CounterServiceServer, @@ -50,12 +54,14 @@ func New( return &App{ Cfg: cfg, - PG: pg, - AMQPConn: amqpConn, + PG: pg, + AMQPConn: amqpConn, + Publisher: publisher, + Consumer: consumer, + + BaristaOrderPub: baristaOrderPub, + KitchenOrderPub: kitchenOrderPub, - BaristaOrderPub: baristaOrderPub, - KitchenOrderPub: kitchenOrderPub, - Consumer: consumer, ProductDomainSvc: productDomainSvc, UC: uc, CounterGRPCServer: counterGRPCServer, diff --git a/internal/counter/app/wire.go b/internal/counter/app/wire.go index 8855e00..9aba0e9 100644 --- a/internal/counter/app/wire.go +++ b/internal/counter/app/wire.go @@ -5,13 +5,13 @@ package app import ( "github.com/google/wire" - "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app/router" "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" infrasGRPC "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" ordersUC "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" @@ -19,16 +19,6 @@ import ( "google.golang.org/grpc" ) -func baristaEventPublisher(amqpConn *amqp091.Connection) ordersUC.BaristaEventPublisher { - pub, _ := pkgPublisher.NewPublisher(amqpConn) - return (ordersUC.BaristaEventPublisher)(pub) -} - -func kitchenEventPublisher(amqpConn *amqp091.Connection) ordersUC.KitchenEventPublisher { - pub, _ := pkgPublisher.NewPublisher(amqpConn) - return (ordersUC.KitchenEventPublisher)(pub) -} - func InitApp( cfg *config.Config, dbConnStr postgres.DBConnString, @@ -39,13 +29,15 @@ func InitApp( New, postgres.DBEngineSet, rabbitmq.RabbitMQSet, + pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, + + event.BaristaEventPublisherSet, + event.KitchenEventPublisherSet, infrasGRPC.ProductGRPCClientSet, router.CounterGRPCServerSet, repo.RepositorySet, ordersUC.UseCaseSet, - baristaEventPublisher, - kitchenEventPublisher, handlers.BaristaOrderUpdatedEventHandlerSet, handlers.KitchenOrderUpdatedEventHandlerSet, )) diff --git a/internal/counter/app/wire_gen.go b/internal/counter/app/wire_gen.go index 4f114de..731cc24 100644 --- a/internal/counter/app/wire_gen.go +++ b/internal/counter/app/wire_gen.go @@ -7,13 +7,13 @@ package app import ( - "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app/router" "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" grpc2 "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" @@ -32,33 +32,25 @@ func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnSt if err != nil { return nil, err } - ordersBaristaEventPublisher := baristaEventPublisher(connection) - ordersKitchenEventPublisher := kitchenEventPublisher(connection) + eventPublisher, err := publisher.NewPublisher(connection) + if err != nil { + return nil, err + } eventConsumer, err := consumer.NewConsumer(connection) if err != nil { return nil, err } + baristaEventPublisher := event.NewBaristaEventPublisher(eventPublisher) + kitchenEventPublisher := event.NewKitchenEventPublisher(eventPublisher) productDomainService, err := grpc2.NewGRPCProductClient(cfg) if err != nil { return nil, err } orderRepo := repo.NewOrderRepo(dbEngine) - useCase := orders.NewUseCase(orderRepo, productDomainService, ordersBaristaEventPublisher, ordersKitchenEventPublisher) + useCase := orders.NewUseCase(orderRepo, productDomainService, baristaEventPublisher, kitchenEventPublisher) counterServiceServer := router.NewGRPCCounterServer(grpcServer, cfg, useCase) baristaOrderUpdatedEventHandler := handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) kitchenOrderUpdatedEventHandler := handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) - app := New(cfg, dbEngine, connection, ordersBaristaEventPublisher, ordersKitchenEventPublisher, eventConsumer, productDomainService, useCase, counterServiceServer, baristaOrderUpdatedEventHandler, kitchenOrderUpdatedEventHandler) + app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, baristaEventPublisher, kitchenEventPublisher, productDomainService, useCase, counterServiceServer, baristaOrderUpdatedEventHandler, kitchenOrderUpdatedEventHandler) return app, nil } - -// wire.go: - -func baristaEventPublisher(amqpConn *amqp091.Connection) orders.BaristaEventPublisher { - pub, _ := publisher.NewPublisher(amqpConn) - return (orders.BaristaEventPublisher)(pub) -} - -func kitchenEventPublisher(amqpConn *amqp091.Connection) orders.KitchenEventPublisher { - pub, _ := publisher.NewPublisher(amqpConn) - return (orders.KitchenEventPublisher)(pub) -} diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go index 939bdb6..9c2abde 100644 --- a/internal/counter/usecases/orders/service.go +++ b/internal/counter/usecases/orders/service.go @@ -8,20 +8,15 @@ import ( "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" + "github.com/thangchung/go-coffeeshop/internal/pkg/event" "golang.org/x/exp/slog" ) -type ( - BaristaEventPublisher pkgPublisher.EventPublisher - KitchenEventPublisher pkgPublisher.EventPublisher -) - type usecase struct { orderRepo domain.OrderRepo productDomainSvc domain.ProductDomainService - baristaEventPub BaristaEventPublisher - kitchenEventPub KitchenEventPublisher + baristaEventPub event.BaristaEventPublisher + kitchenEventPub event.KitchenEventPublisher } var _ UseCase = (*usecase)(nil) @@ -31,8 +26,8 @@ var UseCaseSet = wire.NewSet(NewUseCase) func NewUseCase( orderRepo domain.OrderRepo, productDomainSvc domain.ProductDomainService, - baristaEventPub BaristaEventPublisher, - kitchenEventPub KitchenEventPublisher, + baristaEventPub event.BaristaEventPublisher, + kitchenEventPub event.KitchenEventPublisher, ) UseCase { return &usecase{ orderRepo: orderRepo, diff --git a/internal/pkg/event/publishers.go b/internal/pkg/event/publishers.go index 6f8e914..6813a84 100644 --- a/internal/pkg/event/publishers.go +++ b/internal/pkg/event/publishers.go @@ -1,7 +1,69 @@ package event -type BaristaEventPublisher struct { +import ( + "context" + + "github.com/google/wire" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" +) + +var ( + BaristaEventPublisherSet = wire.NewSet(NewBaristaEventPublisher) + KitchenEventPublisherSet = wire.NewSet(NewKitchenEventPublisher) +) + +type BaristaEventPublisher interface { + Configure(...publisher.Option) + CloseChan() + Publish(context.Context, []byte, string) error +} + +type KitchenEventPublisher interface { + Configure(...publisher.Option) + CloseChan() + Publish(context.Context, []byte, string) error +} + +type baristaEventPublisher struct { + pub publisher.EventPublisher +} + +func NewBaristaEventPublisher(pub publisher.EventPublisher) BaristaEventPublisher { + return &baristaEventPublisher{ + pub: pub, + } +} + +func (p *baristaEventPublisher) Configure(opts ...publisher.Option) { + p.pub.Configure(opts...) +} + +func (p *baristaEventPublisher) CloseChan() { + p.pub.CloseChan() +} + +func (p *baristaEventPublisher) Publish(ctx context.Context, body []byte, contentType string) error { + return p.pub.Publish(ctx, body, contentType) +} + +type kitchenEventPublisher struct { + pub publisher.EventPublisher +} + +func NewKitchenEventPublisher(pub publisher.EventPublisher) KitchenEventPublisher { + return &kitchenEventPublisher{ + pub: pub, + } +} + +func (p *kitchenEventPublisher) Configure(opts ...publisher.Option) { + p.pub.Configure(opts...) +} + +func (p *kitchenEventPublisher) CloseChan() { + p.pub.CloseChan() } -func (p *BaristaEventPublisher) abc() { +func (p *kitchenEventPublisher) Publish(ctx context.Context, body []byte, contentType string) error { + return p.pub.Publish(ctx, body, contentType) } diff --git a/pkg/rabbitmq/publisher/options.go b/pkg/rabbitmq/publisher/options.go index 61d9cda..665820d 100644 --- a/pkg/rabbitmq/publisher/options.go +++ b/pkg/rabbitmq/publisher/options.go @@ -1,21 +1,21 @@ package publisher -type Option func(*publisher) +type Option func(*Publisher) func ExchangeName(exchangeName string) Option { - return func(p *publisher) { + return func(p *Publisher) { p.exchangeName = exchangeName } } func BindingKey(bindingKey string) Option { - return func(p *publisher) { + return func(p *Publisher) { p.bindingKey = bindingKey } } func MessageTypeName(messageTypeName string) Option { - return func(p *publisher) { + return func(p *Publisher) { p.messageTypeName = messageTypeName } } diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index cc46a49..b2463b5 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -21,14 +21,14 @@ const ( _messageTypeName = "ordered" ) -type publisher struct { +type Publisher struct { exchangeName, bindingKey string messageTypeName string amqpChan *amqp.Channel amqpConn *amqp.Connection } -var _ EventPublisher = (*publisher)(nil) +var _ EventPublisher = (*Publisher)(nil) var EventPublisherSet = wire.NewSet(NewPublisher) @@ -39,7 +39,7 @@ func NewPublisher(amqpConn *amqp.Connection) (EventPublisher, error) { } defer ch.Close() - pub := &publisher{ + pub := &Publisher{ amqpConn: amqpConn, amqpChan: ch, exchangeName: _exchangeName, @@ -50,7 +50,7 @@ func NewPublisher(amqpConn *amqp.Connection) (EventPublisher, error) { return pub, nil } -func (p *publisher) Configure(opts ...Option) EventPublisher { +func (p *Publisher) Configure(opts ...Option) EventPublisher { for _, opt := range opts { opt(p) } @@ -59,13 +59,13 @@ func (p *publisher) Configure(opts ...Option) EventPublisher { } // CloseChan Close messages chan. -func (p *publisher) CloseChan() { +func (p *Publisher) CloseChan() { if err := p.amqpChan.Close(); err != nil { slog.Error("failed to close chan", err) } } -func (p *publisher) PublishEvents(ctx context.Context, events []any) error { +func (p *Publisher) PublishEvents(ctx context.Context, events []any) error { for _, e := range events { b, err := json.Marshal(e) if err != nil { @@ -82,7 +82,7 @@ func (p *publisher) PublishEvents(ctx context.Context, events []any) error { } // Publish message. -func (p *publisher) Publish(ctx context.Context, body []byte, contentType string) error { +func (p *Publisher) Publish(ctx context.Context, body []byte, contentType string) error { ch, err := p.amqpConn.Channel() if err != nil { return errors.Wrap(err, "CreateChannel") From c565ee68ee00f2fe39cf32706386b7c119dd7034 Mon Sep 17 00:00:00 2001 From: thangchung Date: Wed, 28 Dec 2022 15:15:36 +0700 Subject: [PATCH 16/16] finish refactor code --- Makefile | 2 +- README.md | 17 +- cmd/barista/main.go | 8 +- cmd/counter/main.go | 17 +- cmd/kitchen/main.go | 8 +- docs/clean_ddd.svg | 16 + docs/diagrams/clean_ddd.excalidraw | 4085 +++++++++++++++++ internal/barista/app/wire.go | 23 +- internal/barista/app/wire_gen.go | 43 +- internal/counter/app/app.go | 14 +- internal/counter/app/wire.go | 29 +- internal/counter/app/wire_gen.go | 53 +- internal/counter/domain/interfaces.go | 9 - .../events/handlers/barista_order_updated.go | 6 +- .../events/handlers/kitchen_order_updated.go | 6 +- .../event => counter/infras}/publishers.go | 42 +- .../counter/infras/repo/orders_postgres.go | 5 +- .../counter/usecases/orders/interfaces.go | 19 + internal/counter/usecases/orders/service.go | 19 +- internal/kitchen/app/wire.go | 23 +- internal/kitchen/app/wire_gen.go | 43 +- pkg/postgres/postgres.go | 3 - pkg/rabbitmq/publisher/interfaces.go | 1 - pkg/rabbitmq/publisher/options.go | 8 +- pkg/rabbitmq/publisher/publisher.go | 19 +- pkg/rabbitmq/rabbitmq.go | 3 - 26 files changed, 4367 insertions(+), 154 deletions(-) create mode 100644 docs/clean_ddd.svg create mode 100644 docs/diagrams/clean_ddd.excalidraw rename internal/{pkg/event => counter/infras}/publishers.go (54%) diff --git a/Makefile b/Makefile index 5bf9dee..b116e74 100755 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ docker-compose-build: wire: cd internal/barista/app && wire && cd - && \ cd internal/counter/app && wire && cd - && \ - cd internal/barista/app && wire && cd - && \ + cd internal/kitchen/app && wire && cd - && \ cd internal/product/app && wire && cd - .PHONY: wire diff --git a/README.md b/README.md index c80fc3f..4796c5a 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # go-coffeeshop -A event-driven microservices coffee shop application has been written in Golang, and deployment using Nomad, Consul Connect, Vault, and Terraform. +An event-driven microservices coffee shop application has been written in Golang and deployed using Nomad, Consul Connect, Vault, and Terraform. -Other versions can be found at: +Other versions in .NET/C# can be found at: - [.NET CoffeeShop with Microservices approach](https://github.com/thangchung/coffeeshop-on-nomad) - [.NET CoffeeShop with Modular Monolith approach](https://github.com/thangchung/coffeeshop-modular) @@ -23,6 +23,7 @@ Other versions can be found at: - [sirupsen/logrus](https://github.com/sirupsen/logrus) - [samber/lo](https://github.com/samber/lo) - [automaxprocs/maxprocs](go.uber.org/automaxprocs/maxprocs) + - [stretchr/testify](github.com/stretchr/testify) - golang/glog - google/uuid - google.golang.org/genproto @@ -45,16 +46,16 @@ No. | Service | URI 1 | grpc-gateway | [http://localhost:5000](http://localhost:5000) 2 | product service | [http://localhost:5001](http://localhost:5001) 3 | counter service | [http://localhost:5002](http://localhost:5002) -4 | barista service | -5 | kitchen service | +4 | barista service | worker only +5 | kitchen service | worker only 6 | web | [http://localhost:8888](http://localhost:8888) ## Starting project -Jump into `.devcontainer`, then +Jump into [`.devcontainer`](https://code.visualstudio.com/docs/devcontainers/containers), then ```bash -> make compose +> make docker-compose ``` From `vscode` => Press F1 => Type `Simple Browser View` => Choose it and enter [http://localhost:8888](http://localhost:8888). @@ -82,6 +83,10 @@ The details of how to run it can be find at [deployment with Nomad, Consult Conn ## Development +### Clean Domain-driven Design + +![clean_ddd](docs/clean_ddd.svg) + ### Generate dependency injection instances with wire ```bash diff --git a/cmd/barista/main.go b/cmd/barista/main.go index 28c4b54..55a5e8f 100755 --- a/cmd/barista/main.go +++ b/cmd/barista/main.go @@ -46,21 +46,17 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) + a, cleanup, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) if err != nil { slog.Error("failed init app", err) cancel() } - defer a.AMQPConn.Close() - defer a.PG.Close() - a.CounterOrderPub.Configure( pkgPublisher.ExchangeName("counter-order-exchange"), pkgPublisher.BindingKey("counter-order-routing-key"), pkgPublisher.MessageTypeName("barista-order-updated"), ) - defer a.CounterOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("barista-order-exchange"), @@ -84,8 +80,10 @@ func main() { select { case v := <-quit: + cleanup() slog.Info("signal.Notify", v) case done := <-ctx.Done(): + cleanup() slog.Info("ctx.Done", done) } } diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 5d73955..6ae701f 100755 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -55,7 +55,7 @@ func main() { <-ctx.Done() }() - prepareApp(ctx, cancel, cfg, server) + cleanup := prepareApp(ctx, cancel, cfg, server) // gRPC Server. address := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port) @@ -89,36 +89,33 @@ func main() { select { case v := <-quit: + cleanup() slog.Info("signal.Notify", v) case done := <-ctx.Done(): - slog.Info("ctx.Done", done) + cleanup() + slog.Info("ctx.Done", "app done", done) } } -func prepareApp(ctx context.Context, cancel context.CancelFunc, cfg *config.Config, server *grpc.Server) { - a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL), server) +func prepareApp(ctx context.Context, cancel context.CancelFunc, cfg *config.Config, server *grpc.Server) func() { + a, cleanup, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL), server) if err != nil { slog.Error("failed init app", err) cancel() <-ctx.Done() } - // defer a.AMQPConn.Close() - // defer a.PG.Close() - a.BaristaOrderPub.Configure( pkgPublisher.ExchangeName("barista-order-exchange"), pkgPublisher.BindingKey("barista-order-routing-key"), pkgPublisher.MessageTypeName("barista-order-created"), ) - // defer a.BaristaOrderPub.CloseChan() a.KitchenOrderPub.Configure( pkgPublisher.ExchangeName("kitchen-order-exchange"), pkgPublisher.BindingKey("kitchen-order-routing-key"), pkgPublisher.MessageTypeName("kitchen-order-created"), ) - // defer a.KitchenOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("counter-order-exchange"), @@ -135,4 +132,6 @@ func prepareApp(ctx context.Context, cancel context.CancelFunc, cfg *config.Conf <-ctx.Done() } }() + + return cleanup } diff --git a/cmd/kitchen/main.go b/cmd/kitchen/main.go index 029feaa..5d3fc4f 100755 --- a/cmd/kitchen/main.go +++ b/cmd/kitchen/main.go @@ -46,21 +46,17 @@ func main() { // integrate Logrus with the slog logger slog.New(logger.NewLogrusHandler(logrus.StandardLogger())) - a, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) + a, cleanup, err := app.InitApp(cfg, postgres.DBConnString(cfg.PG.DsnURL), rabbitmq.RabbitMQConnStr(cfg.RabbitMQ.URL)) if err != nil { slog.Error("failed init app", err) cancel() } - defer a.AMQPConn.Close() - defer a.PG.Close() - a.CounterOrderPub.Configure( pkgPublisher.ExchangeName("counter-order-exchange"), pkgPublisher.BindingKey("counter-order-routing-key"), pkgPublisher.MessageTypeName("kitchen-order-updated"), ) - defer a.CounterOrderPub.CloseChan() a.Consumer.Configure( pkgConsumer.ExchangeName("kitchen-order-exchange"), @@ -84,8 +80,10 @@ func main() { select { case v := <-quit: + cleanup() slog.Info("signal.Notify", v) case done := <-ctx.Done(): + cleanup() slog.Info("ctx.Done", done) } } diff --git a/docs/clean_ddd.svg b/docs/clean_ddd.svg new file mode 100644 index 0000000..b105fb4 --- /dev/null +++ b/docs/clean_ddd.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO19aVMqa7Lu9/5cdTAwMTXG7ohcdTAwMWL3RjTV7zz0NydUnFHQ5elcdTAwMTNcdTAwMDaTgIwyKHqi//vJLJdSUFx1MDAwM1x1MDAxNCBcdTAwMTbeRXSvvbdIrZeqzCefnP/nb1tbf1xyXruVv/619VdlVCo06+Ve4eWvf+DPnyu9fr3ThreY+9/9zrBXcn+zNlx1MDAxOHT7//rnP8efcEqd1vunKs1Kq9JcdTAwMWX04ff+XHUwMDBi/ntr63/cP+Gdelx1MDAxOT+bNiet3EFnu5lLvVxcnlx1MDAxZZxcXO/vpV7dj7q/9HGYXqU0KLSrzcr4rVx1MDAxMZ5EModqram2RFKrhPp8+1x1MDAxNd5OXHUwMDE56lxiJoWCd1x059R8vvtSL1x1MDAwZmrwXHUwMDFilFx1MDAxYvL5w1qlXq1ccvCnhFx1MDAxOYfgS1x1MDAxMqOY4uzzd95cdTAwMGbxr63xp/qDXqdR2e00Oz086d+J0Vx1MDAwZrI4PmexUGpUe51hu/z5O4Neod3vXHUwMDE2enBjxr/3UG82r1x1MDAwNq/N91taKNWGPc+3ff9bbj6OPfXzz8/1O/BcdTAwMDDGn4K/tlprV/r9ic90uoVSffDqftHx98Azdo/K7pP6b+9cdTAwMTXa5d9XaFx1MDAwZpvN8V9cXKngXHUwMDAzpEIqaYnV41x1MDAwYnlcdTAwMDSF6+mfnnXartBQTiQnyozvbL2/XHUwMDA3wjJwr/pQaPYr4zuIZ9hcdTAwMWZcdTAwMGLSxDmG3XLh/UNUacaotEJoNT5Ns95uTH+m2Sk1xn+P+9P//CNIOo9vdtTxLlHD4/rr9mMl01x1MDAxOGUuTueWTsG5IylcYlx1MDAxM6NaUGW5mJROalx1MDAxZKlcZueCXHUwMDFhaYy2zCeezChcdTAwMDe+XHKTXHUwMDE0rsOFksovrJJruFxmNVZcdTAwMTBiiKUyhqxywVx1MDAwYoJEyurfS5WyKFx1MDAxN75XTtnSYkq0lozDc1xiXHUwMDEwU0NZqJhqJbm1ipKVy6lcIoxbuaycRojg+Fx1MDAwYipcdTAwMGbyTX9BIyyeR87zXHUwMDA135XiLtus1Vq56vGtVlxyc3V4ffZLplx1MDAxMyNcdTAwMWZ+oVx1MDAxZrlww5TDlTZKU0ZcdTAwMTVcdTAwMWJf5F1cdTAwMTO1Y+A5XHUwMDFiXHUwMDBiXHUwMDA2xVx1MDAwMijZlWrMpzYr7ShqXHUwMDE5qDFcdTAwMDFJNNyvzJYrR8Pjolx1MDAxNt5X0qPMn3grLHxcdTAwMThQblGNmJbS//r9xlioPM+6keY9e5pJXHUwMDFmpJ9eS88vr+b+/mr8NCcksNDrdV7++nznP7//7b9DNVx1MDAwMFxmq4I/1qJcdTAwMDGShVtcIsKZVpp70HmWXHUwMDA2dLdTR/a82Fx1MDAxY508Zl+P8uogNUg1kq5cdTAwMDFghlxcriSkpIpzj/R9XHUwMDE4I6HBKFtcdTAwMGVcbmAlY/xrdMBcYodcdTAwMWJKjLGGXHUwMDBifFx1MDAwNSiBYFx1MDAwZbHA51xm2EUu2bRcdTAwMGVIZrhcdTAwMDVAN6tSgWigNsxIz9/1lWLKjc8+fYipgDsmuNXzSykt7Tfvxc2wvLdDn1JvzcL+6DaTcCnVXHUwMDAyxENcdTAwMDGN4YZxNS2ixmXzoMtcdTAwMDRwXHUwMDAzhFR9jYgq4khNXHUwMDA1yJ9klFx1MDAxMVx1MDAxNiGimlx1MDAwYjhcclx1MDAxOFx1MDAxNj9OXHUwMDEzK1xmXHUwMDA1XHUwMDAxWo+QcmG1WVpI31x1MDAwNadZOtCkXlx1MDAxOb3aXmrfXFyrs8yv/eLcrFx1MDAxN3isXHUwMDAzyK6loOB0WTLpkjHCXHUwMDFko+BcdMPtXHUwMDExXHUwMDA0sDfAJ5PUf8NVlOT4XFww9/VcdTAwMTNdsLGJ/nhcZvz3T/7jQ0mhtVx1MDAxMppcdTAwMDY5ZtZDPKdwxjJcdTAwMDPOXG6g3lx1MDAwMoQ3hEp8XGLMoDJcdTAwMWFMcod3eeOHz0eD51x1MDAxY9/e3z4oZ3+97b5td089TOJcdTAwMWbBl33/cOX6fDdDuvq00MmUXHUwMDA3jePLY7Nn52MokdfdfWxcdTAwMTRNP5NtPFTtYHu7uPPYsOVcdTAwMTUxXHUwMDFmyanwXCLDUtpcdTAwMWF893zaOnHzXUU1XHUwMDA0uWa4opqZiur5XG5jd/SPooY5oXAjXHUwMDE1XHUwMDE3Rlx1MDAwNvmgyoRcdTAwMTJU8Fx1MDAxMlxyYVSv3lx1MDAwNY0thmOpQmmCe1jNXuxuXVV6cOR/t/8v3IBBpff/PM+p01x1MDAxZVxc1d8qk6ZcdTAwMWZ/mi606s3XiVuN19xu1qt4O/4qwVx1MDAxN6j0JjjSoF4qND9/oVUvl72mp1joV+BcdTAwMGK4VGnMXHUwMDFiSvBXXHUwMDE14Ke9o3ksW6dXr9bbheZ15Ldbwlx1MDAwYjGhsFx1MDAwYi5cdTAwMWQjVFxuNU9A7F3xR7mLymHz4fpcdTAwMTf9NTiqX+d331x1MDAwNrunydGRYIIntHWoXHUwMDAyXFxcdTAwMDFcdTAwMGVHNTV6fEdcXNRR1iGASYQyyZmUTE6dbHnsiKRcdTAwMTlcdTAwMWWz91x1MDAxOTlcdTAwMDJcbkiMN0CyXHUwMDFhM71cIuOZf2xVslx1MDAwM9XI3b9cdTAwMWRayauNt/xgf14jN7jhh1x1MDAwN/e5XHUwMDAz0auOXHUwMDFlXHUwMDBmzlx1MDAwNFx1MDAxM+2n01x1MDAxNVx1MDAxOM907/z6ZWe/LIrb7fxB8dFcdTAwMGXZydNcbq7bP8zI51x1MDAwM/JEc0d3XX139JLmXHUwMDA3Ylx1MDAwNdf9Klx1MDAxMpG6ze+MntPV59xB4fTxsFPNmMzge8lJ5HWH/auKkpdcdTAwMTfV/XIp1b6p7PVM/WVcdTAwMDXX3b/MX/Vyb716vVxc2L97a6SzN61asshUME/y5DJkOHZcdTAwMGIjXGZcdTAwMTNwjrmxO1hrXHUwMDEzjt2YaVBcdTAwMWXsXHUwMDFlY6VcdTAwMGLdRjpyPdDNXHUwMDAzPHLmXHUwMDBmlVx1MDAxYW6VXHUwMDE1anWh0q9iU7Fpkqso3W4g+/HE+pdjVFPcaVx1MDAwNt2Y5k54vMWZklx1MDAxNTZU20D+NLHEzq9tXHUwMDAzWXltPV7RelHe3r4+3Fx1MDAxZjJze5t0bVx1MDAwM/dcdTAwMWT1SWFulVx1MDAwMDn0OFxi7lx1MDAwNcS7Mn6+wnNcdTAwMTbrYkqSXHUwMDFhzE6YOCGvNTKlYebXQa6XvslnRa5Oni5PmMzXvp3RNFxu5eJxNa1Pju6OsqmdQ3VEhptlXHUwMDE5XHUwMDE1XHUwMDBi1VVcdTAwMDHiS6T1lGXMUtXgh5RwVZVcXE6oqp3UVCVcXJdnXHKayqVfUVx1MDAwM1xmI7OUiYmM108yjKVWea2GcYZ1mTaMeLzFXHIjXHUwMDE1nsScL5fPLLOExeChXHUwMDA35y9cdTAwMDe7b+I+27HH+jyfXHUwMDFllcVeKunqpq1wQNGI5kwwX1xigXPjXHUwMDAw6lCU8qm45jdcdTAwMTlGiXFUIM9cdI0gPMgjXCJp52aY70pisy+sv//W/G7PuaTb4r58dVF/0mckf6dHd+XznVx1MDAxNVxc9848pE53T7PVbX55Y0T+slBcdTAwMWbWN8veUkrCy3lcdTAwMDSXzMBJ5i/nXHR+/Fx0h1x1MDAwMEOpw8IhQEqHrlx0XHUwMDAyPFYlyuRcdTAwMTLGgCeRXHUwMDFm6otedPqDaq/S39tZq+WdYb2mLa/nlEtcdTAwMThgQnxcdTAwMTnVz0yN1Fxmq0Xmt78ne79kiV5d7r6yvbY+lY+l4stJ0pVPW4LqRbhSjGKpxoTyKWtcdTAwMWRDMFxuRFx1MDAxOSOahNdcdTAwMTGtyfwqxFx0wnVcdTAwMWPVW6P5fdzeyz1m81x1MDAwN1lCr8TLTj8varuNec3ZaW27W6G8d3exf9JjYr+Vl/dsXHUwMDA1ZrJ1MSpcdTAwMWQ+5Fx1MDAwNXtsXHUwMDFkVYdcdTAwMGbHx4XqUWOzzKS3tnxKT0FqXHUwMDE11pfFXGIhXHUwMDA1P6WEK6qhNkJRNVVcdTAwMGVfi6LagEpXv43UXFxLbSlfOM2eaFx1MDAxM5ktXHUwMDE0i/XB6eVaXHLkXGbzMm0gP8+4hHn0ZkGm1Vx1MDAwZYRNwd1cdTAwMTfz57jvXHUwMDFi5cv+sJo5v7jNXZZuOoXexfU315rPY1x1MDAxZrE40FoqpCTa213gqp01SF2psJIrypn5MrVcdTAwMGK0j55Y3Gfcllx1MDAxOa4xq5NMXHUwMDAzubN3k+22XG4krXLtfJrd9lFcdTAwMTK+O6O5hIGMvO7V04NiN1x1MDAwZsOOuFx1MDAxOGZO9vKpp8tjvYLrXHUwMDBlXHUwMDA2J/ms3sml01x1MDAxNZt93DMvh6N549dJMeiU8FDmLaRcdTAwMTZcblxcrPlcdTAwMDPNwVKVcGQxlEQgiyFcdTAwMDbjYutAXHUwMDE2Ol+gXHUwMDE5WzxcdTAwMDSAy5oq9dds0t3ysN1mfeJcdTAwMTatwarPMIqBVWy/j7m4YTc0vINGc1x1MDAwMf/nMXrItjv109a5fuvc1S9L/LBBS6bYS7r2UVwirWOVkpZcdTAwMGImhJ5M8zDNXHUwMDFjyTlYUlx1MDAwMjRWMP3tpWuMM6w2jZXmWaNdP7wsXd6P2GXLdliDXHUwMDFkb3eK5Pbuuyugrlx1MDAwYqe1wd7D7Uhf9I/O33auh0/3hVx1MDAxNVxcd5+dN6qnXHUwMDE3jVErN2J7uVa3ZHO7K7guXHUwMDFipXa3XHUwMDBi+nj3sGYr4nr77SUjyVxurnuq24SxX7WTp9eD8kMj32/0d+QqKu1MZ++qOCrcn3Qr+cx1oXFQ380kmDe1+/rh5lnpVOnsrXV+ZHOngs9ZIZlcdTAwMTTeJGyoR0a5slRcdTAwMWIzV3tzlNYmXHUwMDFluY12RChyXHUwMDFi65D1ILdccmjgXHUwMDBm4E2aXHUwMDAwdnNDf2a2INev7Fx1MDAwMkvqr5U0zWBcdTAwMWPTpOnzjIszJlx1MDAxYTBt4EPxlOaYXHUwMDE1jlHtf7Fjs6xz+ktfXHUwMDE1XHUwMDFi9vU8r25rO53E61x1MDAxZJfaseiSgFx1MDAxYiC4r9hfOJaDn2LAe5vM4n1XXHSbXCLCXHUwMDAwXGZ4XHUwMDEyhomiTHdcZlxij+xvn2ffrrv66vFNiden76Y2jdrBaX2013jbZdXKr+ZItOupOalNUkyk0eFcdTAwMTl1bUFG55xUXHUwMDEz9ZSSrqqCcEeEqipOUlqPqno6fSNMpFx1MDAxMkIqrWIl9TbHQu63XHUwMDA3+NzWaVx1MDAxZmfYl2n7+PuES1hHxkKjeZJKRnScXjid1u105qVBXHUwMDFm6+yRjbZvd1lmJ/EqXHUwMDA3XHUwMDFjXHUwMDAwrSNTRkqhfFUsQjqau9hjhFZcdTAwMTHTXHUwMDBlvsQ6evTws6HCcmmFTWqB92nV5PNqqIZcdTAwMGavpXSqVrw8XHUwMDExrYPvdtB/je5ad+VcdTAwMTJcdTAwMTlmzlx1MDAxZvtZUlx1MDAxMFx1MDAwN7ZTWoVjWqrfdFxm2Tk7O7zP3p200+3eQWdcdTAwMDXXzfRvXlx1MDAxZoqpwm7vsL5zlNnvZ1x1MDAxYcfnm2XNbXhB+nvLqeAxoCVYqlx1MDAxMlx1MDAwZi3aRkGLJOuCXHUwMDE2TzF80IyAj1Alw4kvKlaT7eaY863tarVXqcJcdTAwMTn+3c52OsHZXHUwMDAyT9f6Slxy+1xm0zht2H1nXcLE83BcdTAwMDeYMYOBXHUwMDE2Pc+Ykd/t7ntcdTAwMDfVMnssPPFaU1x1MDAxZlx1MDAxZlx1MDAxZdKOTCc+YUe54o6y1sqgbndhXVx1MDAwYs+04kqv3/9ccrDwXHUwMDA0VENcbppU//fMXHUwMDFl11x1MDAwZUrV9NPbZbeW29m1uVxchny3JV7CYkb71YvP3os+L8+ed1gnl1x1MDAxYYmbx7eKOLustVfVcqbAQ6SeePPXlcCHx7RccpOcxMhFXHUwMDA2XHUwMDBiVeKBRbNwYJGErFx1MDAwYlh4QG2f375TqdB5YLFqjDbHwO91WmBxt/af4XD/btfRYD9cdTAwMTRKM6fTrLY/O9o+Ttv5sCMvYe5l+FQxxYCEXHUwMDEyM9dcZqN3rSzI/cw+uWnRX4NcdTAwMDOxnTt/O8ucJb4yXHUwMDFlXGYnwZlaOMvHMOZcdTAwMWTG7NJubVx1MDAxY4rznpnSlnD17fZcdTAwMWVcdTAwMGbKjFwiXHR16K+Lj1x1MDAwN+K6cHVYt/X24XO70i1T8d2Z8SU6yFwir7tExn2mWV5XhVx1MDAxZVx1MDAxN6ExPUolXHUwMDAzf4CR+T3v4MefeFxiMFx1MDAxMudfhkGA4euCXHUwMDAwz52OsMxcdTAwMTJcdTAwMWVcdTAwMGJhRP7MQHq20u3064NO7/W7zPJcZjvmK8FcdTAwMGY88Fx1MDAxMkaZcVx1MDAxYaaSXGZcdTAwMDRRScFijGd4k51cZshcdTAwMDa7LVx1MDAxNtP8IP3rJfdwNUy8SnKwyqFZaMEo1u0xK5TWmntcdTAwMDZSf1fZXHUwMDFls5NzW1x1MDAxM2WT+UN+RFPDq3TjpVjcbvVb3frJ2Vx1MDAxYVLFX+WCJ8V2Wlx1MDAxMaqnnFx1MDAxOC6JNPOrafBDSrya6ohcZrTgXHUwMDA2i7TWoaZsrlwiLcG0lYKIONx5c1xmZ77QXHUwMDFjVrbOi49gXFzWmoeeYWGm7eXEOZcwk1HMXHUwMDE1t3EwSedcdTAwMGZVN65cbvvV4+fyme3dPZN2NndebdwnXf20pVx1MDAxOFAyQFlcdJWKTUWUcCi+hVx1MDAxNzFcdTAwMDJcdTAwMDRRfXsyXHUwMDFhXHUwMDBlIbXVSVx1MDAxZMtafmTZYl1ccqq1l9JcdTAwMWVcdTAwMWReP5RyZ+ffXXz9Vc1w1WqZvlx1MDAxY+Zzz0VT61x1MDAxZOWznXKVZDfL/FLiXHUwMDFmy/1cdTAwMDFcdTAwMDDaaKBkZK6R+FFPP+FcdTAwMDBgmHJoXHUwMDA0XHUwMDAwyDVcdTAwMDGAmctvpZxJQzj9oVx1MDAxM1XcuOzWv9uHhTZYy15wrfRX+awz7JevXHUwMDE2zD3r50mXsMJUhFx1MDAwZtlVVnNDdIxWhWt6KbPZdPVKd7q3Zvvw+IzWVNK1UL83IzAlqHLVcEJcdTAwMGLh89jHoFx0YeDUXHUwMDEyMIHf7a1cdTAwMDJRt4TZpDaP7z+ctnq19CUvP3QsXHUwMDFkXHUwMDE51bp/669h3HhS7JrhXHUwMDExzT+MKE7iNE1cdTAwMDffzYRrlOFuY2aYRlGFPZ3r0Cg119hqTNtSrWKVYGyOXTvpVKte07RcdTAwMDaPcoZcdTAwMTmYtma/T7iEXHUwMDE1IzrUilm425aR+TWuWrvq6Fx1MDAxMt1cdTAwMWK0q1dcdTAwMTf128tqk1x1MDAxZSe+UVx1MDAxYVx1MDAwN3TimFx1MDAwMimZIMbISSap3TZpRiixRFjx7Vx1MDAxNoxcbspw25tIaNHTr6Od+861LJzlVOpA9l5q9+rarmHDR1JMmPZcdTAwMTio6UZcdTAwMDGluGaSz++ZXHUwMDA138yE65Mh775XsD4x4XavrkOh2Fxcwy41Qpz8obW8u532Q7067Fx1MDAxNVx1MDAwNiid67RjM0zBtFx1MDAxZJs86Fx1MDAxMuaMha8mo5ZQt2F6flx1MDAwNbxcdTAwMWali5f88qhsM83sXv940Hvs5ZOugG5cdTAwMDZR4qw8d1BcdTAwMWWdjo1oikl9poFMY8HdXHUwMDE3rmLQXHUwMDAx5fRcdTAwMWVG+1x1MDAxOVx1MDAxY9FESWmTOvOyzO46o6PsXHUwMDBlPTq/uU7fPe68ZU/Nd3e+LDFCXCIpptLacFVVRFxuLnWMXHUwMDEyvOCnlHhV1cYhXHUwMDExqqrXpapBq61cdTAwMDPimDjOm9j1LVxu/pbK2H6l91xcL1W+q1xiZ4bRXHSpjVxyOHSgVs6zIWZcbjomRFx1MDAxNqiSg7vQjVREKK2m6Fx1MDAxZOXuXHUwMDEwV6Io4Vx1MDAxMlx1MDAwYql9okZcdTAwMWOkW8D/wFx1MDAxMlx1MDAxM1x1MDAwMyQxKIJOMXCIY2+F1dxKTzBi9p5WrsRDScfXhe+GiEWMXHUwMDFl81x1MDAxOb1cdTAwMGZaa1x1MDAxNFx1MDAxN0T7e5rc91witnLAPcc8xTyFj+vS5W6nPm3Ax/+2NZZcdTAwMDT3Pz7//b//XHUwMDEx/Nuh4ocvv+CNr+ezm81Cf7DbabXqXHUwMDAz+KJcdTAwMTd4yOkv1Fx1MDAxZlx1MDAxNHqDnXq7XFxvVyefX+X9ts2zlMXFrdKw/647XHUwMDFjqFx1MDAxMtxLeLJ4RjWO3qBcdTAwMDRcdTAwMTW6+Hhx+Fx1MDAwMedWcGWEsVb5ZKTSLs8+VPRcbjXPoVJ4S1x1MDAxNVx1MDAwMVx1MDAxM1x1MDAwNlx1MDAxYS88/u3niYQj/XKKt2ZcdTAwMWLxpVYplKdvXHUwMDFjnND7XHUwMDFlKGz9XfEjmcZcdTAwMTRcXHmjXFwmYlx1MDAwYlx1MDAwNVxiXHUwMDA0XHUwMDE3OkZ3X3Sc4tuBJJhrKE4nltlOztfmxDrCXHUwMDAwYChmJefqXHUwMDBix3EqXG5SrFx1MDAxNVx1MDAxNZRcdTAwMTMhvFMxxm660Vx1MDAwZbxcdTAwMDdHXHUwMDEx0ihvou2DiFglJKHLrqnxw+e6MG1OZJhbXHSxZlx1MDAxNMgk/Fx1MDAwMXfMSjaeYv6phXQxXHUwMDFjiPbkJ4+AXHUwMDBmzKDdN0pyTf1HcOBsSlxuYo3GjSjUj1x1MDAwYnOCanzk+Fx1MDAxYXNcdTAwMTIuzfhK+Vx1MDAwNNlvTlx1MDAxNkQ0TUJcdTAwMDONwNJcctda6vk3XHUwMDA2RE+oTCiiWSGweUFLMb04x6WiXGbepZIpQ1x1MDAxOGFcXIZvXGZYlDCO41x1MDAxY9TBdD/gJ1eggEFcdTAwMWJfhXSEtuA+KWBcdTAwMWNcdTAwMTZ+NWBcdTAwMDMskVhcIrJkLXXiIS16rf0kr1x1MDAxMFx1MDAwNEhcZpbYcmOIp0dkWUyLnq83gWlcdTAwMWPkkFx1MDAxYqXdUbp2Qr9/n4E5hlx1MDAwM83SgFx1MDAwMIIqq/xPYMMwzSo36kAsY0Z6c9D4klx1MDAwZTVcdTAwMDJcYjKOXHUwMDA01ODkqVlXXHUwMDBi11x1MDAwZXz59WJlXGJcdKhcdTAwMWOKkFx1MDAxYZ4lpzTG5rHogXRcdEVI6vrj8LwkjmEnbGqUqMVcdNGSXHUwMDE5XHUwMDBiv8a0t5R85Vx1MDAxMCmYI1x1MDAwNFh9QVx1MDAwM/MyXHUwMDEyrKRiXHUwMDA2vFx1MDAxNpBcdTAwMTDjrSf4jDxcdFx1MDAwMuRVxmr92kB0jIFMQDuAXlBtmcTEXHUwMDA29dMtXHUwMDA2XHUwMDA20Fx1MDAxMJxqXHUwMDBm/MNa41x1MDAwZpHPhZbR09YmzySlkZyBuVUgVZ5ccnLeMympKFx1MDAxM1x1MDAxMuy2XCIgdJtcdTAwMGWXwbKNr5RPrFdcdTAwMDZuXHUwMDExc5KNNYaBJ1x1MDAxZaNSKnKcWHKxTTjgbmCDKiiA4pMrx5k1jpBoUsDkoKH6QmzjXHUwMDBll1x1MDAwMKGSXHUwMDEygX3D44N84lx1MDAxYrhcdTAwMDGop1xcYlx1MDAxOTAorZ/9SYZ1ieans7+58Vxy2J8yXHUwMDAy6Fx1MDAxZtxcdTAwMTXNqWLj7McnlnBHXHUwMDFhXG7eJGFcdTAwMDA3XHUwMDAyoHAxfItcdTAwMWU6NXkmLZnAe6RBm7VSXHUwMDAxLu7mw1mYOOPLJ8grQzRg+KF0jXPUcj6/P1x1MDAxYj0+KamIJilzrDacUK6MJONcdTAwMDDK74FcdTAwMWNcdTAwMThLXHUwMDAwyUNsXHUwMDE3YGXDu1xuK0rrZWJ0gjo4OFdIiqFcdTAwMWNtXHUwMDAyXHUwMDE4XHUwMDFinMGRXG5cdTAwMTBLK44hc+YrVZPcXGJwIHicfTpcdTAwMWKIaHHQXHUwMDAzmFx1MDAwMHg6XG5uXHUwMDE5U4ZcdTAwMDWgXHUwMDA3d4hcdTAwMDbFYmDVXGa1ilx1MDAxYf/Xny94XHUwMDFmOV9ngrJcdTAwMDHIwoO2Ulx1MDAxMlx1MDAwNkej2ncmcN+s0Fx1MDAxY4fgKVx1MDAwYniw+YwtVLzxpawjLJFcdTAwMDRcXFxcoHPgI866XHUwMDFhWCFgXHUwMDFmhlx1MDAxMUAwXHUwMDA1pkhw7+X8erIqxGRUhY9cdTAwMGVluE5aa1x1MDAxYqNcdTAwMGY0st09uZBcdDZZWKxcdTAwMWay6NOPMfGdXHUwMDA0UkdxnFx1MDAxYWHBbnNcdTAwMWKe1VhcdTAwMTYxuXVcZoiUwtC75YRcdTAwMDchplx1MDAwMk1cdTAwMDJJXHUwMDAwR1xm40TaXHUwMDFikfyNmMBW4Zv8dFx1MDAwZTi3P4mIXHQuorKgpErAv8jAlFx1MDAwMjxcXIKj5Vx1MDAwMC5cdTAwMDXR/pTCXFyIXHUwMDE53co9iZjwpDlRXHUwMDE07lx1MDAxM7eAhz+PXHUwMDAzhkozvtxZflx1MDAwNFx1MDAxOKLmXHUwMDA2IJKbWVejjqXw+Fx1MDAxNFhcdTAwMTjKmJ5cdTAwMDZIn1qsXGYgQZ9CXHUwMDAx0lx1MDAxMjTGJlx1MDAwNqeMLuVLLkDC7aVcdTAwMWGQkcD99dbcvXNK8KGZRTdKXHUwMDEyQ3R4XHUwMDA0cFmAlMLhoC9UM8NBemSAk8yYwoBcdTAwMTVWLlx1MDAwMFx1MDAwYlx1MDAwMlwi7CtcdTAwMTVFhqIsNbF2NG4gQsbilEYpgrNcdTAwMTBcYvzPcH89XGIgJMUxf/icKZKGxVx1MDAwMDK6LG1rMlx1MDAwZkwpsEmDXHUwMDBmXHUwMDExq8RcdTAwMDKORKm7+dNiXHUwMDA00KqAXHUwMDFhlVxyQ8xQ8cZcdTAwMTfl6NdcdMU1XHUwMDBli6FkJqVkxjFcdTAwMTZ8QFx1MDAwZbSSMjlcdTAwMDG/fi1ZXHUwMDE1XoK/XHUwMDFmWiWjtYDH6Z07MFx1MDAwYi6jR1x1MDAxYSZcdTAwMTUuwbA7XHUwMDA0sJJLbYlcdTAwMDb+OFx0l1x1MDAxY3PKRINcdTAwMGZcdTAwMDRmzJIvjCpcdTAwMTJH4mxg8PZcdFx1MDAxNlVcdTAwMDSssFx1MDAxNcB8XHJyXnAtwFPxzK7/aM4kzDDr9Vx1MDAwMX4kWMZJmSgpKNdwZ0F3uFx0dMDBzlx1MDAwMSyBIyzh3Mb4vv1cXGBcdTAwMTk9SG9cdTAwMTLA4flaXHUwMDAzdMRazLWygJxcdE5hVErAU9ZcdTAwMDJLO/jGl82kwsRcdTAwMWJfPsFeXHUwMDE5IfRO4vKVXHUwMDAxUmlcdTAwMDTe37lcdTAwMTEuevJNQlx1MDAxMc5cbulcdTAwMTCKwVx1MDAxZYPhXHUwMDBmOZlcdTAwMTLGjlx1MDAwM2WZUERcIl9X4Vx1MDAxZFx1MDAwN0tnTYxylDKMXGIwK0p6Yp1jh5lcdTAwMWJHok9cZjZOXHUwMDBi7Zmi/tlcZivhq2hrf7jDXHUwMDFjPeJkXHUwMDAy4SS1XHUwMDE0XHUwMDE0SmCfqfXsXHUwMDBl3Vx1MDAxYedfOcVf0kC/LOeLVlx1MDAwN8dcdTAwMDE4zsArJ1xuXFxJis/7J+ZMwoRcdTAwMTlfKZ9cdTAwMWOvjLDR8CHmmnBFlYpR1Vx1MDAxYz1AJKFwpjh3XHUwMDE4IVxmLKhcdTAwMDIjyibjf5xIjLkz3NijXHUwMDAwJ1wiilx1MDAwMJfv31x1MDAwN2qIXHUwMDEzXHUwMDEziFx1MDAwNN7FbFCVXHUwMDBiOK5cdTAwMGXDYLtcdTAwMTHYRmWFXHUwMDFm0ZRR6Dwt2d6feESLUdeMO6FcdTAwMDTG2aRVnqJXXHUwMDBmOVwiXHUwMDFjRFx1MDAxZFRBXHUwMDAzN1JqQUCLXHUwMDFlvDF5XCIlXHUwMDE1p1x1MDAwNixcdTAwMGaTQrKgmCTQTGFcdTAwMDWwSJBIKf0kctPwLVS48ZXyy/XKXHUwMDEwTtGo3SlcdTAwMTiTUjFcdTAwMThb5fp8N0O6+rTQyZRcdTAwMDeN48tjs5f4gVxuXG5HXHUwMDAyWbDoIP5AeMjUjHaiXHUwMDFkLLi3TFx1MDAxMW1cdTAwMTWPWI+2NGWTXHUwMDA2vCMmrFx1MDAxNthkQGhAnbNcdTAwMTFYtFx1MDAwYlBcZifV2lJfjkNcdEM1LnP7XHUwMDAzcL/hXHUwMDA07lx1MDAwNpBcdTAwMDPFXHK32NZcdTAwMTLA2HBogbLAyzXeO7Jgmcvchdfv63ispIphwa5cZspcdG8+ooXJMr5SPjFeXHUwMDE5oFx1MDAxOVx1MDAxZVx1MDAxZWMjmGePsXOipNvivnx1UX/SZyR/p0d35fPEL5JcdTAwMDU/wDHaXHUwMDEyXHUwMDAyN9eAXHUwMDE5nWJsXHUwMDAy7jxcdTAwMTOYqVx1MDAwMNnTcvpcXGM0KyhaXHUwMDEx5SXQTFx1MDAwMaxKw9xlP1xuOGSAXHUwMDA3ysHWK0OJxYCrwvYmXHUwMDFmYVx1MDAwM1x1MDAxNcHozZKjfVx1MDAxM49n+zUlXHUwMDFihceHk7unfZ5+yd1cdTAwMWWQQUh6lHGLQWutMWDMrVxmimdpyrhcdTAwMDRqJKjWXHUwMDE4xF5cZtFcdTAwMGXOX1x1MDAwZXbfxH22Y4/1eT49Kou9VIhfrPDxcUrBXHUwMDE3Ncb4aSTWJWPsXlFcdTAwMGWoXHUwMDAwYil8Z9o0iFx1MDAwYlx1MDAxNXB8+UV7dZyNRVxy9lx1MDAwMFx1MDAxZlx1MDAxOHPKc4NcXOo2vzN6Tlefc1x1MDAwN4XTx8NONWMyXHTq2lx1MDAwZuNsXHUwMDAwY5jy5mBhXGZcdTAwMTVTWVfBXHUwMDFkNOSAcKC/3Fu1snKUk9qh0q3XxGS+sFx1MDAwMXlX8J5cdTAwMWPghoxJalx1MDAxOTHUP3SOYCVcdTAwMWZoz1x1MDAwZs8kzFxyciniWFxy1lx03Fx1MDAxZK64wlvrXHUwMDAzXHUwMDE04zB4tFZcdTAwMTPClVx1MDAxNiSgam5cdTAwMDLjJr/GhiFNqJDhyydeq1x1MDAwMlx1MDAxYaZ4+CBcdTAwMGJcct43kyZcdTAwMDbQRI9UWlxy0JQ7+ERXmrFcdTAwMTRcdTAwMDA1XHUwMDFjU31cdTAwMWOr4NlkXHUwMDEzLOLQeiZcYlx0o1x1MDAxY8ZcZlx1MDAwMVx1MDAwYitcdTAwMTl2JPlxXHUwMDA2o6RcZoyOO1dUWOafXHUwMDA0XHUwMDA27rxQy45nTjzMzF1JXHUwMDAxMIN9Uljfpq1hWFx1MDAxMFx1MDAxMFBLsVx1MDAxOHe6b5Qv+8Nq5vziNndZuulcdTAwMTR6XHUwMDE31+mQXHUwMDE2WHd4vaBCaHi0hPhcdJ10cFwiMZxUcrdWeNOpU1xuJNlBV0xrkFx1MDAwN46NXHUwMDEw3s9TYVx1MDAxYyzjxjms6LzPLoFLhaqHe71pxVhcdTAwMTlC8vCFapS+f4FcdTAwMTjLh6O3SSZcdTAwMTYhXHT4+lx1MDAwMoguNpFcdTAwMDC+TFwipMDBJ9Rqq8FCWWvCXHUwMDFkzqUzXHUwMDA0lDjAXHUwMDE3wPWlhFx1MDAxM+s9yLimQzjgXHUwMDFmXHUwMDAzL1fYUVx1MDAwZVx1MDAxY3FcdTAwMWFcIi0lKJc/vaYjTnpRXG6GXlx1MDAwZVBcZnyO/lhcdTAwMTVzXHUwMDAwmVx1MDAxODh9XHUwMDEyt22rgJkjq3U28UjagFx1MDAwZlx0XHUwMDA0RCmhifFPRGLwmCWmpPBQXFxcdTAwMDOKbDxkhou3+7ZPsleGcaDZYVx1MDAxOMeNNIp7XHUwMDBiSWdBXFz01qHEepvUYVx1MDAxMvNkuCDBsmlvkziKWC1cdTAwMTU3WkpcdTAwMWKxNGBZb1x1MDAxM1tywWZK0DWjuadw0VPkS40jLVx1MDAwNapHudY8oMiXcnA2mfjpLDCOs6k5f6++dLuIjFx1MDAxZuOAgFx1MDAwMcDB44WbLlx1MDAxOWVcdTAwMGKSwlx1MDAxOIUmxCjjLqmwXHUwMDA0U9ZcIqjuXHUwMDE43F9cIrU7TtBSXHUwMDEwvU3HuFDxfn9zWrBjXCJcXPRga6/v5vN1sZwnzlx1MDAwMLvqoEyvar37TudEVlg9U8pcdTAwMTSPWdJRXHUwMDBlR8+6XHUwMDEz7Fxipt+lt6X/vXRNOtzz+sKdmzygXHUwMDFhV/lqOVxmeN2KqKRuajhtMsWGN61Gtvo01H02unguzb1saIklepHXPa1tdyuU9+4u9k96TOy38vKereC6+5f5q17urVevl1x1MDAwYvt3b4109qZVW8F1XHUwMDFiad6zp5n0QfrptfT88mru768q8133XHUwMDAzXHUwMDEzQk2QYppcdTAwMGKPqH3VMkFcdTAwMTHeeC+ArlFcdTAwMTZnjVmwVCVcdTAwMWRZXHUwMDAwS6KQxbA1IYtcdFx1MDAxOJhcdTAwMWa4TVx1MDAxMChcdTAwMTJ47z9z69LHNsHdTrs/bIXsX/qq4dszLGPwNsHPky5h31xyXHUwMDBirVx1MDAwYlx1MDAxMNYyXHUwMDA1Rmd+LTw8ebt4LNhcdTAwMDLrl8t7h6mCeS48XyZeXHUwMDBiidaYp1VcdTAwMTYtvZ5cdTAwMWFWpqxwVzFcdHBzXGL3WtW1mHd/JMZcdTAwMWRcdTAwMTNt+ZKRmK/bJSgu0qdHXHUwMDE3ucrFbqqoXvXDMH+m5jVrrYtR6fAhL9hj66g6fDg+LlSPXHUwMDFhKzCXXHUwMDE109m7Ko5cbvcn3Uo+c11oXHUwMDFj1HczK7huhmfPO6yTS43EzeNbRZxd1trDlZlhw5inMfarzLCioWZYauynjDPQK/DhJ13/4Ts6JEz/gUu7m6O+Xv9NQFxcNsBcYlx1MDAxYtzxylWs3pPNM8JcdTAwMTfDYrPer63ZXG7PsF/BVnh81EhcdTAwMDWMmKfCQlx1MDAwM4lUXG5NXHUwMDE1xqXmZ8KRrkxikyW4YlRcdTAwMTJKLWW4XmCSXHQrQdblY2viMGxcdTAwMWFcdTAwMTJcdTAwMWOnXHUwMDBlXHRcdTAwMWJkk7WjcdlcdTAwMTWV7/OS/END3blcdTAwMTJG/PRi42jSujWZULbYJKGxdZxYroU/mbtg7PBk75cs0avL3Ve219an8rFUfFx0TNlcdTAwMTBHUKJcdTAwMDS24zCs/aNcdTAwMDH1xdKh0u01gKeK+Z2NXHUwMDBmXHUwMDFkpsJcdTAwMDVcdTAwMWFfPlFcdTAwMWVf72/ef642XHUwMDAzjMxcdTAwMDZ9i7kxLZpcdTAwMTcmXHUwMDE204iWjpaAaMgrwJuazFx1MDAwMGtmXHUwMDFj+FxyJi3cLOktSF79SkrrXGLhzmPG2GDA4iNcdTAwMDZyL3BRqMS5pvA7nlxcze+mfmpxhM5cdTAwMGZHtGhcdTAwMDIwXHUwMDAxJlx1MDAxNGCMXHUwMDE4uGnAyODmXHUwMDA0za+Dpy41YTjDXHUwMDBlp2YsOFx1MDAwNjlcdTAwMDbAXHUwMDExS7FcdTAwMGJUcsssNrz4W8Skw6hmXFwrcCFcdTAwMDU37Fx1MDAwN+R/Q4RcdTAwMWJffrFeXHUwMDE1vlFcdTAwMTPuNmlujIEjzVx1MDAxZjeJXHUwMDBlayfVb3LjJkxqhsFLZqeil1xuXHUwMDFjJ4W1xrhcdTAwMGKKe9V25f1hnDrauEVcdTAwMTg4I1ZcdTAwMDbFUShcdTAwMTBISXAvMMVDXHTqb4HVOKhPWVx1MDAwZtX+kVx1MDAxMFx1MDAxN4e0gW6BTuH+XHS4cYxcdTAwMDX1iFx1MDAwMTU32r5cdTAwMGZKtlx1MDAwYk4tmTv9i1NLKCozLsTQXFxcdTAwMTAmjT9cdTAwMDHMXHUwMDFkglx1MDAxM91cdTAwMDByXHUwMDA13Ev8c/NBLlTE39/2SffKcM6S8KZcbpyRwiyNQeSi82FcdFx1MDAwNTq3c4yC1yBxfK6nXHUwMDE29rNcdTAwMTHW7SxlXHUwMDE0x6F5mMDKgVx1MDAwZec0Uyo441hgTsa7tjxETuFgPXfgnlx1MDAwMEeL+WBcdTAwMGX7cbBcdTAwMDJTJVx1MDAwMObG5//OMlx1MDAxN1x1MDAxY8rLuOLMauPWxPmZnHBcdTAwMDTSJVx0fj1HRrcgzs1ccr1u31x1MDAxODBcdTAwMWHqTq7EnXNcdTAwMDEgh6xcdTAwMDZvI0CcsWbjPVWsY6GGXGIgTrhp0rCpccbaUUxpsDOESynsrMuF6or7d01rycrgkodcdTAwMTdcdTAwMDWCf+2GXHUwMDFmYrSGROctXHUwMDEyXG6X7oJcZoWb6YwwUlx1MDAwYupcdTAwMWZcdTAwMWNcdTAwMDC0XHUwMDAyRFx1MDAxYlx1MDAxOFx1MDAwM+6t+jK4XHUwMDE03NGKSG2kXHUwMDFiL9ckgFx1MDAxN3IrXHUwMDFjbMXWbvifXHUwMDA0VNNYXHUwMDA2XCKnyFx1MDAwZofLOPsxrFx1MDAwMpRcdTAwMDRSj1x1MDAwM0ClXHUwMDBl8HxcdTAwMDGbrDBcdTAwMTh1XHUwMDAybsCE4n5cdTAwMDI2XHUwMDE3XsbwxrXBKVx1MDAwNVx1MDAwMvw/JqlnPuJWeHRxw/BRUMdonDZcdTAwMDNcdTAwMGVcdTAwMTfzXHUwMDAxXHUwMDFhdZ1ghnEkXHUwMDBlnlxm1bMuXHUwMDA3Zlx1MDAwZvuRXHUwMDE5saCpxnqq+PDlU4uYXHUwMDAwXHUwMDE5XXMgaWj/XHUwMDFjzo2kXHUwMDEz3uJMSlx1MDAxOWnoXHUwMDEzipFSWrfwRyphXHUwMDA1XHUwMDE1UyskOaNcdTAwMDCgXG6LOYGOiK9rnlNBXbm+WVx1MDAwM1j7q8Wy2Ywvqzio6cpufiddSF3mzke/hHyr5Zpvc1dcdTAwMDZETuaZ+PtjVVx1MDAwNkSPyFj8utF9XHUwMDAyi183utt98etcdTAwMGVcdTAwMDYn+azeyaXTXHUwMDE1m33cMy+Ho5OnXHUwMDE1XFy33ddcdTAwMGY3z0qnSmdvrfMjmztcdTAwMTX8dFx1MDAwNdddomDzXHUwMDAzXHUwMDE4V2jFIzE2tKKDejjVNFx1MDAwN8VFPkqrXHUwMDE485SD1Svp+Kqj8VWtXHUwMDA3X1nQtHk/vlx1MDAwMk9SXHUwMDE4eYyBr8tWdCzEJVx1MDAxN6ro2Dtcbizh8Mx0WGlcdMdcZjowXcJcdTAwMDGni1SyiEXYJjTBif3whGg1P4mJhsmEKlx1MDAxObZ/aVxmXHUwMDBmK42LQO30RCXlYFx1MDAwYlx1MDAwZVx1MDAxNitJoJLhWrb8rFx1MDAxMetcdTAwMTDkxMSgQ1x1MDAxMqR0wmicO6OIMVpcdTAwMDP99KU3sVx1MDAxYVx1MDAxZHsmk7DhIVx1MDAxOWEx8PM4OHecUlx1MDAwMFGBXHUwMDEze3w+lXRcdTAwMTRcdTAwMDFPgeBsI7izdNFcclx1MDAwZvOOXHUwMDA0cMefKMHgLlx0YaVcbvI9qcPdvY3uMlJsi9r44H+oeOPLJ9jjq/3N+89cdTAwMDVcdTAwMTKc4e1cdTAwMTmMXHUwMDEykFRcdTAwMWP0PDfARfO1xFx1MDAwMlx1MDAxY3dcZmfgkUpGlZpmXHUwMDExgjlcdTAwMWOrOixTXHUwMDFjXHUwMDFjuq9rb1x1MDAxNeBcdTAwMGYyd5dcdTAwMTg3TFxiXHUwMDE20K0hJK4jw8nbhDIprPHnN0GPJVx1MDAwZcD7XHUwMDAzcFx1MDAxZuVgiuG6S8qEVt6J9J9YopElYlx1MDAwM1x1MDAwNFx1MDAwM9Wy8FpcZt/ixNbgMLgyR+H+bGmo8Vx1MDAxZko4XHUwMDE2lFx1MDAxZIVcdTAwMDHnXHUwMDE0ioBcdTAwMTl2XHUwMDFihm8g1I5F6cCNiFx1MDAxOJKazG36JXvWXHUwMDA1Q9XFvVx1MDAxZbdcdTAwMGXoKphcdTAwMGVjsUpmdfPnmNeTmEZMbnB6hp4/9J/p37w+XHUwMDE0U4Xd3mF95yiz3880js+TXHUwMDBlmO4mWY5PXHS4IdXeuZUuYipcdTAwMGKkQmA3ucRcdTAwMTmccupgK1xcJKtcdTAwMTG4mbVScpwly1x1MDAwM0ZcdTAwMDZb7mDuXGbzQJxcdTAwMWFpfZ02jFxiXFxPSpNQXHUwMDEx8pWQXHUwMDE5a+tcdTAwMTd1p1XCI1ZMXHUwMDEw7lx1MDAxZj+HU4Mlw251TVx1MDAwNLVcdTAwMDBmi4FmnL2IXG7rxrU2XFwgXHUwMDEx9Z+JO+6OMiyOM9owtfGUMFxcvPElhVx1MDAwM+JMXHUwMDE4YFx1MDAxYthcdTAwMTEze5Msw5HEOFx1MDAwZpJcdTAwMWJcXLxt9cTlfHqyKsDkQoe60PDNXHUwMDA0XHUwMDA3qzw/w4zuxUpsjTAmS3FcdTAwMTgqI1xm/GTvctb3XHUwMDFhYVx0/lxm7pjAPaTAMkNcdTAwMTGTXHUwMDE4/SCLSzjRwjpcdTAwMTSH7ON+Ys5s0MBOasG7XHUwMDAyM23hMO5cdTAwMGVSP8fEbUaUmW8tXHUwMDEzVjj1g3wlYsZITOK0JfCe0dwxXHUwMDFkRDIp1lDioFx1MDAxMimNy0hcdTAwMTcsXHUwMDEzjrF6W2t4wNhzXHUwMDBlnEpcdTAwMDWubtx0gNSgVIa5XVx1MDAxY+A+UzmRLE3h2FBcdTAwMDP8XHUwMDAyV0BqwDQ1k1KGa4d7QZ9irFx1MDAwZVwiTWg9XHRWcmn839xcdTAwMTBcdTAwMTk9NVwiwVx1MDAxMKlcdTAwMWSCVZ/gg1uss5uASEVAoFx0yDHYdlxy3CPcXHJfXHUwMDFlXCKV877tXHUwMDFiS+iB4vpcdTAwMTFcdTAwMTLX2uJ0ayQ+mIfxjTRcdTAwMDZOqTjonaf++FtcdTAwMTDSKO/SzW+sMsZSN1x1MDAwZSRcdTAwMDVcYj+1XHUwMDFjmylcdTAwMDJcdTAwMTDSKIs1OFx1MDAxY1dcIlx1MDAxOClndIpt8khjJXBtXHUwMDE5k5RS3Fx1MDAxMyknh9lJ4iiLRX/cXHUwMDEwzcHOzcatXHUwMDEwkXUv55PWmLBcdTAwMTWWgVx1MDAxNCp0PVx1MDAxOLWWYrtTjNBh8bBydtIuXHUwMDE0d/afbGZ7+06W0/2wXHUwMDA05JRH+11+MCfcYVx1MDAxNjd8YC32xHJcdTAwMWO8XHUwMDAwTnmlYClcYlx1MDAxOHycPFx1MDAxY1x1MDAwMVmzXHUwMDEzkC+1+qBcdTAwMTLgXHUwMDAxXHUwMDA3XHUwMDE0u3mSdmM4wul9gsVxcVx1MDAxN8894lgq4LiL4I4n9+hpuJ2Re8z1QfqC049i4pc/s4vNysOkSE8mXHUwMDFmXHUwMDA3nW5Y5nHiS0ynXHUwMDE531x1MDAwZlx1MDAxMqlM7sFcdTAwMDKUyVvNNq1NXHUwMDA2zCG8L+ZPNF6rQfvsvHud67zVRF+NXHUwMDBl68ft24QrXHUwMDEzw1x1MDAxNIeW8DUxcDe1h4pa4TAwZtZcYmNcXG94XHUwMDE5Zfq7UaZilV+duHS0RdnlXHUwMDEyZyBcdTAwMDWMauBYn1x1MDAwN6qEeVx1MDAxMe4uv5hWNlxct8uU98NeXVx1MDAxYlu9i5G+7Z1cdTAwMGV/5Trp9PPOUfZ072LY/Sg02bio07Q1XY+dnnhvtbFcdTAwMTdcdTAwMDdXXGJZJriVhk/VTaZcdTAwMTh1gK9yyTTTXHUwMDA2l1x1MDAwZswsxKTojFBcXFx1MDAwMKMsJlrJpMn3idVMX4VjM1x1MDAxMFBMi1x1MDAwM2+JnFxiiOFcdDlG6KS7u5JifefsXHUwMDBihsj+++VcdTAwMWOKXHUwMDBiOsC/xVQwVvvOulxc2O2ei3KEo6SImCYnNCUxxlTetX+dvuZ3blT/tZNcdTAwMTY63b1cdTAwMWTpsDFWXHRcdTAwMDFJIPRcdTAwMGU8cGlAUFx1MDAxNPeWq7+DpHaAM2Jcblx1MDAxMFVbm6Wm2HwpSFIucIS1d/bfXHUwMDFmlPyDkn9Q8vdvz4WSlWaz3u1cdTAwMDdcdTAwMDJl1DxfXHUwMDFjIFxm+qfm7+d8XHUwMDFkXHJF//ZZtm0t2049XFzu3J5VwoZcciVcdTAwMDQpOVeOZlj9ybSgxlN1+fo7xoOwXHUwMDA0YqapXHUwMDAyV/xrkJJcdTAwMWGMXHUwMDE3XHUwMDFhhlx1MDAxYk5wbXpQxzrOXHUwMDFkk1x1MDAxNs5cdTAwMDFcbsQl9e9cdTAwMDBcdTAwMDTnjVx1MDAxYmZ5yMTypaBynfWlXHUwMDBii7JcZnWMMK472cs/s9XuJnf32qzrXFzqSuzfvVxcZbvVZjnZksysdCzlRCtcdTAwMDNIStWUzVx1MDAxN9aBe8BcdTAwMDGCjbtyL8GSTFx1MDAwNda9yJAmu58hyGHM1fLwYFx1MDAxOTxXqVx1MDAxNIkxSeSyXXwpNlx1MDAwZkrpUi/3dnV59nZY21x1MDAwYttamFx1MDAxMDHmRDnA+YTAVWGW0kkxZlx1MDAwNLhcdTAwMDLcXHUwMDAyJYVcIri6M3yOyFLU1YD1ZsBcdTAwMDI0XHUwMDEx1lpKXHUwMDAyXG5GuHAkKJPG/UVcbvxcdH/601x1MDAwMu5gVdFcdTAwMWbuutncXHUwMDE1XHUwMDA3h1hugfxhzGaKuzJcdTAwMDf37jJcdTAwMDJcdTAwMDRcdTAwMDJ+RfOZ5Vx1MDAxNVx1MDAwMJGKXHUwMDExXHSia1xy90xcdTAwMDFzL+dcdTAwMTOqmTxcdTAwMTP/flxcaaMsru6eLFx1MDAwN05hvVx1MDAwMFxcTFx1MDAxOeDISlx1MDAwMpuZuVwiLFxc8t8v6HCwL7ho2OAkSi1mUvWvYq5KRlx1MDAwMCWIssHp5HNcdTAwMDMlzVx1MDAxY5rng363o1x1MDAwNk/X9e7VbllccvZcdTAwMTJcdTAwMGWUaEiN4OBKaJTQaeZqXHUwMDFji5vwpFx1MDAwMWKLObWvsffWMYpailx1MDAwMVfQXHUwMDBlXHUwMDE5ZO+1g1x1MDAxOU4tXHUwMDE1pbjZ2PjtPXpdMrSl9GdcdTAwMTj80KS+XHUwMDFl11f5c/pM0FgrVfqNQrl4XFxN65Oju6NsaudQXHUwMDFkkcS3P3NDXHUwMDFkidtcdTAwMGZcYrjr8HUn02NUa8fimlx1MDAxZobuPmc2fDLisiNcIqxw3P4l7q6PNVx1MDAwMTVP/lx1MDAwMcxcdTAwMDTnsUj5U1xu6cOtd1hufyArr63HK1ovytvb14f7Q2ZuxzmkrclxXXCrmFx1MDAwMFx1MDAwNks1ZZKQoHVNmKBcdTAwMDaDqjW6XHUwMDBmOmCI2Ibl8MOEaurDkfbwXblcdTAwMGKvjc726z1cdTAwMTlcXN40XHUwMDA35dFtvWdoc3zqXHUwMDEwpHn3XHKJXHUwMDAzXHUwMDFjQFx1MDAxMqVcdTAwMDTDXHUwMDFkWFx1MDAxM1x1MDAxYaZxZ1x1MDAxNFx1MDAxOFx0aag7XHUwMDE5VvlcdTAwMTWDYO/DeDFJwEa1KCX36angXHUwMDA1McPilCplUS4kXHUwMDA3uoIt0lx1MDAxMXvo3D1l8k+lw0K1VT4uXHUwMDExdWdiWKRcdTAwMGZcdTAwMTRBXHUwMDA1VrhTM8BAcFx1MDAxNsp0NIFPXHUwMDEyS+cxXHUwMDEw8Vx1MDAwMOU9TrtcYqCsVnnCXHUwMDA0b+rTvjs+J0jEdltcdTAwMTaEllxirT4ynF2fXHUwMDE39/tvtcvd7fyv1u3trfVr9WR1XHIqtSDGXHUwMDAx41xi9MBcbsKxVHtSq4lwgD4wpPKYKlx1MDAxZstI1J5cdTAwMWaPrP3R49h6zChQXWWNXGbSY6ZCd+uA+6i0oZTOU1x1MDAwN7UuRf4om9nttFx1MDAxZurVicdcdTAwMTS79X+hiprg+Vx1MDAwMDHqbMYnX4yae2vap31MTD1cdTAwMTnq7Vx1MDAwMp09vq13t3fMK2+lm179bLtYMd2rbnJcdTAwMTQjLKpcZoxcZlu+KVx1MDAwNfZgmJmiXHUwMDBlUjnC3ZNiqMXRastwc1x1MDAxZlYsRD8+yms0+lx1MDAxM16L+jWavy5tTESU7cu5ckxzXHUwMDFmqdZh9ajgYoZptdFGcE7m4lPvSv2kRIc997pXp3tX3ZJ9YFx1MDAxNXtZSrpSXHUwMDBiol2lJkS8T0Sc1GnBQadxMqUgQjBcdTAwMTJcdTAwMWU5WkanKVxyWsvsn4ZcdTAwMDNcdTAwMWUgKFx1MDAwZVxyXHUwMDFlxLF5arzQ0Jyjtnu8LbCUzVnmcVx1MDAxZPWrXHUwMDEz51nMtIrwLUfg+Usz8chnKeHbdWqb79y0XHUwMDE0yV02ujtcdTAwMDfpXFx+WE+6XHUwMDEyomVcdTAwMTVcdTAwMThcdTAwMGbVVsI/1FSrn5VcdTAwMGXx+OThrX5zjJNYqWHl2IxcdCx3VnZ2UzTyj2FdmWGNoMvY/W1cdTAwMTiP0eixf5yv3LbE6XGn8dxrvpSO1cvuTdKVXHUwMDFhLSsqtcA1XHUwMDE4OE91yic3XGa1WlKpLC5K+Fx1MDAxMqX2tFFHXHUwMDE4VoatU9a7PG2z1Xghw3pT71W2hjNdznXY1I+jLGhOdbjqcardwb4xXHUwMDE2bJ2KfDt7mFWm1nmsXHUwMDBl2zu01NhOfGeoW/QsXHR2XHUwMDA2coJT6CbzSIbTeV3VXHUwMDA1wlpLuap4IKn5T+G4fyzqqiyqt75p2lWVRlxiXHUwMDE1Z4bQ3k1lyI92zl4y1Ze7Ste8Xp6wg+RcdTAwMWJU5io1xWZ6MKiTVVx1MDAwZYaaeV3VZXQ6qHnSX8WATbXCypmtXG6bosaLuaqt7vvJkmBTx4dZzKrK8MxcdTAwMWI8aUYntifOUj85uFx1MDAxY9497rWb9E1sS3rw6zGfXG4rKU6M+jFjXHUwMDFkSsBcdTAwMDclOFx1MDAxYtmzeeZd/ZR1tDtQXHUwMDE5XHUwMDEzmNQsNcFrpSaVas5cdTAwMDR2gc0qKdpcdTAwMTRl/GNTV2VTpVxip8pcdTAwMTa3pmlcdTAwMWVjztTB3a/n0u5NdsTvxdllVj1XTPs56VotiEStXHUwMDA2pkyNYIqwyVnNRipUa/BgNccynC/R6jnjv1xmqDFl8qdQ44VsarnTXHUwMDAyg7c1KPRcdTAwMWJJsKre4yxoVyOGpbvFbHZ+Vnu7N0rv0Gzx4pi0i/f7qtj+dbzqrOpX+KqggdZcdTAwMDDKUSvgXHUwMDFmZtJXtVx1MDAxOEZcdTAwMDJcdTAwMDXlXGaHUpnpg8VcdTAwMWFitFK7ylxirlx1MDAxY1wiXFz/lLDRXHUwMDFmu7oyu6pCW0lcdTAwMTWX2lrCY0z5afb6V3tcdTAwMDfXr2fF1kX3+Vwip1RZJt+sUlepKZFEaEqn4k9WoK9qXGZcdTAwMTg0walaanhjuFn1nDLCrHKCo6xn94puilx1MDAxYS9kV3P9ylblOSG+6vgwgdr3rlx1MDAxNuenL1cvWu9u3/eG5MK0XHUwMDBm85nbu3lqXHUwMDA1Kei9W2NcdTAwMGayKVxm9rJPyCbOQNTuNFxcgnPGmHcg13h/YtCC2TjlgmUwdeThXHUwMDFiylx1MDAwNZex3zFcdTAwMDdgfeyJVFx1MDAwMmlrYFx1MDAxZKAwoXWA4La6m6zFPLH6eLqFpS1koVlbXHUwMDFm6lJcdTAwMWJcZrr9f/3zn9X6oDYsOqVO65+DXHUwMDFhPHV4Ju3qP6udVKnz8FCp9GteXHLYkErBON8tQj/7XHUwMDA3lr/mfj1cdTAwMWbftPfu9+jjxSXdP5yrllcrrOU1XG4oXHUwMDFmmGliJ/OGqXf1tFhcdEM57jLxqScjy6qnUaxiS/9fqCdYX5yvbJlvNdpf7jy+UP2kSnEu+VxcTCamfiohPYxhXHUwMDAx/Uz3XG6tykun19j6P1t7vfrzxLC7XHLRw6DvsFx1MDAxMFx1MDAxYlx1MDAxNTS0q1x1MDAwZdtgLc7UnJ+O3qVfj263K5d3L2fb9edcdTAwMWH9ld8r5lx1MDAxM6RcdTAwMDXBfFRr7Vx1MDAxME9f3WSFkTHvUVx1MDAxZcqMlpSFR3lcdTAwMTbBhXF/aMDYp4DcXHTThGCdU5xJ4UtcdTAwMTBNo4xWdFx1MDAxMWVbsH5vUOk9XHUwMDE0SpWt7XKhO1xinUK57io+36lcdTAwMTZSNUlCozlKXHScNlwi51x1MDAwZudkXHUwMDBlxGGvVD8qXHUwMDFkZvuN9s0gL1x1MDAxYY/9xGtcdTAwMWHoXHUwMDE4xzFcdTAwMDFcdTAwMWOnVykzPbVcdTAwMDJ8PnT9NFhw7J1X4fGcZXTNXHUwMDFiJY3SNUEp+KLeh/K1uqapXYx4LqRr291uXHUwMDEz1GNcdTAwMDBSuLUz7NfxXHUwMDBibGWHzUpcInQu4nRcdTAwMGLpnlwiPlx1MDAwZmOczMBcckrWxuhcdTAwMWXfednt7r2QYoNcdTAwMTOwn0/Pp7XLvd3kK1x1MDAxZtAxJM9Ka1x1MDAxMDdip3VPuSV3OFecWMO/xs4xXHUwMDE5MCEmQPfA2Fx1MDAxYanY+nRPKLu+5a77aFG6vXq/kkDVXHUwMDBiP9y75v3td0Drr0K3ezWA+/jXR8/7X8/1ystOgDQ8uC/8vKu3qCBcdTAwMTW3v/8/f/vP/1x1MDAwMkePJlx1MDAxNSJ9 + + + + gRPC Server(router)appcmdPostgresDBRabbitMQgRPC ClientUseCasesEntity AggregateRootDomain EventinterfaceRepositoryinterfaceValue ObjectEvent HandlersLoggerConfigurationDomain serviceinterfaceEvent ConsumerEvent PublisherDIUsersConfigureInbound callWire upImplementdomain taskUse eventhttps://github.com/thangchung/go-coffeeshopFramework & DriversInterface AdaptersApplication Business RulesEnterprise Business Rules \ No newline at end of file diff --git a/docs/diagrams/clean_ddd.excalidraw b/docs/diagrams/clean_ddd.excalidraw new file mode 100644 index 0000000..458d7ab --- /dev/null +++ b/docs/diagrams/clean_ddd.excalidraw @@ -0,0 +1,4085 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "F8LmUGoAlU-wQMGLTED-y", + "type": "rectangle", + "x": 252.17771790519646, + "y": -81.4254690503318, + "width": 1380, + "height": 1028.0000050862632, + "angle": 0, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 1456590970, + "version": 237, + "versionNonce": 130530682, + "isDeleted": false, + "boundElements": null, + "updated": 1672215944760, + "link": null, + "locked": false + }, + { + "id": "KWB6KC06uKiyAjeJkxJPM", + "type": "rectangle", + "x": 433.51102217416934, + "y": -19.56833418588792, + "width": 286.47625151134656, + "height": 537.5618940080915, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "groupIds": [], + "roundness": null, + "seed": 1077523174, + "version": 812, + "versionNonce": 1765399610, + "isDeleted": false, + "boundElements": null, + "updated": 1672215602395, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 618, + "versionNonce": 1849672250, + "isDeleted": false, + "id": "ZRlhhmUgKX76k8SHTNY5F", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 1326.367867121621, + "y": -17.85398925296829, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 267.6192656017483, + "height": 936.7618197486515, + "seed": 1449017594, + "groupIds": [], + "roundness": null, + "boundElements": [ + { + "id": "kF3r9MJFGFqycvwy8__Se", + "type": "arrow" + } + ], + "updated": 1672216266162, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 527, + "versionNonce": 1032767334, + "isDeleted": false, + "id": "pA-I9OblxLjRyIV6G-t-k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 1022.1777455163383, + "y": -19.473059396895223, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 284.38108898344444, + "height": 942.095198858352, + "seed": 528390778, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215828578, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 384, + "versionNonce": 488943974, + "isDeleted": false, + "id": "1cEl_4WudDB1q-zlaExXJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 20, + "angle": 0, + "x": 744.36809382363, + "y": -18.425452701628956, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 260.57141985212024, + "height": 942.0951734270367, + "seed": 1409481722, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215349788, + "link": null, + "locked": false + }, + { + "id": "lcG70iexy9r-E8T6NJYEb", + "type": "rectangle", + "x": 792.2667541503906, + "y": 203.86680094401038, + "width": 151, + "height": 60, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3 + }, + "seed": 547764710, + "version": 995, + "versionNonce": 928741158, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "3HvItvU3AEAGdRYzCzApM" + }, + { + "id": "eTOCJ0p7MaoJdtkKQK8D9", + "type": "arrow" + }, + { + "id": "Cjkb8sJRkfg9tAAbBjk9d", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "id": "3HvItvU3AEAGdRYzCzApM", + "type": "text", + "x": 806.7667541503906, + "y": 208.86680094401038, + "width": 122, + "height": 50, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 944634854, + "version": 687, + "versionNonce": 395802170, + "isDeleted": false, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "text": "gRPC Server\n(router)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 43, + "containerId": "lcG70iexy9r-E8T6NJYEb", + "originalText": "gRPC Server\n(router)" + }, + { + "type": "rectangle", + "version": 585, + "versionNonce": 1752015462, + "isDeleted": false, + "id": "xUPeHlfTY1YtIiTVCztCM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 479.16668701171875, + "y": 269.06670125325525, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 1012008250, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "VjmeRt6kU_zH953gkzVtE" + }, + { + "id": "tW3HG_UG4rgxjGN424nqM", + "type": "arrow" + }, + { + "id": "FrOTwBEd4bAnVGbj9u2Lq", + "type": "arrow" + }, + { + "id": "sHJ5vG0q1UIZp7ZIwF3G4", + "type": "arrow" + }, + { + "id": "eTOCJ0p7MaoJdtkKQK8D9", + "type": "arrow" + }, + { + "id": "-XVBxvFgvUGaMjHogJ8Jt", + "type": "arrow" + }, + { + "id": "Cjkb8sJRkfg9tAAbBjk9d", + "type": "arrow" + }, + { + "id": "usSe65QPgEdc-nWeDr8iw", + "type": "arrow" + }, + { + "id": "EQVSrUzriidaEZzkFRWmh", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 255, + "versionNonce": 1484824314, + "isDeleted": false, + "id": "VjmeRt6kU_zH953gkzVtE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 537.6666870117188, + "y": 285.56670125325525, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 34, + "height": 25, + "seed": 1839694694, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "app", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "xUPeHlfTY1YtIiTVCztCM", + "originalText": "app" + }, + { + "type": "rectangle", + "version": 949, + "versionNonce": 1687709094, + "isDeleted": false, + "id": "t5eymjS1ib5XXyf_H28XX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 477.56660970052087, + "y": 147.66666666666669, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 1518449082, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "uJYGUrFWVR4Ui0qQL25Vh" + }, + { + "id": "FrOTwBEd4bAnVGbj9u2Lq", + "type": "arrow" + }, + { + "id": "skadbKgF7LIZIR-BH6I0u", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 629, + "versionNonce": 466005946, + "isDeleted": false, + "id": "uJYGUrFWVR4Ui0qQL25Vh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 535.5666097005209, + "y": 164.16666666666669, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 35, + "height": 25, + "seed": 1291247334, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "cmd", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "t5eymjS1ib5XXyf_H28XX", + "originalText": "cmd" + }, + { + "type": "rectangle", + "version": 1423, + "versionNonce": 1829290214, + "isDeleted": false, + "id": "GOwGCz4_Ro9K7OVFxd4D-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 794.7000732421875, + "y": 338.60011291503906, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 586685370, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "f5I051oWuVp509Rw2sEzl" + }, + { + "id": "eTOCJ0p7MaoJdtkKQK8D9", + "type": "arrow" + }, + { + "id": "c7n4_dSPiq7N0VZ7xZdOB", + "type": "arrow" + }, + { + "id": "Z8f-MCMRgA3QW84VQaiui", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1108, + "versionNonce": 1435281530, + "isDeleted": false, + "id": "f5I051oWuVp509Rw2sEzl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 811.2000732421875, + "y": 355.10011291503906, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 118, + "height": 25, + "seed": 1022629094, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "PostgresDB", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "GOwGCz4_Ro9K7OVFxd4D-", + "originalText": "PostgresDB" + }, + { + "type": "rectangle", + "version": 1000, + "versionNonce": 357282854, + "isDeleted": false, + "id": "LDY5c1SQCy2Dn7M5jcbwL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 790.1000366210938, + "y": 699.8001251220703, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 600070374, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "jADUjRVGR01S4wBsV4hCk" + }, + { + "id": "MhApe13rZPELr24EmV5_2", + "type": "arrow" + }, + { + "id": "mPxcHfV42jmIgufKKagIk", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 682, + "versionNonce": 2106951994, + "isDeleted": false, + "id": "jADUjRVGR01S4wBsV4hCk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 819.1000366210938, + "y": 716.3001251220703, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 93, + "height": 25, + "seed": 737579130, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "RabbitMQ", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LDY5c1SQCy2Dn7M5jcbwL", + "originalText": "RabbitMQ" + }, + { + "type": "rectangle", + "version": 1314, + "versionNonce": 2001616742, + "isDeleted": false, + "id": "_kdQsugJOPXUQcWoarPTF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 792.0999145507812, + "y": 798.2001495361328, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 46, + "seed": 1528376870, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "BDWRpma0F6UnVF2XsUQcW" + }, + { + "id": "usSe65QPgEdc-nWeDr8iw", + "type": "arrow" + }, + { + "id": "mPxcHfV42jmIgufKKagIk", + "type": "arrow" + }, + { + "id": "Sqf62Wfuo4PuJLDV-qQK7", + "type": "arrow" + }, + { + "id": "ttLVR7BUFFe9RjD8wHxLq", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1030, + "versionNonce": 457462266, + "isDeleted": false, + "id": "BDWRpma0F6UnVF2XsUQcW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 810.0999145507812, + "y": 808.7001495361328, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 115, + "height": 25, + "seed": 1626648378, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "gRPC Client", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_kdQsugJOPXUQcWoarPTF", + "originalText": "gRPC Client" + }, + { + "type": "rectangle", + "version": 817, + "versionNonce": 1734173350, + "isDeleted": false, + "id": "AoiMmO7zoZiQc3Hk1c8br", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1059.966593424479, + "y": 272.53337605794275, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 232485434, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "HQcQ_x2Qm9o2k2KAob0XZ" + }, + { + "id": "Cjkb8sJRkfg9tAAbBjk9d", + "type": "arrow" + }, + { + "id": "TaMhtDfXx7PsIOzBTuq_a", + "type": "arrow" + }, + { + "id": "E2OkgMPkxmUx2DUmpc9UC", + "type": "arrow" + }, + { + "id": "2x-CAa7KCHh9e4TAzwJ50", + "type": "arrow" + }, + { + "id": "M7n022YhLqyGdfkVsksB5", + "type": "arrow" + }, + { + "id": "e8oDSbxa_LpeVJTakGiCJ", + "type": "arrow" + }, + { + "id": "Sqf62Wfuo4PuJLDV-qQK7", + "type": "arrow" + }, + { + "id": "ns7fWv67-cNzmOI9UM43M", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 494, + "versionNonce": 1369178810, + "isDeleted": false, + "id": "HQcQ_x2Qm9o2k2KAob0XZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1087.466593424479, + "y": 289.03337605794275, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 96, + "height": 25, + "seed": 1702323814, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "UseCases", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AoiMmO7zoZiQc3Hk1c8br", + "originalText": "UseCases" + }, + { + "type": "rectangle", + "version": 1174, + "versionNonce": 673586662, + "isDeleted": false, + "id": "PB9R2oMY7Sbk9yOV6XhBo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1357.900146484375, + "y": 264.93328857421875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 1604876006, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Z2Rkf5sAORzTp7Sjz64yq" + }, + { + "id": "TaMhtDfXx7PsIOzBTuq_a", + "type": "arrow" + }, + { + "id": "khGMixDkzC2geYlx4ni-C", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 878, + "versionNonce": 1799330682, + "isDeleted": false, + "id": "Z2Rkf5sAORzTp7Sjz64yq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1403.400146484375, + "y": 281.43328857421875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 60, + "height": 25, + "seed": 644567674, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Entity", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PB9R2oMY7Sbk9yOV6XhBo", + "originalText": "Entity" + }, + { + "type": "rectangle", + "version": 1220, + "versionNonce": 515207462, + "isDeleted": false, + "id": "7F7nFJwk1ji2j2xAXC2JB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1358.9002685546875, + "y": 345.7333068847656, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 60, + "seed": 1893594982, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Mg8VV6u6ufycF-hbQL4mG" + }, + { + "id": "E2OkgMPkxmUx2DUmpc9UC", + "type": "arrow" + }, + { + "id": "YxZmZdc0uJOjsR0a4G9oc", + "type": "arrow" + }, + { + "id": "nciWo80BNNH_RZLnFnrGo", + "type": "arrow" + }, + { + "id": "JsWyfb-aCrHiBIJEsJkKO", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 929, + "versionNonce": 1718754362, + "isDeleted": false, + "id": "Mg8VV6u6ufycF-hbQL4mG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1379.9002685546875, + "y": 350.7333068847656, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 109, + "height": 50, + "seed": 229382650, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " Aggregate\nRoot", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7F7nFJwk1ji2j2xAXC2JB", + "originalText": " Aggregate\nRoot" + }, + { + "type": "rectangle", + "version": 1374, + "versionNonce": 228427578, + "isDeleted": false, + "id": "xDGgd2jaq3hl7KHH1o5FW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1363.699951171875, + "y": 495.7332763671875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 60, + "seed": 1053154106, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "N9KhGcgFqzQphUBC9UUJ0" + }, + { + "id": "YxZmZdc0uJOjsR0a4G9oc", + "type": "arrow" + }, + { + "id": "JsWyfb-aCrHiBIJEsJkKO", + "type": "arrow" + }, + { + "id": "kF3r9MJFGFqycvwy8__Se", + "type": "arrow" + }, + { + "id": "J3ROo2oU-x4Wjze4NQhnu", + "type": "arrow" + } + ], + "updated": 1672216288194, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1104, + "versionNonce": 18253050, + "isDeleted": false, + "id": "N9KhGcgFqzQphUBC9UUJ0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1372.699951171875, + "y": 500.7332763671875, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 133, + "height": 50, + "seed": 1568935270, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Domain Event\ninterface", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "xDGgd2jaq3hl7KHH1o5FW", + "originalText": "Domain Event\ninterface" + }, + { + "type": "rectangle", + "version": 1595, + "versionNonce": 624680870, + "isDeleted": false, + "id": "a5EJE0Wm1YtG4AUOzNJNL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1060.7665201822915, + "y": 378.1334126790365, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 60, + "seed": 652028602, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "TbjG4TaSHi9inHvnepd14" + }, + { + "id": "2x-CAa7KCHh9e4TAzwJ50", + "type": "arrow" + }, + { + "id": "Z8f-MCMRgA3QW84VQaiui", + "type": "arrow" + }, + { + "id": "M7n022YhLqyGdfkVsksB5", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1340, + "versionNonce": 1152374202, + "isDeleted": false, + "id": "TbjG4TaSHi9inHvnepd14", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1085.2665201822915, + "y": 383.1334126790365, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 102, + "height": 50, + "seed": 552302054, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Repository\ninterface", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "a5EJE0Wm1YtG4AUOzNJNL", + "originalText": "Repository\ninterface" + }, + { + "type": "rectangle", + "version": 1231, + "versionNonce": 2133654246, + "isDeleted": false, + "id": "z5oJpId2XbbF3GFYwUfSu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1360.900146484375, + "y": 421.5332946777344, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 22964710, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "3fVx1-uSFkwbbAmsmpiLN" + }, + { + "id": "khGMixDkzC2geYlx4ni-C", + "type": "arrow" + }, + { + "id": "JsWyfb-aCrHiBIJEsJkKO", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 941, + "versionNonce": 308350586, + "isDeleted": false, + "id": "3fVx1-uSFkwbbAmsmpiLN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1373.400146484375, + "y": 438.0332946777344, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 126, + "height": 25, + "seed": 427954042, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Value Object", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z5oJpId2XbbF3GFYwUfSu", + "originalText": "Value Object" + }, + { + "type": "rectangle", + "version": 1340, + "versionNonce": 181972518, + "isDeleted": false, + "id": "kSaEgKvdN9rZv0nRUOgk_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 791.6998291015625, + "y": 560.5999908447266, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 60, + "seed": 472579750, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "dj2Rbi6tghwcD1uTfcUNO" + }, + { + "id": "M7n022YhLqyGdfkVsksB5", + "type": "arrow" + }, + { + "id": "usSe65QPgEdc-nWeDr8iw", + "type": "arrow" + }, + { + "id": "ggd1wHVUvb8hrIVRodg0R", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1054, + "versionNonce": 787296058, + "isDeleted": false, + "id": "dj2Rbi6tghwcD1uTfcUNO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 826.1998291015625, + "y": 565.5999908447266, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 82, + "height": 50, + "seed": 1325803194, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Event \nHandlers", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "kSaEgKvdN9rZv0nRUOgk_", + "originalText": "Event Handlers" + }, + { + "type": "rectangle", + "version": 1145, + "versionNonce": 1697380710, + "isDeleted": false, + "id": "T1Q5RRFgS7opX8AHKN1h6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 789.0332641601562, + "y": 100.46670023600257, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 437902970, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "EfMmrhFQ3dfo91x86m_zs" + }, + { + "id": "sHJ5vG0q1UIZp7ZIwF3G4", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 834, + "versionNonce": 1320630266, + "isDeleted": false, + "id": "EfMmrhFQ3dfo91x86m_zs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 832.5332641601562, + "y": 116.96670023600257, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 64, + "height": 25, + "seed": 332717606, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Logger", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "T1Q5RRFgS7opX8AHKN1h6", + "originalText": "Logger" + }, + { + "type": "rectangle", + "version": 1075, + "versionNonce": 91479206, + "isDeleted": false, + "id": "ghSo7c1DtngSPiXQgl1Kr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 794.0999552408855, + "y": 7.53332010904947, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151, + "height": 58, + "seed": 1412141946, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "YIB_oT5aNU6-G5rwh_6T9" + }, + { + "id": "tW3HG_UG4rgxjGN424nqM", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 766, + "versionNonce": 566372538, + "isDeleted": false, + "id": "YIB_oT5aNU6-G5rwh_6T9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 805.5999552408855, + "y": 24.03332010904947, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 128, + "height": 25, + "seed": 77920550, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Configuration", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ghSo7c1DtngSPiXQgl1Kr", + "originalText": "Configuration" + }, + { + "type": "rectangle", + "version": 1287, + "versionNonce": 1901942758, + "isDeleted": false, + "id": "_xFbQ3QId9JlRDsKtrjrV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1360.5001220703125, + "y": 571.1332702636719, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 179, + "height": 62, + "seed": 1370655974, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "d2ZoxIRB1IOWTFZjBzRM8" + }, + { + "id": "nciWo80BNNH_RZLnFnrGo", + "type": "arrow" + }, + { + "id": "Sqf62Wfuo4PuJLDV-qQK7", + "type": "arrow" + } + ], + "updated": 1672215314722, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 997, + "versionNonce": 1605435770, + "isDeleted": false, + "id": "d2ZoxIRB1IOWTFZjBzRM8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1378.0001220703125, + "y": 577.1332702636719, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 144, + "height": 50, + "seed": 1110809722, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Domain service\ninterface", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_xFbQ3QId9JlRDsKtrjrV", + "originalText": "Domain service\ninterface" + }, + { + "id": "FrOTwBEd4bAnVGbj9u2Lq", + "type": "arrow", + "x": 550.3444856046765, + "y": 213.10000610351568, + "width": 0.0550010275085242, + "height": 51.466695149739564, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 2 + }, + "seed": 786340774, + "version": 783, + "versionNonce": 1895131942, + "isDeleted": false, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0550010275085242, + 51.466695149739564 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "t5eymjS1ib5XXyf_H28XX", + "focus": 0.036555317745149675, + "gap": 7.433339436848996 + }, + "endBinding": { + "elementId": "xUPeHlfTY1YtIiTVCztCM", + "focus": -0.05602203514538, + "gap": 4.5 + }, + "startArrowhead": null, + "endArrowhead": "triangle" + }, + { + "type": "arrow", + "version": 1088, + "versionNonce": 1405534778, + "isDeleted": false, + "id": "tW3HG_UG4rgxjGN424nqM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 631.1666870117188, + "y": 309.4807762953368, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 161.03761413044674, + "height": 287.0442954586145, + "seed": 1964501370, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xUPeHlfTY1YtIiTVCztCM", + "focus": 0.9037890345895293, + "gap": 1 + }, + "endBinding": { + "elementId": "ghSo7c1DtngSPiXQgl1Kr", + "focus": 0.9295483515865371, + "gap": 1.8956540987200015 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 161.03761413044674, + -287.0442954586145 + ] + ] + }, + { + "type": "arrow", + "version": 1706, + "versionNonce": 1118377574, + "isDeleted": false, + "id": "Cjkb8sJRkfg9tAAbBjk9d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 944.2667541503906, + "y": 224.2152680202353, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 171.73804803603434, + "height": 45.479110624290485, + "seed": 1805031910, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "lcG70iexy9r-E8T6NJYEb", + "focus": -0.4043614643880102, + "gap": 1 + }, + "endBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": 0.31003867966599674, + "gap": 2.838997413416962 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 96.13320922851562, + 5.184695358671036 + ], + [ + 171.73804803603434, + 45.479110624290485 + ] + ] + }, + { + "type": "arrow", + "version": 1376, + "versionNonce": 1759931130, + "isDeleted": false, + "id": "TaMhtDfXx7PsIOzBTuq_a", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1213.184659914029, + "y": 299.96528921327126, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 142.4498741128, + "height": 5.046283390348606, + "seed": 1140666554, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": 0.03728717925019411, + "gap": 2.218066489549983 + }, + "endBinding": { + "elementId": "PB9R2oMY7Sbk9yOV6XhBo", + "focus": 0.05585322356892094, + "gap": 2.2656124575460126 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 142.4498741128, + -5.046283390348606 + ] + ] + }, + { + "type": "arrow", + "version": 1494, + "versionNonce": 898825146, + "isDeleted": false, + "id": "E2OkgMPkxmUx2DUmpc9UC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1214.5481334501637, + "y": 298.4529043437103, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 143.35213510452377, + "height": 87.79253558038715, + "seed": 1522970810, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": -0.6844369707316219, + "gap": 3.581540025684717 + }, + "endBinding": { + "elementId": "7F7nFJwk1ji2j2xAXC2JB", + "focus": -0.7524147278607661, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 143.35213510452377, + 87.79253558038715 + ] + ] + }, + { + "type": "arrow", + "version": 1880, + "versionNonce": 133133434, + "isDeleted": false, + "id": "YxZmZdc0uJOjsR0a4G9oc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1512.9783013685071, + "y": 377.04278514645324, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 41.42184511586788, + "height": 146.56708763466628, + "seed": 538428538, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7F7nFJwk1ji2j2xAXC2JB", + "focus": -0.8331066763268261, + "gap": 3.0780328138196182 + }, + "endBinding": { + "elementId": "xDGgd2jaq3hl7KHH1o5FW", + "focus": 0.8448459550206617, + "gap": 1.7947322846923726 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 41.42184511586788, + 69.49050953128113 + ], + [ + 3.5163820880602543, + 146.56708763466628 + ] + ] + }, + { + "type": "arrow", + "version": 2169, + "versionNonce": 1212917798, + "isDeleted": false, + "id": "khGMixDkzC2geYlx4ni-C", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1510.7496559959934, + "y": 291.6354249524398, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 39.82186952993038, + "height": 161.76709984169753, + "seed": 590465510, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "PB9R2oMY7Sbk9yOV6XhBo", + "focus": -0.8601698676401651, + "gap": 1.8495095116184075 + }, + "endBinding": { + "elementId": "z5oJpId2XbbF3GFYwUfSu", + "focus": 0.8821306114739372, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 39.82186952993038, + 72.69052173831238 + ], + [ + 1.9164065021227543, + 161.76709984169753 + ] + ] + }, + { + "type": "arrow", + "version": 2590, + "versionNonce": 1908331834, + "isDeleted": false, + "id": "nciWo80BNNH_RZLnFnrGo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1511.7179340841997, + "y": 374.5295815508076, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 54.34731728390557, + "height": 226.21839430789782, + "seed": 2066691878, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7F7nFJwk1ji2j2xAXC2JB", + "focus": -0.8866069980980835, + "gap": 1.817665529512169 + }, + "endBinding": { + "elementId": "_xFbQ3QId9JlRDsKtrjrV", + "focus": 0.9211020878976046, + "gap": 1.1114953960129696 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 54.34731728390557, + 132.9746376484103 + ], + [ + 28.8936833821258, + 226.21839430789782 + ] + ] + }, + { + "type": "arrow", + "version": 1048, + "versionNonce": 774878054, + "isDeleted": false, + "id": "2x-CAa7KCHh9e4TAzwJ50", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1130.0340357907291, + "y": 334.21077267549003, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 0.58253785095485, + "height": 40.78599387612809, + "seed": 302829798, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": 0.06541375823073861, + "gap": 3.677396617547288 + }, + "endBinding": { + "elementId": "a5EJE0Wm1YtG4AUOzNJNL", + "focus": -0.09598908991562294, + "gap": 3.1366461274183735 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -0.58253785095485, + 40.78599387612809 + ] + ] + }, + { + "type": "arrow", + "version": 2344, + "versionNonce": 1415847418, + "isDeleted": false, + "id": "M7n022YhLqyGdfkVsksB5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 945.0150208511559, + "y": 577.6924605065069, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 186.6682047806571, + "height": 138.5590478274704, + "seed": 1450157990, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "kSaEgKvdN9rZv0nRUOgk_", + "focus": 0.5191954425389479, + "gap": 2.315191749593396 + }, + "endBinding": { + "elementId": "a5EJE0Wm1YtG4AUOzNJNL", + "focus": -0.3207506299182041, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 186.6682047806571, + -138.5590478274704 + ] + ] + }, + { + "type": "arrow", + "version": 1195, + "versionNonce": 703616678, + "isDeleted": false, + "id": "sHJ5vG0q1UIZp7ZIwF3G4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 633.2002989689024, + "y": 305.83322293679953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 154.29147053022928, + "height": 187.28459840809944, + "seed": 1686211046, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xUPeHlfTY1YtIiTVCztCM", + "focus": 0.855443937596706, + "gap": 3.033611957183666 + }, + "endBinding": { + "elementId": "T1Q5RRFgS7opX8AHKN1h6", + "focus": 0.8656318157254521, + "gap": 1.541494661024558 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 154.29147053022928, + -187.28459840809944 + ] + ] + }, + { + "type": "arrow", + "version": 1614, + "versionNonce": 1826770618, + "isDeleted": false, + "id": "eTOCJ0p7MaoJdtkKQK8D9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 632.5942567045005, + "y": 307.51589260796356, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 158.67249744589014, + "height": 84.40468945077913, + "seed": 648173306, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xUPeHlfTY1YtIiTVCztCM", + "focus": 0.733590638391964, + "gap": 2.4275696927817307 + }, + "endBinding": { + "elementId": "lcG70iexy9r-E8T6NJYEb", + "focus": 0.7332951626034517, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 158.67249744589014, + -84.40468945077913 + ] + ] + }, + { + "type": "arrow", + "version": 1838, + "versionNonce": 70947302, + "isDeleted": false, + "id": "c7n4_dSPiq7N0VZ7xZdOB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 629.8790089483184, + "y": 344.4244199603751, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 162.55822468062001, + "height": 3.8668109218369295, + "seed": 1260989050, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": 0.23921257787803954, + "gap": 3.7123524541777897 + }, + "endBinding": { + "elementId": "GOwGCz4_Ro9K7OVFxd4D-", + "focus": 0.5669293112078886, + "gap": 2.2628396132491844 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 162.55822468062001, + 3.8668109218369295 + ] + ] + }, + { + "type": "arrow", + "version": 1627, + "versionNonce": 1608279930, + "isDeleted": false, + "id": "-XVBxvFgvUGaMjHogJ8Jt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 634.4406534978147, + "y": 343.17306031673934, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 157.15535589529497, + "height": 361.3642251920818, + "seed": 1084289318, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": -0.9737540836368952, + "gap": 8.273997003674026 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 157.15535589529497, + 361.3642251920818 + ] + ] + }, + { + "type": "arrow", + "version": 2633, + "versionNonce": 1746625830, + "isDeleted": false, + "id": "Sqf62Wfuo4PuJLDV-qQK7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1434.330283256826, + "y": 634.1332702636719, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 486.2280112522501, + "height": 186.2668914794922, + "seed": 177046970, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "_xFbQ3QId9JlRDsKtrjrV", + "focus": -0.24574950798236836, + "gap": 1 + }, + "endBinding": { + "elementId": "_kdQsugJOPXUQcWoarPTF", + "focus": 0.32580341447522004, + "gap": 5.00235745379473 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -228.73027715331045, + 148.06674194335938 + ], + [ + -486.2280112522501, + 186.2668914794922 + ] + ] + }, + { + "type": "arrow", + "version": 2331, + "versionNonce": 1167419450, + "isDeleted": false, + "id": "Z8f-MCMRgA3QW84VQaiui", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1058.409231330466, + "y": 409.4197974229981, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110.08379010309466, + "height": 44.62968168997173, + "seed": 910730298, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "a5EJE0Wm1YtG4AUOzNJNL", + "focus": -0.542582236441977, + "gap": 2.357288851825615 + }, + "endBinding": { + "elementId": "GOwGCz4_Ro9K7OVFxd4D-", + "focus": -0.5784916626647088, + "gap": 2.6253679851837433 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -110.08379010309466, + -44.62968168997173 + ] + ] + }, + { + "type": "arrow", + "version": 2092, + "versionNonce": 385863782, + "isDeleted": false, + "id": "usSe65QPgEdc-nWeDr8iw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 631.2555440266927, + "y": 340.60975638755957, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 214.2775885873258, + "height": 218.59117713773782, + "seed": 134282470, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": -0.7338780548763487, + "gap": 5.088887532552121 + }, + "endBinding": { + "elementId": "kSaEgKvdN9rZv0nRUOgk_", + "focus": 0.08686416090862145, + "gap": 1.3990573194291755 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 214.2775885873258, + 218.59117713773782 + ] + ] + }, + { + "type": "rectangle", + "version": 1719, + "versionNonce": 179174778, + "isDeleted": false, + "id": "gtd1Shr_ooL5e2iJcJbK2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1080.1666056315103, + "y": 575.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 135, + "height": 64, + "seed": 827060646, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Ml262uWmkRgqu7s2xPvcs" + }, + { + "id": "ggd1wHVUvb8hrIVRodg0R", + "type": "arrow" + }, + { + "id": "MhApe13rZPELr24EmV5_2", + "type": "arrow" + }, + { + "id": "EQVSrUzriidaEZzkFRWmh", + "type": "arrow" + }, + { + "id": "kF3r9MJFGFqycvwy8__Se", + "type": "arrow" + } + ], + "updated": 1672216273435, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1480, + "versionNonce": 481612710, + "isDeleted": false, + "id": "Ml262uWmkRgqu7s2xPvcs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1103.1666056315103, + "y": 582.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 89, + "height": 50, + "seed": 1323426746, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Event \nConsumer", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gtd1Shr_ooL5e2iJcJbK2", + "originalText": "Event Consumer" + }, + { + "type": "rectangle", + "version": 1828, + "versionNonce": 499261350, + "isDeleted": false, + "id": "HLzPja9a2sddDH-a8vavQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1077.566691080729, + "y": 694.5333455403646, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 135, + "height": 73, + "seed": 666959398, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "E4PFMIPUePC-b6y7fuVN6" + }, + { + "id": "mPxcHfV42jmIgufKKagIk", + "type": "arrow" + }, + { + "id": "e8oDSbxa_LpeVJTakGiCJ", + "type": "arrow" + }, + { + "id": "J3ROo2oU-x4Wjze4NQhnu", + "type": "arrow" + } + ], + "updated": 1672216282276, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1610, + "versionNonce": 57110246, + "isDeleted": false, + "id": "E4PFMIPUePC-b6y7fuVN6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1102.066691080729, + "y": 706.0333455403646, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 50, + "seed": 1869733690, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Event \nPublisher", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "HLzPja9a2sddDH-a8vavQ", + "originalText": "Event Publisher" + }, + { + "type": "arrow", + "version": 2122, + "versionNonce": 1547164282, + "isDeleted": false, + "id": "MhApe13rZPELr24EmV5_2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1116.501191249673, + "y": 640.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 170.25062434692495, + "height": 77.71360152813816, + "seed": 1767098406, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gtd1Shr_ooL5e2iJcJbK2", + "focus": -0.29836670789093744, + "gap": 1 + }, + "endBinding": { + "elementId": "LDY5c1SQCy2Dn7M5jcbwL", + "focus": 0.41064799527897117, + "gap": 5.150530281654255 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -170.25062434692495, + 77.71360152813816 + ] + ] + }, + { + "type": "arrow", + "version": 2331, + "versionNonce": 128229926, + "isDeleted": false, + "id": "mPxcHfV42jmIgufKKagIk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1075.7591208074996, + "y": 728.0752592335184, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129.441846527062, + "height": 2.1545663581337067, + "seed": 31927546, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "HLzPja9a2sddDH-a8vavQ", + "focus": 0.1093084561863197, + "gap": 1.8075702732294303 + }, + "endBinding": { + "elementId": "LDY5c1SQCy2Dn7M5jcbwL", + "focus": 0.09165715392964811, + "gap": 5.217237659343823 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -129.441846527062, + 2.1545663581337067 + ] + ] + }, + { + "type": "arrow", + "version": 1810, + "versionNonce": 738884410, + "isDeleted": false, + "id": "ggd1wHVUvb8hrIVRodg0R", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1077.5257263152903, + "y": 606.6306048993722, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 131.78419777925535, + "height": 10.350088516306414, + "seed": 1743986982, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314722, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gtd1Shr_ooL5e2iJcJbK2", + "focus": -0.1292705996885224, + "gap": 2.64087931621998 + }, + "endBinding": { + "elementId": "kSaEgKvdN9rZv0nRUOgk_", + "focus": -0.013581967473402585, + "gap": 3.0416994344724344 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + -131.78419777925535, + -10.350088516306414 + ] + ] + }, + { + "type": "arrow", + "version": 1907, + "versionNonce": 1280929126, + "isDeleted": false, + "id": "EQVSrUzriidaEZzkFRWmh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 629.8195253268836, + "y": 307.17330210699197, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 512.1143235830093, + "height": 264.5241997490924, + "seed": 2001167462, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": -1.422363297883743, + "gap": 14.493435767357028 + }, + "endBinding": { + "elementId": "gtd1Shr_ooL5e2iJcJbK2", + "focus": 0.5668461021229645, + "gap": 3.635831477248985 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 218.18044415553823, + 147.6267162035549 + ], + [ + 512.1143235830093, + 264.5241997490924 + ] + ] + }, + { + "type": "arrow", + "version": 1392, + "versionNonce": 1380647930, + "isDeleted": false, + "id": "e8oDSbxa_LpeVJTakGiCJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1214.6614184857415, + "y": 307.2921022191696, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 43.760578533690705, + "height": 394.7445766910064, + "seed": 892620602, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": -0.9637489986605797, + "gap": 3.6948250612624634 + }, + "endBinding": { + "elementId": "HLzPja9a2sddDH-a8vavQ", + "focus": 0.7892784062251003, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 41.87195757220093, + 219.44123518317417 + ], + [ + -1.888620961489778, + 394.7445766910064 + ] + ] + }, + { + "type": "rectangle", + "version": 1513, + "versionNonce": 297913722, + "isDeleted": false, + "id": "Eh65kajfLZqE3FwUXG0tu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 559.1666564941406, + "y": 321.666737874349, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 67, + "height": 35, + "seed": 763474406, + "groupIds": [], + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "h7eCVBFa-QUOxY45zhUlz" + }, + { + "id": "eTOCJ0p7MaoJdtkKQK8D9", + "type": "arrow" + }, + { + "id": "c7n4_dSPiq7N0VZ7xZdOB", + "type": "arrow" + }, + { + "id": "usSe65QPgEdc-nWeDr8iw", + "type": "arrow" + }, + { + "id": "-XVBxvFgvUGaMjHogJ8Jt", + "type": "arrow" + }, + { + "id": "ttLVR7BUFFe9RjD8wHxLq", + "type": "arrow" + }, + { + "id": "ns7fWv67-cNzmOI9UM43M", + "type": "arrow" + }, + { + "id": "EQVSrUzriidaEZzkFRWmh", + "type": "arrow" + } + ], + "updated": 1672215314723, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1196, + "versionNonce": 1042767654, + "isDeleted": false, + "id": "h7eCVBFa-QUOxY45zhUlz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 579.1666564941406, + "y": 326.666737874349, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 27, + "height": 25, + "seed": 706168698, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "DI", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Eh65kajfLZqE3FwUXG0tu", + "originalText": "DI" + }, + { + "type": "arrow", + "version": 1781, + "versionNonce": 568200762, + "isDeleted": false, + "id": "ttLVR7BUFFe9RjD8wHxLq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 631.7705967019494, + "y": 346.3194733654459, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 159.0220008605727, + "height": 487.8666088775137, + "seed": 1480647078, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": -0.9379731156448109, + "gap": 5.603940207808819 + }, + "endBinding": { + "elementId": "_kdQsugJOPXUQcWoarPTF", + "focus": -0.9764231444956797, + "gap": 1.3073169882591174 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 159.0220008605727, + 487.8666088775137 + ] + ] + }, + { + "type": "arrow", + "version": 1880, + "versionNonce": 2105313894, + "isDeleted": false, + "id": "ns7fWv67-cNzmOI9UM43M", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 633.8327915216606, + "y": 342.3499692635597, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 421.2249523824429, + "height": 45.749947901254984, + "seed": 115658790, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Eh65kajfLZqE3FwUXG0tu", + "focus": 0.4626850124761559, + "gap": 7.666135027519999 + }, + "endBinding": { + "elementId": "AoiMmO7zoZiQc3Hk1c8br", + "focus": -0.12466556180658189, + "gap": 4.908849520375497 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 238.96722678888625, + -45.749947901254984 + ], + [ + 421.2249523824429, + -39.59779789929695 + ] + ] + }, + { + "type": "arrow", + "version": 2349, + "versionNonce": 238302970, + "isDeleted": false, + "id": "JsWyfb-aCrHiBIJEsJkKO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1512.3254963117295, + "y": 369.9344876548315, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 47.832299553603434, + "height": 93.77245241318593, + "seed": 2049550182, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7F7nFJwk1ji2j2xAXC2JB", + "focus": -0.8162839254624032, + "gap": 2.4252277570419665 + }, + "endBinding": { + "elementId": "z5oJpId2XbbF3GFYwUfSu", + "focus": 0.8601197783407802, + "gap": 3.098089165878264 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 47.832299553603434, + 54.85902695618813 + ], + [ + 2.6727393385237974, + 93.77245241318593 + ] + ] + }, + { + "type": "arrow", + "version": 3471, + "versionNonce": 955436154, + "isDeleted": false, + "id": "J3ROo2oU-x4Wjze4NQhnu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1214.0921202365753, + "y": 725.3089686401355, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 149.13022184532997, + "height": 191.3267964047393, + "seed": 1141581286, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672216291703, + "link": null, + "locked": false, + "startBinding": { + "elementId": "HLzPja9a2sddDH-a8vavQ", + "focus": 0.5367481483127559, + "gap": 1.5254291558462683 + }, + "endBinding": { + "elementId": "xDGgd2jaq3hl7KHH1o5FW", + "focus": 0.7732905637906835, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 74.08228165159153, + -57.187001296731864 + ], + [ + 149.13022184532997, + -191.3267964047393 + ] + ] + }, + { + "type": "arrow", + "version": 3482, + "versionNonce": 200170170, + "isDeleted": false, + "id": "kF3r9MJFGFqycvwy8__Se", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1217.0358116693021, + "y": 600.7006858774257, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 146.9959931723963, + "height": 74.56810196611968, + "seed": 2046337914, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672216286048, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gtd1Shr_ooL5e2iJcJbK2", + "focus": 0.5638816771936186, + "gap": 1.8692060377918551 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 64.32025111386156, + -50.69831438073629 + ], + [ + 146.9959931723963, + -74.56810196611968 + ] + ] + }, + { + "type": "text", + "version": 465, + "versionNonce": 1991233594, + "isDeleted": false, + "id": "bHeNLnabBEq9JAAZ5dFsz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 303.29445864146356, + "y": 228.1404084670467, + "strokeColor": "#000000", + "backgroundColor": "white", + "width": 45, + "height": 20, + "seed": 2045440422, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672216064575, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "Users", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Users" + }, + { + "type": "line", + "version": 1003, + "versionNonce": 893010042, + "isDeleted": false, + "id": "T6tnNOpTUozh4s6xHiKnX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 327.8675042597705, + "y": 194.26049848853603, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 35.7945753534266, + "height": 31.874041389322468, + "seed": 598926266, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.008992439583778, + -21.121352727864227 + ], + [ + 17.181396169644806, + -31.874041389322464 + ], + [ + 30.353799899705802, + -23.425500298176694 + ], + [ + 35.7945753534266, + -2.1931848303946246 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1040, + "versionNonce": 48471078, + "isDeleted": false, + "id": "ZnYMyVBW6syoF47FpXx7Q", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 286.9895841363602, + "y": 197.43811562153786, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 35.7945753534266, + "height": 31.874041389322468, + "seed": 1344916710, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.008992439583778, + -21.121352727864227 + ], + [ + 17.181396169644806, + -31.874041389322464 + ], + [ + 30.353799899705802, + -23.425500298176694 + ], + [ + 35.7945753534266, + -2.1931848303946246 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 719, + "versionNonce": 1137713466, + "isDeleted": false, + "id": "yxu4sXv5n9hRn-fQBXNe2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 336.7266732741898, + "y": 146.24681697163206, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 18.32682258095445, + "height": 16.035969758335195, + "seed": 2043829370, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 753, + "versionNonce": 967386982, + "isDeleted": false, + "id": "eWUZyli7U-S4EZwSRpgld", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 295.9130768527162, + "y": 149.38632438866816, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 18.32682258095445, + "height": 16.035969758335195, + "seed": 141246502, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 935, + "versionNonce": 1852566010, + "isDeleted": false, + "id": "QnbwblGcFcrUzSQNzHhDt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 306.4494426369112, + "y": 201.16016546052452, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 38.792802704999104, + "height": 34.54387674160783, + "seed": 1996799290, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.344793902959898, + -22.890520732390733 + ], + [ + 18.6205452983996, + -34.54387674160783 + ], + [ + 32.89629669383927, + -25.387668448651468 + ], + [ + 38.792802704999104, + -2.3768905087164747 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 655, + "versionNonce": 1847282342, + "isDeleted": false, + "id": "1JH8vGspo6tqTipSCd6tD", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 316.0843193734478, + "y": 148.97453582746388, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 19.86191498495955, + "height": 17.379175611839685, + "seed": 1989954406, + "groupIds": [ + "Px7XrMuYUoFFvBIRMDPup" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 774, + "versionNonce": 202414778, + "isDeleted": false, + "id": "skadbKgF7LIZIR-BH6I0u", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 381.5110308934776, + "y": 177.98686296163294, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 94.0596735294987, + "height": 0, + "seed": 1096455590, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1672215314723, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "t5eymjS1ib5XXyf_H28XX", + "focus": -0.04552401017125005, + "gap": 1.9959052775445798 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 94.0596735294987, + 0 + ] + ] + }, + { + "id": "aykoAy_0tQWltdxXir81l", + "type": "arrow", + "x": 290.8445066421755, + "y": 718.5746581062436, + "width": 90.66660563151038, + "height": 0, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "seed": 1531460730, + "version": 325, + "versionNonce": 701530918, + "isDeleted": false, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "triangle" + }, + { + "id": "I832TObEszhQCAVYmXXX9", + "type": "text", + "x": 408.17777894035237, + "y": 704.2412027025978, + "width": 89, + "height": 25, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "seed": 2137969850, + "version": 268, + "versionNonce": 2076781114, + "isDeleted": false, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "text": "Configure", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Configure" + }, + { + "type": "arrow", + "version": 393, + "versionNonce": 1616981606, + "isDeleted": false, + "id": "e8rZDK3ezcWriNAbe8pSp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 291.95644115068285, + "y": 756.4080728197853, + "strokeColor": "#364fc7", + "backgroundColor": "#ced4da", + "width": 90.66660563151038, + "height": 0, + "seed": 570308730, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ] + }, + { + "type": "text", + "version": 347, + "versionNonce": 878433018, + "isDeleted": false, + "id": "q64o2vrpSMDSpc9f2e9Qc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 407.9564004605785, + "y": 743.4079304044208, + "strokeColor": "#364fc7", + "backgroundColor": "#ced4da", + "width": 118, + "height": 25, + "seed": 779822118, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Inbound call", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Inbound call" + }, + { + "type": "arrow", + "version": 422, + "versionNonce": 775582118, + "isDeleted": false, + "id": "zT-A3BWm60UQkpBGFUVui", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 291.4396879539663, + "y": 795.0746581062435, + "strokeColor": "#a61e4d", + "backgroundColor": "#ced4da", + "width": 90.66660563151038, + "height": 0, + "seed": 395547962, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ] + }, + { + "type": "text", + "version": 393, + "versionNonce": 1041982394, + "isDeleted": false, + "id": "EKVeXm4MKokvrlwcK6wCW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 407.43964726386207, + "y": 782.074515690879, + "strokeColor": "#a61e4d", + "backgroundColor": "#ced4da", + "width": 71, + "height": 25, + "seed": 218559334, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Wire up", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Wire up" + }, + { + "type": "arrow", + "version": 473, + "versionNonce": 1317620966, + "isDeleted": false, + "id": "mM4VnRHR68hojgunB1ckA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 286.95006033096706, + "y": 831.4080728197853, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 90.66660563151038, + "height": 0, + "seed": 581975738, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ] + }, + { + "type": "text", + "version": 452, + "versionNonce": 858446970, + "isDeleted": false, + "id": "DWeu3IBNwJgwZep8yQL2G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 402.9500196408628, + "y": 818.4079304044208, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 95, + "height": 25, + "seed": 1317249510, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Implement", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Implement" + }, + { + "type": "arrow", + "version": 525, + "versionNonce": 249217062, + "isDeleted": false, + "id": "5tQuZjDnl1z4A51GYjV-d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 289.1081008743645, + "y": 869.7414061531185, + "strokeColor": "#e67700", + "backgroundColor": "#ced4da", + "width": 90.66660563151038, + "height": 0, + "seed": 1732410406, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ] + }, + { + "type": "text", + "version": 543, + "versionNonce": 1900887354, + "isDeleted": false, + "id": "GZYvcCWRx3_4NQR6ve8nv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 405.10806018426024, + "y": 856.741263737754, + "strokeColor": "#e67700", + "backgroundColor": "#ced4da", + "width": 118, + "height": 25, + "seed": 21971258, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "domain task", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "domain task" + }, + { + "type": "arrow", + "version": 581, + "versionNonce": 52153190, + "isDeleted": false, + "id": "XDxFB1RbPK0nb_E6bnYKp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 285.19815119481586, + "y": 907.408032129681, + "strokeColor": "#087f5b", + "backgroundColor": "#ced4da", + "width": 90.66660563151038, + "height": 0, + "seed": 2095750374, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "triangle", + "points": [ + [ + 0, + 0 + ], + [ + 90.66660563151038, + 0 + ] + ] + }, + { + "type": "text", + "version": 569, + "versionNonce": 635799034, + "isDeleted": false, + "id": "blrsSDGTyNbmPpvPU66d5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 401.1981105047116, + "y": 894.4078897143165, + "strokeColor": "#087f5b", + "backgroundColor": "#ced4da", + "width": 100, + "height": 25, + "seed": 306613370, + "groupIds": [ + "I2foZqJVqcHagmdKc06Z8" + ], + "roundness": null, + "boundElements": null, + "updated": 1672215958377, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Use event", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Use event" + }, + { + "id": "OMwSw77CA_ru0P8nHVJXZ", + "type": "text", + "x": 1151.5110105484255, + "y": -57.758782038612594, + "width": 433, + "height": 25, + "angle": 0, + "strokeColor": "#d9480f", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 864712550, + "version": 488, + "versionNonce": 1737473446, + "isDeleted": false, + "boundElements": null, + "updated": 1672215878405, + "link": null, + "locked": false, + "text": "https://github.com/thangchung/go-coffeeshop", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "https://github.com/thangchung/go-coffeeshop" + }, + { + "id": "sG93yUYvKWnD_D1jPQ1EH", + "type": "text", + "x": 476.17786032056097, + "y": -7.758792211138825, + "width": 203, + "height": 25, + "angle": 0, + "strokeColor": "#862e9c", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 1652365926, + "version": 228, + "versionNonce": 1166335334, + "isDeleted": false, + "boundElements": null, + "updated": 1672215864516, + "link": null, + "locked": false, + "text": "Framework & Drivers", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Framework & Drivers" + }, + { + "type": "text", + "version": 414, + "versionNonce": 1468925114, + "isDeleted": false, + "id": "ZFyIXAeQZwNAivh1YVDbV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 777.0110308934773, + "y": 886.741212875124, + "strokeColor": "#862e9c", + "backgroundColor": "#ced4da", + "width": 196, + "height": 25, + "seed": 1270006246, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215868761, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Interface Adapters", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Interface Adapters" + }, + { + "type": "text", + "version": 501, + "versionNonce": 664886650, + "isDeleted": false, + "id": "JG4HrciIcHRsknWtV4kjs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1033.3443642268112, + "y": 889.4077879890561, + "strokeColor": "#862e9c", + "backgroundColor": "#ced4da", + "width": 258, + "height": 25, + "seed": 1411894650, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215871905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Application Business Rules", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Application Business Rules" + }, + { + "type": "text", + "version": 600, + "versionNonce": 1967889978, + "isDeleted": false, + "id": "BwCpDw0bk30414qvMhQDC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1334.177677215092, + "y": 886.0745563809834, + "strokeColor": "#862e9c", + "backgroundColor": "#ced4da", + "width": 254, + "height": 25, + "seed": 1128856250, + "groupIds": [], + "roundness": null, + "boundElements": null, + "updated": 1672215874693, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Enterprise Business Rules", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Enterprise Business Rules" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/internal/barista/app/wire.go b/internal/barista/app/wire.go index 1f3349f..fa0a45b 100644 --- a/internal/barista/app/wire.go +++ b/internal/barista/app/wire.go @@ -5,6 +5,7 @@ package app import ( "github.com/google/wire" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -17,13 +18,29 @@ func InitApp( cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, -) (*App, error) { +) (*App, func(), error) { panic(wire.Build( New, - postgres.DBEngineSet, - rabbitmq.RabbitMQSet, + dbEngineFunc, + rabbitMQFunc, pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, eventhandlers.BaristaOrderedEventHandlerSet, )) } + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil +} diff --git a/internal/barista/app/wire_gen.go b/internal/barista/app/wire_gen.go index dcb390e..970c273 100644 --- a/internal/barista/app/wire_gen.go +++ b/internal/barista/app/wire_gen.go @@ -7,6 +7,7 @@ package app import ( + "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/barista/config" "github.com/thangchung/go-coffeeshop/internal/barista/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -17,24 +18,50 @@ import ( // Injectors from wire.go: -func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, error) { - dbEngine, err := postgres.NewPostgresDB(dbConnStr) +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, func(), error) { + dbEngine, cleanup, err := dbEngineFunc(dbConnStr) if err != nil { - return nil, err + return nil, nil, err } - connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + connection, cleanup2, err := rabbitMQFunc(rabbitMQConnStr) if err != nil { - return nil, err + cleanup() + return nil, nil, err } eventPublisher, err := publisher.NewPublisher(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } eventConsumer, err := consumer.NewConsumer(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } baristaOrderedEventHandler := eventhandlers.NewBaristaOrderedEventHandler(dbEngine, eventPublisher) app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, baristaOrderedEventHandler) - return app, nil + return app, func() { + cleanup2() + cleanup() + }, nil +} + +// wire.go: + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp091.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil } diff --git a/internal/counter/app/app.go b/internal/counter/app/app.go index 4d36352..375173d 100644 --- a/internal/counter/app/app.go +++ b/internal/counter/app/app.go @@ -9,7 +9,7 @@ import ( "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" ordersUC "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" - sharedevents "github.com/thangchung/go-coffeeshop/internal/pkg/event" + shared "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" pkgPublisher "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" @@ -24,8 +24,8 @@ type App struct { Publisher pkgPublisher.EventPublisher Consumer pkgConsumer.EventConsumer - BaristaOrderPub sharedevents.BaristaEventPublisher - KitchenOrderPub sharedevents.KitchenEventPublisher + BaristaOrderPub ordersUC.BaristaEventPublisher + KitchenOrderPub ordersUC.KitchenEventPublisher ProductDomainSvc domain.ProductDomainService UC ordersUC.UseCase @@ -42,8 +42,8 @@ func New( publisher pkgPublisher.EventPublisher, consumer pkgConsumer.EventConsumer, - baristaOrderPub sharedevents.BaristaEventPublisher, - kitchenOrderPub sharedevents.KitchenEventPublisher, + baristaOrderPub ordersUC.BaristaEventPublisher, + kitchenOrderPub ordersUC.KitchenEventPublisher, productDomainSvc domain.ProductDomainService, uc ordersUC.UseCase, counterGRPCServer gen.CounterServiceServer, @@ -78,7 +78,7 @@ func (a *App) Worker(ctx context.Context, messages <-chan amqp.Delivery) { switch delivery.Type { case "barista-order-updated": - var payload sharedevents.BaristaOrderUpdated + var payload shared.BaristaOrderUpdated err := json.Unmarshal(delivery.Body, &payload) if err != nil { @@ -100,7 +100,7 @@ func (a *App) Worker(ctx context.Context, messages <-chan amqp.Delivery) { } } case "kitchen-order-updated": - var payload sharedevents.KitchenOrderUpdated + var payload shared.KitchenOrderUpdated err := json.Unmarshal(delivery.Body, &payload) if err != nil { diff --git a/internal/counter/app/wire.go b/internal/counter/app/wire.go index 9aba0e9..5ed0b98 100644 --- a/internal/counter/app/wire.go +++ b/internal/counter/app/wire.go @@ -5,13 +5,14 @@ package app import ( "github.com/google/wire" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app/router" "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" + "github.com/thangchung/go-coffeeshop/internal/counter/infras" infrasGRPC "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" ordersUC "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" pkgConsumer "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" @@ -24,16 +25,16 @@ func InitApp( dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, grpcServer *grpc.Server, -) (*App, error) { +) (*App, func(), error) { panic(wire.Build( New, - postgres.DBEngineSet, - rabbitmq.RabbitMQSet, + dbEngineFunc, + rabbitMQFunc, pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, - event.BaristaEventPublisherSet, - event.KitchenEventPublisherSet, + infras.BaristaEventPublisherSet, + infras.KitchenEventPublisherSet, infrasGRPC.ProductGRPCClientSet, router.CounterGRPCServerSet, repo.RepositorySet, @@ -42,3 +43,19 @@ func InitApp( handlers.KitchenOrderUpdatedEventHandlerSet, )) } + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil +} diff --git a/internal/counter/app/wire_gen.go b/internal/counter/app/wire_gen.go index 731cc24..4ca531e 100644 --- a/internal/counter/app/wire_gen.go +++ b/internal/counter/app/wire_gen.go @@ -7,13 +7,14 @@ package app import ( + "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/counter/config" "github.com/thangchung/go-coffeeshop/internal/counter/app/router" "github.com/thangchung/go-coffeeshop/internal/counter/events/handlers" + "github.com/thangchung/go-coffeeshop/internal/counter/infras" grpc2 "github.com/thangchung/go-coffeeshop/internal/counter/infras/grpc" "github.com/thangchung/go-coffeeshop/internal/counter/infras/repo" "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" "github.com/thangchung/go-coffeeshop/pkg/postgres" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/consumer" @@ -23,28 +24,35 @@ import ( // Injectors from wire.go: -func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, grpcServer *grpc.Server) (*App, error) { - dbEngine, err := postgres.NewPostgresDB(dbConnStr) +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, grpcServer *grpc.Server) (*App, func(), error) { + dbEngine, cleanup, err := dbEngineFunc(dbConnStr) if err != nil { - return nil, err + return nil, nil, err } - connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + connection, cleanup2, err := rabbitMQFunc(rabbitMQConnStr) if err != nil { - return nil, err + cleanup() + return nil, nil, err } eventPublisher, err := publisher.NewPublisher(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } eventConsumer, err := consumer.NewConsumer(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } - baristaEventPublisher := event.NewBaristaEventPublisher(eventPublisher) - kitchenEventPublisher := event.NewKitchenEventPublisher(eventPublisher) + baristaEventPublisher := infras.NewBaristaEventPublisher(eventPublisher) + kitchenEventPublisher := infras.NewKitchenEventPublisher(eventPublisher) productDomainService, err := grpc2.NewGRPCProductClient(cfg) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } orderRepo := repo.NewOrderRepo(dbEngine) useCase := orders.NewUseCase(orderRepo, productDomainService, baristaEventPublisher, kitchenEventPublisher) @@ -52,5 +60,26 @@ func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnSt baristaOrderUpdatedEventHandler := handlers.NewBaristaOrderUpdatedEventHandler(orderRepo) kitchenOrderUpdatedEventHandler := handlers.NewKitchenOrderUpdatedEventHandler(orderRepo) app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, baristaEventPublisher, kitchenEventPublisher, productDomainService, useCase, counterServiceServer, baristaOrderUpdatedEventHandler, kitchenOrderUpdatedEventHandler) - return app, nil + return app, func() { + cleanup2() + cleanup() + }, nil +} + +// wire.go: + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp091.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil } diff --git a/internal/counter/domain/interfaces.go b/internal/counter/domain/interfaces.go index 01a924a..51173fa 100644 --- a/internal/counter/domain/interfaces.go +++ b/internal/counter/domain/interfaces.go @@ -2,18 +2,9 @@ package domain import ( "context" - - "github.com/google/uuid" ) type ( - OrderRepo interface { - GetAll(context.Context) ([]*Order, error) - GetByID(context.Context, uuid.UUID) (*Order, error) - Create(context.Context, *Order) error - Update(context.Context, *Order) (*Order, error) - } - ProductDomainService interface { GetItemsByType(context.Context, *PlaceOrderModel, bool) ([]*ItemModel, error) } diff --git a/internal/counter/events/handlers/barista_order_updated.go b/internal/counter/events/handlers/barista_order_updated.go index aa1f72e..c0c34be 100644 --- a/internal/counter/events/handlers/barista_order_updated.go +++ b/internal/counter/events/handlers/barista_order_updated.go @@ -5,20 +5,20 @@ import ( "github.com/google/wire" "github.com/pkg/errors" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) type baristaOrderUpdatedEventHandler struct { - orderRepo domain.OrderRepo + orderRepo orders.OrderRepo } var _ events.BaristaOrderUpdatedEventHandler = (*baristaOrderUpdatedEventHandler)(nil) var BaristaOrderUpdatedEventHandlerSet = wire.NewSet(NewBaristaOrderUpdatedEventHandler) -func NewBaristaOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.BaristaOrderUpdatedEventHandler { +func NewBaristaOrderUpdatedEventHandler(orderRepo orders.OrderRepo) events.BaristaOrderUpdatedEventHandler { return &baristaOrderUpdatedEventHandler{ orderRepo: orderRepo, } diff --git a/internal/counter/events/handlers/kitchen_order_updated.go b/internal/counter/events/handlers/kitchen_order_updated.go index 1295930..ab33b01 100644 --- a/internal/counter/events/handlers/kitchen_order_updated.go +++ b/internal/counter/events/handlers/kitchen_order_updated.go @@ -5,20 +5,20 @@ import ( "github.com/google/wire" "github.com/pkg/errors" - "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/events" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" "github.com/thangchung/go-coffeeshop/internal/pkg/event" ) type kitchenOrderUpdatedEventHandler struct { - orderRepo domain.OrderRepo + orderRepo orders.OrderRepo } var _ events.KitchenOrderUpdatedEventHandler = (*kitchenOrderUpdatedEventHandler)(nil) var KitchenOrderUpdatedEventHandlerSet = wire.NewSet(NewKitchenOrderUpdatedEventHandler) -func NewKitchenOrderUpdatedEventHandler(orderRepo domain.OrderRepo) events.KitchenOrderUpdatedEventHandler { +func NewKitchenOrderUpdatedEventHandler(orderRepo orders.OrderRepo) events.KitchenOrderUpdatedEventHandler { return &kitchenOrderUpdatedEventHandler{ orderRepo: orderRepo, } diff --git a/internal/pkg/event/publishers.go b/internal/counter/infras/publishers.go similarity index 54% rename from internal/pkg/event/publishers.go rename to internal/counter/infras/publishers.go index 6813a84..a8581dc 100644 --- a/internal/pkg/event/publishers.go +++ b/internal/counter/infras/publishers.go @@ -1,9 +1,10 @@ -package event +package infras import ( "context" "github.com/google/wire" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" ) @@ -12,23 +13,16 @@ var ( KitchenEventPublisherSet = wire.NewSet(NewKitchenEventPublisher) ) -type BaristaEventPublisher interface { - Configure(...publisher.Option) - CloseChan() - Publish(context.Context, []byte, string) error -} - -type KitchenEventPublisher interface { - Configure(...publisher.Option) - CloseChan() - Publish(context.Context, []byte, string) error -} - -type baristaEventPublisher struct { - pub publisher.EventPublisher -} +type ( + baristaEventPublisher struct { + pub publisher.EventPublisher + } + kitchenEventPublisher struct { + pub publisher.EventPublisher + } +) -func NewBaristaEventPublisher(pub publisher.EventPublisher) BaristaEventPublisher { +func NewBaristaEventPublisher(pub publisher.EventPublisher) orders.BaristaEventPublisher { return &baristaEventPublisher{ pub: pub, } @@ -38,19 +32,11 @@ func (p *baristaEventPublisher) Configure(opts ...publisher.Option) { p.pub.Configure(opts...) } -func (p *baristaEventPublisher) CloseChan() { - p.pub.CloseChan() -} - func (p *baristaEventPublisher) Publish(ctx context.Context, body []byte, contentType string) error { return p.pub.Publish(ctx, body, contentType) } -type kitchenEventPublisher struct { - pub publisher.EventPublisher -} - -func NewKitchenEventPublisher(pub publisher.EventPublisher) KitchenEventPublisher { +func NewKitchenEventPublisher(pub publisher.EventPublisher) orders.KitchenEventPublisher { return &kitchenEventPublisher{ pub: pub, } @@ -60,10 +46,6 @@ func (p *kitchenEventPublisher) Configure(opts ...publisher.Option) { p.pub.Configure(opts...) } -func (p *kitchenEventPublisher) CloseChan() { - p.pub.CloseChan() -} - func (p *kitchenEventPublisher) Publish(ctx context.Context, body []byte, contentType string) error { return p.pub.Publish(ctx, body, contentType) } diff --git a/internal/counter/infras/repo/orders_postgres.go b/internal/counter/infras/repo/orders_postgres.go index 79bd11c..85b4d2f 100644 --- a/internal/counter/infras/repo/orders_postgres.go +++ b/internal/counter/infras/repo/orders_postgres.go @@ -13,6 +13,7 @@ import ( "github.com/samber/lo" "github.com/thangchung/go-coffeeshop/internal/counter/domain" "github.com/thangchung/go-coffeeshop/internal/counter/infras/postgresql" + "github.com/thangchung/go-coffeeshop/internal/counter/usecases/orders" shared "github.com/thangchung/go-coffeeshop/internal/pkg/shared_kernel" "github.com/thangchung/go-coffeeshop/pkg/postgres" ) @@ -23,11 +24,11 @@ type orderRepo struct { pg postgres.DBEngine } -var _ domain.OrderRepo = (*orderRepo)(nil) +var _ orders.OrderRepo = (*orderRepo)(nil) var RepositorySet = wire.NewSet(NewOrderRepo) -func NewOrderRepo(pg postgres.DBEngine) domain.OrderRepo { +func NewOrderRepo(pg postgres.DBEngine) orders.OrderRepo { return &orderRepo{pg: pg} } diff --git a/internal/counter/usecases/orders/interfaces.go b/internal/counter/usecases/orders/interfaces.go index 5f4c191..b899b8f 100644 --- a/internal/counter/usecases/orders/interfaces.go +++ b/internal/counter/usecases/orders/interfaces.go @@ -3,10 +3,29 @@ package orders import ( "context" + "github.com/google/uuid" "github.com/thangchung/go-coffeeshop/internal/counter/domain" + "github.com/thangchung/go-coffeeshop/pkg/rabbitmq/publisher" ) type ( + OrderRepo interface { + GetAll(context.Context) ([]*domain.Order, error) + GetByID(context.Context, uuid.UUID) (*domain.Order, error) + Create(context.Context, *domain.Order) error + Update(context.Context, *domain.Order) (*domain.Order, error) + } + + BaristaEventPublisher interface { + Configure(...publisher.Option) + Publish(context.Context, []byte, string) error + } + + KitchenEventPublisher interface { + Configure(...publisher.Option) + Publish(context.Context, []byte, string) error + } + UseCase interface { GetListOrderFulfillment(context.Context) ([]*domain.Order, error) PlaceOrder(context.Context, *domain.PlaceOrderModel) error diff --git a/internal/counter/usecases/orders/service.go b/internal/counter/usecases/orders/service.go index 9c2abde..a688f82 100644 --- a/internal/counter/usecases/orders/service.go +++ b/internal/counter/usecases/orders/service.go @@ -8,15 +8,14 @@ import ( "github.com/google/wire" "github.com/pkg/errors" "github.com/thangchung/go-coffeeshop/internal/counter/domain" - "github.com/thangchung/go-coffeeshop/internal/pkg/event" "golang.org/x/exp/slog" ) type usecase struct { - orderRepo domain.OrderRepo + orderRepo OrderRepo productDomainSvc domain.ProductDomainService - baristaEventPub event.BaristaEventPublisher - kitchenEventPub event.KitchenEventPublisher + baristaEventPub BaristaEventPublisher + kitchenEventPub KitchenEventPublisher } var _ UseCase = (*usecase)(nil) @@ -24,10 +23,10 @@ var _ UseCase = (*usecase)(nil) var UseCaseSet = wire.NewSet(NewUseCase) func NewUseCase( - orderRepo domain.OrderRepo, + orderRepo OrderRepo, productDomainSvc domain.ProductDomainService, - baristaEventPub event.BaristaEventPublisher, - kitchenEventPub event.KitchenEventPublisher, + baristaEventPub BaristaEventPublisher, + kitchenEventPub KitchenEventPublisher, ) UseCase { return &usecase{ orderRepo: orderRepo, @@ -40,7 +39,7 @@ func NewUseCase( func (uc *usecase) GetListOrderFulfillment(ctx context.Context) ([]*domain.Order, error) { entities, err := uc.orderRepo.GetAll(ctx) if err != nil { - return nil, fmt.Errorf("counterGRPCServer-GetListOrderFulfillment-g.orderRepo.GetAll: %w", err) + return nil, fmt.Errorf("orderRepo.GetAll: %w", err) } return entities, nil @@ -49,12 +48,12 @@ func (uc *usecase) GetListOrderFulfillment(ctx context.Context) ([]*domain.Order func (uc *usecase) PlaceOrder(ctx context.Context, model *domain.PlaceOrderModel) error { order, err := domain.CreateOrderFrom(ctx, model, uc.productDomainSvc) if err != nil { - return errors.Wrap(err, "usecase-domain.CreateOrderFrom") + return errors.Wrap(err, "domain.CreateOrderFrom") } err = uc.orderRepo.Create(ctx, order) if err != nil { - return errors.Wrap(err, "usecase-uc.orderRepo.Create") + return errors.Wrap(err, "orderRepo.Create") } slog.Debug("order created", "order", *order) diff --git a/internal/kitchen/app/wire.go b/internal/kitchen/app/wire.go index 4c7bf5d..742b2b0 100644 --- a/internal/kitchen/app/wire.go +++ b/internal/kitchen/app/wire.go @@ -5,6 +5,7 @@ package app import ( "github.com/google/wire" + amqp "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -17,13 +18,29 @@ func InitApp( cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr, -) (*App, error) { +) (*App, func(), error) { panic(wire.Build( New, - postgres.DBEngineSet, - rabbitmq.RabbitMQSet, + dbEngineFunc, + rabbitMQFunc, pkgPublisher.EventPublisherSet, pkgConsumer.EventConsumerSet, eventhandlers.KitchenOrderedEventHandlerSet, )) } + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil +} diff --git a/internal/kitchen/app/wire_gen.go b/internal/kitchen/app/wire_gen.go index 0905115..c3955cb 100644 --- a/internal/kitchen/app/wire_gen.go +++ b/internal/kitchen/app/wire_gen.go @@ -7,6 +7,7 @@ package app import ( + "github.com/rabbitmq/amqp091-go" "github.com/thangchung/go-coffeeshop/cmd/kitchen/config" "github.com/thangchung/go-coffeeshop/internal/kitchen/eventhandlers" "github.com/thangchung/go-coffeeshop/pkg/postgres" @@ -17,24 +18,50 @@ import ( // Injectors from wire.go: -func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, error) { - dbEngine, err := postgres.NewPostgresDB(dbConnStr) +func InitApp(cfg *config.Config, dbConnStr postgres.DBConnString, rabbitMQConnStr rabbitmq.RabbitMQConnStr) (*App, func(), error) { + dbEngine, cleanup, err := dbEngineFunc(dbConnStr) if err != nil { - return nil, err + return nil, nil, err } - connection, err := rabbitmq.NewRabbitMQConn(rabbitMQConnStr) + connection, cleanup2, err := rabbitMQFunc(rabbitMQConnStr) if err != nil { - return nil, err + cleanup() + return nil, nil, err } eventPublisher, err := publisher.NewPublisher(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } eventConsumer, err := consumer.NewConsumer(connection) if err != nil { - return nil, err + cleanup2() + cleanup() + return nil, nil, err } kitchenOrderedEventHandler := eventhandlers.NewKitchenOrderedEventHandler(dbEngine, eventPublisher) app := New(cfg, dbEngine, connection, eventPublisher, eventConsumer, kitchenOrderedEventHandler) - return app, nil + return app, func() { + cleanup2() + cleanup() + }, nil +} + +// wire.go: + +func dbEngineFunc(url postgres.DBConnString) (postgres.DBEngine, func(), error) { + db, err := postgres.NewPostgresDB(url) + if err != nil { + return nil, nil, err + } + return db, func() { db.Close() }, nil +} + +func rabbitMQFunc(url rabbitmq.RabbitMQConnStr) (*amqp091.Connection, func(), error) { + conn, err := rabbitmq.NewRabbitMQConn(url) + if err != nil { + return nil, nil, err + } + return conn, func() { conn.Close() }, nil } diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 29d0af9..ee2f8b3 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -5,7 +5,6 @@ import ( "log" "time" - "github.com/google/wire" "golang.org/x/exp/slog" ) @@ -25,8 +24,6 @@ type postgres struct { var _ DBEngine = (*postgres)(nil) -var DBEngineSet = wire.NewSet(NewPostgresDB) - func NewPostgresDB(url DBConnString) (DBEngine, error) { slog.Info("CONN", "connect string", url) diff --git a/pkg/rabbitmq/publisher/interfaces.go b/pkg/rabbitmq/publisher/interfaces.go index 4884180..ac97399 100644 --- a/pkg/rabbitmq/publisher/interfaces.go +++ b/pkg/rabbitmq/publisher/interfaces.go @@ -7,5 +7,4 @@ import ( type EventPublisher interface { Configure(...Option) EventPublisher Publish(context.Context, []byte, string) error - CloseChan() } diff --git a/pkg/rabbitmq/publisher/options.go b/pkg/rabbitmq/publisher/options.go index 665820d..61d9cda 100644 --- a/pkg/rabbitmq/publisher/options.go +++ b/pkg/rabbitmq/publisher/options.go @@ -1,21 +1,21 @@ package publisher -type Option func(*Publisher) +type Option func(*publisher) func ExchangeName(exchangeName string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.exchangeName = exchangeName } } func BindingKey(bindingKey string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.bindingKey = bindingKey } } func MessageTypeName(messageTypeName string) Option { - return func(p *Publisher) { + return func(p *publisher) { p.messageTypeName = messageTypeName } } diff --git a/pkg/rabbitmq/publisher/publisher.go b/pkg/rabbitmq/publisher/publisher.go index b2463b5..6e94ac7 100644 --- a/pkg/rabbitmq/publisher/publisher.go +++ b/pkg/rabbitmq/publisher/publisher.go @@ -21,14 +21,14 @@ const ( _messageTypeName = "ordered" ) -type Publisher struct { +type publisher struct { exchangeName, bindingKey string messageTypeName string amqpChan *amqp.Channel amqpConn *amqp.Connection } -var _ EventPublisher = (*Publisher)(nil) +var _ EventPublisher = (*publisher)(nil) var EventPublisherSet = wire.NewSet(NewPublisher) @@ -39,7 +39,7 @@ func NewPublisher(amqpConn *amqp.Connection) (EventPublisher, error) { } defer ch.Close() - pub := &Publisher{ + pub := &publisher{ amqpConn: amqpConn, amqpChan: ch, exchangeName: _exchangeName, @@ -50,7 +50,7 @@ func NewPublisher(amqpConn *amqp.Connection) (EventPublisher, error) { return pub, nil } -func (p *Publisher) Configure(opts ...Option) EventPublisher { +func (p *publisher) Configure(opts ...Option) EventPublisher { for _, opt := range opts { opt(p) } @@ -58,14 +58,7 @@ func (p *Publisher) Configure(opts ...Option) EventPublisher { return p } -// CloseChan Close messages chan. -func (p *Publisher) CloseChan() { - if err := p.amqpChan.Close(); err != nil { - slog.Error("failed to close chan", err) - } -} - -func (p *Publisher) PublishEvents(ctx context.Context, events []any) error { +func (p *publisher) PublishEvents(ctx context.Context, events []any) error { for _, e := range events { b, err := json.Marshal(e) if err != nil { @@ -82,7 +75,7 @@ func (p *Publisher) PublishEvents(ctx context.Context, events []any) error { } // Publish message. -func (p *Publisher) Publish(ctx context.Context, body []byte, contentType string) error { +func (p *publisher) Publish(ctx context.Context, body []byte, contentType string) error { ch, err := p.amqpConn.Channel() if err != nil { return errors.Wrap(err, "CreateChannel") diff --git a/pkg/rabbitmq/rabbitmq.go b/pkg/rabbitmq/rabbitmq.go index e68e24e..d9d375b 100644 --- a/pkg/rabbitmq/rabbitmq.go +++ b/pkg/rabbitmq/rabbitmq.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "github.com/google/wire" amqp "github.com/rabbitmq/amqp091-go" "golang.org/x/exp/slog" ) @@ -18,8 +17,6 @@ type RabbitMQConnStr string var ErrCannotConnectRabbitMQ = errors.New("cannot connect to rabbit") -var RabbitMQSet = wire.NewSet(NewRabbitMQConn) - func NewRabbitMQConn(rabbitMqURL RabbitMQConnStr) (*amqp.Connection, error) { var ( amqpConn *amqp.Connection