eminetto / clean-architecture-go-v2

Clean Architecture sample
1.28k stars 228 forks source link

feat: improvements in structures #4

Closed eminetto closed 4 years ago

eminetto commented 4 years ago

New structure

.
|____cmd
| |____main.go
| |____main_test.go
|____go.mod
|____config
| |____config_staging.go
| |____config_testing.go
| |____config_prod.go
| |____config_dev.go
|____driver
| |____repository
| | |____user_mysql.go
| | |____book_mysql.go
|____Makefile
|____entity
| |____error.go
| |____user.go
| |____user_test.go
| |____book.go
| |____entity.go
| |____book_test.go
|____go.sum
|____README.md
|____.gitignore
|____api
| |____handler
| | |____loan_test.go
| | |____user.go
| | |____user_test.go
| | |____book.go
| | |____loan.go
| | |____book_test.go
| |____middleware
| | |____cors.go
| | |____metrics.go
| |____presenter
| | |____user.go
| | |____book.go
| |____main.go
|____ops
| |____db
| | |____init.sql
| |____prometheus
| | |____prometheus.yml
|____docker-compose.yml
|____usecase
| |____loan
| | |____service.go
| | |____service_test.go
| | |____mock
| | | |____loan.go
| | |____interface.go
| |____user
| | |____inmem.go
| | |____service.go
| | |____service_test.go
| | |____mock
| | | |____user.go
| | |____interface.go
| |____book
| | |____inmem.go
| | |____service.go
| | |____service_test.go
| | |____mock
| | | |____book.go
| | |____interface.go
|____.travis.yml
|____pkg
| |____password
| | |____fake.go
| | |____interface.go
| | |____password.go
| |____metric
| | |____interface.go
| | |____prometheus.go
eminetto commented 4 years ago

@cjslep @aschrijver @hurrycaner can you take a look in this new structure?

aschrijver commented 4 years ago

Again with the same (un)healthy dose :wink: of lacking Go knowledge as before I have the following observations:

Some of the same considerations I mentioned in #3 still apply:

These kinds of folder structures we are talking about are most appropriate for large-scale projects (as @cjslep also mentioned in #3) . Imagine what could happen when the project grows:

I'll add more observations to #3 as well. Despite what @cjslep mentioned my proposed folder structure still looks the cleanest, and - although it may require a bit more discipline - looks still do-able in Go.

eminetto commented 4 years ago

Again with the same (un)healthy dose 😉 of lacking Go knowledge as before I have the following observations:

Some of the same considerations I mentioned in #3 still apply:

  • To me the clean architecture structure here implies there's one bounded context where book, user, loan are part of it. If they are separate contexts they should not be interspersed throughout the project structure like this, imho.

I agree with that. What i'm trying to do is make this example as simple as possible. So, i'm assuming that this is a single domain.

  • Your repository_mysql.go - the choice for MySQL - is a techstack decision that has nothing to do with your domain. It should not exist in the domain folder. A layered / hexagonal architecture places this in the Infrastructur layer. The repository interface could be part of the domain, though.

Thanks! I made a new commit to change this. Can you review again?

These kinds of folder structures we are talking about are most appropriate for large-scale projects (as @cjslep also mentioned in #3) . Imagine what could happen when the project grows:

  • The MySQL code may no longer fit repository_mysql.go but has a sprawl of related files, that are further polluting the domain folder. It becomes harder to maintain "inversion of control" (i.e. your domain has zero dependencies to application and infrastructure layers).
  • Your usecase folder structure is undescriptive. The current design would only work for CRUD of the entity. With a more full-blown DDD - where CRUD can be an anti-pattern that leads to an anemic domain model - I'd have to open each service.go file to find what use cases are actually in there.

I will try to make my domain to not be anemic. Let me think in an improvement to this example... Thanks for pointing that.

I'll add more observations to #3 as well. Despite what @cjslep mentioned my proposed folder structure still looks the cleanest, and - although it may require a bit more discipline - looks still do-able in Go.

aschrijver commented 4 years ago

I think having the fixtures in the same location as the domain entities makes sense. If there are a lot of them they could be in a subdir.

Note that in a non-anemic domain model the entity definitions (e.g. book aggregate root) would contain domain-specific business logic. There's many ways to slice this, but this could be related to business rules, validation (e.g. determining the strength of a password value object), or applying / triggering domain events + errors. This means that in the domain folder you would also find tests.

In the infrastructure ➜ application ➜ domain layered design, the usecase folder is typically in Application layer and not under domain. The use cases retrieve domain entities (via the repositories) and invoke domain logic on them. They contain the glue code that puts stuff together. There may be many use cases that are not directly domain-related (not part of the ubiquitous language). Just a consideration.. you may have a different preference.

The api would be in infra/api. In the hexagonal architecture it is one of the endpoints to the outside world, just as the db interface.

aschrijver commented 4 years ago

My biggest issue with the current setup is that usecase folder is not showing me use cases. It is not self-describing to that extent. It has subfolders that contain a service that seems to be 1-to-1 connected to a domain entity. But it is not.

//Service loan usecase
type Service struct {
    userService user.UseCase
    bookService book.UseCase
}

If a use case has a 1-to-1 match to a feature that was agreed upon with the customer, and hence - based on domain design - is described in a snippet of text formulated in the ubiquitous language (and note that a use case is not necessarily part of the domain itself. It uses the domain), then having a separate folder that clearly shows me its about that feature, and where I also find the BDD tests that are pre-loaded with the plaintext use case description, would be much cleaner architecture imho.

In your design I would move domain/usecase to application i.e. this is the application layer.

Plus a Service != a Use Case. A service is the entry point to multiple use cases. And that is also how it is implemented here.

With this - even though I don't know how my OOP concepts translate exactly to Go - I am still very much in favor of a folder structure that looks like my other proposal: https://github.com/eminetto/clean-architecture-go-v2/issues/3#issue-680180916