An interesting idea, though I’d use a slightly different architecture. Instead of organizing by layers first, Id rather package by feature, and each feature will have separate folders for layers.
Consider the folder structure in the article:
Id organize in this way instead:
The advantage is that usually we add features much more often than we add layers. In my proposed architecture, adding a feature is easy. If you want to add a comment system to post, just add a subfolder called comment/ next to draft/ and post/. You dont need to touch the other folders, which enables easier share/split of work among teams.
There is a lot to be said for organizing by feature instead of by layer; while you get a different set of tradeoffs, those tradeoffs might be better for one situation over another. Thanks for the good example!
But the aggregates organization only spreads over 3 layers (Domain, Application & Infrastructure|Adapter), that’s not so much and this is furthermore bounded as you said.
The problem I ran into was that, because they did not have differentiating suffixes, the “PublishDraft” Use Case had the same class name as the “PublishDraft” Domain Service. That caused local name conflicts when they were both used together. I would have had to alias one or the other in the use statement, so I figured I might as well give one (or the other) a class name suffix, to preempt name conflicts.
I use the same pattern, which is quite common, for the same reason.
I also find necessary to not put my aggregate names directly under Domain/ because I have also non-aggregate related namespaces such as common entities / VOs, constraint…
So instead of:
I find more clear to use this pattern
And since I use a 2-level Domain Layer organization (subdomains & aggregates) I end up with:
Also, to keep domain-level exceptions from escaping to the User Interface layer, the Application Services should have a catchall for exceptions emanating from the Domain. For example, add some wrapper or decorator logic on your Application Services to catch all exceptions from Domain activity, and that catchall can return a Payload (with the error in it) back to the calling User Interface code.
The command pattern is an easy & clear way to achieve this, it also allows to stack other cross-cutting concerns such as:
error wrapper
logging
permission / ownership management
SQL transaction boundary
input DTOs validation
workflow validation
events firing
related jobs scheduling
The command pattern is an easy & clear way to achieve this
This is another great example. Yes, having your Application Service (or Use Case, whatever) take the incoming input, collate it into a Command, and hand it off to a Command Handler, offers many organizational benefits.
One tradeoff is that it adds one more layer to pass through before getting to the core Domain work. That may not be such a big deal.
A bigger tradeoff is that, if you stick to the Command pattern properly, the return result from the Use Case cannot be any form of domain results. The return payload from the Use Case will have to be either “Command Accepted” or “Command Rejected”, and nothing else. You’ll need at least one more user interface interaction (a Query) to get back the domain results.
But in many systems that is the exact operational intent, so it would work very well there.
Fixed formatting.
Hello, noisebynorthwest: code blocks using triple backticks (“`) don’t work on all versions of Reddit!
Some users see this / this instead.
To fix this, indent every line with 4 spaces instead.
FAQ
You can opt out by replying with backtickopt6 to this comment.
Members
Online