Two years ago I attended Wroclove.rb, my first big conference. I was thrilled: so many interesting topics, patterns, designs, methodologies, libraries… massive amount of knowledge. And the most interesting thing was that I could immediately use them in my daily work. This is what I like in language or framework-specific conferences. The concepts that stole my heart at that time were DDD (Domain Driven Design) and EDA (Event Driven Architecture).
What is DDD
In general, DDD is a methodology for creating complex software from a technical perspective. DDD gives us a bunch of building blocks for correct implementation of domain business logic. It handles the project also from a business perspective (strategic domain driven design). This approach puts stress on connecting these two worlds through some particular rules:
Designing a model around the domain.Ubiquitous Language: a common language used by all team members about the domain and interactions between software and the team.DDD is well suited with some architectural patterns like CQRS, Event Driven Architecture, Event sourcing, etc but they are not required to use DDD.
How to start using DDD in a new project
Luckily, after the conference, we started a new project at Briisk and I knew I’d want to use some of the DDD concepts I heard of at Wroclove.rb. The problem was that I only listened to a few presentations and that was it. No one at the company had any experience with DDD before. I had to convince my tech lead that this was the only way to go.
I spent my evenings reading blog posts, watching presentations on YouTube, reading about authorisation or persisting data.
These are some of the materials that I explored:
Domain-Driven Design: Tackling Complexity in the Heart of Software (Eric Evans)Talk: From legacy to DDD (Andrzej Krzywda)Library: Rails Event StoreBlog post: CQRS example in the Rails app (Arkency)Blog post: From legacy to DDD: Start with publishing events (Arkency)Presentation: CQRS / ES with Rails (Mirosław Pragłowski)Talk: Domain Driven Design and Hexagonal Architecture with Rails (RailsConf 2014)
What worked best though was a list of problems we could have prevented in our previous projects if we only tried the new approach. Problems that we encountered in previous projects that we wanted to solve with a new approach:
Fat controllersFat modelsCallbacks (especially cross model callbacks)Lack of API standardization
The new project was big enough to take the risk. Why was the size so important? Because DDD impacts the way we discuss the project and requires a significant amount of time in the beginning. For smaller projects, it is some sort of overkill.
I picked one thing to start with. It turned out that Arkency, the co-organiser of Wroclove.rb created a solution for Rails applications to ease the process of implementing DDD. In short, it was a gem with the specific structure for events, aggregates and designing the domain.
From there it was quite easy. We just had to jump into the water and start the development. One by one we were able to deal with the challenges we knew from previous projects. Of course, DDD is no silver bullet. It’s not that you implement it and it works like a charm. But the interesting thing is that you can use it both for the technological side of the project as well as to manage relationships between the development team and the client.
In our case, we started small with the tech side. For the project we started at Briisk we decided to use CQRS and Event Driven Architecture.
Command and Query Responsibility Segregation (CQRS)
The general idea of CQRS is that every method in the system should be a command which executes an action (mutates the data) or a query which returns some particular date to the caller (query data). It shouldn’t perform both at the same time. When you think of it, it’s natural that asking a question shouldn’t change the answer.
Event Driven Architecture (EDA)
In simple words, EDA is a software architecture build with communicating over events. An event is defined as a significant change in a state. So they are actually mutating the state and this is where the fit with CQRS pattern and the command object. For example:
- Commands: RegisterUser, DeliverPackage, ChangeStatus
- Events: UserRegistered, PackageDelivered, StatusChanged
It means that the state of an object can be represented as a sum of events. For example:
- Object: Invoice
- Event 1: InvoiceCreated
- Event 2: InvoiceItemAdded
- Event 3: InvoiceSent
The flow uses two databases – event store for inserting events and a read module for reading them. In short, the main state of the app is in the event store. You’re asking for data only from the read module.
Thanks to event driven architecture everything can be asynchronous but it doesn’t have to. That’s why it’s so important to treat the event store as a single source of truth and not compare anything with the read model. We can imagine that there were two events that create a user with [email protected] email address and they hit the event store at the same time (or almost the same time).
After the first event was persisted into the event store, it was synchronised with the read model. If by the second event we check states against the read model, there’s a chance that the synchronisation is not over yet and we can’t see that there’s already a user with [email protected]
Final architectural flow
When it comes to our first DDD project, it turned out to be a great approach when it comes to three main points.
Implementing activity stream
In our application, the user was able to review, update or delete products. This way, the products jumped from one state to the other. One of the things we were supposed to display was product’s history – everything that happened with that product from the moment it was created. In our event driven architecture, we had all this information from events. So the only thing that we needed to write were search queries for specific events. This way we had a really sophisticated or complex feature out of the box.
Event structure is flat. It means that there is no such thing as nested events. The beauty of it is only one event table. It’s helpful when testers report bugs. When it happens, I go to the event store and check. If the expected event is not in the event store, I know that the bug lays in the code before the event trigger. If it’s there – it’s somewhere after the trigger.
Time to market
In the beginning, it was really hard to deliver something. The complexity of the new patterns and concepts were overwhelming. It was both because we were all learning and because it’s just hard to put the entire architecture in place. The app we were working on had SaaS architecture so each user could have their own instances of a database. So there was a lot of additional complexity. But then adding new features was really simple. Each feature had to have command, event, validation. If a trigger was needed, all it took was to define “after this event new event command handle”. Fine.
I think the DDD approach is a very natural way of understanding how the app works. It is like a diagram that can be constantly updated after an event storming session. In a common relational database and traditional architecture based on resources, the development is easy until the architecture is small. But when it becomes bigger, it’s more and more difficult to maintain. Maybe the most challenging thing that comes with the growth of the application is naming the events.
When it comes to the event store and event driven architecture, the concepts are not very niche or unused in the community. These patterns are basically the same as we nowadays use in frontend frameworks. Frontend apps are asynchronous by definition. Each app is based on events and both React or Angular use an event store. So an event driven architecture is not something new or something only for the backend. One difference is that the events on the frontend are less related to business. They are more related to user behaviour (user clicked or form sent). So the DDD patterns with slightly different names and marketing are used in modern frameworks.