Let the application service call both methods
The application service is usually a great starting place for this, however you should always try to shift behavior as close to the entity as possible. The application service plays an orchestrating role and it sets up the stage for executing domain behavior and as such it provides all required dependencies. However, when possible, it should delegate behavior to the domain model.
Use method injection/double dispatch to inject the domain service into
the entity, letting the entity do it's thing and then let it call the
method of the domain service (or the other way around, letting the
domain service call the method on the entity)
This is a better approach because more of the behavior is delegated to the entity or domain service. The most decoupled way to implement this is to have an entity express a dependency on a service is as a parameter of the method providing the behavior at hand.
Raise a domain event in the entity method, a handler of which calls
the domain service.
The domain event pattern, as explain by Udi and also Evans himself, is quite versatile and can be applied in a variety of scenarios. There are a few complications that come with it however. First, you have to make sure that you have proper scoping within the domain event publisher. Most of the time, your domain event handlers will have dependencies and if you are using an IoC container, you have to make sure that a proper instances are injected. For example, in a web application, the [ThreadStatic]
attribute is problematic for scoping. Another complexity is that of transcriptional boundaries. You have to take transactions into consideration, because if an entity raises a domain event, but a subsequent commit to the database fails, all domain event handlers will need a way to roll back, otherwise you will end up raising an invalid event. If however you've got these bases covered, then domain events are a great pattern for encapsulating domain logic within the entities themselves.
The difference between approach 2 and 3 depends on the use case. A domain event applies when you want to invoke additional behaviors in response to an event, which is in the past tense. This is an important constraint, since the domain event handler can't affect behavior of the entity. On the other hand, with approach 2, the injected service has the potential to affect behavior because it is declared a dependency for that particular behavior.
In retrospect, I think I was complicating the issue.
In general, commands should either throw an exception or raise one or more events.
If I could summarise the architecture of Event Sourcing it would be as follows:
- Commands are inputs representing instructions to do something.
- Events are outputs representing historical facts of what was done.
- Event handlers can listen events in order to issue commands, helping coordinate the different parts of the system.
Having one command create another command causes ambiguity in the general meaning of commands and events: if one command "triggers" another command, it is implying that a command is "an historical fact of what was done". This contradicts the intent of these two types of messages, and could become a slippery path, in that other developers could trigger events from events, which could lead to corrupt data and eventual inconsistency.
In regards to the specific scenario I posed, the complicating factor was that I didn't want the main thread to interact with the payment gateway, as (being a persistent, single-threaded process), this would not allow other commands to be processed. The simple solution here is to spawn another thread/process to handle the payment.
To illustrate, the client sends a PayInvoice
command. The PayInvoiceHandler
starts a new process and passes it the payment details. The new process communicates with the payment gateway. If the payment was successful, it calls invoice.markAsPaid()
with the receipt number (which produces the InvoicePaid
event). If the payment was unsuccessful, it calls invoice.paymentFailed()
with passes a reference code for further investigation (which produces the InvoicePaymentFailed
event). So despite the fact that a separate process/thread is involved, the pattern remains Command -> Event
.
In regards to failure, there are three scenarios, the system could crash after the PayInvoice
command is persisted but before the InvoicePaid
or InvoicePaymentFailed
events are persisted. In this case, we don't know if the user's credit card was charged. In such a case, the user will notice the charge on their credit card and make a complaint, in which case a member of staff can investigate the issue and manually mark the invoice as paid.
Best Answer
Short answer: call the external services from the Saga but invert the dependency by using an
Interface
Unlike Aggregates, which must be pure (no dependency to anything that touches the infrastructure, nor abstract neither concrete), Sagas are domain models that may call external services. But because they are also from the Domain layer, they may not depend on the Infrastructure. You manage to do that inverting the dependency, by defining an
Interface
in the Domain layer with an implementation in the Infrastructure. In this way, the Domain owns the interface and not the Infrastructure.In other words, you must use an Anti-corruption layer when communicating with external models, and that is that
Interface
for.Btw, in this way you increase the Saga's testability, for free.
Me neither, only Aggregates should generate domain events.
This feels weird.