Unintrusive
Design your domain model without any dependency on Occurrent or any other library. Express it with pure functions that return events, and use Occurrent to store them.
Write your domain as a pure function from past events to new events. The application service loads the stream, runs your function, and appends the new events under optimistic concurrency.
// Your domain is a pure function from past events to new events,// with no dependency on Occurrent.List<GameEvent> handle(List<GameEvent> history, GameCommand command) { ... }
// Occurrent's application service loads the stream, runs your function,// and appends the new events under optimistic concurrency.ApplicationService<GameEvent> applicationService = new GenericApplicationService<>(eventStore, cloudEventConverter);applicationService.execute(gameId, history -> handle(history, command));// Your domain is a pure function from past events to new events,// with no dependency on Occurrent.fun handle(history: List<GameEvent>, command: GameCommand): List<GameEvent> { ... }
// Occurrent's application service loads the stream, runs your function,// and appends the new events under optimistic concurrency.val applicationService = GenericApplicationService(eventStore, cloudEventConverter)applicationService.execute(gameId) { history -> handle(history, command) }Both application services run against the same event store, a single store with stream and DCB capabilities. The decide and evolve are identical too. A DCB decider only adds the consistency boundary the command reads and the tags for the events it writes, so a rule can span more than one entity without a shared stream.
// One boundary per streamvar game = Decider.create( initialState, this::decide, this::evolve);deciderApplicationService.execute(gameId, command, game);// One boundary per streamval game = Decider.create( initialState, ::decide, ::evolve)applicationService.execute(gameId, command, game)// A boundary you define per decision, across entitiesvar game = DcbDecider.create( initialState, this::decide, this::evolve, this::boundaryFor, this::tagsFor);dcbDeciderApplicationService.execute(command, game);// A boundary you define per decision, across entitiesval game = DcbDecider.create( initialState, ::decide, ::evolve, ::boundaryFor, ::tagsFor)dcbApplicationService.execute(command, game)Occurrent is built in layers. Each one is built from the one below it, and you can use any of them directly, even different ones in the same application.
Unintrusive
Design your domain model without any dependency on Occurrent or any other library. Express it with pure functions that return events, and use Occurrent to store them.
Simple
Pick only the libraries you need. There’s no all or nothing.
Control
You’re in control. Magic is kept to a minimum, and data is stored in a standard format, CloudEvents. You serialize and deserialize the event data yourself.
Composable
Use the Occurrent components as building blocks for your own pipelines. They’re small, so you can rewrite one to fit your needs, or write your own domain specific layer on top of Occurrent.
Library
Designed to be used as a library, not a framework, as far as possible.
Pragmatic
Need consistent projections? Write projections and events in the same transaction, with tools you already know such as Spring’s @Transactional.
Interoperable
CloudEvents is a CNCF specification for describing event data in a common way, across services and platforms.
Data format
Events are stored as CloudEvents in the database too, so you can use the database to your advantage. Create your own indexes for fast and fully consistent queries on one event stream, or several.