This is in order to discuss and decide on things around the CQRS/ES foundation that is currently built under GitHub - neos/Neos.EventSourcing: A library for Event Sourcing and CQRS for Flow projects.
Defining the recommended project structure:
Following up on https://github.com/dfeyer/Ttree.Cqrs/issues/1
@robert:
Your.Package/
Application/
Controller/
Command/
Service/
Domain/ Service/ Aggregate/ YourAggregate/ Command/ CommandHandler/ Event/ EventListener/ Service/ YourAggregate.php YourAggregateRepository.php
Projection/ MostValuableBooks/ MostValuableBooksProjector MostValuableBooksReadModel MostValuableBooksFinder
CommandHandler/ EventListener/
@robert: I currently can’t think of an example for using the top-level CommandHandler - is that necessary at all?
@dfeyer: Based on the discussion with @akii some people say that the command handler are not part of the domain … so I’m balanced, but accept divergence of visions and our implement support command handler located any where so it’s not
@dfeyer: In this case CustomerDetails.php is the ReadModel right ?
@bwaidelich: correct. We could also call it CustomerDetailsReadModel.php but I’m not sure that would be necessary
@aberl: A couple of questions:
- why have command and commandhandler included in the Aggregate? Like that, those are just useless (?) wrappers around directly calling the appropriate aggregate method. Normally I see CommandHandler as an application wide class, that itself contains the logic of which command translates to which Aggregate method call (basically a router - ie. not as part of the domain, but rather of the application layer). A lot of times even that is not necessary though in Web environments, especially for Flow where we already have a nicely working router that translates requests (commands if !GET) to controller actions (~ command handler methods) - or do we want to completely separate that package from Flow?
- what exactly do Aggregate EventListeners achieve? The Aggregate itself is a “kind-of” EventListener, listening to it’s own events - what is the use case to listen to external events inside the Aggregate? Are those maybe supposed to be what is called “Process Manager”, ie. business logic that happens depending on some other events?
- Aggregate Service? A bit same as above, but instead of listening to events, what does such a specialized Service do (that the aggregate does not do)? Is that really something that needs to be in the structure?
- do we really want to break the current Flow conventions (which also deal with DDD Aggregate and Repository) and introduce folders for each single aggregate type and even colocate the Repository with the Aggregate? IMO the Repository is not part of the Aggregate and especially not of the (core) domain. IMO the domain substructure should only contain the classes that deal with business logic.
Also, what I would also wish for is to be able to easily create some kind of mixed model codebase, where some models are event-sourced and some aren’t (following best practices to only use ES where it fits instead of as a global architecture of the application - though maybe that could just be documented to separate those in different packages).
- as I understood, the *Finder is the new read side Repository. Do we really need to separate that? What if the read side is just a good old Flow doctrine entity? Won’t that confuse users as to how to implement that Finder and what it’s technical (!) purpose is?
- what I kind of miss in most of the implementations I found so far is the notion of the “Bounded Context”. I think we should push people to understand, that they should try to find and separate (into distinct packages/subpackages/w.e.) their Bounded Contexts. If only by making “BoundedContext” part of the recommended folder structure - I don’t know where that should be put though…
@bwaidelich: Thanks for your input. I feel we should probably move some of these discussions to https://discuss.neos.io but let me give it a try:
- why have command and commandhandler included in the Aggregate? Like that, those are just useless (?) wrappers around directly calling the appropriate aggregate method
With the current implementation they are not completely useless because they kind of control the transaction (retrieving the aggregate from its repository, calling one (or more) method(s) on it and persist it back…
But, as mentioned in my little ES session I agree that this whole layer can probably be removed (or at least greatly simplified). Especially because there can always be exactly one handler for a command. But that discussion is probably out of scope here.
- what exactly do Aggregate EventListeners achieve? The Aggregate itself is a “kind-of” EventListener, listening to it’s own events - what is the use case to listen to external events inside the Aggregate?
EventListeners can be quite useful to solve some x-cutting concerns (i.e. event logging, inter-system-communication, …). ProjectionHandlers are actually just a specialized EventHandler that a) has a state (which we usually map to a ReadModel) and b) is also invoked when events are replayed (in contrast to regular EventListeners).
That the Aggregate reacts to events is IMO an implementation detail (and even one that I’d like to get rid of → different discussion)
Aggregate Service?
I also doubt that you’ll have a lot of those. I have the feeling that services within domains are mostly an anti-pattern and result from a lack of better fitting names/concepts… However, when creating a domain service that belongs to a certain aggregate, that’s where you would put it according to the suggested directory structure.
do we really want to break the current Flow conventions […] and introduce folders for each single aggregate type and even colocate the Repository with the Aggregate?
Good point, I share your doubts. (Again: My take on it is that we can get rid of the custom repository and command handler which would partly resolve that issue)
as I understood, the *Finder is the new read side Repository. Do we really need to separate that?
Yes, I think so. A repository “mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.” (P of EAA: Repository). A finder doesn’t deal with domain objects to begin with. Furthermore it is only used for reading operations and IMO the name communicates that well.
For the “regular-style” entities *Repository still makes sense
what I kind of miss in most of the implementations I found so far is the notion of the “Bounded Context”
Good point. I mentioned that I consider it one of the most important & neglected DDD concepts but forgot to mention how ES can solve this.
So IMO a BC should always be implemented in its own package (or at the very least in its own subpackage). That would solve the question regarding directory structure.
I do think that we might need to implement the notion of a BC in our event store/cqrs implementation but that’s out of scope again…
Note: This is just my personal take on it.
Thanks again for your input, it was nice to finally meet you in person!