Event Sourcing
June 2, 2024 • ☕️ 7 min read • 🏷 computer, software, software-architecture
Translated by author into: English
Event sourcing is a modern and effective pattern used in data management and system design. It differs significantly from traditional data management approaches. In traditional methods, data is usually stored in its final state; That is, the current state of an object is kept directly in the database and this latest state is updated when any changes are made. However, in the event sourcing approach, data is stored as a series of events. Each event represents a change or action that takes place in the system. These events are recorded and stored in a chronological order.
The basic principle of event sourcing is that instead of directly storing the current state of an object, it stores a list of all events that determine that state. The state of an object is reconstructed by a sequential replay of these events. In this way, it becomes possible to access any past state of an object at any time or to monitor changes over a certain period of time.
This approach has many advantages. First, data consistency is ensured because each change is recorded as an event and these events are processed sequentially. Second, system monitoring and fault tolerance increases; because all changes occurring in the system can be monitored through events and undone when necessary. Third, auditability and transparency are ensured; Because all events are recorded, how and why the system came to this state can be examined in detail.
Event sourcing also offers great advantages in data synchronization and consistency, especially in distributed systems. Each event is recorded in a central event log and these events can be shared between different components. In this way, a consistent data state can be ensured between different parts of the system. Additionally, event sourcing can increase the performance and scalability of the system when used with other modern architectural patterns such as CQRS (Command Query Responsibility Segregation).
Event sourcing is a powerful and flexible data management approach that plays an important role in modern software architectures. Thanks to its advantages such as data consistency, system monitoring, fault tolerance and auditability, it is a preferred method, especially in complex and large-scale systems.
Basic concepts
- Event: Any changes that occur within the system are recorded as an event. For example, registering a user, adding a product, or completing an order is an event.
- Event Store: It is where events are permanently stored. Unlike traditional databases, only events are stored here.
- Aggregate: A structure that represents a series of events and creates a state by processing these events.
- Command: Defines the operations to be performed by the system. For example, “Place Order” is a command.
- Projection: Creates the situation from events and transforms this situation into different data models.
Advantages of Event Sourcing
- Auditability: All events are recorded to understand what happened in the past. Thus, it can be monitored how the system came to this state.
- Flexibility: It is easier to create new data models or modify existing ones. Thanks to projections, you can present data in different ways.
- Real-Time Processing: Events can be processed instantly and transmitted to other parts of the system.
Difference Between Event Sourcing and Traditional Data Management
Traditional data management typically works by providing direct access to a database model and instantly applying updates to that model. In this approach, database tables are a reflection of a specific current state, and each data update is performed by overwriting the previous state. This method is widely used to ensure data consistency and perform data operations simply and quickly. However, some difficulties may arise when it is necessary to track historical changes or reconstruct data from a certain time period.
Event sourcing offers a completely different approach. In this model, data management records each change made as an independent event. Each event represents an action or change that occurs in the system, and events are recorded in chronological order. These events can then be used to reconstruct the current state of the data. Thus, reconstructing the state of the system at a specific time is possible only by reprocessing the relevant events. This approach allows to track the exact change process of the data and offers great advantages for performing retrospective analysis, debugging or going back to a specific time. In addition, event sourcing is a compatible model for microservice architectures and distributed systems and provides significant benefits in terms of scalability and flexibility.
The main difference between these two approaches is that traditional data management preserves the current state, while event sourcing preserves the evolution and change process of data. While in the traditional model, recording the current situation is at the forefront, in event sourcing every change is recorded step by step and thus the entire history of the data can be monitored in detail.
Event Sourcing Example with Golang and Redis
Let’s create a simple event sourcing application using Golang and Redis. In this example we will create an order management system. Users will place orders and these orders will be recorded as events.
Step 1: Project Setup
First, let’s install the necessary dependencies:
go mod init event-sourcing-example
go get github.com/go-redis/redis/v8
Step 2: Redis Connection
package main
import (
"context"
"github.com/go-redis/redis/v8"
"log"
)
var ctx = context.Background()
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
_, err := rdb.Ping(ctx).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
log.Println("Connected to Redis")
}
Step 3: Event Structure
Let’s define a structure that represents events:
package main
import "time"
type Event struct {
EventType string
Data interface{}
Timestamp time.Time
}
type OrderCreated struct {
OrderID string
UserID string
Amount float64
}
Step 4: Save and Load Event
Let’s write functions that will save and load events to Redis:
package main
import (
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"log"
"time"
)
func SaveEvent(rdb *redis.Client, event Event) error {
key := fmt.Sprintf("events:%s", event.EventType)
data, err := json.Marshal(event)
if err != nil {
return err
}
return rdb.RPush(ctx, key, data).Err()
}
func LoadEvents(rdb *redis.Client, eventType string) ([]Event, error) {
key := fmt.Sprintf("events:%s", eventType)
data, err := rdb.LRange(ctx, key, 0, -1).Result()
if err != nil {
return nil, err
}
var events []Event
for _, d := range data {
var event Event
if err := json.Unmarshal([]byte(d), &event); err != nil {
return nil, err
}
events = append(events, event)
}
return events, nil
}
Step 5: Command and Projections
Let’s write the order creation command and projection:
package main
import (
"log"
"time"
)
func CreateOrder(rdb *redis.Client, orderID, userID string, amount float64) error {
event := Event{
EventType: "OrderCreated",
Data: OrderCreated{
OrderID: orderID,
UserID: userID,
Amount: amount,
},
Timestamp: time.Now(),
}
return SaveEvent(rdb, event)
}
func GetOrderSummary(rdb *redis.Client) {
events, err := LoadEvents(rdb, "OrderCreated")
if err != nil {
log.Fatalf("Could not load events: %v", err)
}
for _, event := range events {
order := event.Data.(map[string]interface{})
log.Printf("Order ID: %s, User ID: %s, Amount: %v", order["OrderID"], order["UserID"], order["Amount"])
}
}
Step 6: Running the App
Finally, we create the order in the main function and get the summary:
package main
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
_, err := rdb.Ping(ctx).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}
CreateOrder(rdb, "1", "user1", 99.99)
CreateOrder(rdb, "2", "user2", 49.99)
GetOrderSummary(rdb)
}
This code connects to Redis, creates two orders, and then prints a summary of those orders to the screen.
When the program is run, its output will be as follows.
Order ID: 1, User ID: user1, Amount: 99.99
Order ID: 2, User ID: user2, Amount: 49.99
Event sourcing is a powerful pattern in data management and system design. We created a simple event sourcing application using Golang and Redis. We have seen the advantages of this approach and how events are stored and loaded. More advanced topics related to event sourcing include event processing, distributed systems, and microservices architectures. This basic example will help you understand and implement event sourcing.
Resources
- https://microservices.io/patterns/data/event-sourcing.html
- https://www.martinfowler.com/eaaDev/EventSourcing.html
- https://www.linkedin.com/pulse/event-sourcing-pattern-distributed-designpatterns-pratik-pandey/