I'm a little confused about
- commands role in event sourcing
- distinction between domain and external events
If my understanding is right
- a command represents an action initiated by an actor in terms of the domain
- a domain event is an event can be consumed and produced by aggregate roots
- an external event is just like a DTO - a data contract and it needs to be translated to either a domain event or a command
Example
I have a Product
aggregate root. A product can have multiple active special offers. In order to manage it's SpecialOffer
s, the product accepts 2 domain events:
- SpecialOfferActivated
- SpecialOfferDeactivated
So it's public interface is just 2 overloaded Apply
methods:
class Product{
...
Apply(SpecialOfferActivated){...}
Apply(SpecialOfferDeactivated){...}
}
1st case: The request comes form front-end to the api
So a Controller
is first in the line. It basically translates caller intention from data contract (DTO) to the domain language (command):
class ProductController{
Post(SpecialOfferDto dto){
ActivateSpecialOfferCommand command = Map(dto)
_commandBus.Send(command)
}
}
Command sent, now we need a command handler
class ActivateSpecialOfferCommandHandler{
Handle(ActivateSpecialOfferCommand command){
SpecialOfferActivated domainEvent = Map(command)
_eventBus.Publish(domainEvent)
}
}
Event published, now time for the event handler
class SpecialOfferActivatedDomainEventHandler{
Handle(SpecialOfferActivated domainEvent){
var product = GetFromDatabase()
product.Apply(domainEvent)
Save(product)
}
}
Done.
2nd case: The process is initiated by an external event published to the service bus.
This time NewPromotionExternalEvent
is the data contract (ExternalEvent
) and again we need to translate it to the domain language (Command
)
class NewPromotionExternalEventHandler{
Handle(NewPromotionExternalEvent extenalEvent){
ActivateSpecialOfferCommand command = Map(extenalEvent)
_commandBus.Send(command)
}
}
And then it falls back to the ActivateSpecialOfferCommandHandler
from the fist case. So it's the same case as the first one basically.
3rd case: Skip the domain events layer (variation of either the 1st or the 2nd case)
So either by an api or an external event a command was produced. We simply create a domain event in order to apply it to the aggregate root. We do not publish the event to the service bus.
class ActivateSpecialOfferCommandHandler{
Handle(ActivateSpecialOfferCommand command){
SpecialOfferActivated domainEvent = Map(command)
var product = GetFromDatabase()
product.Apply(domainEvent )
Save(product)
}
}
Done.
4th case: Skip the commands layer (variation of the 1st case)
We can easily skip the commands layer
class ProductController{
Post(SpecialOfferDto dto){
SpecialOfferActivated domainEvent = Map(dto)
_eventBus.Publish(domainEvent)
}
}
and fallback to the SpecialOfferActivatedDomainEventHandler
5th case: Aggregate root creation.
So either by an api or an external event a command CreateNewProductCommand
was produced. And we need another handler:
CreateNewProductCommandHandler{
Handle(CreateNewProductCommand command){
var product = Map(command)
SaveToDatabase(product)
NewProductCreated domainEvent = Map(product)
_eventBus.Publish(domainEvent) // in case somebody is interested
}
}
In this case there's really no place to stick the domain events layer.
6th case: Domain event produced by Product (aggregate root)
class Product{
Apply(SpecialOfferActivated domainEvent){
var specialOffer = Map(domainEvent)
_specialOffers.Add(specialOffer)
if(...){
// For simplicity sake, assume the aggregate root can access _eventBus
_eventBus.Publish(new ProductReceivedTooManySpromotionsDomainEvent(this.Id))
}
}
}
Questions
- The events layer is cool, it allows us to distribute jobs across multiple instances or other microservices. However, what's the point of the command layer? I could easily produce domain events right away (in a controller or external an event handler - 4th case).
- Is 3rd case legit (create a domain event just to apply it to the aggregate root, without publishing it)?
- Does command layer only make sense in 5th case where it gives us the benefit of delegating product creation to another microservice while domain events layer is not applicable?
- Where is the line between external and domain events? Is
NewPromotionExternalEvent
from the 2nd really an external event or is it rather a domain event? - Who can produce domain events? Aggregate root? Command handler? Domain event handler? External event handler? Another microservice? All of them?
- Can domain events be dispatched to another micro-service or would it become an external event then?
- What is the proper way of handling product creation and special offer activation when it the request comes form controller or external event?