polygene-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Kent Sølvsten <kent.soelvs...@gmail.com>
Subject Re: [Proposal] EventSourcing support
Date Wed, 29 Jul 2015 21:45:45 GMT
Den 26-07-2015 kl. 08:17 skrev Niclas Hedhman:
> On Sun, Jul 26, 2015 at 5:00 AM, Kent Sølvsten <kent.soelvsten@gmail.com>
> wrote:
>
>
> I explicitly asked to keep the "Write" case out of the discussion, but
> obviously that was not possible from the first line ;-)
>
>
>> In DDD lingo an Aggregate is a web of connected interdependent entities
>> with a common aggregate root - only the aggregate root is queryable and
>> all updates should go through the aggregate root, which is responsible
>> for preserving consistency.  The only associations to an aggregate form
>> the outside must go to the aggregate root (allowing it to ensure
>> consistency). In EventSourcing a Domain Event should only touch a single
>> aggregate root - so if we modify multiple aggregates in a UOW that means
>> multiple (Domain) Events.
> Yes. And?
>
>
>> I think using the aggregate name is correct - but we might simply let it
>> be a special Entity - and might in the future consider restricting
>> associations and querying/indexing according to the above.
> GutFeeling(tm) says that it is not an entity.In Event Sourcing, the
> Aggregate is the transactional, concurrency, consistency and distribution
> boundary. Making the UnitOfWork responsible to handle these concerns
> on-top-of, seems wrong to me. This is why I wanted to leave the "wrte" case
> out for a little while.
Actually I am convinced that we should leverage the UOW for writing
events. It can serve as a nice integration point in an application where
some modules use eventstores and others are old-style Entities.
And I think the similarities between updating an entity (with the
sideeffect of updating indexers)  and inserting an event (with the
sideeffect of updating Read models) are too obvious to ignore.
And in both cases the sideeffects *might* be postponed until after
completion of the UOW (leading to eventual consistency instead of strong
consistency).Even the nice feature of throwing away and rebuilding a
Read Model is very similar to the existing Reindexer.

I am pretty certain we could find a solution where the EventStore (or
AggregateBuilderFactory?) requires a current UOW and tells the UOW that
it wants to get notified if the UOW is completed - without adding
explicit EntityStore support to UOW (the same principle could probably
apply for MessageComposites too). AggregateBuilderFactory could either
be one more interface implemented by the module or (preferred I think)
simply be a service.

In a distant future we could have a list of (optional) services like
EntityBuilderFactory
QueryBuilderFactory
SpatialQueryBuilderFactory
MessageBuilderFactory
AggregateBuilderFactory
(each backed by a store and requiring an active UOW).

And then a very small UOW interface
public interface UnitOfWork {
  public void complete();  // invoked by user code
  public void discard();     // invoked by user code
  public void registerStore(UOWStore)  // invoked by 'structural
services' when needed
}

>
>>> Keep the above in mind, because I haven't figured out whether what I am
>>> writing below applies to Read Model, Write Model or both...
>> I think mostly the Read Model. For the Write model i imagine something
>> more like an EntityComposite with no indexer/querybuilder - in principle
>> loaded from an eventstore - constraints being very important.
>> We might be able to use existing entitystores for saving snapshots if
>> necessary.
> I haven't analyzed what Snapshotting would encompass, but it wouldn't
> surprise me if it would be straight forward to leverage the EntityComposite
> mechanism within the Aggregate Root, for snapshot reasons. However, it
> seems that Greg Young's "Event Store" does snapshotting in the DB itself,
> but I am not totally sure (I couldn't get it (3.0.5) to run for more than a
> few minutes before starting to crash on me), so I think such support is
> probably inside the actual Zest EcentStore extension implementation.
Snapshotting is an implementation detail inside the EventStore - which
is probably only necessary if an aggregate has a long history. The
EventStore implementation might choose to delegate snapshotting to an
"old-fashioned entity-store" if appropriate. I guess it is not too
important right now.
>
>
>>> // User code
>>> public interface Order extends Aggregate<OrderEvent> {}
>>>
>>> public class CurrentItemListProjection extends Collector<OrderEvent,
>>> List<OrderItem>, List<OrderItem>>
>>> {
>>>     // impl details.
>>> }
>>>
>> In general the may be multiple types of events affecting the same
>> aggregate (OrderCreatedEvent, ItemAddedEvent, OrderPayedEvent,
>> OrderShippedEvent, OrderCancelledEvent).
>> Each should be handlede specifically - so I think a shared
>> BaseOrderEvent is not of much value.
> Good Point! I somehow managed to forget that DomainEvents should be
> strongly typed, and as you mention, it makes a lot more sense that Zest is
> capable to direct the event straight to its respective handler, and not
> happen in user code. That said, the Collector is Java8 Stream API, and it
> defines the three Generics, and I think it makes sense to leverage that
> bit. Some re-think needed to figure out how to make this as smooth as
> silk...
>
>> This might be solved with multiple
>> Projections (each projection updates a single read model based on a
>> specific type of event).  But then
>>
>> Aggregate<OrderEvent>
>>
>> would not make much sense. So maybe another solution for that small part
>> may have to be sorted out.
> Yes, Agree. After all, I haven't spent weeks of analysis and
> experimentation, just read some details around Greg's "Event Store"
> (horrible name) and a few things clicked in my brain.

