In a message-driven architecture such as SOA or Microservices, organizing commands and events, along with their respected handlers, can have a big impact on have you navigate your codebase. You might be surprised by the progression of how I organized them initially to what I do now! Also a tip on how to name commands and Events instead of after CRUD.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
When moving into a message-driven system, you’re likely going to fall down the path of creating very explicit actions for your system rather than CRUD. When you do this, you’ll find yourself following CQRS and likely also moving to an event-driven architecture (where it makes sense).
The building blocks are messages and message handlers. Messages can be in the form of Commands, Queries, and Events. Commands and Queries will have a single handler that consumes the message. Events can have zero or many handlers that consume them.
Organizing commands and events (and their handlers) can be a bit of a message when you first start. I get asked often on my channel how I organize these building blocks of messages and handlers. Here’s how my progression over time.
Organize by Feature Folder
Put all relevant messages and handlers in the same folder. This is likely the most common or intuitive approach you might start with when moving into a message-driven system. Putting all the relevant types into their own files in a single folder.
I often put them nested inside a folder called Features. This is because I view a single or collection of messages and handlers as an individual feature. For example, setting an order as ReadyToShip is a command, but also occurs when the ShippingCreatedEvent occurs.
Single File with Suffix
The next step in the journey of organization for me was putting all relevant types into a single file. Especially for commands and queries, where there is only a single handler. To me, this simplifies not having to jump around files as the command/query and handler are directly related to each other. Why bother with two files?
The following example contains a Controller, Command, Handler, and Saga. Everything related to the feature of PlaceOrder is located in this file.
In the above example, there is an OrderPlaced event that is published. However, it is not defined in this file. This is because this event is consumed and shared with other boundaries/services. For that reason, I put shared messages into a Contracts project which is shared with other services.
Static Wrapper Class
My next progression in organizing was I did not like having a redundant name for Command/Handler/Event. In the above examples for the PlaceOrder feature, there was a PlaceOrderCommand and PlaceOrderHandler. I found the naming to be a bit redundant and tiresome for typing.
To combat this, I simply place the types inside a static class. The static class is the name of the feature.
In the following example, the feature CancelOrder contains a static class named that, and nested within are the Command and Handler. When creating a new instance of a command: new CancelOrder.Command()
Suffix & Consistency
I tend not to suffix events with “Event”. Hence why you see OrderPlaced and OrderCancelled and not OrderPlacedEvent or OrderCancelledEvent. This is a matter of preference because events are always named in the past tense.
At the end of the day, it’s your preference. My recommendation is ultimately to be consistent in your naming. Regardless of how you organize or name, just be consistent as it will make navigating your codebase that much easier.
Developer-level members of my CodeOpinion YouTube channel get access to the full source for the working demo application available in a git repo. Check out the membership for more info.