I've had the same problem and "solved" it by modelling REST resources differently, e.g.:
/users/1 (contains basic user attributes)
/users/1/email
/users/1/activation
/users/1/address
So I've basically split the larger, complex resource into several smaller ones. Each of these contain somewhat cohesive group of attributes of the original resource which is expected to be processed together.
Each operation on these resources is atomic, even though it may be implemented using several service methods - at least in Spring/Java EE it's not a problem to create larger transaction from several methods which were originally intended to have their own transaction (using REQUIRED transaction propagation). You often still need to do extra validation for this special resource, but it's still quite manageable since the attributes are (supposed to be) cohesive.
This is also good for HATEOAS approach, because your more fine-grained resources convey more information on what you can do with them (instead of having this logic on both client and server because it can't be easily represented in resources).
It's not perfect of course - if UIs is not modelled with these resources in mind (especially data-oriented UIs), it can create some problems - e.g. UI presents big form of all attributes of given resources (and its subresources) and allows you to edit them all and save them at once - this creates illusion of atomicity even though client must call several resource operations (which are themselves atomic but the whole sequence is not atomic).
Also, this split of resources is sometimes not easy or obvious. I do this mainly on resources with complex behaviors/life cycles to manage its complexity.
This is a very broad question but I will try to give you an answer.
...there is some ambiguity with regard to the use of domain models and application services
There is no ambiguity if you design well your bounded contexts, the domain models and the relationships between them.
However, what happens if there are multiple database hits that depend on business logic?
In DDD
, all the operations go through the Aggregate root
(AR
). The application services
load the ARs
from the persistence, send commands to them, then persist those ARs
back. ARs
don't need to hit the database at all. In fact a good designed AR
does not even know that databases exists at all. All they touch/see/smell is their internal state and the immutable arguments that they receive in their command methods. If an AR
needs something from the database then the Application service
pass that thing as argument.
ARs
should be pure, side effects free objects/functions. One reason is that commands applied on them must be retry-able, in case of concurrent modifications.
As an example: ARs
don't send emails, they return a value object
that holds the email data (from, to, subject and body) and the Application service takes that value object and passes it to a infrastructure service that does the actual sending of the email.
For example, I have a domain model "Order" that has a method "IsValid". To determine whether an order is valid, a database read must be performed.
You don't need an isValid
method, as an Order
cannot get into an invalid state anyway because any modifications are done through it's methods. If you are referring to the existence of an Order
then this kind of validation is done by the Application service
: if it does not find the Order
in the persistence then it does not exist, as simple as that. Maybe you are referring to the ShoppingCart
as being valid, not the Order
. What about then? Well, you could try to create an Order
from a ShoppingCart
and if you succeed then a Cart
is ready to be ordered. As the Order
is side effect free, no order will actually be created. Just an example of how you might think in DDD
.
DDD seems like it provides a lot of nice value, but I'm worried about these kinds of issues leading to design "rot".
If you follow the DDD
approach well, your design will never rot. Never.
As a foot note: In the beginning I was a little miss-leaded by the Layered architecture. Don't make the same mistake. Take a look at some newer architectures like CQRS
that fits very well with DDD
.
Best Answer
My take on this is that the "ApiGateway" services are acting as a "backend for front end" and converting between the front ends ViewModels and the back ends Models with some simple logic. You could add a service layer, but its unimportant to the overall architecture.
You could just as easily do this kind of "check the item Ids are valid" logic in the front end and connect direct to the back-end services, but you cant do it in the back-end services without coupling them.
The basket service accepts the itemId as an external Id from another system and it doesn't care if that other system has the item or not. Adding that kind of coupling is something that they have chosen not to do when they choose the aggregate roots for the system as a whole and you wouldnt want to add it back in for fear of ending up with one massive service, rather than multiple microservices.
This choice is a key factor in DDD and questions such as:
Are left to higher layer "Delivery" and "WebShop" Domain Services or Objects. You have to be careful to keep your services hierarchical, loosely coupled and avoid circular references.
A common pitfall for example would be to add a ICatalogue reference to the Basket service and then later add a IBasket reference to the Catalogue service. Everything would compile, but in reality you wire up one service to the other and have potential infinite loops, or at least inefficient calling back and forth and your choice of ARs is called into question.