My Gut feeling is that we should leverage your streaming/collector idea
for receiving the events, but take a closer look at the way of
declaring/emitting events currently used in the existing Eventsourcing
library.
>> Another complication is that in general a read model may span multiple
>> aggregates. I think a read model for a single aggregate is the most
>> common case - so specific handling that case is in order.
> Ah! Good call. Of course, all of the above is to support "read" in the
> "Write Model", which is what I have always wondered how it should be done.
> Greg told me long time ago that, "just put in a SQL table with
> AggregateRootID and SeralizedDomainEvent columns" and "have the state
> resolver inside your object", which felt awkward to me.
>
>> Still a few loose ends - but a good start.
> You made the most important contribution in that "One Aggregate Root
> receives/contains 0..m DomainEvents", so the Aggregate<E> is not the right
> abstraction. Perhaps the write/update case must be considered before
> finding the best abstraction.
>
> I would like to keep CQRS out of the picture, even though Greg says "You
> can do CQRS without ES, but EventSourcing requires CQRS.", mainly because I
> think CQRS is somewhat incompatible with HATEOAS, which to me seems much
> more relevant for web apps.
>
> Now, WRITES.... Event Sourcing is effectively saying that the Aggregate
> Root produces a DomainEvent, appends it to its State and publishes it
> through some 'global' mechanism. Other destinations should be able to pick
> that up. One could then claim that the EventStore extension could just as
> well listen on the same listener mechanism as everything else. And the
> 'transactional' aspect becomes a concern for the event distribution
> mechanism. Further, in a distributed environment, there is a need to
> support consumer groups (as Kafka calls it).
>
> So then, should the Aggregate Root actually append a Domain Event to itself
> and then publish it, or should it actually only publish it and depend on
> that the state update is coming back to it "somehow"?? IF SO, then another
> weird question rise up; What if someone else publishes a DomainEvent that
> the Aggregate Root is supposed to manage? Is that OK? If not, how can that
> be enforced? Private Domain Events?
>
> module.aggregates( OrderAggregate.class )
>       .events( OrderCreated.class, OrderLineAddedEvent.class, ... )
>       .scope( EventScope.aggregate );
>
> It doesn't "click" yet for me...

Inside our UOW we have an in-memory representation of the aggregate. I
think we could invoke the mutating method naturally and capture the event.
On UOW#complete() all events are persisted - but the state of the
aggregate is NOT persisted.
Next time we load the aggregate (or if a snapshot is taken) the events
are replayed ie. the same mutating method is invoked.
Look at declaring domain events in the ES library.
(unfortunately i think that library is utterly wrong when completing a
UOW - it seems to persist state first and events afterwards - should
have been the other way around).

>
> Let's ponder about "replay of past events"...
>
> EventSourcing prides itself that one can change the algorithm, replay the
> events and hence retro-actively fix bugs in the past. BUT, what happens if
> the new algorithm emits new events, but in past time? Should those be
> injected into history? Or vice versa, what if the bug was that too many
> events were emitted, should those now be removed? Or is it that ONLY the
> State Resolver is allowed to be modified, and that the replay is
> effectively "never" happening as a global thing, but just internally in the
> Aggregate Root whenever its state is loaded??
>
> I bet that this has been discussed endlessly on various forums, and I
> should probably go digging.
I remember Greg Young discussing that past events should never be
modified - but new compensating events might be added to the history (If
money are transfered from a bank account by a mistake, the transfer is
not deleted - instead a compensating transfer is added).

I think we should simply disallow nesting events - so replaying an event
can never emit other events. And otherwise the problem to the
applications - and wait and see if anything more should be done to ease
their task.


/Kent

>
>
> Niclas
>


Mime
View raw message