Skip to content

Latest commit

 

History

399 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ci build - mvn package Status: Beta Quickstart Docs

About Eventstore

A DCB (Dynamic Consistency Boundary) compliant EventStore implementation in Java

Persistence options: Postgres and In-Memory (for dev and demo purposes)

Supports all features described described by the DCB Specification:

  • Tagging of Events for dynamic retrieval
  • Optimistic locking / conditional append via AppendCriteria

Getting started

Step-by-step introduction with the quickstart guide or the documentation

Shutting a store down

EventStore and EventStorage are AutoCloseable. A store that lives as long as the process needs nothing; one created per tenant, per test or per hot reload should be closed, because the Postgres backend runs two LISTEN/NOTIFY threads holding JDBC connections — and those threads keep the storage alive, so dropping the reference does not help.

try ( EventStore eventStore = PostgresEventStorage.newBuilder().buildStore() ) {
    ...
}   // monitors stopped, and pools the builder created are closed

Closing blocks until the background threads have really stopped, is idempotent and terminal, and never closes a DataSource you supplied yourself. Afterwards every operation throws EventStorageClosedException rather than half-working with dead notifications. The full contract is on EventStorage.close(); PostgresEventStorageImpl.stop() is deprecated and delegates to it.

Closing an EventStore shuts down that store, not the storage under it — a storage can back several stores and usually outlives them, so you close it yourself once the stores built on it are closed. The store from buildStore() is the exception: it created the storage and hands you nothing else, so it closes both. When you build the pair yourself and want one handle, compose it the same way:

EventStorage storage = PostgresEventStorage.newBuilder().build();
try ( EventStore eventStore = EventStore.owning(EventStoreFactory.get().eventStore(storage), storage) ) {
    ...
}

Moving events between stores

EventStoreImporter copies events from one storage backend into another, keeping each event's id, timestamp and idempotency key. Position and transaction are always reassigned by the target, so an import reproduces the source order but not its ordering numbers.

ImportReport report = EventStoreImporter.from(sourceStorage).to(targetStorage).run();

It works below the serialization layer, so no domain classes are needed and legacy event types are not upcasted on the way through. A transformation can remap the stream, retag, or rewrite the payload — which also makes it a stream-cloning and schema-migration tool, with no fidelity guarantee beyond what the transformation asks for. See the javadoc on org.sliceworkz.eventstore.migration for the caveats that matter: an import is atomic per batch only, nothing is verified afterwards, and one importer should run at a time per target.

Testing

sliceworkz-eventstore-testing is published for two audiences.

Testing your application. EventStoreFixture covers the shape every DCB application has — read the relevant facts, decide, append conditionally:

EventStoreFixture<LearningEvent> fixture =
    EventStoreFixture.inMemory(EventStreamId.forContext("learning"), LearningEvent.class);

fixture.given(event(new CourseDefined("abc001", "Java basics", 12)).tagged("course", "abc001"))
       .when(stream -> new Registrations(stream).subscribe("123", "abc001"))
       .expectResult(true)
       .expectAppended(event(new StudentSubscribed("123", "abc001"))
                           .tagged("student", "123").tagged("course", "abc001"));

The decider gets a real EventStream, so the code under test is unmodified production code. Only the payload and tags are compared — stream, reference and timestamp are assigned by the store. whenConcurrently(...) appends into the window between the decider's query and its own append, which is the only deterministic way to provoke the conflict a consistency boundary exists to catch:

fixture.given(event(new CourseDefined("abc001", "Java basics", 12)).tagged("course", "abc001"))
       .whenConcurrently(
           stream -> new Registrations(stream).subscribe("123", "abc001"),
           event(new StudentSubscribed("123", "abc001")).tagged("course", "abc001"))
       .expectOptimisticLockingFailure()
       .matchingTags("course", "abc001");

Implementing your own EventStorage. The same module carries the compliance suite. Implement EventStoreBackend, register it in META-INF/services/org.sliceworkz.eventstore.testing.EventStoreBackend, and point surefire at the artifact:

<dependenciesToScan>
    <dependency>org.sliceworkz:sliceworkz-eventstore-testing</dependency>
</dependenciesToScan>

Every scenario then runs against your storage. Optional parts of the contract (importEvents, table prefixes, result limits, direct database access) are declared as capabilities and skipped rather than failed where you do not support them.

Other

Contributing

Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the LGPL-3.0 License - see the LICENSE file for details. External components on which this project depends are listed in the NOTICE file.

About

DCB-compliant Eventstore implementation in Java/Postgres

Topics

Resources

Contributing

Stars

12 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages