· 6 min read
Event Sourcing - Good Naming Convention, File Organization and Enforcing with ArchUnit tests
Find out a naming convention and package structure to generate clean event sourcing code.
I find naming conventions and general software organization to be the most important thing more than I like well-tested code. This is because if everything has a good naming convention and is organized, it’s easier to maintain and add new features.
This post explains
- How SOLID applies to my implementation of Event Sourcing.
- Challenges of naming convention and organization in Event Sourcing
- Naming conventions and organizations, I created
- How I used Arch Unit Tests to enforce the architecture.
Throughout this post, I’ll explain the thought process I had to come up with.
This post builds over the ideas of my previous one: ”5 Step Implementation of Custom Clean Event Sourcing in Spring Boot (without AggregateRoot)“.
How SOLID applies to my implementation of Event Sourcing
When doing Event Sourcing, your service methods are tiny. At least the way I implemented it, without creating a specific aggregate root class, I made a lot of smaller CommandHandler
classes that could be added with time. (Open to Extension. Closed to Modification).
Each CommandHandler
would be created and modified for only that purpose. This ensured Single Responsibility.
Instead of @Autowiring
specific CommandHandlers
, I managed to get the CommandHandlers
at run time. Also, Spring helps here. So, I autochecked the Dependency Injection Box. (This has been described in the previous blog post)
Lucky for me, all the CommandHandler
Interface has things that were needed. No class was forced to use an interface they didn’t need. Therefore, I auto-checked Interface Segregation.
Since I didn’t have a major Parent Class child class thing, I auto-checked Liskov’s Substitution.
Even with all these sorted, I had a few challenges to take care of.
Challenges of naming convention and general organization
Without Event Sourcing, you’d have a single Service class with all the service logic. The likelihood of this service class being too large is high.
With Event Sourcing, the business logic inside the service class was distributed to smaller Commands and CommandHandlers
. This made the service class much smaller.
But this made the number of Command and CommandHandler
too be too large. Enough that:
- I had to look through the package to find out what classes existed.
My package looked something like this
-
events package
-
MovieCreateCommand
-
MovieCreateCommandHandler
-
UpdateMovieNameCommand
-
UpdateMovieNameCommandHandler
-
MovieReleaseDateChangeCommand
-
MovieReleaseNameChangeCommandHandler
-
MovieActorAddCommand
-
MovieActorAddCommandHandler
-
ActorRemoveFromMovieCommand
-
ActorRemoveFromMovieCommandHandler
-
DeleteActorFromMovieCommand
-
DeleteActorFromMovieCommandHandler
-
…
As you can see, looking at these many classes is an eye soar. Mainly because they are flat.
I needed to organize this in a way that.
- I don’t have to guess which Command to use.
- If I have a good naming convention, I can quickly type a name, and IDE autocomplete could suggest the appropriate one.
- If I need to go and see the file and package, it would be in a way that makes sense.
- I needed to reduce the file names. They had started becoming too big.
Package Structure
I thought about what kind of Commands I have.
I have and will always have 3.
Create
Update
Delete
Therefore, I created three packages.
- commands (renaming events to commands)
creation
updation
deletion
I found it helpful to consider the domain structure for packages inside updation.
For example, if the movie’s JSON response looks something like
{
"movieId": "",
"movieName": "",
"shootingLocations": [],
"mainRoles": {
"director": "",
"leadActor": ""
}
}
It’s better to model the nested keys inside the updation.
commands
creation
updation
shootinglocations
...commands related to shooting locations...
mainroles
...commands related to main roles ...
deletion
Standardizing naming convention
Knowing a Command
and CommandHandler
from its name was important to distinguish it from other classes. Therefore, I initially added the postfix Command
and CommandHandler
.
Since Command
and CommandHandler
make file names really long, they’d be used repeatedly; I came up with the following abbreviations.
C
=Command
CH
=CommandHandler
Commands can be of Three Types: `Create, Update, Delete. So Therefore
- Naming a Command:
{Type}{NameThatIncludes}C
- Naming a Command Handler:
{Command}H
For example,
CreateMovieC
,CreateMovieCH
Update Commands should include entity name: Update{Entity}{Attribute}C
Example:
UpdateMovieNameC
,UpdateMovieNameCH
UpdateMovieReleaseDateC
,UpdateMovieReleaseDateCH
This made it easy to develop a new name and guess existing names. I could be at a specific part of the code with a particular movieId, and I wanted to update its attribute. All I have to do is guess it.
For example, If I want to add a new actor to a movie, I can guess the name like this.
UpdateMovieAppendActorC
And the probability is high that such a command exists since everyone uses the same convention.
Note: I created a command that focused on updating attributes. You could also create Command
s that focus on the domain. For example, consider a banking command.
- Instead of
UpdateAccountRemainingC
, which focuses on attributes, you could createUpdateAccountCreditC
or simplyCreditAccountC
. This depends on what’s right for your domain. Read my article: How to Choose and Name Commands
‘ll be writing a separate blog post on what commands to create [Todo]
Combining Package Name and New Naming Convention
So, I have two entities: movie
s and actor
s.
For movies, the output is like this. Note how the actor is a nested key inside the movie.
{
"movieId": "007"
"movieName": "Skyfall"
"actors": [
{
"actorId": "1",
"name": "Daniel Craig"
}
]
}
Note: The actor is a separate entity and can be returned separately.
Using the above naming convention and package structure, you’d get something like this
movie
command
- Package that includes all the commandscreation
CreateMovieC
CreateMovieCH
updation
UpdateMovieNameC
UpdateMovieNameCH
actors
- nestingUpdateMovieAppendActorC
UpdateMovieAppendActorCH
- …
- deletion
DeleteMovieC
DeleteMovieCH
- … other packages related to the movie…
actor
command
creation
CreateActorC
CreateActorCH
updation
UpdateActorAgeC
UpdateActorAgeCH
- …
deletion
DeleteActorC
DeleteActorCH
Arch Unit Tests
ArchUnit Tests allow you to enforce architectural decisions such as certain fields in certain classes should always be final. I use ArchUnit all the time to enforce architecture.
I’m going to provide the pseudocode as they are a one-to-one translation to arch units fluent API. These are the ArchUnit tests are wrote for ES.
- Classes that are end with
C
should extendCommand
class. - Classes that extend
Command
class should end withC
- Classes that extend
Command
class should have correspondingCH
class. - Classes that end with
CH
should extendCommandHandler
class. - Classes that extend
Command
should start withCreate
orUpdate
orDelete
. - Classes that extend
Command
and that start withCreate
should be declared in*.command.creation.*
package. - Classes that extend
Command
and that start withUpdate
should be declared in*.command.updation.*
package. - Classes that extend
Command
and that start withDelete
should be declared in*.command.deletion.*
package.
There are many more ArchUnit tests you can think of.
Concluding
Event sourcing is generally cleaner than typical architecture. But there are some things to keep in mind.
The number of Command
s and CommandHandler
s will explode, so you need some way to organize it.
Organize it such that you know precisely where each code will go. This will help you and your team be highly productive when creating an event-driven system.
Use ArchUnit tests which can help you structure and provide better maintainability to your code base.