I am currently working on a monolithic system which I would like to bring into the modern day and incorporate DDD and CQRS. I have been presented with a request to re-write the importing mechanism for the solution and feel this could present a good opportunity to start this re-architecting process.
Currently the process is:
- User uploads CSV
- System parses CSV and shows each row on screen. Validation takes place for each row and errors/warnings associated with each row
- User can modify each line an re-validate all rows
- User then selects rows that don't have errors and submits the import
- Rows import and any non-selected rows, or rows with errors go into a holding area so they can deal with them at a later date
Additional details for this is that multiple rows could belong to the same entity (E.g. 2 rows could be line items in an order, so would have the same Order Ref).
I was thinking of having an import saga that would generate a bunch of import aggregates (e.g. OrderImportAggregate), and then when the import is submitted those would get converted into the class used across the system currently, which would hopefully become aggregates in their own right when re-architected further down the line! So the saga process would take on something along the lines of:
- [EntityType]FileImportUploaded - Stores the CSV
- [EntityType]FileImportParsed - Generates n number of [EntityType]Import aggregates.[EntityType]ImportItemCreated events raised/handled
- Process would call the validation routine that the current entities go through to generate a list of errors, if any, and store against each item. [EntityType]ImportItemValidated events raised/handled
- Each time a row is changed on screen, it calls a web api method for the saga and and item id to update the details and re-validate the row as per point 3.
- User submits import, service groups entities together, based on ref for example, they get converted into the current system entity and calls their import/save routine. [EntityType]ImportItemCompleted event raised.
- Saga completes when all aggregates are at ImportItemComplete state
As this was my first implementation of CQRS/Event Sourcing/DDD, I wanted to start off on the right foundation, so was wondering if this is a desired approach for this functionaility?
I suggest that you break your domain into two separate sub-domains implemented as to separate bounded context, one bounded context being the Import bounded context
(ImportBC
) and the other being the receiving bounded context
(ReceivingBC
, the actual name is not know to me, please replace it accordingly).
Then, in the Import BC
you should implement using the CRUD
style, having an entity for each import file and use a persistence to remember the progress on the validation and import process (this entity holds a list of not-yet imported items). After each item is validated by a human, a command could be sent to the aggregates in the ReceivindBC
to test if the aggregate is valid according to the business rules, but without committing the changes to the repository! You do this so that the human user would know if the item is indeed valid and to enable/disable an import button
. In this way you don't duplicate the validation logic inside the two bounded contexts. When the user actually presses the import button
send the import command to the aggregate in the ReceivingBC
and you actually commit the changes to the repository. Also, you remove the import item from the import file CRUD entity
.
This technique of sending commands but without actually persisting into the repository is useful in helping the user experience in the UI
(without duplicating logic inside the UI
) and it is doable if you follow the DDD
best practices and design your aggregates to be pure, side-effect free objects (to be Repository agnostic, to not know of their existing, to not use them at all!).
Well first of all you have to ask yourself why are you using CQRS. CQRS is the heavy 18 wheeler amongst architecture. I know of 2 good reasons that scream CQRS
1) You need to support undo functionality
2) in the future when new requirements are implemented you want to apply those to past data too.
The part of the requirements that you are describing however feels very much like crud. (You import a set of rows, you list a set of rows, you edit those rows and the ones marked as completed are then deleted from their input state and converted into some other kind of entity.
If you feel there is a lot of complexity describing the specific entities and the validation rules that apply then DDD would be a good fit. but still i would consider scaling it down and build a simle mvc style app to implement this (depending what else is required of this project)
and even if this were part of a larger domain i would suggest a microservices approach where this would be a completely standalone import application (and in that case you could still raise a ImportCompleted Event and put it on a service bus with multiple other applications listening to that event)
NOTE: CQRS is not event sourcing, cqrs is separating a command (update) stack from a query stack. It's often applied in combination with event sourcing. But having events that pop up everywhere can be a pain to maintain especially since it's often less obvious who is raising the event and if events have interactions on eachother (what happens to an order if both a ordercompleted and ordercanceled event are raised, possibly with timing issues which one is handled first)