bakerface / easy-source

From zero to event sourcing
MIT License
5 stars 3 forks source link

Understanding the example #2

Open yonahforst opened 7 years ago

yonahforst commented 7 years ago

Hey there,

I've been reading up on DDD and I'm trying to understand the way the example is structured.

So first, with the aggregates folder: Is an aggregate here equivalent to a DDD aggregate?

I was wondering about value objects and entities and how they should be represented. I assumed that idea, project, and user are each a root entity inside some aggregate, with value objects maybe added directly from the event, without being explicitly modeled in the domain.

But then I looked at the projections folder and saw that projects and ideas are properties of the user which made me think that maybe user is the root entity and idea and project are additional entities inside the same DDD aggregate, and that maybe aggregate here means something different.

Moving on to projections: projects and ideas are initialized as empty arrays on the userRegistered event. Is that where default/initial values should be set? and not in the defaultState of the aggregate?

Thanks so much for your patience and sorry if these questions are really annoying 😜

bakerface commented 7 years ago

No worries. I am glad to answer any questions. I will do my best at explaining the structure of the domain in the example. Just let me know if you have any additional questions that I did not answer.

example
β”œβ”€β”€ domain
β”‚Β Β  β”œβ”€β”€ aggregates - the write-models for your domain (command handlers)
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ idea.js - the bounded context for an idea
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ project.js - the bounded context for a project
β”‚Β Β  β”‚Β Β  └── user.js - the bounded context for a user
β”‚Β Β  └── projections - the read-models for your domain (query handlers)
β”‚Β Β      └── users.js - a projection that joins the idea, project, and user models
└── external - domain-agnostic data storage mechanisms
    β”œβ”€β”€ aggregate-stores
    β”‚Β Β  └── memory.js - stores aggregate snapshots in an in-memory database
    β”œβ”€β”€ event-stores
    β”‚Β Β  └── memory.js - stores events in an in-memory database
    └── projection-stores
        └── memory.js - stores projection snapshots in an in-memory database

This example combines concepts from DDD and CQRS. The way that the example is structured assumes that each aggregate has it's own external event store and aggregate store implementation. There are 3 aggregates (users, projects, and ideas), each representing a bounded context as well as a consistency boundary. The aggregates are only responsible for processing a command and either returning new events or throwing an error. A single command may create multiple events, but those events may not span multiple aggregates (in order to support atomic commits to the event store). On the other hand, the projections are only responsible for processing queries and returning values. A single projection, unlike an aggregate, may listen to events from multiple aggregates in order to support complex views. This example shows how a single projection spans multiple bounded contexts to support a rich query.

yonahforst commented 7 years ago

Thanks! that helps a lot. So projections interpret events to construct some representation of the data. And the answer to my earlier question about default values is that it depends if they are a property of the entity or default values in the representation that your query is building; which are two completely separate things. Got it.

Would it make sense to use this library if I plan to separate my bounded contexts across multiple microservices (each running in it's own AWS lambda function)? As I understand it, if my projections span multiple bounded contexts, then they would need to be in some distinct microservice which subscribes to all the events required to build those projections.

bakerface commented 7 years ago

I designed this package to work with either a monolithic application or a suite of microservices. In your lambda functions, you will need to create an instance of the domain class (see this) with any event stores and projection stores needed for that particular service. It may be easier to just create the instance with all of the externals in every service. This package will derive dependencies on the fly based on your projections, and only query event stores that are required.

yonahforst commented 7 years ago

So defining a projection within a domain instance is requires that all events processed by that projection are also defining within aggregates of that same domain instance? or can they be separate.

Also, are projections also built by 'folding' some past events? or does it just store the current state and apply the current event to it.

Another quick question: The output of aggregate events (e.g. ideaCreated), is that "given the current state + this event, return the updated state" i.e. do we return the updated state? or do we return some transformed event to be stored and replayed back later

bakerface commented 7 years ago

The names of the aggregates and projections do not need to match. The projection states are built the same way that the aggregate states are built: by folding past events. Each aggregate function should be side-effect free (similar to redux reducers). The event handlers take the current state and an event, and return a new derived state. The command handlers take the current state and a command, and either accept a new event or reject with an error.

bakerface commented 7 years ago

I will take some time this week to expand on the example application and add comments detailing each function.