diff --git a/readme.md b/readme.md index 2b1e04f..a9c485f 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# eGo +## eGo [![build](https://img.shields.io/github/actions/workflow/status/Tochemey/ego/build.yml?branch=main)](https://github.com/Tochemey/ego/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/Tochemey/ego/branch/main/graph/badge.svg?token=Z5b9gM6Mnt)](https://codecov.io/gh/Tochemey/ego) @@ -7,7 +7,7 @@ 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 [goakt](https://github.com/Tochemey/goakt) to scale out and guarantee performant, reliable persistence. -## Features +### Features - Write Model: - Commands handler: The command handlers define how to handle each incoming command, @@ -26,13 +26,147 @@ Under the hood, ego leverages [goakt](https://github.com/Tochemey/goakt) to scal - [Cluster Mode](https://github.com/Tochemey/goakt#clustering) - Examples (check the [examples](./example)) -## Installation +### Installation ```bash go get github.com/tochemey/ego ``` -## Contribution +### Sample + +```go +package main + +import ( + "context" + "errors" + "log" + "os" + "os/signal" + "syscall" + + "github.com/google/uuid" + "github.com/tochemey/ego" + "github.com/tochemey/ego/eventstore/memory" + samplepb "github.com/tochemey/ego/example/pbs/sample/pb/v1" + "google.golang.org/protobuf/proto" +) + +func main() { + // create the go context + ctx := context.Background() + // create the event store + eventStore := memory.NewEventsStore() + // create the ego engine + e := ego.NewEngine("Sample", eventStore) + // start ego engine + _ = e.Start(ctx) + // create a persistence id + entityID := uuid.NewString() + // create an entity behavior with a given id + behavior := NewAccountBehavior(entityID) + // create an entity + entity, _ := ego.NewEntity[*samplepb.Account](ctx, behavior, e) + + // send some commands to the pid + var command proto.Message + // create an account + command = &samplepb.CreateAccount{ + AccountId: entityID, + AccountBalance: 500.00, + } + // send the command to the actor. Please don't ignore the error in production grid code + account, _, _ := entity.SendCommand(ctx, command) + + log.Printf("current balance: %v", account.GetAccountBalance()) + + // send another command to credit the balance + command = &samplepb.CreditAccount{ + AccountId: entityID, + Balance: 250, + } + account, _, _ = entity.SendCommand(ctx, command) + log.Printf("current balance: %v", account.GetAccountBalance()) + + // capture ctrl+c + interruptSignal := make(chan os.Signal, 1) + signal.Notify(interruptSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + <-interruptSignal + + // stop the actor system + _ = e.Stop(ctx) + os.Exit(0) +} + +// AccountBehavior implements persistence.Behavior +type AccountBehavior struct { + id string +} + +// make sure that AccountBehavior is a true persistence behavior +var _ ego.EntityBehavior[*samplepb.Account] = &AccountBehavior{} + +// NewAccountBehavior creates an instance of AccountBehavior +func NewAccountBehavior(id string) *AccountBehavior { + return &AccountBehavior{id: id} +} + +// ID returns the id +func (a *AccountBehavior) ID() string { + return a.id +} + +// InitialState returns the initial state +func (a *AccountBehavior) InitialState() *samplepb.Account { + return new(samplepb.Account) +} + +// HandleCommand handles every command that is sent to the persistent behavior +func (a *AccountBehavior) HandleCommand(_ context.Context, command ego.Command, _ *samplepb.Account) (event ego.Event, err error) { + switch cmd := command.(type) { + case *samplepb.CreateAccount: + // TODO in production grid app validate the command using the prior state + return &samplepb.AccountCreated{ + AccountId: cmd.GetAccountId(), + AccountBalance: cmd.GetAccountBalance(), + }, nil + + case *samplepb.CreditAccount: + // TODO in production grid app validate the command using the prior state + return &samplepb.AccountCredited{ + AccountId: cmd.GetAccountId(), + AccountBalance: cmd.GetBalance(), + }, nil + + default: + return nil, errors.New("unhandled command") + } +} + +// HandleEvent handles every event emitted +func (a *AccountBehavior) HandleEvent(_ context.Context, event ego.Event, priorState *samplepb.Account) (state *samplepb.Account, err error) { + switch evt := event.(type) { + case *samplepb.AccountCreated: + return &samplepb.Account{ + AccountId: evt.GetAccountId(), + AccountBalance: evt.GetAccountBalance(), + }, nil + + case *samplepb.AccountCredited: + bal := priorState.GetAccountBalance() + evt.GetAccountBalance() + return &samplepb.Account{ + AccountId: evt.GetAccountId(), + AccountBalance: bal, + }, nil + + default: + return nil, errors.New("unhandled event") + } +} +``` + + +### Contribution Contributions are welcome! The project adheres to [Semantic Versioning](https://semver.org) and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). @@ -44,7 +178,7 @@ To contribute please: - Create a feature branch - Submit a [pull request](https://help.github.com/articles/using-pull-requests) -### Test & Linter +#### Test & Linter Prior to submitting a [pull request](https://help.github.com/articles/using-pull-requests), please run: