Domain-Driven Design has become quite a spread practice in the small bubble where I have interactions upon. For developers is like this desired way of doing things and for managers and HR people is just a marketing word, to feel that they have an advanced company for the former and to attract talent for the latter.
DDD is actually not that new, from the wikipedia:
The term was coined by Eric Evans in his book of the same title published in 2003.
so it's been there for about 20 years, and if you happen to have gone to informatics/computer science faculty afterwards - I started right when this book was published - you probably studied many of the patterns and high level concepts this book explained. I encourage you to read the book, known as the blue book for its cover - you might experience like me a trip back to when you were studying "software engineering I" and "II".
It was 10 years later though that Vaughn Vernon came with his red book -also called like that because of its cover- to make DDD practice to really explode. This book is also a must-read. I must confess that I fought against slumberness while doing that, but it's OK to try to read it first, and then come back to it on specific doubts, because you'll remember that there was something written about that, like I did here.
I'd say he's been the one to push the usage of DDD with ports and adapters architecture (hexagonal architecture) and the next steps to Event Driven Architecture or CQRS+ES.
Now, the way I came across those practices was even later, the PHP world is high-jacked by all of those CMS (Wordpress, Drupal, etc) and that's what gives results fast - and the faster way to get a job after college - so it's been thanks to these guy: codely.com and this other guys: youtube.com/c/carlosbuenosvinoszamora to bring DDD to the PHP world, at least in Spain, and might I dare to say even to other Spanish-speaking countries. Carlos Buenosvinos co-authored book about DDD in PHP is also a must-read if you work with PHP. (I came across them thanks to my boss at the time, thanks for that!).
My experience then with DDD was that one of success, even though it was just the beginning, but knowing where was the appropriate place to write each piece of logic and how to find that easily, and start having less and less side effects from each function, it was giving results quite fast. But it took me many years of practice and especially reading the books that it all fall into place, and I could understand the whole narrative including even that of what I was taught at college.
Unfortunately, DDD is not as spread as it should be, and many early adopters have rookie mistakes when trying to apply them. Most of those mistakes are actually symptoms of the same problem: a miss-understanding or lack of the mindset. Hopefully, by pointing them out, I can help you adopt the proper mindset, but nothing will achieve that as nicely as you spending about 20 hours reading the books and some dollars (that your company can and should pay).
Well let's begin.
Mistake 1: thinking DDD === Hexagonal Architecture
It's very common. I have a project, I write three folders under my source folder like this:
├── src │ ├── Application │ ├── Domain │ └── Infrastructure
and I call it a day. Hey I am doing DDD!
Well no, you are stating your intention on using hexagonal architecture, which is always a good thing, but that by itself is not DDD.
Hexagonal architecture, in my opinion, is the only way to go, for every project. DDD works very well with hexagonal architecture, it's the best way to protect your domain, and the easiest way to realize where each piece of logic goes: oh that's business! goes to the domain, oh! that is about infrastructure, then it goes to infra. But, while using hexagonal architecture is a good start and something that will save you of many headaches in the future, it's not the same as doing Domain Driven-Design.
In other words, DDD is very hard to do without hexagonal architecture, but you can have hexagonal architecture and not having DDD at all. You can still have a mess in it. Most of the tactical patterns of DDD fall into the Domain layer, so you need to take extra care of that layer.
You should start with that layer, coding all the business logic, and then show via tests how all of this logic is being executed. How the domain is being executed. Even better if you start with the test. And unit tests should suffice for that! You need to make sure the infrastructure doesn't creep all the way to the domain and try "broom inwards" to the inner layers all what could be considered business logic. So the separation of concerns is clear.
Even if you are applying properly hexagonal architecture, you can still have something that looks more like a CRUD application than something done via DDD. It's all about the Domain, if your domain is not rich and what you simply do is just CRUD operations on some half-baked entities that will eventually become meaningless, you might as well give access to the DB to the end user, and you'll save lots of hours!
Mistake 2: overly modular directory structure
For some reason, I see this quite often. And I frankly don't get it. It's overly complicated! And it gives the wrong intent by the programmer. This is what I am referring to:
├── src │ ├── User │ │ ├── Application │ │ ├── Domain │ │ ├── Infrastructure │ └── UserRoles │ │ ├── Application │ │ ├── Domain │ │ ├── Infrastructure │ └── Role │ │ ├── Application │ │ ├── Domain │ │ ├── Infrastructure
Well, that would make sense in the case you are looking forward to split this code base into different services, so you are preparing yourself for it. But what is clear out of it, is the complete disregard of the domain! Again people focus on the technical details and forget that DDD is. all. about. the. domain.
On the example it's obvious that User and Roles are highly related. They should probably belong to the same subdomain. And while there might be exceptions, a subdomain should be a bounded context, meaning a service - microservice or application if you want. Would you add a different infrastructure in the same service?
This might even break aggregate rules! One transaction per aggregate. If you just go and split each entity and make it a "service" you are forgetting that maybe they belong to the same aggregate! You definitely are in for a lof of pain. Microservices work on the premise on information hiding, if you have an aggregate defined in different services, you would want to break this principle...
I don't understand why this became such a practice. (I am afraid it's because of some public repos of codely.tv taken out of context, or people used to the horrible laravel's ORM eloquent). But you don't want to make a service per entity, you want to make a service per subdomain. A service - or microservice, application- per entity will for sure lead to a CRUD practice, and eventually to an anemic domain and for sure to a distributed monolith.
All of this belongs to the strategic patterns of DDD. Showing this directory structure implies that each of those are bounded contexts and that you expect them to work totally independently of each other. Again, we need to have the Domain in the heart of our mindset, is that really the case or not? It's ok, and you should, put all the related aggregates in the same bounded context. Maybe you will see that you can split the bounded context further to ease the cognitive load. But you start from a big group and split it. You don't start with everything split by default, and then you join it, uf, that will be a lot of pain...
Mistake 3: CRUD implementations
This is kind the sum of mistake 1 and 2. You can have a very well understandable code in hexagonal architecture, divided nicely in modules per aggregates but with an anemic model that only does CRUD operations.
When I started working in a new company I was rudely told by a colleague "oh I did that in DDD it should be understandable". Seeing CRUD operations on an entity doesn't really explain the business logic. As easy as it can be to maintain it since it's in hexagonal architecture. To explain them you'd better use tests that execute the domain. Even some acceptance tests (the ones that test acceptance criteria) could be simply unit tests.
CRUD operations is almost a synonym of an anemic domain, which is the opposite of a rich domain. Guess which one DDD prefers, having the word "Domain" at the core? Yes, rich domain. An anemic domain is easy to degenerate. You will have meaningless boxes that can either be apples or pears. In that case, you might as well give admin access to the DB to the end users.
It always pays off to make an effort at avoiding CRUD verbs, you will soon realize that your domain is, indeed, more complex than just doing CRUD operations, the move to Event Driven Architecture will be smoother also, having domain events that make actual sense to business, not to databases.
As a side note here, I think that being very dogmatic on RESTful implementations is harmful. REST works very well with CRUD and oh, with active record ORM and anemic domains, so if you have to make very tricky and complicated things just to comply with RESTful principle, I say, ditch RESTful, at least temporarily, you might realize later how to solve it. The dogma of a rich domain should trump the dogma of a RESTful API. I have seen codebases degenerated into un-understandable messes just in an effort to keep RESTful.
Mistake 4: concealed logic at the infrastructure layer
Here is where the hexagonal architecture will pay off. It will be very easy to identify that. The problem is that is difficult to keep strong and to not succumb to the temptation. The alternative is also a lot of work! But well, you know, it should be! and business people should be aware of it, that features have a price and high quality code has too.
So what is this logic? The most common case would be this one, have you ever seen this: @Gedmo\Mapping\Annotation\Timestampable ? You magically add "createdAt" and "updatedAt" fields to the entity, and you forget about it. As I said, it's easy, and the alternative? do it yourself. But you should. Does business need to know when this entity was createdAt or updatedAt? (also, those are CRUD verbs, not a good path) Does the domain need to work with that? If yes, put it where it belongs, in the domain! The entity should have attributes/value objects that indicate this information.
Yes it's a lot of work, but worth it! And if the domain doesn't care, then it's OK to leave it? It's extra information! Well no, it's not OK. Main issue is that it might fail when you retrieve or save a perfectly fine Domain object to the DB because it doesn't comply with logic that is "hidden" in the DB layer.
All the foreign key checks on the Database? well, I'd recommend to get rid of them, the infrastructure layer should be free of business logic, if aggregates have relationships, state that clearly in the Domain code, the infrastructure won't have other choice than to follow. Map the aggregates to the DB as simple as possible. You will see how easy it will be if you ever want to change to NoSQL if you have all the business logic on the domain! As soon as you allow logic on the DB, you will have the domain split in different layers, breaking the hexagonal architecture.
Other examples are making an entity sluggable or even translatable! it might not look like it, but try to bring everything to the domain layer, so infrastructure/application layer are thin and dumb.
That means also that the latest technology and cool stuff, well, it doesn't belong to the domain, so you can make use of it, but on the layer it belongs. You will thank me when you need to upgrade the version of that latest technology.
Mistake 5: leaving domain for the last.
Well, I hope you can see that this is much of the same, all above (maybe except #4) are symptoms of this one. On Domain-Driven Design guess what comes first? Infrastructure? if that were the case don't you think it would have been named infrastructure-driven design? So yeah, Domain comes first.
What does that mean? That you need to speak a lot with business, or the product owner needs to speak a lot with business, or have things very very clear, so you can start modeling the domain first. And you need to speak a lot, communicate a lot, ask questions, maybe even show the code, explain the tests, are we speaking the same language and on the same page? All of that comes first, the rest follows.
And that comes first, is also what will probably evolve the most, will change the most. Imagine if you have it all in a single place and not all spread everywhere how easy it will be to maintain it?
When designing the domain is when DDD truly shines, it might not be easy, and as said, it will change, either because needs change or because we did such a terrible job at the beginning that we need to change it! Making proper use of Value Objects, entities, aggregates, applying design patterns here, it all makes sense, and it's pretty.
If you don't care about the domain, you're making sure you're having a bad design out of it. I was also told "no, in this project there's no domain". Oh, that's impossible, there's no attempt at designing it, which means a terrible design, since there's no such thing as "no-design". As the red book says, that means it's a bad design.
DDD is all about the domain. A rich domain. I hope that by pointing out this common mistakes, you get a general sense on the proper angle to approach problem-solving.
I would like to finish saying that DDD and Hexagonal architecture are great, but they are not silver bullets. There's a more important thing than DDD when coding, something conveyed on Clean Code by George.R.R. Martin, I mean, Robert C. Martin commonly known as Uncle Bob. (I just had to google them, I had the idea in my mind that they were physically similar, but not at all). The idea is that when coding you should strive for making yourself be understood.
You should strive always for simplicity (and control, I hate the no-code thing, if you like that, just go and become a "website builder" from Drupal, and leave the actual work to the "smart people"). Don't be afraid of hard work. The code with more "WTF" per second I've seen, it's been always something written by somebody that thought what was written was very smart. Oh, it was indeed, but, if you have a message to convey, would you encrypt it, or would you make sure that it is as easy to understand as possible?
We are doing complicated things, let's not make them more complicated than they need to be...(except when you try to be funny, it's forbidden by Uncle Bob on Clean Code, but a pun in a specific place can make for a very expensive joke!)