问题
I've been learning about CQRS/ES. Looking at small example projects I often see events mutating the entity state. For instance If we look at the Order
aggregate root:
public class Order : AggregateRoot {
private void Apply(OrderLineAddedEvent @event) {
var existingLine = this.OrderLines.FirstOrDefault(
i => i.ProductId == @event.ProductId);
if(existingLine != null) {
existingLine.AddToQuantity(@event.Quantity);
return;
}
this.OrderLines.Add(new OrderLine(@event.ProductId, @event.ProductTitle, @event.PricePerUnit, @event.Quantity));
}
public ICollection<OrderLine> OrderLines { get; private set; }
public void AddOrderLine(/*parameters*/) {
this.Apply(new OrderLineAddedEvent(/*parameters*/));
}
public Order() {
this.OrderLines = new List<OrderLine>();
}
public Order(IEnumerable<IEvent> history) {
foreach(IEvent @event in history) {
this.ApplyChange(@event, false);
}
}
}
public abstract class AggregateRoot {
public Queue<IEvent> UncommittedEvents { get; protected set; }
protected abstract void Apply(IEvent @event);
public void CommitEvents() {
this.UncommittedEvents.Clear();
}
protected void ApplyChange(IEvent @event, Boolean isNew) {
Apply(@event);
if(isNew) this.UncommittedEvents.Enqueue(@event);
}
}
when OrderLineAddedEvent
is applied it mutates Order
by adding new order line. But I don't understand these things:
- if it is a right approach how then the changes made are persisted?
- Or should I publish the event somehow to a corresponding handler from
Order
? How do I implement this technically? Should I use a service bus to transmit events?
回答1:
I am also still experimenting with ES so this is still somewhat of an opinion rather than any guidance :)
At some stage I came across this post by Jan Kronquist: http://www.jayway.com/2013/06/20/dont-publish-domain-events-return-them/
The gist of it is that event should be returned from the domain rather than being dispatched from within the domain. This really struck a chord with me.
If one were to take a more traditional approach where a normal persistence-oriented repository is used the Application Layer would handle transactions and repository access. The domain would simply be called to perform the behaviour.
Also, the domain should always stick to persistence ignorance. Having an aggregate root maintain a list of events always seemed somewhat odd to me and I definitely do not like having my ARs inheriting from some common base. It does not feel clean enough.
So putting this together using what you have:
public OrderLineAddedEvent AddOrderLine(/*parameters*/) {
return this.Apply(new OrderLineAddedEvent(/*parameters*/));
}
In my POC I have also not been using an IEvent
marker interface but rather just an object
.
Now the Application Layer is back in control of the persistence.
I have an experimental GitHub repository going:
- https://github.com/Shuttle/shuttle-recall-core
- https://github.com/Shuttle/shuttle-recall-sqlserver
I haven't had time to look at it for a while and I know I have already made some changes but you are welcome to have a look.
The basic idea is then that the Application Layer will use the EventStore
/EventStream
to manage the events for an aggregate in the same way that the Application Layer would use a Repository
. The EventStream
will be applied to the aggregate. All events returned from the domain behaviours would be added to the EventStream
after which it is persisted again.
This keeps all the persistence-oriented bits out of the domain.
回答2:
An Entity doesn't update itself magically. Something (usually a service) will invoke the update behaviour of the entity. So, the service uses the entity which will generate and apply the events, then the service will persist the entity via a repository, then it will get the new events from the entity and publish them.
An alternate method is when the Events Store itself does the publishing of the events.
Event Sourcing is about expressing an entity state as a stream of events, that's why the entity updates itself by generating and applying events, it needs to create/add the changes to the events stream. That stream is also what it's stored in the db aka the Event Store.
回答3:
Lately I am splitting my entities into two objects.
First is what I call a Document object. This is mostly a state only, ORM class with all the configuration related with how the information is persisted.
Then I wrap that Document with an Entity object, which is basically a mutation service containing all the behaviour.
My entities are basically stateless objects except of course for the contained document, but in any case, I mostly avoid any exposure to the outside world.
来源:https://stackoverflow.com/questions/28719019/who-is-responsible-for-entitys-mutation-when-domain-event-is-raised-ddd