Event-Based CRUD
CRUD is an acronym for Create, Read, Update, and Delete. In the database world, it is a set of operations that are essential for managing data in a database. However, Freym is not a product from the world of databases and implements CRUD in a different way. This article explains how CRUD is implemented in Freym.
CRUD in the Database World
In the database world, CRUD is a common pattern for storing and retrieving data. The idea is to store data in a normalized form. All operations are based on the data schema and fail if they deviate from it. Create, Update, and Delete operations cause permanent changes to the data, overwriting the old data. Read operations are used to retrieve data from the database.
Example CRUD Workflow with Relational Databases
Let's say we want to set up "The Beatles Fanshop". This is a simple example of a CREATE operation in a database:
CREATE TABLE concerts (
id INT PRIMARY KEY,
concert_name VARCHAR(255),
concert_date DATE
);
The state of the database after the CREATE operation is an empty table called concerts
.
Let's say you want to add a concert to the table:
INSERT INTO concerts (id, concert_name, concert_date)
VALUES (1, 'The Beatles Cover Band', '2024-12-24');
The state of the database after the INSERT operation is:
id | name | date |
---|---|---|
1 | The Beatles Cover Band | 2024-12-24 |
Let's retrieve this dataset from the database:
SELECT * FROM concerts;
The result of the SELECT operation is:
id | name | date |
---|---|---|
1 | The Beatles Cover Band | 2024-12-24 |
So far, so good. We can now update or delete the concert from the table, or add more concerts to it. I think you got the idea though, so let's move on to the concept of Event Sourcing.
Event Sourcing
In a nutshell, Event Sourcing is a design pattern that records all data changes as events. This way, you can reconstruct the entire sequence of events leading to the current state of the data. Therefore, CRUD operations in the traditional sense are not usable in an Event Sourcing system.
Freym is based on Event Sourcing, but still offers CRUD operations. How is that possible? Since we are not working with a database, CRUD operations are not executed directly on the data. Instead, CRUD data is stored in event-sourced projections.
What are event-sourced projections? Projections are to the event stream what a table is to an SQL database. Each projection represents a view of the data that is more or less displayed directly to the user. Projections are stored in a way that is optimized for reading. The event stream generating the projections still has the complete record of previous events that led to the current state of the data. This way of storing and retrieving data is especially useful in systems that require high availability and performance.
If you have more questions than answers now, don't worry. Let's have a look at an example workflow to make things clearer.
Example Read Workflow with Event Sourcing
Let's say our user Thomas is shopping in "The Beatles Fanshop". This is the event stream for Thomas' shopping cart:
All information from Thomas' shopping cart are stored, also the changes that are invisible in the database. This detailed history of the data allows for jumping back in time to any point in the past.
That's great, but how do we get the current state of Thomas' shopping cart? This is where projections come into play. Projections are updated asynchronously and provide a view of the data that is optimized for reading. In case of a change, the projection is updated.
Coming back to the CRUD operations, this explains how the "Read" operation is implemented in Freym. Since every view of the data is stored in a projection, the "Read" operation is executed on the projection. This way, the CRUD service can provide the current state of the data to the user.
We are not finished now that we talked about "Read" operations. What about the "Create", "Update", and "Delete" operations?
CRUD Service - the Freym Solution
As already mentioned, the concepts of CRUD and Event Sourcing seem to be incompatible. Event Streams are append-only, which means that the original event is not modified. However, CRUD operations cause permanent changes to the data. How can we combine these two concepts?
Freym offers a solution with the CRUD service. The CRUD service is an abstraction layer for simple requests from the client side. All operations against the database are routed through the Projections service. The CRUD service serves as an interface for the client to interact with the Projections service.
You can imagine the CRUD service as the waiter in a restaurant. The waiter takes your order and forwards it to the kitchen. The kitchen prepares the food and the waiter serves it to you. In the same way, the CRUD service forwards your request to the Projections service. The Projections service updates the projection and the CRUD service serves the current state of the data to you. At the same time, the event stream is updated with the new event.
Just as the kitchen cannot undo prepared meals, the event stream is immutable. However, that does not mean you cannot change your order. If you want to change your order, the waiter will take a new order and the kitchen will prepare a new meal. This might cause additional costs in the kitchen, but the end result is the same.
You as the developer are the customer in this analogy. You can interact with the CRUD service to create, update, and delete data. The Freym services take care of the rest.
Example Write Workflow with Event Sourcing
Let's say you want to add a new concert to the event stream. This is the event that is created:
{
"type": "ConcertAdded",
"payload": {
"id": 2,
"name": "The Beatles Tribute Band",
"date": "2025-12-24"
}
}
The event stream is updated with the new event.
Now, you want to add this concert to the projection. This is the projection that is updated:
{
"id": 2,
"name": "The Beatles Tribute Band",
"date": "2025-12-24"
}
- The CRUD service forwards your request to the Projections service.
- The Projections service updates the projection with the new concert.
- The CRUD service serves the current state of the data to you.
This is how the "Create" operation is implemented in Freym. The "Update" and "Delete" operations are implemented in a similar way.
Wrap-Up
How can we combine CRUD operations with Event Sourcing? We want the benefits of Event Sourcing like high availability and performance, as well as the complete history of the data. At the same time, we as humans think in CRUD operations and want to interact with the data in a familiar way.
Freym offers a solution with the CRUD service. The CRUD service is an abstraction layer for simple requests from the client side. All operations against the database are routed through the Projections service. The CRUD service serves as an interface for the client to interact with the Projections service.
You can try out this service in our Example Application. Furthermore, you can learn more about the CRUD service in the CRUD Documentation chapter.