Giter Site home page Giter Site logo

threedotslabs / wild-workouts-go-ddd-example Goto Github PK

View Code? Open in Web Editor NEW
4.9K 4.9K 451.0 830 KB

Go DDD example application. Complete project to show how to apply DDD, Clean Architecture, and CQRS by practical refactoring.

Home Page: https://threedots.tech

License: MIT License

Makefile 1.94% Dockerfile 0.35% Shell 2.67% Go 90.23% HCL 4.80%
clean-architecture cqrs ddd firebase firestore gcp go golang google-cloud hexagonal-architecture refactoring serverless terraform

wild-workouts-go-ddd-example's People

Contributors

krzysztofreczek avatar m110 avatar maratori avatar orphail avatar roblaszczak avatar testwill avatar tullo avatar wcomnisky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wild-workouts-go-ddd-example's Issues

Pattern Saga

Hello again! I want to say again that I love your articles very much and look forward to updates.

My request is that you show an approach to the implementation of the saga pattern (orchestration or choreography). It would be interesting to see how this can be implemented with a clean architecture and your library Watermill.

Must.. for unmarshaling from repository

func UnmarshalTrainingFromDatabase(
uuid string,
userUUID string,
userName string,
trainingTime time.Time,
notes string,
canceled bool,
proposedNewTime time.Time,
moveProposedBy UserType,
) (*Training, error) {
tr, err := NewTraining(uuid, userUUID, userName, trainingTime)

Shouldn't it be adequate to use NewMustTraining() when unmarshaling from the repository?

In my reading that would support the notion that we should afford to assume from the domain that the repository does its job and does not get magically corrupted.

Include BDD in Test Architecture?

BDD is also a good part in tests. It focuses on business logic and may need cases from different layers in test pyramid. It tries to reduce the gap between business and development which also align with DDD.

Really hope to see BDD in this go example! Thanks

Request: Unit of Work pattern

Would be very interesting see your take on how to use the repository pattern when you need to make changes across different entities/domains.

I read about Unit of Work, that basically means implementing an intermediate in-memory structure that does trasaction-commit-rollback at the application level, without having a dependency on the underlying database.

External validation against rule: Always Keep a Valid State in the Memory

Hey there! Thanks for the work.
I've been utilizing your methodology in building my own project for near 2 years.

However I still have a question about this rule when external validation is needed: always keep a valid state in the memory :
Say if I have a big list of accounts, and they are organized in parent-children relationship, i.e. an account can be a top level account, or it can be child of a top level account.
I have this Account entity in domain layer and a NewAccount method which is supposed to create a valid Account entity:

type Account struct {
    id uuid.UUID
    parentAccountId uuid.UUID
    ...
}

By following the rule, I have to verify if parentAccountId is valid in the NewAccount method. But how? It requires to check database if this parentAccountId exists or not. Seems not possible in domain layer.

Right now I'm putting this check logic in repository layer, but seems a bit violating the rule : (

There are also other scenarios I can think of that maybe certain fields requires remote call to external system to valid - it requires services in application layer - it's not accessible from domain layer.

Any suggestions?

Thank you!

Saving Aggregate with entity list

Hello Guys,

Initially, I want to say "Thank you" for your articles and the book.

Problem

Updating message is the most blur process for me.

Context

I have Chat (Aggregate) and Message (Entity, which knows nothing about Chat).

When I send a message, I want to create Chat if it doesn't exist. Then, a Message is attached to the Chat; change observing and message saving are tricky for me.

I track domain events and can handle them to update Chat and Message. *sqlx.Tx is passed in an observer to save transactionality; however, it looks complicated and hard to optimize the command with a few similar events. For example, to mark a bunch of messages as read.

Another way is to compare an old value with a new one and generate SQL for updating.

Questions

  • Could you give me another way or advice, which way is better?
  • May I ask an approachable lib with an observable pattern?
  • Could you advise a lib for comparing? I saw go-cmp, but maybe there is a more convenient lib.
  • What do you think about additional repository argument ([]Option)? Should it be moved in context?

Code

// /domain/chat.go

// Key is an Identity Field
type Key struct {
    ID int64 // Artificial ID
    ProductID ItemID // Each product can have several chats 
    SellerID UserID
    BuyerID UserID
}

type Chat struct {
    Key
    IsDeleted bool
    CreatedAt time.Time
    DeletedAt time.Time
    messages []*Message
    events
}

func (c *Chat) SendMessage(m *Message) error {
    if err := m.Validation(); err != nil {
        return err
    }
    
    c.raise(SendMessageEvent{
        event:   event{chat: c},
        message: m,
    })
    
    c.messages = append(c.messages, m)

    return nil
}
// /domain/event.go

type Event interface {
    isEvent()
    Entity() *Chat
}

type events interface {
    Events() []Event
    raise(e Event)
}


type SendMessageEvent struct {
    Event
    message *Message
}
// /domain/message.go

type Message struct {
    Text      string
    AuthorID  UserID  
    ReplyID   int64      // if it is a reply on the previous message
    IsRead    bool   
    IsDeleted bool
    CreatedAt time.Time
    DeletedAt *time.Time
}
// /adapters/mysql_repository.go

type observable interface {
    Dispatch(event Event) error
}

type mysqlRepository struct{
	obs observer
}

func (c *mysqlRepository) getOrCreate(ctx context.Context, key chat.Key, opts ...chat.Option) (*chat.Chat, error) {
    ...
    var cfg config
    for _, opt := range opts {
        opt(&cfg)
    }

    if row.version != nil && cfg.version != 0 && cfg.version <= *row.version {
        return fmt.Errorf("chat is already updated: %w", chat.ErrDBUnRetryable)
    }
    ...
}

func (c *mysqlRepository) Update(
    ctx context.Context,
    key chat.Key,
    updateFn func(entity *chat.Chat) error,
    opts ...chat.Option,
) {
    // beginTransaction
    // defer closeTransaction

	entity, err := c.getOrCreate(ctx, key, opts) 
	if err != nil {
       return err
    }

    if err = sendFn(entity); err != nil {
        return err
    }

    if err = c.upsert(ctx, entity); err != nil {
        return err
    }

    // TODO MESSAGE SAVING

    return c.processEvents(ctx, tx, entity, opts)
}

func (c *mysqlRepository) processEvents(ctx context.Context, tx *sqlx.Tx, entity *chat.Chat, opts []chat.Option) (err error) {
    for _, e := range entity.Events() {
        if err = c.obs.Dispatch(e); err != nil {
            return err
        }
    }
    
    return nil
}

Couple of ideas

While continuing to work on ddd-gen (example) I had a couple of ideas / made a couple of choices worth sharing here:

  • Policeable interface to enforce access policies at the application level:

  • Move Repository to application layer (single aggregate per application)
  • Implement updateFn for Add and Remove as well, so that the domain does not need to know about the behaviour of the repository interface (that only works on an aggregate)
  • Pass Identifiables to the repository so that the auto generated code does not need to know too much about the commands data structures, while still verifying identifiability of the object (domain aggregate) on which the command should be executed.
    โ†’ Repo Interface

ID foreign keys directly from clients or not?

Prologue

Let's say I'm creating a simple invoice.

  • domain/invoice.go:
type Invoice struct {
	id        int
	createdAt time.Time
	updatedAt *time.Time
	date         time.Time
	note         *string
	number       int
	payed        bool
	amount       int
	customer     *Customer
	paymentType  *PaymentType
	tax          *Tax
	rows         []InvoiceRow
	// ... many others
}

func NewInvoice(
	id int,
	date time.Time,
	note *string,
	number int,
	payed bool,
	customer *Customer,
	paymentType *PaymentType,
	tax *Tax,
	rows []Row,
) (*Invoice, error) {
	i := &Invoice{
		date:         date,
		note:         note,
		number:       number,
		payed:        payed,
		customer:     customer,
		paymentType:  paymentType,
		tax:          tax,
		rows:         rows,
	}

	i.SetAmount()

	return i, nil
}

// ... all methods like ID(), Date() and so on...

// and methods that use the values of those structs, such as:

func (i *Invoice) SetAmount() {
	for x := range i.rows {
		i.amount += i.rows[x].Qty() * i.rows[x].Price()
	}

	i.amount += i.amount * i.Tax().Amount()
}

I'm using specific types for Invoice creation (directly from API port):

  • app/commands/types.go:
type CreateInvoiceRequest struct {
	Date          time.Time
	Note          *string
	Number        int
	Payed         bool
	CustomerID    int
	PaymentTypeID int
	TaxID         int
	Rows          []InvoiceRowInput
}

type InvoiceRowInput struct {
	ID          int
	Description *string
	Qty         int
	Price       int
	ProductID   *int
}

Then in my `app/command/invoice_create.go:

func (h CreateInvoiceHandler) Handle(ctx context.Context, createInvoiceRequest *CreateInvoiceRequest) (*domain.Invoice, error) {
	var domainInvoice *domain.Invoice

	domainInvoice, err := domain.NewInvoice(
		createInvoiceRequest.Date,
		createInvoiceRequest.Note,
		createInvoiceRequest.Number,
		createInvoiceRequest.Payed,
		createInvoiceRequest.CustomerID,
		createInvoiceRequest.PaymentTypeID,
		createInvoiceRequest.TaxID,
		createInvoiceRequest.Rows,
	)
	if err != nil {
		return nil, err
	}

	return h.repo.Create(ctx, createInvoiceRe)
}

Question

As you can see I'm getting from clients only the ID of customer, paymentType, tax, and so on...

But I use those structs value for my NewInvoice func.

Now I can fetch those values from repo using methods like yours:

UpdateTraining(
  ctx context.Context,
  trainingUUID string,
  user User,
  updateFn func(ctx context.Context, tr *Training) (*Training, error),
) error

in updateFn I can get each of them and only after I can use NewInvoice().

Or maybe I can change API and get that data from client (e.g.: Tax instead of taxID).

And maybe this is also a business doubt because maybe those ID references must be present and correct (in DB) at creation and not taken by the client itself or maybe not.

What do you think of how I am progressing? Do you have any other ideas?

Where am I doing wrong?

Htmx

Examples using htmx pattern seem perfect since htmx can also push over SSE or websockets.

in golang there are plenty of htmx examples.

you just use go templates to compose the UI .

even a decent admin gui for watermill can be built with it

Improving the series, code & site, remove horrible trackers

As I said on another issue: I โค๏ธ love your article series, and the real-world example it elaborates. This is constructive feedback, not criticism.

Unfortunately also real-world is surveillance capitalism, and Big Tech (read: ad-tech) domination of the (increasingly corporate) internet. As initiator and facilitator of Humane Tech Community I am advocating anyone that teaches others to adopt best-practices that do not make the current situation even worse, or better, are actual improvements.

There's two parts to this issue:

  • Your website itself
  • The example codebase

First something about Google. Google is an advertising agency, and all their services are ad-tech! Plain and simple. Any use of them puts security and privacy of your users in jeopardy. And encouragement to do so I consider a worst-practice / anti-pattern.

Website

  • You use the worst-possible commenting system Disqus. Luckily Privacy Badger blocks it by default.
  • I temporarily unblocked Google Fonts trackers. There's hardly a difference. You can use standard browser fonts, or self-host.
  • Instead of Google Analytics you might use Plausible or choose from this list I maintain.

Example code

"You should not build your own authentication. Let Firebase do it for you" .. Please NO! ๐Ÿค•

  • Delegating your auth to a surveillance capitalist Big Tech provider is a big no-no for privacy and ultimately internet health.
  • Firebase is soooo convenient. It is a great service in terms of features. But what does it do in terms of ad-tech? Potentially gather a humongous amount of data from your use of the service!

There's great, test-driven, production-ready Golang OAuth2 libraries and auth provider projects, that would be way cooler to demonstrate in the integration.

Why OpenAPI?

I'm curious what motivated openAPI and it's code generator?

It seems like all of the functionality provided there could be delivered by:

I am a fan of grpc-web for consuming gRPC services with a Vue client, but realize a REST analog is often useful too.

It seems like it would be simpler and with less overhead to use tools from within the same gRPC ecosystem.

I am really enjoying the book! Thank you so much for publishing all of this!

Aggregate Root > Package Oriented Design

Hi guys ๐Ÿ‘‹

First of all: what a great material this GitHub project, has helped me a lot, thanks for that ๐Ÿ™

My question: is ok that a domain entity could call another domain entity at the same level ? I'm trying to refactor a project I have, and the concept of Aggregate Root was working fine, but now I want to separate them and I'm a little confused about how implement it under this new concept.

How would you transform this architecture into Microservices?

sup,
Im currently struggling to find an appropriate architecture for building Microservices in Go. Everyone says something different - no crystal-clear standardization.

It'd be great if you can show me how you would transform this approach to Microservices, bc I pretty like this architecture

User management CRUD vs. generic domain

This is just a FYI of a consideration I am making currently about how to implement User Management.

The article series advises to go with straightforward CRUD to implement user management. This is certainly prudent if you know that you only need very basic user management features. In many ocassions I think the amount of these features start to warrant following a DDD approach too.

I wrote some of the features I need, and think I'll choose the latter approach:

User (generic domain)

Standard, not core to the service this subdomain has use cases for User stakeholders:

Ref. Stakeholder Use case and description
USR-01 Public Register new user: To become a user a person must register their username, password and a valid email address. The username cannot be changed after registration.
USR-02 Public Send email verification: After signing up the system sends an email notification with a link to confirm and verify the email.
USR-03 Public Verify email address: Clicking the link in the email verification message triggers the verification on the server. If the verification succeeds, then this is recorded and the user is valid. Only with a verified email address the user is allowed access to the system.
USR-04 Public Sign in user: To gain access to the center services a user signs in with a valid username and password.
USR-05 User Sign out user: A user that is signed in can sign out at any time. This action will sign them out from all devices they were signed in with.
USR-06 User Reset password: If the user forgot their password they can request a password reset and receive an email notification with a link to a password reset screen where they can set a new password. A successful reset allows them to subsequently log in with that password.
USR-07 User View private profile: A user can view their own user profile which shows all the user details of the user including email address and other personal information.
USR-08 Public View public profile: Anyone can view the public profile of another user, which displays a subset of the user details of that user's private profile.
USR-09 User Edit user profile: A user can edit the user details of their private profile and save the changes. Only their username cannot be changed after registration. Changing the password is a separate use case.
USR-10 User Change password: When signed in a user can change their password by typing the old password, followed by the new one twice to ensure it is typed correctly.
USR-11 User Close user account: A user can terminate their own account . When doing this they are provided they are provided the option to have all their activities to be either removed or anonymized (depending on whether it is private or public data). Then their personal profile is deleted, after which the user is automatically signed out of the system.
USR-12 Public Request authentication: When actions are requested that require authentication, then a redirection is made to the login and signup facility. After authentication is established the user is directed back to the page where the request was made.

This list is not yet even complete, e.g. I need 'Export user data' and 'Anonymize user content' for GDPR-compliance, and there are touchpoints to other subdomains, like Admin (with Auditing), Reporting (with Dashboard config), Notification (user-type differentiation), etc.

Potentially inconsistent state

Thanks for the project and articles I have been learning a lot from them.

I am studying your transactions in the firestore adapter in trainings. In particular, the call to UpdateTraining from the CancelTrainingHandler seems like it could leave the state of your system inconsistent if the userService command passes, but the command to the trainerService does not. The way it looks to me is that the user would get their balance back and the training would still be scheduled. This would lead to the trainer showing up to a cancelled training or the user getting a free one.

Am I missing something?

Delve debugging?

I was wondering how to debug the go code in development?
Normaly i would use delve to set breakpoints in VSCode , but i'm not quite sure how to do this in a docker-compose setup.
Any insights would be appreciated.

Unit tests for HTTP (gRPC or anything else) API layer

Hi, your project is just wonderful. I would like to know your opinion on the unit tests for the HTTP layer in service. After all, you need to make sure that your API meets the requirement, right? Also it would be useful in regression testing after refactoring this layer. If you think you need such tests, do you need to mock the application layer?

Conceptualizing policy layer

While thinking about

type Repository interface {
AddTraining(ctx context.Context, tr *Training) error
GetTraining(ctx context.Context, trainingUUID string, user User) (*Training, error)
UpdateTraining(
ctx context.Context,
trainingUUID string,
user User,
updateFn func(ctx context.Context, tr *Training) (*Training, error),
) error
}

I realized that user is passed to implement access policy. As a fan of OPA I immediately wondered how an integration would be best devised.

  1. Generically speaking an external agent only can ever interact with the domain through the application service layer.
  2. The necessary metadata to decide upon an access policy:
  • Identifier of the actor
  • eventually an elevation token
  • an instance of the domain entity (to base decisions on domain values)
  • the command to be executed (probably the most important data point to make a policy decision)
  1. Hence, I'm inclined to thing the policy could be well implemented at the application layer and the domain be freed of any policy decision. Although being part of the business logic, it will be flexibly implemented in the policy service (OPA) based on those data points.

I feel this mental model is advantageous, since the domain logic could be implemented free of any policy considerations.

// StartRecordingHandler knows how to start a recording
type StartRecordingHandler struct {
	aggregate domain.Repository
	commMgr domain.CommManager // interface
	policy app.Policy // policy interface implemented at the application, not the domain layer?
}

// Handle starts a recording
func (h StartRecordingHandler) Handle(ctx context.Context, livecallToStartRecordingFor uuid.UUID, userID uuid.UUID, elevationToken string) error {
	err := h.aggregate.Update(ctx, livecallToStartRecordingFor, func(l *livecall.Livecall) error {
		if ok := h.policy.Can(ctx, "StartRecording", userID, elevationToken, *l); !ok {
			return ErrNotAuthorizedToStartRecording
		}
		if err := l.StartRecording(h.commMgr); err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return ErrUnableToStartRecording
	}

	return nil
}

}

An article for successful Golang Error Handling?

It would be great to read your article on error handling "the correct way" in Go.

I mean, how to handle (where? in app layer? in ports layer?) different validation/temporary (to retry)/logic/instrumentation/network errors.

Background jobs

Thank you for your book and sample project.
Can you share your mind about the place of background workers in clear architecture? I think it may be some entities in the app module or specific commands. Anyway, this is an important point of the project.
What do you think?

Failed to create shim task: OCI runtime create failed

Summary

  • docker-compose up fails due to a Failed to create shim task: OCI runtime create failed error

Setup

Steps To Reproduce:

Result:

...
wild-workouts-web-1                        | + yarn install
Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "./start.sh": permission denied: unknown

v1.0 Web Service Breaking Due to Outdated Packages

I was seeing this error when running the Vue frontend in v1.0,

M; 299 96 9313

C; 685 36 0135

 web_1             |  error  in ./src/main.js
web_1             |
web_1             | Module build failed (from ./node_modules/babel-loader/lib/index.js):
web_1             | Error: [BABEL] /web/src/main.js: Cannot find module '/web/node_modules/@babel/preset-env/node_modules/@babel/compat-data/data/corejs3-shipped-proposals' (While processing: "/web/node_modules/@vue/cli-plugin-babel/preset.js")
web_1             |     at createEsmNotFoundErr (internal/modules/cjs/loader.js:842:15)
web_1             |     at finalizeEsmResolution (internal/modules/cjs/loader.js:835:15)
web_1             |     at resolveExports (internal/modules/cjs/loader.js:424:14)
web_1             |     at Function.Module._findPath (internal/modules/cjs/loader.js:464:31)
web_1             |     at Function.Module._resolveFilename (internal/modules/cjs/loader.js:802:27)
web_1             |     at Function.Module._load (internal/modules/cjs/loader.js:667:27)
web_1             |     at Module.require (internal/modules/cjs/loader.js:887:19)
web_1             |     at require (internal/modules/cjs/helpers.js:74:18)
web_1             |     at Object.<anonymous> (/web/node_modules/@babel/preset-env/lib/polyfills/corejs3/usage-plugin.js:10:55)
web_1             |     at Module._compile (internal/modules/cjs/loader.js:999:30)
web_1             |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
web_1             |     at Module.load (internal/modules/cjs/loader.js:863:32)
web_1             |     at Function.Module._load (internal/modules/cjs/loader.js:708:14)
web_1             |     at Module.require (internal/modules/cjs/loader.js:887:19)
web_1             |     at require (internal/modules/cjs/helpers.js:74:18)
web_1             |     at Object.<anonymous> (/web/node_modules/@babel/preset-env/lib/index.js:29:44)
web_1             |
web_1             |  @ multi (webpack)-dev-server/client/index.js (webpack)/hot/dev-server.js ./src/main.js

But I was able to get it running with the following,

diff --git a/web/package.json b/web/package.json
index 4f686c9..45c644c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,6 +8,7 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@babel/compat-data": "^7.9.0",
     "@fullcalendar/core": "^4.4.0",
     "@fullcalendar/daygrid": "^4.4.0",
     "@fullcalendar/interaction": "^4.4.0",
@@ -15,7 +16,7 @@
     "@fullcalendar/timegrid": "^4.4.0",
     "@fullcalendar/vue": "^4.4.0",
     "bootstrap": "^4.4.1",
-    "core-js": "^3.6.4",
+    "core-js": "3.27.2",
     "firebase": "^7.14.0",
     "firebase-auth": "^0.1.2",
     "jquery": "^3.4.1",

Example use asyncapi

The closing PR of asyncapi/spec#113 implemented:

channels:
  user/created:
    subscribe:
      headers:
        application/x-protobuf:
          $ref: 'path/to/user-created.proto#Headers'
      payload:
        application/x-protobuf:
          $ref: 'path/to/user-created.proto#UserCreated'

As two valuable learning units (and productivity candys), it would be nice to:

See also:
โ†’ https://github.com/asyncapi/generator/blob/master/docs/authoring.md
โ†’ https://github.com/asyncapi/generator/blob/master/docs/templates-recipes.md

Given an invalid hour, it returns internal-server-error

Given an invalid hour, it returns internal-server-error. I was expecting a bad request.

$ curl --location --request PUT 'http://localhost:3000/api/trainer/calendar/make-hour-available' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3V1aWQiOiIxIiwiZW1haWwiOiJ0cmFpbmVyQHRocmVlZG90cy50ZWNoIiwicm9sZSI6InRyYWluZXIiLCJuYW1lIjoiVHJhaW5lciIsImlhdCI6MTYzMDkyNDE3NH0.veziMkcul16lUnQwV7HnNPSf19AkntaDlPDRfNYBkKI' \
--header 'Content-Type: application/json' \
--data-raw '{
    "hours": [
        "aaaaaa"
    ]
}'

{"slug":"internal-server-error"}

Lincense of this code

I want to attribute proper authorship and SPDX-License-Identifier for code that I'm adopting from the ./internal/common and other packages. I would be going with:

// Copyright ยฉ 2020 Miล‚osz Smรณล‚ka <[email protected]>
// Copyright ยฉ 2020 Robert Laszczak <[email protected]>
// SPDX-License-Identifier: MIT

Please let me know if that is fine or if there are any objections.

M1 mac support

Attempted to run the example on m1 mac today, but hit a few issues.

I'll document the changes I made so docker-compose up at least runs. The web page won't allow logins, but it's a start :)

Issue: mysql has no arm64/v8 docker image
solution: use MariaDB

diff --git a/docker-compose.yml b/docker-compose.yml
index cb19149..20485db 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -107,7 +107,7 @@ services:
     restart: unless-stopped

   mysql:
-    image: mysql:8
+    image: mariadb:10.5.8
     env_file:
       - .env
     volumes:

Issue: npm install fails to compile node-sass, sass-loader doesn't like new webpack etc
Solution: Remove old web/package-lock.json, apply these patches to use newer node, update vue etc.

diff --git a/web/package.json b/web/package.json
index 5e2d250..fce85fa 100644
--- a/docker/web/Dockerfile
+++ b/docker/web/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:13.11.0-alpine3.11
+FROM node:lts-alpine

 ENV NODE_ENV development
diff --git a/web/package.json b/web/package.json
index 4f686c9..eef1feb 100644
--- a/web/package.json
+++ b/web/package.json
@@ -20,23 +20,25 @@
     "firebase-auth": "^0.1.2",
     "jquery": "^3.4.1",
     "jsonwebtoken": "^8.5.1",
-    "node-sass": "^4.13.1",
     "popper.js": "^1.16.1",
-    "sass-loader": "^8.0.2",
     "superagent": "^5.2.2",
-    "vue": "^2.6.11",
+    "vue": "^2.6.14",
     "vue-router": "^3.1.6",
     "vue-toast-notification": "^0.2.0",
-    "vuejs-dialog": "^1.4.1"
+    "vuejs-dialog": "^1.4.1",
+    "yarn": "^1.22.17"
   },
   "devDependencies": {
-    "@vue/cli-plugin-babel": "~4.2.0",
-    "@vue/cli-plugin-eslint": "~4.2.0",
-    "@vue/cli-service": "~4.2.0",
+    "@vue/cli-plugin-babel": "^4.5.15",
+    "@vue/cli-plugin-eslint": "^4.5.15",
+    "@vue/cli-service": "^4.5.15",
     "babel-eslint": "^10.0.3",
     "eslint": "^6.7.2",
     "eslint-plugin-vue": "^6.1.2",
-    "vue-template-compiler": "^2.6.11"
+    "sass": "^1.43.4",
+    "sass-loader": "^10.1.1",
+    "vue-template-compiler": "^2.6.14",
+    "webpack": "^5.64.0"
   },
   "eslintConfig": {
     "root": true,

Guidelines for starting afresh

Thank you for your repo and articles. This is awesome work!

The articles focus on rewriting legacy services to your modern approaches, which would be great for >99% of the cases.

However, for starting new projects, how things should be done to avoid all those avoidable problems?
I was hoping to find some condensed guideline in this repo's readme, as a summary/conclusion for the series of the articles.

Hope such condensed guideline/summary/conclusion will be in this repo's readme someday.

Application add query services

Hi!
I really like this project of yours. I have the following problem, please help.
In internal/trainings/app/command I see there is a services.go, similarly in internal/trainings/app/query I also want a services.go, but when I do so Go gives an error in https: //github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/blob/master/internal/trainings/service/service.go#L39 because this function is passing command.TrainerService, command.UserService.
Please help me to know more.
Thank you!

Failing to run at docker-compose up - page 10 in the book.

Trying to follow the instructions on page 10 of the book.

Docker is running. Compose is running (all the one on Windows 10)

I run docker-compose up

I get a loooong series of outputs in the terminal. The only error message I can see is this.

**

web_1 | /start.sh: set: line 1: illegal option -
wild-workouts-go-ddd-example_web_1 exited with code 2

**

There are a few warnings, but that seems to be the only error. The web_1 service is also not running. If I CTRL-C, the other services all shut down. Web_1 does not say it's shutting down. It seems to have died at that error.

Can we use a pointer for Repository assignment?

Given the repository in repository.go:

type Repository interface {
  AddTraining(ctx context.Context, tr *Training) error
  //...
}

we are creating new structs which implement that interface using code such as in trainings_firestore_repository.go:

type TrainingsFirestoreRepository struct {
  firestoreClient *firestore.Client
}

func NewTrainingsFirestoreRepository(
  firestoreClient *firestore.Client,
) TrainingsFirestoreRepository {
  return TrainingsFirestoreRepository{
    firestoreClient: firestoreClient,
  }
}

and we are create commands and queries with those structs, such as in service.go:

func newApplication(ctx context.Context, trainerGrpc command.TrainerService, usersGrpc command.UserService) app.Application {
  client, err := firestore.NewClient(ctx, os.Getenv("GCP_PROJECT"))
  //...
  trainingsRepository := adapters.NewTrainingsFirestoreRepository(client)
  //...
  return app.Application{
    Commands: app.Commands{
      ApproveTrainingReschedule: command.NewApproveTrainingRescheduleHandler(trainingsRepository, usersGrpc, trainerGrpc, logger, metricsClient),
      //...
    },
    Queries: app.Queries{
      AllTrainings:     query.NewAllTrainingsHandler(trainingsRepository, logger, metricsClient),
      //...
    },
  }
}

satisfying func signature like:

func NewApproveTrainingRescheduleHandler(
  repo training.Repository,
  //...
) decorator.CommandHandler[ApproveTrainingReschedule] {}

Question

In each func like command.NewApproveTrainingRescheduleHandler(trainingsRepository, /*...*/) we are passing trainingsRepository each time different (passed-by-value).

Can we use a pointer there avoiding maybe useless memory consumption?

Not able to run code with the Docker-Compose method

Hi all,

Update: it's working fine on a mac. The problem seems to be on Window's machines.

I've cloned the repo and followed the instructions in the README.md for the Docker method using docker-compose up (I've tried on windows 10, via powershell 5, and through Window's WSL2 via ubuntu 18.04) to start this application. The Docker method's web_1 service dies early (please see output below).

Perhaps there is a git clone param I'm missing? (There's a suspcious error below | /start.sh: set: line 1: illegal option - ), and google seems to indicate it's a line-ending issue.

Here's the docker-compose output:

[+] Running 10/10
 - Network wild-workouts-go-ddd-example_default                        Created                                                                                              0.8s
 - Container wild-workouts-go-ddd-example_mysql_1                      Created                                                                                              4.1s
 - Container wild-workouts-go-ddd-example_web_1                        Created                                                                                              0.2s
 - Container wild-workouts-go-ddd-example_firestore_1                  Created                                                                                              4.1s
 - Container wild-workouts-go-ddd-example_firestore-component-tests_1  Created                                                                                              5.2s
 - Container wild-workouts-go-ddd-example_users-http_1                 Created                                                                                              2.0s
 - Container wild-workouts-go-ddd-example_trainer-http_1               Created                                                                                              1.2s
 - Container wild-workouts-go-ddd-example_trainings-http_1             Created                                                                                              3.1s
 - Container wild-workouts-go-ddd-example_trainer-grpc_1               Created                                                                                              1.2s
 - Container wild-workouts-go-ddd-example_users-grpc_1                 Created                                                                                              1.2s
Attaching to firestore-component-tests_1, firestore_1, mysql_1, trainer-grpc_1, trainer-http_1, trainings-http_1, users-grpc_1, users-http_1, web_1
web_1                        | /start.sh: set: line 1: illegal option -
mysql_1                      | 2021-12-30 21:55:19+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.27-1debian10 started.
mysql_1                      | 2021-12-30 21:55:19+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
mysql_1                      | 2021-12-30 21:55:19+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.27-1debian10 started.
mysql_1                      | 2021-12-30 21:55:20+00:00 [Note] [Entrypoint]: Initializing database files
mysql_1                      | 2021-12-30T21:55:20.114353Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.27) initializing of server in progress as process 43
mysql_1                      | 2021-12-30T21:55:20.138797Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
web_1 exited with code 2
mysql_1                      | 2021-12-30T21:55:22.041116Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
firestore-component-tests_1  | โš   emulators: You are not currently authenticated so some features may not work correctly. Please run firebase login to authenticate the CLI.
firestore-component-tests_1  | i  emulators: Starting emulators: firestore
firestore-component-tests_1  | โš   firestore: Did not find a Cloud Firestore rules file specified in a firebase.json config file.
firestore-component-tests_1  | โš   firestore: The emulator will default to allowing all reads and writes. Learn more about this option: https://firebase.google.com/docs/emulator-suite/install_and_configure#security_rules_configuration.
firestore-component-tests_1  | i  firestore: Firestore Emulator logging to firestore-debug.log
firestore_1                  | โš   emulators: You are not currently authenticated so some features may not work correctly. Please run firebase login to authenticate the CLI.
firestore_1                  | i  emulators: Starting emulators: firestore
firestore_1                  | โš   firestore: Did not find a Cloud Firestore rules file specified in a firebase.json config file.
firestore_1                  | โš   firestore: The emulator will default to allowing all reads and writes. Learn more about this option: https://firebase.google.com/docs/emulator-suite/install_and_configure#security_rules_configuration.
firestore_1                  | i  firestore: Firestore Emulator logging to firestore-debug.log
trainer-grpc_1               | [00] Starting service
trainer-grpc_1               | [00] go: downloading github.com/go-chi/chi/v5 v5.0.5
trainer-grpc_1               | [00] go: downloading google.golang.org/grpc v1.40.0
trainer-grpc_1               | [00] go: downloading github.com/sirupsen/logrus v1.5.0
trainer-grpc_1               | [00] go: downloading github.com/pkg/errors v0.9.1
trainer-grpc_1               | [00] go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
trainings-http_1             | [00] Starting service
trainer-grpc_1               | [00] go: downloading firebase.google.com/go v3.12.0+incompatible
trainer-grpc_1               | [00] go: downloading github.com/go-chi/cors v1.0.1
trainer-grpc_1               | [00] go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
trainer-grpc_1               | [00] go: downloading google.golang.org/api v0.21.0
trainer-grpc_1               | [00] go: downloading github.com/golang/protobuf v1.5.2
trainings-http_1             | [00] go: downloading github.com/go-chi/chi/v5 v5.0.5
trainings-http_1             | [00] go: downloading firebase.google.com/go v3.12.0+incompatible
trainer-http_1               | [00] Starting service
trainings-http_1             | [00] go: downloading github.com/go-chi/cors v1.0.1
trainings-http_1             | [00] go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
users-http_1                 | [00] Starting service
trainings-http_1             | [00] go: downloading github.com/sirupsen/logrus v1.5.0
firestore-component-tests_1  | i  ui: Emulator UI logging to ui-debug.log
trainings-http_1             | [00] go: downloading google.golang.org/api v0.21.0
trainings-http_1             | [00] go: downloading google.golang.org/grpc v1.40.0
trainings-http_1             | [00] go: downloading cloud.google.com/go v0.38.0
users-grpc_1                 | [00] Starting service
trainer-http_1               | [00] go: downloading github.com/go-chi/chi/v5 v5.0.5
trainer-http_1               | [00] go: downloading github.com/pkg/errors v0.9.1
trainer-http_1               | [00] go: downloading google.golang.org/grpc v1.40.0
trainer-http_1               | [00] go: downloading github.com/sirupsen/logrus v1.5.0
trainer-http_1               | [00] go: downloading cloud.google.com/go/firestore v1.2.0
trainings-http_1             | [00] go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
trainer-http_1               | [00] go: downloading firebase.google.com/go v3.12.0+incompatible
firestore-component-tests_1  | 
firestore-component-tests_1  | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
firestore-component-tests_1  | โ”‚ โœ”  All emulators ready! View status and logs at http://0.0.0.0:4000 โ”‚
firestore-component-tests_1  | โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
firestore-component-tests_1  |
firestore-component-tests_1  | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
firestore-component-tests_1  | โ”‚ Emulator  โ”‚ Host:Port    โ”‚ View in Emulator UI           โ”‚
firestore-component-tests_1  | โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
firestore-component-tests_1  | โ”‚ Firestore โ”‚ 0.0.0.0:8787 โ”‚ http://0.0.0.0:4000/firestore โ”‚
firestore-component-tests_1  | โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
firestore-component-tests_1  |   Other reserved ports: 4400, 4500
firestore-component-tests_1  |
firestore-component-tests_1  | Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
firestore-component-tests_1  |
trainings-http_1             | [00] go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
users-http_1                 | [00] go: downloading cloud.google.com/go/firestore v1.2.0
users-http_1                 | [00] go: downloading firebase.google.com/go v3.12.0+incompatible
trainer-http_1               | [00] go: downloading github.com/go-chi/cors v1.0.1
trainer-http_1               | [00] go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
users-http_1                 | [00] go: downloading github.com/go-chi/chi/v5 v5.0.5
users-http_1                 | [00] go: downloading github.com/go-chi/render v1.0.1
users-http_1                 | [00] go: downloading github.com/golang/protobuf v1.5.2
users-http_1                 | [00] go: downloading github.com/pkg/errors v0.9.1
trainer-http_1               | [00] go: downloading google.golang.org/api v0.21.0
users-http_1                 | [00] go: downloading github.com/sirupsen/logrus v1.5.0
trainings-http_1             | [00] go: downloading github.com/deepmap/oapi-codegen v1.9.0
users-http_1                 | [00] go: downloading google.golang.org/api v0.21.0
users-grpc_1                 | [00] go: downloading firebase.google.com/go v3.12.0+incompatible
users-grpc_1                 | [00] go: downloading cloud.google.com/go/firestore v1.2.0
users-grpc_1                 | [00] go: downloading github.com/go-chi/chi/v5 v5.0.5
users-http_1                 | [00] go: downloading google.golang.org/grpc v1.40.0
users-grpc_1                 | [00] go: downloading github.com/go-chi/render v1.0.1
users-grpc_1                 | [00] go: downloading github.com/golang/protobuf v1.5.2
trainings-http_1             | [00] go: downloading github.com/go-chi/render v1.0.1
users-grpc_1                 | [00] go: downloading github.com/pkg/errors v0.9.1
trainer-grpc_1               | [00] go: downloading google.golang.org/protobuf v1.27.1
users-grpc_1                 | [00] go: downloading github.com/sirupsen/logrus v1.5.0
trainings-http_1             | [00] go: downloading github.com/google/uuid v1.1.2
firestore_1                  | i  ui: Emulator UI logging to ui-debug.log
trainer-grpc_1               | [00] go: downloading cloud.google.com/go/firestore v1.2.0
trainer-grpc_1               | [00] go: downloading github.com/deepmap/oapi-codegen v1.9.0
trainer-grpc_1               | [00] go: downloading github.com/go-chi/render v1.0.1
trainer-grpc_1               | [00] go: downloading cloud.google.com/go v0.55.0
users-http_1                 | [00] go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
users-grpc_1                 | [00] go: downloading google.golang.org/api v0.21.0
firestore_1                  | 
firestore_1                  | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
firestore_1                  | โ”‚ โœ”  All emulators ready! View status and logs at http://0.0.0.0:4000 โ”‚
firestore_1                  | โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
firestore_1                  |
firestore_1                  | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
firestore_1                  | โ”‚ Emulator  โ”‚ Host:Port    โ”‚ View in Emulator UI           โ”‚
firestore_1                  | โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
firestore_1                  | โ”‚ Firestore โ”‚ 0.0.0.0:8787 โ”‚ http://0.0.0.0:4000/firestore โ”‚
firestore_1                  | โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
users-grpc_1                 | [00] go: downloading cloud.google.com/go v0.55.0
firestore_1                  |   Other reserved ports: 4400, 4500
firestore_1                  | 
firestore_1                  | Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
firestore_1                  |  
users-grpc_1                 | [00] go: downloading google.golang.org/grpc v1.40.0
users-http_1                 | [00] go: downloading github.com/go-chi/cors v1.0.1
trainings-http_1             | [00] go: downloading golang.org/x/sys v0.0.0-20211031064116-611d5d643895
users-http_1                 | [00] go: downloading cloud.google.com/go v0.55.0
users-grpc_1                 | [00] go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
trainer-http_1               | [00] go: downloading github.com/golang/protobuf v1.5.2
trainings-http_1             | [00] go: downloading github.com/golang/protobuf v1.5.2
trainer-http_1               | [00] go: downloading cloud.google.com/go v0.55.0
users-grpc_1                 | [00] go: downloading github.com/go-chi/cors v1.0.1
trainer-http_1               | [00] go: downloading google.golang.org/protobuf v1.27.1
trainer-http_1               | [00] go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
trainer-http_1               | [00] go: downloading github.com/deepmap/oapi-codegen v1.9.0
trainer-http_1               | [00] go: downloading github.com/go-chi/render v1.0.1
users-http_1                 | [00] go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
trainer-grpc_1               | [00] go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
trainer-grpc_1               | [00] go: downloading golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
trainer-grpc_1               | [00] go: downloading golang.org/x/sys v0.0.0-20211031064116-611d5d643895
trainings-http_1             | [00] go: downloading golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
trainer-grpc_1               | [00] go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
users-grpc_1                 | [00] go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
users-http_1                 | [00] go: downloading google.golang.org/protobuf v1.27.1
users-http_1                 | [00] go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
users-grpc_1                 | [00] go: downloading github.com/x-cray/logrus-prefixed-formatter v0.5.2
trainer-grpc_1               | [00] go: downloading go.uber.org/multierr v1.1.0
trainer-http_1               | [00] go: downloading golang.org/x/sys v0.0.0-20211031064116-611d5d643895
trainer-grpc_1               | [00] go: downloading golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
trainer-http_1               | [00] go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
users-http_1                 | [00] go: downloading golang.org/x/sys v0.0.0-20211031064116-611d5d643895
users-http_1                 | [00] go: downloading golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
users-grpc_1                 | [00] go: downloading google.golang.org/protobuf v1.27.1
trainings-http_1             | [00] go: downloading github.com/pkg/errors v0.9.1
trainings-http_1             | [00] go: downloading google.golang.org/protobuf v1.27.1
mysql_1                      | 2021-12-30T21:55:33.948379Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1 is enabled for channel mysql_main
mysql_1                      | 2021-12-30T21:55:33.948442Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1.1 is enabled for channel mysql_main
users-grpc_1                 | [00] go: downloading golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
users-grpc_1                 | [00] go: downloading golang.org/x/sys v0.0.0-20211031064116-611d5d643895
trainer-grpc_1               | [00] go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
mysql_1                      | 2021-12-30T21:55:34.211173Z 6 [Warning] [MY-010453] [Server] root@localhost is created with an empty password ! Please consider switching off the 
--initialize-insecure option.
trainer-grpc_1               | [00] go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
trainer-grpc_1               | [00] go: downloading github.com/go-sql-driver/mysql v1.4.0
trainings-http_1             | [00] go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
trainings-http_1             | [00] go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
trainings-http_1             | [00] go: downloading golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
trainer-http_1               | [00] go: downloading github.com/go-sql-driver/mysql v1.4.0
trainer-http_1               | [00] go: downloading github.com/jmoiron/sqlx v1.2.0
trainer-grpc_1               | [00] go: downloading github.com/jmoiron/sqlx v1.2.0
users-grpc_1                 | [00] go: downloading github.com/googleapis/gax-go/v2 v2.0.5
users-grpc_1                 | [00] go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
users-http_1                 | [00] go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
users-http_1                 | [00] go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
trainer-http_1               | [00] go: downloading go.uber.org/multierr v1.1.0
users-http_1                 | [00] go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
trainer-http_1               | [00] go: downloading golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
users-http_1                 | [00] go: downloading golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
trainings-http_1             | [00] go: downloading github.com/googleapis/gax-go/v2 v2.0.5
users-grpc_1                 | [00] go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
users-grpc_1                 | [00] go: downloading golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
users-grpc_1                 | [00] go: downloading cloud.google.com/go/storage v1.6.0
trainings-http_1             | [00] go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
trainer-grpc_1               | [00] go: downloading github.com/mattn/go-colorable v0.1.8
users-http_1                 | [00] go: downloading cloud.google.com/go/storage v1.6.0
trainer-grpc_1               | [00] go: downloading github.com/googleapis/gax-go/v2 v2.0.5
trainer-http_1               | [00] go: downloading google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
trainer-grpc_1               | [00] go: downloading cloud.google.com/go/storage v1.6.0
trainer-http_1               | [00] go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
trainer-http_1               | [00] go: downloading github.com/googleapis/gax-go/v2 v2.0.5
users-http_1                 | [00] go: downloading github.com/googleapis/gax-go/v2 v2.0.5
trainer-grpc_1               | [00] go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
trainer-grpc_1               | [00] go: downloading go.uber.org/atomic v1.4.0
trainer-grpc_1               | [00] go: downloading github.com/mattn/go-isatty v0.0.14
users-grpc_1                 | [00] go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
trainer-http_1               | [00] go: downloading github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
trainer-http_1               | [00] go: downloading golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
trainings-http_1             | [00] go: downloading github.com/mattn/go-colorable v0.1.8
users-http_1                 | [00] go: downloading github.com/mattn/go-colorable v0.1.8
trainer-http_1               | [00] go: downloading cloud.google.com/go/storage v1.6.0
trainer-http_1               | [00] go: downloading go.uber.org/atomic v1.4.0
trainings-http_1             | [00] go: downloading go.opencensus.io v0.22.3
trainings-http_1             | [00] go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
trainings-http_1             | [00] go: downloading github.com/mattn/go-isatty v0.0.14
trainings-http_1             | [00] go: downloading golang.org/x/text v0.3.7
users-http_1                 | [00] go: downloading go.opencensus.io v0.22.3
users-http_1                 | [00] go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
users-http_1                 | [00] go: downloading github.com/mattn/go-isatty v0.0.14
users-grpc_1                 | [00] go: downloading go.opencensus.io v0.22.3
users-grpc_1                 | [00] go: downloading github.com/mattn/go-colorable v0.1.8
users-grpc_1                 | [00] go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
users-http_1                 | [00] go: downloading golang.org/x/text v0.3.7
users-grpc_1                 | [00] go: downloading github.com/mattn/go-isatty v0.0.14
users-grpc_1                 | [00] go: downloading github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
users-grpc_1                 | [00] go: downloading github.com/google/go-cmp v0.5.5
users-grpc_1                 | [00] go: downloading golang.org/x/text v0.3.7
trainer-http_1               | [00] go: downloading go.opencensus.io v0.22.3
trainer-http_1               | [00] go: downloading github.com/mattn/go-colorable v0.1.8
trainer-http_1               | [00] go: downloading golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
users-http_1                 | [00] go: downloading github.com/google/go-cmp v0.5.5
users-http_1                 | [00] go: downloading github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
trainings-http_1             | [00] go: downloading github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
trainings-http_1             | [00] go: downloading github.com/google/go-cmp v0.5.5
trainer-http_1               | [00] go: downloading github.com/mattn/go-isatty v0.0.14
trainer-http_1               | [00] go: downloading golang.org/x/text v0.3.7
trainer-grpc_1               | [00] go: downloading go.opencensus.io v0.22.3
trainer-grpc_1               | [00] go: downloading golang.org/x/text v0.3.7
mysql_1                      | 2021-12-30 21:55:59+00:00 [Note] [Entrypoint]: Database files initialized
mysql_1                      | 2021-12-30 21:55:59+00:00 [Note] [Entrypoint]: Starting temporary server
mysql_1                      | 2021-12-30T21:56:00.212693Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.27) starting as process 92
mysql_1                      | 2021-12-30T21:56:00.276397Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
trainer-http_1               | [00] go: downloading github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
trainer-http_1               | [00] go: downloading github.com/google/go-cmp v0.5.5
trainer-grpc_1               | [00] go: downloading github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
trainer-grpc_1               | [00] go: downloading github.com/google/go-cmp v0.5.5
mysql_1                      | 2021-12-30T21:56:02.042656Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysql_1                      | 2021-12-30T21:56:03.746068Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1 is enabled for channel mysql_main
mysql_1                      | 2021-12-30T21:56:03.746110Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1.1 is enabled for channel mysql_main
mysql_1                      | 2021-12-30T21:56:03.747354Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
mysql_1                      | 2021-12-30T21:56:03.747410Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported 
for this channel.
mysql_1                      | 2021-12-30T21:56:03.944622Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
mysql_1                      | 2021-12-30T21:56:04.024520Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.27'  socket: '/var/run/mysqld/mysqld.sock'  port: 0  MySQL Community Server - GPL.
mysql_1                      | 2021-12-30T21:56:04.025241Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: /var/run/mysqld/mysqlx.sock
mysql_1                      | 2021-12-30 21:56:04+00:00 [Note] [Entrypoint]: Temporary server started.
mysql_1                      | Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
mysql_1                      | Warning: Unable to load '/usr/share/zoneinfo/leap-seconds.list' as time zone. Skipping it.
mysql_1                      | Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
mysql_1                      | Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.
mysql_1                      | 2021-12-30 21:56:18+00:00 [Note] [Entrypoint]: GENERATED ROOT PASSWORD: gaK8Iepahw8eicee8quip1vuoQu4vohd
mysql_1                      | 2021-12-30 21:56:19+00:00 [Note] [Entrypoint]: Creating database db
mysql_1                      | 2021-12-30 21:56:19+00:00 [Note] [Entrypoint]: Creating user user
mysql_1                      | 2021-12-30 21:56:20+00:00 [Note] [Entrypoint]: Giving user user access to schema db
mysql_1                      |
mysql_1                      | 2021-12-30 21:56:20+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/schema.sql
mysql_1                      |
mysql_1                      |
mysql_1                      | 2021-12-30 21:56:21+00:00 [Note] [Entrypoint]: Stopping temporary server
mysql_1                      | 2021-12-30T21:56:21.787831Z 14 [System] [MY-013172] [Server] Received SHUTDOWN from user root. Shutting down mysqld (Version: 8.0.27).
mysql_1                      | 2021-12-30T21:56:35.520565Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.27)  MySQL Community Server - GPL.
mysql_1                      | 2021-12-30 21:56:35+00:00 [Note] [Entrypoint]: Temporary server stopped
mysql_1                      |
mysql_1                      | 2021-12-30 21:56:35+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.
mysql_1                      |
mysql_1                      | 2021-12-30T21:56:36.187154Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.27) starting as process 1
mysql_1                      | 2021-12-30T21:56:36.224049Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
mysql_1                      | 2021-12-30T21:56:37.420449Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
mysql_1                      | 2021-12-30T21:56:38.583462Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1 is enabled for channel mysql_main
mysql_1                      | 2021-12-30T21:56:38.583508Z 0 [Warning] [MY-013746] [Server] A deprecated TLS version TLSv1.1 is enabled for channel mysql_main
mysql_1                      | 2021-12-30T21:56:38.584980Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
mysql_1                      | 2021-12-30T21:56:38.585050Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported 
for this channel.
mysql_1                      | 2021-12-30T21:56:38.640498Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
mysql_1                      | 2021-12-30T21:56:38.716164Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
mysql_1                      | 2021-12-30T21:56:38.716365Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.27'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
users-http_1                 | [00] [0000]  INFO core: parsed scheme: "" system=system
users-http_1                 | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
users-http_1                 | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{firestore:8787  <nil> 0 <nil>}] <nil> <nil>} system=system
users-http_1                 | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
users-http_1                 | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
users-http_1                 | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
users-http_1                 | [00] [0000]  INFO Starting HTTP server
users-http_1                 | [00] [0000] DEBUG Waiting for users service
users-http_1                 | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
users-http_1                 | [00] [0000]  INFO core: Subchannel picks a new address "firestore:8787" to connect system=system
users-http_1                 | [00] [0000]  INFO core: Subchannel Connectivity change to READY system=system
users-http_1                 | [00] [0000]  INFO core: Channel Connectivity change to READY system=system
trainings-http_1             | [00] [0000]  INFO core: parsed scheme: "" system=system
trainings-http_1             | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
trainings-http_1             | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{trainer-grpc:3000  <nil> 0 <nil>}] <nil> <nil>} system=system
trainings-http_1             | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  INFO core: parsed scheme: "" system=system
trainings-http_1             | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
trainings-http_1             | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{users-grpc:3000  <nil> 0 <nil>}] <nil> <nil>} system=system
trainings-http_1             | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  INFO core: parsed scheme: "" system=system
trainings-http_1             | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
trainings-http_1             | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{firestore:8787  <nil> 0 <nil>}] <nil> <nil>} system=system
trainings-http_1             | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  INFO Starting HTTP server
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel picks a new address "firestore:8787" to connect system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel picks a new address "trainer-grpc:3000" to connect system=system
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel picks a new address "users-grpc:3000" to connect system=system
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0000]  WARN core: grpc: addrConn.createTransport failed to connect to {trainer-grpc:3000 trainer-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.4:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0000]  WARN core: grpc: addrConn.createTransport failed to connect to {users-grpc:3000 users-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.9:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0000]  INFO core: Subchannel Connectivity change to READY system=system
trainings-http_1             | [00] [0000]  INFO core: Channel Connectivity change to READY system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel picks a new address "trainer-grpc:3000" to connect system=system
trainings-http_1             | [00] [0001]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0001]  WARN core: grpc: addrConn.createTransport failed to connect to {trainer-grpc:3000 trainer-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.4:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0001]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel picks a new address "users-grpc:3000" to connect system=system
trainings-http_1             | [00] [0001]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0001]  WARN core: grpc: addrConn.createTransport failed to connect to {users-grpc:3000 users-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.9:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0001]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0001]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
users-grpc_1                 | [00] [0000]  INFO core: parsed scheme: "" system=system
users-grpc_1                 | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
users-grpc_1                 | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{firestore:8787  <nil> 0 <nil>}] <nil> <nil>} system=system
users-grpc_1                 | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
users-grpc_1                 | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
users-grpc_1                 | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
users-grpc_1                 | [00] [0000]  INFO Starting: gRPC Listener grpcEndpoint=:3000
users-grpc_1                 | [00] [0000]  INFO core: Subchannel picks a new address "firestore:8787" to connect system=system
users-grpc_1                 | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
users-grpc_1                 | [00] [0000]  INFO core: Subchannel Connectivity change to READY system=system
users-grpc_1                 | [00] [0000]  INFO core: Channel Connectivity change to READY system=system
users-http_1                 | [00] [0004] DEBUG Users service is available after=4.5285621s
users-http_1                 | [00] [0004]  INFO core: parsed scheme: "" system=system
users-http_1                 | [00] [0004]  INFO core: scheme "" not registered, fallback to default scheme system=system
users-http_1                 | [00] [0004]  INFO core: ccResolverWrapper: sending update to cc: {[{users-grpc:3000  <nil> 0 <nil>}] <nil> <nil>} system=system
users-http_1                 | [00] [0004]  INFO core: ClientConn switching balancer to "pick_first" system=system
users-http_1                 | [00] [0004]  INFO core: Channel switches to new LB policy "pick_first" system=system
users-http_1                 | [00] [0004]  INFO core: Subchannel Connectivity change to CONNECTING system=system
users-grpc_1                 | [00] [0000]  WARN core: grpc: Server.Serve failed to create ServerTransport:  connection error: desc = "transport: http2Server.HandleStreams failed to receive the preface from client: read tcp 172.18.0.9:3000->172.18.0.8:46410: read: connection reset by peer" system=system
users-http_1                 | [00] [0004]  INFO core: Subchannel picks a new address "users-grpc:3000" to connect system=system
users-http_1                 | [00] [0004]  INFO core: blockingPicker: the picked transport is not ready, loop back to repick system=system
users-http_1                 | [00] [0004]  INFO core: Channel Connectivity change to CONNECTING system=system
users-http_1                 | [00] [0004]  INFO core: Subchannel Connectivity change to READY system=system
users-http_1                 | [00] [0004]  INFO core: Channel Connectivity change to READY system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel picks a new address "trainer-grpc:3000" to connect system=system
trainings-http_1             | [00] [0002]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0002]  WARN core: grpc: addrConn.createTransport failed to connect to {trainer-grpc:3000 trainer-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.4:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0002]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel picks a new address "users-grpc:3000" to connect system=system
trainings-http_1             | [00] [0002]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0002]  INFO core: Subchannel Connectivity change to READY system=system
trainings-http_1             | [00] [0002]  INFO core: Channel Connectivity change to READY system=system
users-grpc_1                 | [00] [0001]  INFO finished unary call with code OK grpc.code=OK grpc.method=GetTrainingBalance grpc.service=users.UsersService grpc.start_time=2021-12-30T21:56:57Z grpc.time_ms=1745.835 peer.address=172.18.0.8:46412 span.kind=server system=grpc
users-http_1                 | [00] [0006] DEBUG Credits set to attendee attendee_uuid=2
users-http_1                 | [00] [0006]  INFO core: Channel Connectivity change to SHUTDOWN system=system
users-http_1                 | [00] [0006]  INFO core: Subchannel Connectivity change to SHUTDOWN system=system
users-http_1                 | [00] [0006] DEBUG Users fixtures loaded after=6.6761767s
users-grpc_1                 | [00] [0002]  INFO finished unary call with code OK grpc.code=OK grpc.method=UpdateTrainingBalance grpc.service=users.UsersService grpc.start_time=2021-12-30T21:56:58Z grpc.time_ms=374.209 peer.address=172.18.0.8:46412 span.kind=server system=grpc
trainings-http_1             | [00] [0004]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0004]  INFO core: Subchannel picks a new address "trainer-grpc:3000" to connect system=system
trainings-http_1             | [00] [0004]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0004]  WARN core: grpc: addrConn.createTransport failed to connect to {trainer-grpc:3000 trainer-grpc:3000 <nil> 0 <nil>}. Err: connection error: desc = "transport: Error while dialing dial tcp 172.18.0.4:3000: connect: connection refused". Reconnecting... system=system
trainings-http_1             | [00] [0004]  INFO core: Subchannel Connectivity change to TRANSIENT_FAILURE system=system
trainings-http_1             | [00] [0004]  INFO core: Channel Connectivity change to TRANSIENT_FAILURE system=system
trainer-http_1               | [00] [0000]  INFO core: parsed scheme: "" system=system
trainer-http_1               | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
trainer-http_1               | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{firestore:8787  <nil> 0 <nil>}] <nil> <nil>} system=system
trainer-http_1               | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
trainer-http_1               | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
trainer-http_1               | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainer-http_1               | [00] [0000]  INFO Starting HTTP server
trainer-http_1               | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
trainer-http_1               | [00] [0000]  INFO core: Subchannel picks a new address "firestore:8787" to connect system=system
trainer-http_1               | [00] [0000] DEBUG Waiting for trainer service
trainer-http_1               | [00] [0000]  INFO core: Subchannel Connectivity change to READY system=system
trainer-http_1               | [00] [0000]  INFO core: Channel Connectivity change to READY system=system
trainer-grpc_1               | [00] [0000]  INFO core: parsed scheme: "" system=system
trainer-grpc_1               | [00] [0000]  INFO core: scheme "" not registered, fallback to default scheme system=system
trainer-grpc_1               | [00] [0000]  INFO core: ccResolverWrapper: sending update to cc: {[{firestore:8787  <nil> 0 <nil>}] <nil> <nil>} system=system
trainer-grpc_1               | [00] [0000]  INFO core: ClientConn switching balancer to "pick_first" system=system
trainer-grpc_1               | [00] [0000]  INFO core: Channel switches to new LB policy "pick_first" system=system
trainer-grpc_1               | [00] [0000]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainer-grpc_1               | [00] [0000]  INFO core: Channel Connectivity change to CONNECTING system=system
trainer-grpc_1               | [00] [0000]  INFO core: Subchannel picks a new address "firestore:8787" to connect system=system
trainer-grpc_1               | [00] [0000]  INFO Starting: gRPC Listener grpcEndpoint=:3000
trainer-grpc_1               | [00] [0000]  INFO core: Subchannel Connectivity change to READY system=system
trainer-grpc_1               | [00] [0000]  INFO core: Channel Connectivity change to READY system=system
trainer-http_1               | [00] [0000] DEBUG Trainer service is available after=806.6604ms
trainer-grpc_1               | [00] [0000]  WARN core: grpc: Server.Serve failed to create ServerTransport:  connection error: desc = "transport: http2Server.HandleStreams failed to receive the preface from client: read tcp 172.18.0.4:3000->172.18.0.7:46702: read: connection reset by peer" system=system
trainer-http_1               | [00] [0000] DEBUG AvailableHoursHandler executed duration=90.9724ms error=<nil>
trainer-http_1               | [00] [0002] DEBUG Trainer fixtures loaded after=2.532037s
trainings-http_1             | [00] [0008]  INFO core: Subchannel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0008]  INFO core: Subchannel picks a new address "trainer-grpc:3000" to connect system=system
trainings-http_1             | [00] [0008]  INFO core: Channel Connectivity change to CONNECTING system=system
trainings-http_1             | [00] [0008]  INFO core: Subchannel Connectivity change to READY system=system
trainings-http_1             | [00] [0008]  INFO core: Channel Connectivity change to READY system=system

Not completed logic which needs transaction.

Thank you for your repo and articles. This is awesome work!
I'm trying to rewrite my legacy service to the yours approach.

And I have a question:

How to guarantee that all hours in this command will be updated atomic?

Or what will happen when repo will be updated here:
https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/blob/master/internal/trainings/app/command/schedule_training.go#L56
but one of external service won't be available? Will we have incompleteness here?

What to do in "cyclic import" situation?

I'm learning DDD and Golang thanks to your work! Thanks will never be enough!

I have a big doubt.

I'm building an app for tennis players and I have structured files like this:

pkg/reservation
  /app
    /query
      /get_reservation.go
    /command
      /create_reservation.go
    /app.go
  /domain
    /reservation
      /repository.go
      /reservation.go
  /adapters
    /postgres
      /repository.go
      /reservation.go
  /ports
    /http
      /reservation.go
      /service.go

pkg/game
  /app
    /query
      /get_game.go
    /command
      /create_game.go
    /app.go
  /domain
    /game
      /game.go
      /repository.go
  /adapters
    /postgres
      /game.go
      /repository.go
  /ports
    /http
      /game.go
      /service.go

pkg/player/... is the same

with /pkg/reservation/domain/reservation/reservation.go:

import (
  "pkg/game/domain/game"
  "pkg/game/domain/player"
)

type Reservation struct {
  id int

  start time.Time
  end time.Time

  game *game.Game
  player *player.Player
}

and /pkg/game/domain/game/game.go:

import (
  "pkg/reservation/domain/reservation"
  "pkg/game/domain/player"
)

type Game struct {
  id int

  finalScore string

  reservation *reservation.Reservation
  players []player.Player
}

At least for the moment this application is a simple CRUD and perhaps this structure is excessive. But soon the application will grow and I would like to be ready.

As you can see there is a cyclic import:

reservation.go imports pkg/game/domain/game and game.go imports pkg/reservation/domain/reservation.

The big doubt is how to fix this cyclic import.

As read here I can:

  • duplicate Game and Reservation structs: but I don't like this very much because they are perfectly the same, but maybe I'm wrong here;

  • use everywhere IDs only: but this is not a fix, because sometimes I really need *Reservation in my Game struct.

What do you recommend?

This is a very common situation I think.

Authorization in repository?

Thanks for your work!

Why repositories do calls such as training.CanUserSeeTraining? Looks like domain logic. We need to test it in all repository implementations instead of testing once on domain (or maybe application level).

Web frontend upgrade to vue3 and python3

Any plan to upgrade web/frontend from vue2 to vue3? The reason is dependency on webpack 4 and python2 etc, while python2 is not officially supported and I get error with python2 not found in environment path.

I tried to migrate web/frontend to vue3 by replacing package.json, but still gets tons of compiling errors. And curious to see anyone has luck on vue3 yet.

Maybe a better name for internal would be services

First off this is an amazing project. This is one of those rare gems that you find on the internet. I've gone through all the articles and am using this repo as a boilerplate for my project. So thank you so much!!!

I did have an observation to make. It seems to me that the folder internal ought to be called services since it in fact holds the different services - users, trainer and trainings. Each of the services can also be published under github.com/ThreeDotsLabs/wild-workouts-go-ddd-example/services/.... common could be a separate package under services that is dependancy of the other 3 services.

What do you think?

Don't use alpine for node

it's actual less in size, but consumes much RAM.

node:13.11.0-alpine3.11

Use, LTS versions ๐Ÿ‘๐Ÿพ

Seperate Go structs for the repository pattern

Hello,

Let me start by saying that I really enjoy reading your book, I find it extremely useful and very well written.

We are trying to follow the clean architecture principles in our team, and we have a question about the correct way to separate between models.

Assuming we have the following layers:

  1. API layer - using http
  2. Service layer - contains business logic
  3. Persistence layer - using the Repository pattern

We have some confusion around the best practices around which models to use where.

For example, what we currently do:

  1. Accept a request in HTTP, decode the request body into a DTO struct which is defined in the api package.
  2. Convert this struct into a "domain" struct (using mapstructure or a plain function), and pass it to the Service layer.
  3. In the service layer, we need to call some function from the Repository layer.

Here comes the problem:
Should the repository layer accept and return a domain model, or a database model?
If it accepts and returns a domain model, each implementation is responsible for decoding it to the whatever it wants, which feels more robust. However, it means that the repository layer is now aware of the domain layer, which doesn't feel right.

So basically my question is: which of the following options is considered more correct:

  1. Pass the repository a service model, which means the implementation will decide on how to use it and return it
type Repository interface {
  Insert(model ServiceModel) (ServiceModel,error)
}
  1. Add a DbModel struct to the database package, which means that the service model instantiates it and passes it to the repository
type Repository interface {
 Insert(model DbModel) (DbModel,error)
}

Appreciate your response!

Not persisting hours after creating them with the in-memory repository

So, you're creating a new Hour, but returning it immediately without storing it in the map.

return m.hourFactory.NewNotAvailableHour(hourTime)

It looks like ok will be always false.

Am I missing something here?

Demo with two repos?

Hi, first, I love your articles and this repo very much and want to learn from them.

One thing not quite clear to me is that the two services trainer and trainings are in the same repo. It's different from our usual case that different services are in their own repos.

Since they are in the same repo, there is common folder that used for some shared info. This is not easy if they are two repos. Also component test and end-to-end test seem a little easier.

It'll be better to demo the same with two repos and I'd love to see how it looks.

Thanks!

Can't find a license

I see in @blaggacao 's comment on #9 that he cited the license for this repo as MIT, but I don't see a license file in the root directory. I did a search of the repo and didn't find any mention of a license either. Is this code MIT-licensed? If so, can you add a copy of the MIT license somewhere in the repository?

How to re-arrange oapi-codegen based petstore example using the Repository Pattern?

The oapi-codegen based petstore example has a very simple and straightforward file structure. Using the fiber server as an example, as far as the server end is concerned, here are all the files it needs:

`--petstore-expanded.yaml
`--fiber
| `--petstore.go
| `--petstore_test.go
| `--api
| | `--petstore.go
| | `--petstore-server.gen.go
| | `--server.cfg.yaml
| | `--petstore-types.gen.go
| | `--types.cfg.yaml

How to re-arrange oapi-codegen based petstore example using the Repository Pattern?

I tried to split the code as per the Repository Pattern myself following the folder structure in #69:

`--api
| `--openapi
| | `--petstore-expanded.yaml
`--internal
| `--petstore
| | `--main.go
| | `--main_test.go
| | `--core
| | | `--app
| | | `--domain
| | | | `--petstore.go
| | | `--services
| | `--interfaces
| | | `--adapters
| | | `--ports
| | | | `--petstore-server.gen.go
| | | | `--petstore-types.gen.go
| | | | `--server.cfg.yaml
| | | | `--types.cfg.yaml

However in file domain/petstore.go (changed a bit from here):

// FindPets implements all the handlers in the ServerInterface
func (p *PetStore) FindPets(c *fiber.Ctx, params ports.FindPetsParams) error {

I.e., I have to use the type FindPetsParams defined in ports (ports/petstore-types.gen.go) in domain, and then extract the server agnostic part of algorithm into function FindPets in the domain package, and call such domain FindPets function in http.go file within the ports package.

In other words, I'll be doing import cycling, which is not allowed in Go. So, all in all,

How to re-arrange oapi-codegen based petstore example using the Repository Pattern? thx.

Authorization/permissions for single domain's struct fields

Thanks for your work on this: it's great!

I have read https://threedots.tech/post/repository-secure-by-design, but I have a doubt about how to fix permissions for individual domain struct fields.

Let's say that for domain.Training struct if the logged in user is an attendee he cannot see the field notes (it's just an example).

In that amazing article you suggest to put the auth check on repository, using the domain's method CanUserSeeTraining(), great. I can use the same technique with that field only, but now this is a problem if in my repositories I use a code like:

func (r TrainingsFirestoreRepository) GetTraining(
 ctx context.Context,
 trainingUUID string,
 user training.User,
) (*training.Training, error) {
 firestoreTraining, err := r.trainingsCollection().Doc(trainingUUID).Get(ctx)

 if status.Code(err) == codes.NotFound {
  return nil, training.NotFoundError{trainingUUID}
 }
 if err != nil {
  return nil, errors.Wrap(err, "unable to get actual docs")
 }

 tr, err := r.unmarshalTraining(firestoreTraining)
 if err != nil {
  return nil, err
 }

 if err := training.CanUserSeeTrainingNotes(user, *tr); err != nil {
  // this is not beautiful this here because we already fetched data from DB that the user cannot see (notes)
  // return nil, err
 }

 return tr, nil
}

Plus now we have a problem when we call repository's methods like Relation("Training.Notes") maybe using relational DBs and tools like Gorm: we cannot control what fields are fetched in other repository's methods!

Another way I can think of this is to use the domain's method Notes() (which is also more "secure" as no one can forget to call that method now and it's used each time we unmarshal from DB, whatever repository's method is called):

func (user training.User, t Training) Notes() string {
  if training.CanUserSeeTrainingNotes {
    return t.notes
  }

  return ""
}

but I have doubts on this too.

Am I completely wrong to think this way?

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.