Closed samuelgfeller closed 3 years ago
This is a complex topic that cannot be explained in a few sentences. There are a lot of concepts and many more opinions about this topic. Everyone uses different terms for the same thing, which makes it really hard to explain and compare these different architectural styles. Even in the DDD world there is still a lot of confusion and discussion about even small things.
DDD is like looking at an abstract Kandinsky painting. Everyone sees something different.
I've come to the conclusion that you have to find a style that fits your technical project requirements, your personal skills, and your own "mindset." It makes no sense to overengineer a simple application with DDD.
I prefer ADR + a simplified DDD architectural style that works really well, even in very big projects. You can find more details in my eBook (Slim 4 - Architecture) about this topic.
ADR is basically MVC Model 2 with other names. MVC Model 2 is a design for web applications.
And View should never be in contact with Model, kind of like on this image, right?
Yes. The View layer is only for the output and it never fetches data from an external source.
Your example graphic describes the classic MVC design pattern which (is outdated and) doesn't work for web applications.
ADR doesn't specify how you design your "Domain" layer. The point is that the Action invokes the Domain layer and takes the result to render the response (HTML, JSON, XML etc...). The ADR Action is the Controller in terms of MVC Model 2. So the archtecture looks basically like this:
https://github.com/qossmic/deptrac/blob/master/examples/ControllerServiceRepository1.png
You see, the Service depends on the repository because the Service needs data from the database. In DDD the Repository Interface belongs to the Domain, but the Repository class belongs to the infrastructure. The Repository mediates between the domain and data mapping layers. Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.
https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
The UserRepository
creates a domain specific object that can consume by the service. The is valid, because the Repository Interface belongs to the Domain. The UserRepository creates this object using the Infrastructure (database) to generate this object or list of objects.
Good books about this topic:
Just discovered Hexagonal Architecture, DDD, and Spring | Baeldung what's written there as well as the first illustration go in the direction of what I was doing and thinking about all this.
I think an important thing that I didn't understand well is that Infrastructure is really just an interchangeable adapter, it's outside like written here:
What we'll do instead is divide our application into three layers; application (outside), domain (inside), and infrastructure (outside)
Edit: Just saw you replied. I'll read through and make a new comment after
Well the graphic alone in your e-book (architecture) VERY clearly answers my question lol. I love the red arrow with the 🚫 symbol.
In DDD the Repository Interface belongs to the Domain, but the Repository class belongs to the infrastructure.
I think adding this phrase to the e-book would be value added (and maybe even adapt the image). I really like these images as well.
After reading more though I saw that you already mentioned all this with "In the best case, externalize the infrastructure with proper interfaces to ensure loose coupling between the application and the database."
And you didn't do this in slim4-skeleton
because you didn't want to over-engineer a simple DDD and keep the project as light as possible oriented for small to medium API ? That would make a lot of sense to me as development with interfaces is admittedly a bit annoying as the IDE doesn't jump to the concerned function directly but first to the interface and its just not worth the extra complexity when the project stays small (I did exactly that very early on in the project). The repository functions can act as definition too and directly implement what they should do.
The transition to a new adapter is a bit less smooth but easily doable. The repo could be duplicated, change code content, replace the old one. Or would you still not recommend skipping the interface in the Domain as there could be multiple data sources simultaneously and which one is taken depends on use cases?
The UserRepository creates a domain specific object that can consume by the service. The is valid, because the Repository Interface belongs to the Domain. The UserRepository creates this object using the Infrastructure (database) to generate this object or list of objects.
Alright, so instantiating a Domain object in the Infrastructure layer is okay because it only implements / does what is defined in the Domain (repo interface) so it's like the things happen from the Domain or better said we could imagine that return values indirectly come from the Domain too, right?
By extension the Hydrator
would be at the correct place in the Infrastructure layer as it's only instantiating the Domain objects with a foreach loop around it. Would that mean that I couldn't access it from Application and Domain anymore if I did this?
I had decided to make an additional folder "Common" and put it there after I read this question on StackOverflow. What convinced me was (in second answer):
It should be a library that could easily be carried on to the next project, as it would be so generic and without any dependency on current domain logic
Thanks for the book recommendation. Knowing where good resources are, is very valuable for me.
P.S. There's a tiny typo in point 7 of the Description "ir" instead of "or"
- The service can read ir write data to the database using a repository
And you didn't do this in slim4-skeleton because you didn't want to over-engineer a simple DDD and keep the project as light as possible oriented for small to medium API ?
Yes, keep it simple (KISS). This concept is a simplified ADR+DDD architecture, without the confusing Buzzword-Bingo.
I don't use interfaces for repositories. I just implement the repository directly for the sake of simplicity. The query builder already abstracts the driver and SQL. It's still excellent testable, less bloated and easier to handle during the development process (IDE).
Alright, so instantiating a Domain object in the Infrastructure layer is okay
Yes. You can create Domain objects in the Domain and in the Infrastructure layer. This is what is described in the book "Object Design Style Guide". Sometimes I just return a simple array as long as the result is only used "for internal calls" ;-)
By extension the Hydrator would be at the correct place in the Infrastructure layer as it's only instantiating the Domain objects with a foreach loop around it. Would that mean that I couldn't access it from Application and Domain anymore if I did this?
The Hydrator
is just a "tool" to automate object creation and belongs not to the Domain and not to the Infrastructure.
You should use it where you need it AND where it makes sense.
I had decided to make an additional folder "Common" and put it there after I read this question on StackOverflow. What convinced me was (in second answer):
I put classes like this into src/Support/...
. Some people put it into src/Util
, src/Utility
, src/Tools
or src/Helper
... it's a matter of taste.
P.S. There's a tiny typo in point 7 of the Description "ir" instead of "or"
Thanks for the hint.
Yes, keep it simple (KISS). This concept is a simplified ADR+DDD architecture, without the confusing Buzzword-Bingo.
Nice, that makes me kind of little proud of my intuition when I was a total noob ^^ (tbh it was more laziness than something else, but I felt bad doing it)
And thank you so much for all the other answers. I understand a lot better and have a little more confidence about what I'm doing now.
I have just a last small question: when implementing repository without interface, I personally find it more beautiful, and also it gives a hint that repo is an external adapter when repository with database manipulation is in its Interface Layer / Adapter and not in the domain.
Even though I can very well understand that having it in the Domain makes more sense to you as like you said, the Cake Query Builder abstracts from the domain and is the adapter, in my earlier projects I wrote MySQL statements directly in the repo function and executed it there as well. Building the query with the query builder still feels like doing the same (even though it's not at all, the query and db connection is done somewhere else). Who knows, probably I'll change my mind soon.
Is that just a matter of choice, or would you recommend building those queries in the Domain like you did in slim4-skeleton
?
Is that just a matter of choice, or would you recommend building those queries in the Domain like you did in slim4-skeleton?
The Repository pattern is a way of working with a data source. It doesn't matter what kind of database (Relational, NoSQL etc) you are dealing with and how you implement these "technical details" within the repository. In other words, you can use plain SQL (PDO with prepared statements), a QueryBuilder, or native driver specific functions like OCI8. As long as the parameters and the results are technically separated, everything is fine.
Personally I would recommend to build SQL queries like I did in slim4-skeleton
. If you don't like the CakePHP-QueryBuilder you can also install other components like laminas/laminas-db
or doctrine/dbal
etc...
I don't think I was accurate in my wording. What I meant is, I like "building" those queries (CakePHP QueryBuilder) in the Repositories that are located inside the Infrastructure folder and not the Domain.
Does it make sense to put that repository (which is using the query builder) in the Infrastructure layer rather than Domain when not working with interfaces like we do?
Please tell me if its still unclear. Here would be an example:
https://github.com/samuelgfeller/slim-example-project/blob/master/src/Infrastructure/Security/RequestTrackRepository.php
From a DDD perspective it would make more sense to put the Repositories into the Infrastructure and not into the Domain. The Repository Interfaces should then be placed under the Domain.
The official Slim-Skeleton promotes this DDD style: https://github.com/slimphp/Slim-Skeleton/tree/master/src
In practice, I had a lot of troubles with this directory structure because it takes more and more time to switch between these directories when you have a project with 1000+ classes. When I open a "module / feature" I want to see the services and the repositories at the same "place" to be faster and more productive.
When I open a "module / feature" I want to see the services and the repositories at the same "place" to be faster and more productive.
Alright, I totally understand that. Guess I'll have to make my own experiences and see what I find the most practical.
Thank you again Daniel, genuinely. I wish you an awesome weekend!
I've read the docs about slim4-skeleton architecture and other resources about DDD and Hexagonal Architecture but seem to not understand everything yet.
There is quite some debate about where the repository should go and the following made the most sense for me: repository interfaces in the Domain layer (ports) and the actual implementation (adapter) and database connection / manipulation in an additional Infrastructure layer that could easily be swapped as long as repository classes implement interfaces defined in Domain.
From what I remember when I learned MVC is the different layers should depend only on "deeper" ones like View depends on Controller, but controller must not depend on view. Same for Model and Controller. Now is it the same in a Domain Driver Design architecture where Application depends on Domain which depends on Infrastructure but not the other way around? In this case I wanted to ask, in the
UserRepository.php
ofslim4-skeleton
you instantiateUserData
still inside the repo, does that mean that the repo (which is in the Infrastructure layer) depends on Domain becauseUserData
is in the Domain layer and therefore is not good when doing DDD with an Infrastructure layer?Also, I have troubles understanding phrases like these from here
And also on the question "Can Entities, Value Objects or Domain Services call Repositories?" he answers with
That directly contradicts everything I thought I knew about the subject and is definitely not the path I was taking. My Actions in Application layer call Service classes in Domain layer which calls Repositories that are in the Interface layer.
For me Application / Responder were near the MVC "View" layer (not similar at all; I compare only the order from nearest to client and nearest to db), Domain near Controller which contains business logic and Infrastructure / Repositories near Model.
And View should never be in contact with Model, kind of like on this image, right? Did I get everything wrong?
Apologies for the long and complicated question.