In DDD, you're strongly encouraged to have all business logic within the domain entities instead of separate from it. Which makes sense.
You also have the idea of Domain Services to encapsulate certain pieces of logic.
What I can't work out is how to have the domain entities perform their business logic that itself depends on external services.
Take, for example, a user management system. In this there is a User
domain entity, on which there are various business actions to perform. One of these is verify_email_address
- which sends an email to the users email address in order to verify that it's valid.
Except that sending an email involves interactions with external services. So it seems reasonable for this logic to be encapsulated within a Domain Service that the User
entity then makes use of. But how does it actually do this?
I've seen some things suggest that the User
entity is constructed with references to every domain service that it needs. Which makes it quite big and bloated, and especially hard to test.
I've seen some things that suggest that you should pass in the domain service to the method that you are calling - which just feels weird. Why would someone outside of the domain entity need to pass in the EmailClient
service when calling verify_email_address
?
I've also then seen the suggestion that you instead pass the User
entity in to the domain service, except that this seems to imply that the logic is in the domain service and not the entity.
And finally, I've seen suggestions that the entity should raise a domain event to trigger the email, which the domain service then reacts to. That means that all of the logic for whether to send the email or not - no need if it's already verified - and which email address to send it to are in the right place, and it also means that the user only needs a way to raise events and nothing more. So it feels less tightly coupled. But it also means you need a whole eventing mechanism, which is itself quite complicated.
So, am I missing something here? How can I achieve this?
(I'm working in Rust if that matters, but I don't see that it should)
That's not your fault; the literature is a mess.
That's not quite right; the common idea is that all business logic belongs within the domain layer (somewhere). But that doesn't necessarily mean that the logic must be within a domain entity.
Evans, in Chapter 5, writes:
It's a very Kingdom of Nouns idea; we have code that actually does something useful, so there must be an object it can belong to.
Having a module (in the Parnas sense) in the domain layer that is responsible for the coordination of an email client and a domain entity (or for that matter, a repository) to achieve some goal is a perfectly reasonable option.
Could that module be the domain entity itself? It certainly could.
You might find, however, that coupling the management of the in-memory representation of domain information and the orchestration of a domain process that interacts with "the real world", as it were, complicates your maintenance responsibilities by introducing a heavy coupling between two distinct concepts that should instead be lightly coupled.
Clean Architecture (and the predecessors in that lineage) suggests to separate "entities" from "use cases". Ivar Jacobson's Objectory Process distinguished "entities" from "controls". So the notion of a service that is decoupled from the entity shouldn't be too alien.
Ruth Malan writes:
Finding the right design depends a lot on finding the right "what we want" for our local context (including our best guess at how this local context is going to evolve over the time window we care about).