This is a robust and flexible Go library designed for implementing the Event Sourcing pattern in your applications, leveraging the power of Go's generics. Event Sourcing is a design pattern that enables you to capture changes to an application state as a sequence of events, rather than just storing the current state.
The use of generics in this library provides several advantages:
- Type Safety: Generics allow for compile-time type checking, reducing the likelihood of runtime errors and increasing overall code safety.
- Code Reusability: With generics, you can write functions or types that are abstracted over types, meaning they can be reused with different types.
- Improved Performance: Generics can lead to performance improvements as they eliminate the need for type assertions and reflection.
- Clearer and Cleaner Code: Generics result in code that's easier to read, understand, and maintain.
The library includes key components such as Aggregates, Events, and an Event Store, all fundamental to the Event Sourcing architecture. By facilitating an efficient method of handling data changes, this library serves as a valuable tool for maintaining and auditing the complete history of data changes over time in your applications.
This Go Event Sourcing Library offers a broad range of features to make implementing Event Sourcing in your Go applications as straightforward and efficient as possible. Here's a rundown of the key features this library provides:
-
Generics-Based Architecture: The library leverages Go's generics capabilities to provide type-safe and reusable components, resulting in clearer and cleaner code, improved performance, and reduced likelihood of runtime errors.
-
Aggregate Management: It provides robust support for managing aggregates, entities with unique IDs whose state evolves over time through a sequence of events. Aggregates also have invariants that represent business rules to maintain consistency.
-
Event Handling: The library offers comprehensive tools for creating and handling events, which are fundamental units of state change in the Event Sourcing pattern. It includes functionality to apply events to aggregates, track event metadata, and even marshal events to JSON.
-
Event Store Interface: It provides a standard interface for an Event Store, the storage system for events, enabling the saving, loading, and retrieving of an aggregate's history. This interface can be implemented with various storage systems according to your application's needs.
-
Version Management: It includes functionality for managing the version of an aggregate, which is incremented each time an event is applied. This feature aids in tracking the evolution of the aggregate over time.
These features come together to provide a solid foundation for implementing the Event Sourcing pattern in your Go applications, facilitating efficient data change handling and thorough auditing of changes over time.
Certainly! Here's a completed section that references the to-do list example as a practical illustration of how to use the event sourcing library:
To use this library, you would define your own aggregate states and event states that satisfy the AggregateState
and EventState
interfaces, respectively. Then, you can create events and apply them to aggregates, and store the aggregates in an event store.
For a hands-on illustration of how to apply this library, check out our to-do list example. This repository demonstrates a simple yet effective implementation of event sourcing in a practical application: managing a to-do list. You'll see how aggregate and event states are defined and utilized within a Go application, providing a clear blueprint for incorporating event sourcing into your projects.
- Example Repository: Event Sourcing To-Do Example
In this example, you will learn how to:
- Define aggregate states for your to-do items and event states for actions such as creating a to-do or adding a task.
- Create and apply events to these aggregates, effectively demonstrating the event sourcing process.
- Utilize the Command Query Responsibility Segregation (CQRS) pattern to separate the read and write operations of your application, enhancing its architecture and scalability.
This example serves as a practical guide to understanding and implementing event sourcing with our library. By exploring the repository, you can gain insights into structuring your application for event sourcing and managing state through events in a real-world scenario.
Aggregate
represents an entity in your system that is identified by a unique ID. It encapsulates state that evolves over time through a sequence of events.
type Aggregate[S AggregateState] struct {...}
Event
represents something that has occurred within your system. In the context of event sourcing, events are the source of truth and they determine the current state of an aggregate. Each event is associated with an aggregate and contains data that describes the state change.
type Event[S AggregateState] struct {...}
EventStore
is the storage interface for events. It provides methods to save, load, and retrieve the history of an aggregate.
type EventStore[S AggregateState] interface {...}
AggregateState
interface represents the state of an aggregate. It defines the Type
method to return the type of the aggregate, and the Zero
method to return a zero-valued instance of the aggregate state.
type AggregateState interface {...}
EventState
is an interface representing an event state that can be applied to an aggregate. It has Type
method to return the type of the event and Apply
method to apply the event to an aggregate.
type EventState[S AggregateState] interface {...}
Identifiable
is an interface representing objects that can be identified by an Aggregate ID. It's used to ensure that the state of an event can provide an Aggregate ID when the event is applied to an aggregate.
type Identifiable interface {...}
InceptionRecorder
is an interface representing objects that can record the inception date of an aggregate. It's used to ensure that the state of an event can record the inception date of an aggregate when the event is applied to an aggregate.
type InceptionRecorder interface {...}
ModificationRecorder
is an interface representing objects that can record the modification date of an aggregate. It's used to ensure that the state of an event can record the modification date of an aggregate when the event is applied to an aggregate.
type ModificationRecorder interface {...}
The v0.2.0
release introduces a significant enhancement to the Go Event Sourcing Library: the StorageNamer
interface. This interface provides a customizable way for aggregates to define their corresponding storage entities, such as database tables or collections. This feature is especially beneficial as your project scales up, offering a more adaptable approach to storage naming conventions.
type StorageNamer interface {
StorageName() string
}
- Implement the
StorageNamer
interface in your aggregate state if you require a custom storage name different from the default aggregate state type name. - If an aggregate state does not implement
StorageNamer
, the library defaults to using the aggregate state's type as the storage identifier.
Suppose you have an aggregate state UserState
. By default, the storage name would be UserState
. If you need a different storage name, say user_table
, implement the StorageNamer
interface as shown below:
type UserState struct {...}
func (u *UserState) StorageName() string {
return "user_table"
}
func (u *UserState) Type() string {
return "user"
}
This implementation allows UserState
to explicitly specify user_table
as its storage name.
- Explicit Naming: Offers a clear and explicit way to define storage names, enhancing readability and maintainability of your code.
- Flexibility: Adapts to various storage backends and user requirements, making the library more versatile.
- Backward Compatibility: Ensures existing codebases remain functional without modifications.
- This interface is optional; you can choose to implement it based on your project's requirements.
- Ensure consistency in the naming conventions used across your project for clarity.
The Go Event Sourcing Library includes a PostgreSQL implementation of the Event Store interface. This provides the ability to persist and query your events in a PostgreSQL database, giving you the ability to leverage robust and scalable SQL database capabilities for your Event Sourcing needs.
The PostgreSQL Event Store implementation is located in the pgeventstore
package, and includes the following key functionalities:
-
Database Setup and Initialization: The library must be initialized with the necessary environment variables or config for your database connection and schema name, and establishes a connection to the PostgreSQL server. If necessary, it will also create the schema and corresponding tables for the specified aggregates in your database.
-
Event Marshaling: The library provides a mechanism to marshal your events into a format that can be stored in PostgreSQL. It takes care of null checks and type conversions, ensuring your data is safe and reliable.
-
Event Store Interface Implementation: This includes the following operations:
Load
: Retrieves the latest aggregate state based on the aggregate ID and version number.History
: Returns the history of changes to an aggregate, based on the aggregate ID, starting from a specific version.Save
: Persists the changes to an aggregate into the PostgreSQL database.
The implementation also includes functionalities like creating the necessary tables and indexes in your PostgreSQL database and handling batch inserts to optimize the storage of events.
The PostgreSQL Event Store implementation is designed to be highly scalable, capable of handling large volumes of events and providing fast query performance. It leverages PostgreSQL's capabilities for handling JSON data types, making it an ideal choice for Event Sourcing applications.
For the detailed code of this implementation, please refer to the pgeventstore
package in the source code. Be sure to initialize library properly for your PostgreSQL server's URL and other necessary details before running your application.
Remember to carefully handle your database connections and transactions to ensure data consistency and reliability. The library supports transaction management, allowing you to handle operations atomically and safely.
Note: Always ensure that your PostgreSQL server is properly configured and optimized for your workload. Performance can vary based on server specifications, data volume, query complexity, and other factors.
The PostgreSQL Event Store implementation requires certain config variables to be set for its proper functioning. These are:
-
EVENT_STORE_PG_URL
: This should be the connection string for your PostgreSQL database. This must be a valid connection string that includes the username, password, host, port, and database name. For example,postgresql://user:pass@localhost:5432/mydatabase
. -
EVENT_STORE_SCHEMA
: This variable represents the name of the schema under which your event tables will be created. If not provided, the implementation will default to usingeventsourcing
as the schema name. -
EVENT_STORE_AGGREGATES
: A comma-separated list of all aggregate tables to be created in your PostgreSQL database. These tables will hold your events.
For example, if you have a user
and order
aggregates, you can set EVENT_STORE_AGGREGATES=user,order
.
You can also initialize using pgeventstore.EventStorageConfig struct:
config := pgeventstore.EventStorageConfig{
PostgresURL: "postgres://user:pass@localhost:5432?sslmode=disable",
Schema: "eventsourcing",
Aggregates: "user,order",
}
if err := pgeventstore.Init(config); err != nil {
return err
}
The library uses the godotenv package for loading environment variables from a .env
file. You can also set these environment variables manually in your deployment environment.
For security reasons, make sure that your database connection string (EVENT_STORE_PG_URL
) is kept secure and not exposed in your code or version control system.
Remember to restart your application after changing your environment variables to ensure the changes take effect.
Feel free to submit a pull request if you find any bugs or you want to make improvements to the library. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License.
This Go library provides a solid foundation for working with Event Sourcing in Go. It is designed with flexibility in mind to allow it to be adapted to various application needs. We hope this library proves to be a valuable tool in your software development toolkit.