I was asked to implement CQRS/Event sourcing patterns into a legacy web application, in order to prepare to migrate it from a monolithic/state oriented model to a distributed, service oriented app.
I have some questions on how i can design a Domain oriented code bundle that would connect the legacy entities strongly coupled to database, with a new Event sourced model.
The first things i did was:
- writing a small "framework" for CQRS/ES, with classes like AggregateRoot, DomainEvent, Command, Handlers, Messaging, Eventstore, AggregateIds, etc.
- trying to group and "migrate" the legacy Entities into some Aggregates to reconstructe all the history and states of the app into EventSoourced Aggregates
- plug some Commands dispatching in the old controllers in order to let the app work as is, but also to feed the new CQRS/ES system on the side.
The context:
The legacy app contains several entities, mapped to database, that hold the model layer. (Our domain is Human resources (manpower). Let's say we have those existing entities:
Worker, with various fields and related entities (OneToOne, OneToMany), like
- name
- address 1-1
- competences 1-N
Society, in which worker works, with various fields and related entities (OneToOne, OneToMany), like
- name
- address 1-1
- hours
Contract, with various fields and related entities (OneToOne, OneToMany), like
- address 1-1
- Worker 1-1
- Society 1-1
- documents 1-N
- days 1-N
- hours
- etc.
From this legacy model, i designed a MissionAggregate that holds:
- A db independant ID, like UUID
- some Value objects: address, days (they were an entity in the legacy model, they became VOs here)
I also designed a WorkerAggregate and a SocietyAggregate, with fields and UUIDS, and in the MissionAggregate i added:
- a reference to WorkerAggregate's UUID
- a reference to SocietyAggregate's UUID
As i said earlier, my aim is to leave the legacy app as is, but just introduce in the CRUD controller's methods some calls to dispatch Commands to the new CQRS system.
For example:
After flushing newly created Contrat in bdd, i want to dispatch a "CreateMissionCommand" to the new command bus.
It targets the appropriate Command Handler, that handles all the command's datas, passes it to a newly created Aggregate with a new UUID and stores "MissionCreatedDomainEvent" in the EventStore.
The DomainEvent is indexed with an AggregateId, a playhead, and has a payload wich contains the fields necessary to be applied to and build the MissionAggregate.
The newly Contract created in the app has now its former lifecycle, as usual, with all the updates that the legacy app does on it. But i also need to reflects all those changes to the corresponding EventSourcedAggregate, so everytime there is a flush in database in the app, i dispatch a Command that translates the "crud like operations" of the legacy app into a Domain oriented /Command oriented pattern.
To sum up the workflow is:
- A Crud legacy operation occurs and flushes some changes on the Contract Entity
- In just a row of code in the controller, i dispatch a command builded with necessary fields (AggregateId of the MissionAggregate...that i need to have stored somewhere..see next problems) to the Domain command bus , so that the impact on the existing code base is very low.
- The bus passes the command to the corresponding command handler
- The handler loads the aggregate and applies the changes it by calling the appropriate Aggregate method
- then after some validation, the aggregate raises and stores the appropriate event
My problems and questions (some of them at least ;)) are:
I feel like i am rewriting all big portions of the legacy app, with the same kind of relations between the Aggregates that i have between the Entities, and with the same type of validations, checks etc.
Having references, to both WorkerAggregate and SocietyAggregate UUID in MissionAggregate implies that i have to build those aggregate also (hence to dispatch commands from legacy app when the Worker and Society entities are flushed). Can't i have only references to Worker's entity id and Society's entity id ?
How can i avoid having a eternally growing MissionAggregate ? The Contract Entity is quite huge, it has a looot of fields that are constantly updated (hours, days, documents, etc.) If i want to store all those events, i need to have a large MissionAggregate to reflect all those changes; and so i need to have a tons of CommandHandlers that react to all the Commands of add, update, etc that i am going to dispatch from the legacy app.
How "free" is an Aggregate from the Root entity it is supposed to refer to ? For example, a Contract Entity needs to relate somewhere to it's related Mission Aggregate, like for example when i want to dispatch a Command from the app, just after the legacy code having flushed something on the Entity. Where to store this relation ? In the Entity itself, in a AggregateId field ? in the Aggregate, should i have a ContratId field ? Or should i have some kind of Mapping Table somewhere that holds the relationship between Contract ID and MissionAggregate ID ?
What to do with the past ?should i migrate all the existing datas through a script that generates Aggregates and events on all the historical data ?
Thanks in advance for your time.