Skip to content

Commit

Permalink
style: fix formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
Tochemey committed Dec 17, 2024
1 parent b714af1 commit 9c75be3
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 57 deletions.
5 changes: 3 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Library Version:**
- Go-Akt version: [e.g. 2.1.0]
- Go version: [e.g. 1.22]

- Go-Akt version: [e.g. 2.1.0]
- Go version: [e.g. 1.22]

**Additional context**
Add any other context about the problem here.
5 changes: 3 additions & 2 deletions .github/ISSUE_TEMPLATE/performance_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Library Version:**
- Go-Akt version: [e.g. 2.5.0]
- Go version: [e.g. 1.22]

- Go-Akt version: [e.g. 2.5.0]
- Go version: [e.g. 1.22]

**Additional context**
Add any other context about the problem here.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
Expand Down
3 changes: 2 additions & 1 deletion contributing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Contributions are welcome

The project adheres to [Semantic Versioning](https://semver.org) and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
The project adheres to [Semantic Versioning](https://semver.org)
and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
This repo uses [Earthly](https://earthly.dev/get-earthly).

There are two ways you can become a contributor:
Expand Down
6 changes: 3 additions & 3 deletions plugins/statestore/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ func NewStateStore(config *Config) *DurableStore {
// create the underlying db connection
db := postgres.New(postgres.NewConfig(config.DBHost, config.DBPort, config.DBUser, config.DBPassword, config.DBName))
return &DurableStore{
db: db,
sb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
connected: atomic.NewBool(false),
db: db,
sb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
connected: atomic.NewBool(false),
}
}

Expand Down
124 changes: 81 additions & 43 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,96 +6,130 @@
![GitHub Release](https://img.shields.io/github/v/release/Tochemey/ego)
[![codecov](https://codecov.io/gh/Tochemey/ego/branch/main/graph/badge.svg?token=Z5b9gM6Mnt)](https://codecov.io/gh/Tochemey/ego)

eGo is a minimal library that help build event-sourcing and CQRS application through a simple interface, and it allows developers to describe their **_commands_**, **_events_** and **_states_** **_are defined using google protocol buffers_**.
Under the hood, ego leverages [Go-Akt](https://github.com/Tochemey/goakt) to scale out and guarantee performant, reliable persistence.
eGo is a minimal library that help build event-sourcing and CQRS application through a simple interface, and it allows
developers to describe their **_commands_**, **_events_** and **_states_** **_are defined using google protocol buffers_
**.
Under the hood, ego leverages [Go-Akt](https://github.com/Tochemey/goakt) to scale out and guarantee performant,
reliable persistence.

## Table of Content

- [Features](#features)
- [Event Sourced Behavior](#event-sourced-behavior)
- [Howto](#howto)
- [Events Stream](#events-stream)
- [Projection](#projection)
- [Events Store](#events-store)
- [Offset Store](#offsets-store)
- [Durable State Behavior](#durable-state-behavior)
- [State Store](#state-store)
- [Howto](#howto-1)
- [State Stream](#events-stream-1)
- [Event Sourced Behavior](#event-sourced-behavior)
- [Howto](#howto)
- [Events Stream](#events-stream)
- [Projection](#projection)
- [Events Store](#events-store)
- [Offset Store](#offsets-store)
- [Durable State Behavior](#durable-state-behavior)
- [State Store](#state-store)
- [Howto](#howto-1)
- [State Stream](#events-stream-1)

## Features

### Event Sourced Behavior

The [`EventSourcedBehavior`](./behavior.go) is crucial for maintaining data consistency, especially in distributed systems. It defines how to handle the various commands (requests to perform actions) that are always directed at the event sourced entity.
In eGo commands sent to the [`EventSourcedBehavior`](./behavior.go) are processed in order. When a command is processed, it may result in the generation of events, which are then stored in an event store. Every event persisted has a revision number
and timestamp that can help track it. The [`EventSourcedBehavior`](./behavior.go) in eGo is responsible for defining how to handle events that are the result of command handlers.
The end result of events handling is to build the new state of the event sourced entity. When running in cluster mode, aggregate root are sharded.
The [`EventSourcedBehavior`](./behavior.go) is crucial for maintaining data consistency, especially in distributed
systems. It defines how to handle the various commands (requests to perform actions) that are always directed at the
event sourced entity.
In eGo commands sent to the [`EventSourcedBehavior`](./behavior.go) are processed in order. When a command is processed,
it may result in the generation of events, which are then stored in an event store. Every event persisted has a revision
number
and timestamp that can help track it. The [`EventSourcedBehavior`](./behavior.go) in eGo is responsible for defining how
to handle events that are the result of command handlers.
The end result of events handling is to build the new state of the event sourced entity. When running in cluster mode,
aggregate root are sharded.

- `Commands handler`: The command handlers define how to handle each incoming command,
which validations must be applied, and finally, which events will be persisted if any. When there is no event to be persisted a nil can
which validations must be applied, and finally, which events will be persisted if any. When there is no event to be
persisted a nil can
be returned as a no-op. Command handlers are the meat of the event sourced actor.
They encode the business rules of your event sourced actor and act as a guardian of the event sourced entity consistency.
They encode the business rules of your event sourced actor and act as a guardian of the event sourced entity
consistency.
The command handler must first validate that the incoming command can be applied to the current model state.
Any decision should be solely based on the data passed in the commands and the state of the Behavior.
In case of successful validation, one or more events expressing the mutations are persisted. Once the events are persisted, they are applied to the state producing a new valid state.
- `Events handler`: The event handlers are used to mutate the state of the event sourced entity by applying the events to it.
Event handlers must be pure functions as they will be used when instantiating the event sourced entity and replaying the event store.
In case of successful validation, one or more events expressing the mutations are persisted. Once the events are
persisted, they are applied to the state producing a new valid state.
- `Events handler`: The event handlers are used to mutate the state of the event sourced entity by applying the events
to it.
Event handlers must be pure functions as they will be used when instantiating the event sourced entity and replaying
the event store.

#### Howto

To define an event sourced entity, one needs to:

1. define the state of the event sourced entity using google protocol buffers message
2. define the various commands that will be handled by the event sourced entity
3. define the various events that are result of the command handlers and that will be handled by the event sourced entity to return the new state of the event sourced entity
3. define the various events that are result of the command handlers and that will be handled by the event sourced
entity to return the new state of the event sourced entity
4. implement the [`EventSourcedBehavior`](./behavior.go) interface.
5. call the `Entity` method of eGo [engine](./engine.go)

#### Events Stream

Every event handled by event sourced entity are pushed to an events stream. That enables real-time processing of events without having to interact with the events store.
Just use `Subscribe` method of [Engine](./engine.go) and start iterating through the messages and cast every message to the [Event](./protos/ego/v3/ego.proto).
Every event handled by event sourced entity are pushed to an events stream. That enables real-time processing of events
without having to interact with the events store.
Just use `Subscribe` method of [Engine](./engine.go) and start iterating through the messages and cast every message to
the [Event](./protos/ego/v3/ego.proto).

#### Projection

One can add a projection to the eGo engine to help build a read model. Projections in eGo rely on an offset store to track how far they have consumed events
One can add a projection to the eGo engine to help build a read model. Projections in eGo rely on an offset store to
track how far they have consumed events
persisted by the write model. The offset used in eGo is a timestamp-based offset. One can also:

- remove a given projection: this will stop the projection and remove it from the system
- check the status of a given projection

#### Events Store

One can implement a custom events store. See [EventsStore](persistence/events_store.go). eGo comes packaged with two events store:
One can implement a custom events store. See [EventsStore](persistence/events_store.go). eGo comes packaged with two
events store:

- [Postgres](plugins/eventstore/postgres/postgres.go): Schema can be found [here](./resources/eventstore_postgres.sql)
- [Memory](plugins/eventstore/memory/memory.go) (for testing purpose only)

#### Offsets Store

One can implement a custom offsets store. See [OffsetStore](./offsetstore/iface.go). eGo comes packaged with two offset store:
One can implement a custom offsets store. See [OffsetStore](./offsetstore/iface.go). eGo comes packaged with two offset
store:

- [Postgres](./offsetstore/postgres/postgres.go): Schema can be found [here](./resources/offsetstore_postgres.sql)
- [Memory](./offsetstore/memory/memory.go) (for testing purpose only)

### Durable State Behavior

The [`DurableStateBehavior`](./behavior.go) represents a type of Actor that persists its full state after processing each command instead of using event sourcing.
This type of Actor keeps its current state in memory during command handling and based upon the command response persists its full state into a durable store. The store can be a SQL or NoSQL database.
The whole concept is given the current state of the actor and a command produce a new state with a higher version as shown in this diagram: (State, Command) => State
[`DurableStateBehavior`](./behavior.go) reacts to commands which result in a new version of the actor state. Only the latest version of the actor state is persisted to the durable store.
The [`DurableStateBehavior`](./behavior.go) represents a type of Actor that persists its full state after processing
each command instead of using event sourcing.
This type of Actor keeps its current state in memory during command handling and based upon the command response
persists its full state into a durable store. The store can be a SQL or NoSQL database.
The whole concept is given the current state of the actor and a command produce a new state with a higher version as
shown in this diagram: (State, Command) => State
[`DurableStateBehavior`](./behavior.go) reacts to commands which result in a new version of the actor state. Only the
latest version of the actor state is persisted to the durable store.
There is no concept of history regarding the actor state since this is not an event sourced actor.
However, one can rely on the _version number_ of the actor state and exactly know how the actor state has evolved overtime.
[`DurableStateBehavior`](./behavior.go) version number are numerically incremented by the command handler which means it is imperative that the newer version of the state is greater than the current version by one.
However, one can rely on the _version number_ of the actor state and exactly know how the actor state has evolved
overtime.
[`DurableStateBehavior`](./behavior.go) version number are numerically incremented by the command handler which means it
is imperative that the newer version of the state is greater than the current version by one.
[`DurableStateBehavior`](./behavior.go) will attempt to recover its state whenever available from the durable state.
During a normal shutdown process, it will persist its current state to the durable store prior to shutting down. This behavior help maintain some consistency across the actor state evolution.
During a normal shutdown process, it will persist its current state to the durable store prior to shutting down. This
behavior help maintain some consistency across the actor state evolution.

#### State Store

One can implement a custom state store. See [StateStore](persistence/state_store.go). eGo comes packaged with two state stores:
One can implement a custom state store. See [StateStore](persistence/state_store.go). eGo comes packaged with two state
stores:

- [Postgres](plugins/statestore/postgres/postgres.go): Schema can be found [here](./resources/durablestore_postgres.sql)
- [Memory](plugins/statestore/memory/memory.go) (for testing purpose only)

#### Howto

To define a durable state entity, one needs to:

1. define the state of the entity using google protocol buffers message
2. define the various commands that will be handled by the entity
3. implements the [`DurableStateBehavior`](./behavior.go) interface
Expand All @@ -104,9 +138,10 @@ To define a durable state entity, one needs to:

#### Events Stream

[`DurableStateBehavior`](./behavior.go) full state is pushed to an events stream.
[`DurableStateBehavior`](./behavior.go) full state is pushed to an events stream.
That enables real-time processing of state without having to interact with the state store.
Just use `Subscribe` method of [Engine](./engine.go) and start iterating through the messages and cast every message to the [DurableState](./protos/ego/v3/ego.proto).
Just use `Subscribe` method of [Engine](./engine.go) and start iterating through the messages and cast every message to
the [DurableState](./protos/ego/v3/ego.proto).

### Cluster

Expand Down Expand Up @@ -279,25 +314,28 @@ The version system adopted in eGo deviates a bit from the standard semantic vers
The version format is as follows:

- The `MAJOR` part of the version will stay at `v3` for the meantime.
- The `MINOR` part of the version will cater for any new _features_, _breaking changes_ with a note on the breaking changes.
- The `MINOR` part of the version will cater for any new _features_, _breaking changes_ with a note on the breaking
changes.
- The `PATCH` part of the version will cater for dependencies upgrades, bug fixes, security patches and co.

The versioning will remain like `v3.x.x` until further notice.

### Contribution

Contributions are welcome!
The project adheres to [Semantic Versioning](https://semver.org) and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
The project adheres to [Semantic Versioning](https://semver.org)
and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
This repo uses [Earthly](https://earthly.dev/get-earthly).

There are two ways you can become a contributor:

1. Request to become a collaborator, and then you can just open pull requests against the repository without forking it.
2. Follow these steps
- Fork the repository
- Create a feature branch
- Set your docker credentials on your fork using the following secret names: `DOCKER_USER` and `DOCKER_PASS`
- Submit a [pull request](https://help.github.com/articles/using-pull-requests)

- Fork the repository
- Create a feature branch
- Set your docker credentials on your fork using the following secret names: `DOCKER_USER` and `DOCKER_PASS`
- Submit a [pull request](https://help.github.com/articles/using-pull-requests)

## Test & Linter

Expand Down
3 changes: 2 additions & 1 deletion renovate.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended", ":dependencyDashboardApproval"
"config:recommended",
":dependencyDashboardApproval"
]
}
3 changes: 1 addition & 2 deletions resources/durablestore_postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ CREATE TABLE IF NOT EXISTS states_store
shard_number BIGINT NOT NULL,

PRIMARY KEY (persistence_id, version_number)
CREATE INDEX IF NOT EXISTS idx_states_store_persistence_id ON events_store(persistence_id);
CREATE INDEX IF NOT EXISTS idx_states_store_version_number ON events_store(version_number);
);

--- create an indexes
CREATE INDEX IF NOT EXISTS idx_states_store_persistence_id ON states_store(persistence_id);
CREATE INDEX IF NOT EXISTS idx_states_store_version_number ON states_store(version_number);
2 changes: 1 addition & 1 deletion resources/eventstore_postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS events_store
PRIMARY KEY (persistence_id, sequence_number)
);

--- create an index on the is_deleted column
--- create an indexes
CREATE INDEX IF NOT EXISTS idx_events_store_persistence_id ON events_store(persistence_id);
CREATE INDEX IF NOT EXISTS idx_events_store_seqnumber ON events_store(sequence_number);
CREATE INDEX IF NOT EXISTS idx_events_store_timestamp ON events_store (timestamp);
Expand Down
2 changes: 1 addition & 1 deletion resources/offsetstore_postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ CREATE TABLE IF NOT EXISTS offsets_store
PRIMARY KEY (projection_name, shard_number)
);

--- create an index on the projection_name column
--- create an indexes
CREATE INDEX IF NOT EXISTS idx_offsets_store_name ON offsets_store (projection_name);
CREATE INDEX IF NOT EXISTS idx_offsets_store_shard ON offsets_store (shard_number);

0 comments on commit 9c75be3

Please sign in to comment.