Return-Path: X-Original-To: apmail-zest-dev-archive@minotaur.apache.org Delivered-To: apmail-zest-dev-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B1E5818ED0 for ; Wed, 29 Jul 2015 21:46:32 +0000 (UTC) Received: (qmail 71381 invoked by uid 500); 29 Jul 2015 21:46:32 -0000 Delivered-To: apmail-zest-dev-archive@zest.apache.org Received: (qmail 71345 invoked by uid 500); 29 Jul 2015 21:46:32 -0000 Mailing-List: contact dev-help@zest.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@zest.apache.org Delivered-To: mailing list dev@zest.apache.org Received: (qmail 71333 invoked by uid 99); 29 Jul 2015 21:46:32 -0000 Received: from Unknown (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 29 Jul 2015 21:46:32 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id D28D1193CDB for ; Wed, 29 Jul 2015 21:46:31 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -0.1 X-Spam-Level: X-Spam-Status: No, score=-0.1 tagged_above=-999 required=6.31 tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=disabled Authentication-Results: spamd3-us-west.apache.org (amavisd-new); dkim=pass (2048-bit key) header.d=gmail.com Received: from mx1-eu-west.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id 0ZnyHWd8KcTA for ; Wed, 29 Jul 2015 21:46:23 +0000 (UTC) Received: from mail-la0-f44.google.com (mail-la0-f44.google.com [209.85.215.44]) by mx1-eu-west.apache.org (ASF Mail Server at mx1-eu-west.apache.org) with ESMTPS id F3F5A20F48 for ; Wed, 29 Jul 2015 21:46:22 +0000 (UTC) Received: by lahh5 with SMTP id h5so14260517lah.2 for ; Wed, 29 Jul 2015 14:45:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=message-id:date:from:user-agent:mime-version:to:subject:references :in-reply-to:content-type:content-transfer-encoding; bh=5kDFAGlsdkN9OJuoYZIJLxD1NXnM/Z33QP8VLB3i2to=; b=VXaDNae7XJwnBZqfXAgNPPSjEN3wTQF1iQSvxePthw1941w933nbzeEV7ypfLRtS7J F2OTtu1wjz9kSc3j+yiSIq8f7mgj+7A197VUBkYrfgHZI2tfQoqpg0vtDjHdKLLCtp3G oh95S/zP3MIoZ1s4rRswawwWAqMyaC6pip3ndgm6i5+liYDi/MrCpzcLbGhH/Wy58vsr WJBG2fG8h6HnCCh5ASnDppV2awFic3h68FycYy5YsFsflC1q0OH/E5SzXpfSOD2C7pDf JvhxLxejRGHdt8J4aaixAA/yzfAI2ZOVlvHD7yaQDrYaVIR9rxfZj0sNWgrZftvsVzPc npJw== X-Received: by 10.152.10.72 with SMTP id g8mr40550089lab.97.1438206337443; Wed, 29 Jul 2015 14:45:37 -0700 (PDT) Received: from [192.168.1.10] (x1-6-2c-b0-5d-ac-a5-ea.cpe.webspeed.dk. [62.243.119.227]) by smtp.googlemail.com with ESMTPSA id dx12sm5519564lac.48.2015.07.29.14.45.35 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 29 Jul 2015 14:45:36 -0700 (PDT) Message-ID: <55B94989.5040103@gmail.com> Date: Wed, 29 Jul 2015 23:45:45 +0200 From: =?UTF-8?B?S2VudCBTw7hsdnN0ZW4=?= User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Thunderbird/31.7.0 MIME-Version: 1.0 To: dev@zest.apache.org Subject: Re: [Proposal] EventSourcing support References: <55B3F8D8.7000305@gmail.com> In-Reply-To: Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Den 26-07-2015 kl. 08:17 skrev Niclas Hedhman: > On Sun, Jul 26, 2015 at 5:00 AM, Kent Sølvsten > 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 {} >>> >>> public class CurrentItemListProjection extends Collector>> List, List> >>> { >>> // 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 >> >> 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 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 >