Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
CRUD (Create Read Update Delete) needs to burn in a tire fire. While it seems simple, it often leads to a spaghetti code mess of complexity in a large system. Start being explicit and capturing intent. Once you do this, the floodgates open possibilities for extending your system by following the path to event-driven architecture.
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Create, Read, Update, Delete (CRUD) is pretty common. Generally, it’s just simple forms to add data to a database and provide a listing of the records that you can drill into a specific record to modify/update existing records or possibly delete them. Often, they really are just a UI and API around a database.
In the above form, the product’s current price is $30.99. What if someone changes that value to $10.99 and saves it? Sure, it updated the database record for that product, and now the product will be sold at the new price.
But what were we really doing there as an end user? Why were we changing the price?
We have no idea. We have no clue their business reason for changing the price. There is a business reason for changing the price, but a CRUD-type system isn’t going to capture it in many situations. Sure, we can try and imply the reason, but we don’t know.
We want our end users to be explicit. We want them to tell us exactly what they are doing and capture the intent. They aren’t just updating a product or changing the price. They were doing something specific. For example, I wasn’t just changing the price to $10.99. This might of been because we were having a limited time flash sale at a heavily discounted price.
There is business intent behind changing the price. It’s not just “changing the price.”
We could try and infer to a degree. For example, if it was CRUD and the price was lower than the current price, we know it is being lowered. But why?
To know why, we must provide users with the business capabilities they need. If having a flash sale is a concept, then we need to provide them with the capability to do so. To do this, we need to be task-driven.
Now that our end-users can be explicit about their intent, this can open the floodgates on extending our system. We can persist this differently so we have a history of when sales have occurred, we could schedule sales in the future, and more importantly, you can be reactionary to when it occurs.
If we schedule a flash sale or a flash sale starts, we can publish an event to indicate that. Then we can be reactive to that occurring and perform other types of actions. In large systems, we often want another thing to occur once one thing occurs.
In this example, maybe we want to email all customers who have that specific product in their wishlist to know that the product is going on sale.
With an event and publish-subscribe, this decouples the additional behavior of email customers from the actual flash sale.
If you were using CRUD, how would you know you’re having the sale? How could you email all potential customers about the sale if you don’t know you’re having it?
Publish-Subscribe allows us to decouple the producer and the consumer. When we schedule a flash sale, that’s producing the FlashSaleScheduled event. We can have zero to many different consumers.
Each consumer can process the FlashSaleScheduled and perform whatever operation it needs to. As mentioned, one consumer might send out an email to customers. Another consumer might integrate with some other system when it occurs. The possibilities are endless because we know explicitly what has happened.
Extending Your System
Event-driven architecture allows you to extend your system. When you know explicitly when certain behaviors occur, you can decide to perform other operations. If some operations are no longer valid, you can remove them without affecting other consumers or the producer. Let’s say we no longer need to integrate with another system when a Flash sale occurs. We remove that consumer. It’s independent from any other consumer.
Doing this avoids the code complexity mess that often happens because various actions are coupled together. If A occurs then B, C, D need to occur and all that happens procedurally. Next thing you know, you have a turd pile of complexity that’s really hard to change. If something fails when executing B, then C and D will never happen, even though they should.
You want to be extending your system, not modifying it.
And that finally leads us to Event Sourcing.
Once you’re task-driven and know explicitly what the users are doing from a business perspective, you can think about using events as a means for recording state.
This means instead of recording the current state, you record all the events that have occurred, which ultimately can get you to the current state. You have a log of all the state transitions in the form of Events. These events not only give you the data but “why” the state transitioned.
Check out my post Event Sourcing Example & Explained in plain English for more on event sourcing.
Follow the path to event-driven architecture. It all starts by capturing intent and being explicit. CRUD has its place, but you cannot infer the business concepts that end-users are applying. Once you start capturing intent, this opens up the possibilities for extending your system rather than modifying it, leading to a much more maintainable system.
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.