Event Horizon

repository·main·Indexed 23 days ago

https://github.com/looplab/eventhorizon

A toolkit for implementing Command Query Responsibility Segregation (CQRS) and Event Sourcing patterns in Go applications. It provides official implementations for event stores (Memory, MongoDB), event buses (GCP Cloud Pub/Sub, NATS, Kafka, Redis), and repositories, along with support for OpenTracing distributed tracing.

Tokens
11.5K
Snippets
31
Records
92
Agent score
81%

What's inside eventhorizon

  1. What is Event Horizon?

    main

    Event Horizon is a CQRS (Command Query Responsibility Segregation) and ES (Event Sourcing) toolkit for Go.

    • CQRS separates object access (Query) from modification (Command), allowing independent scaling and design of data models and outputs.
    • Event Sourcing records all system events as the source of truth, enabling full traceability, audit logging, and the ability to compensate for past errors by emitting corrective events.

    NOTE: The API is not final, though it is used in production systems.

  2. Choose between GlobalPositionInTX and GlobalPositionOutsideTX

    main

    When configuring the mongodb_v2 event store, select a strategy based on your consumer requirements and workload concurrency:

    GlobalPositionInTX (Default)

    • Position Guarantees: Strict commit order with no gaps in the global sequence. The $all increment happens inside the save transaction.
    • Behavior on Abort: If a transaction aborts (e.g., due to an optimistic lock failure on an aggregate), the $all increment rolls back. The sequence remains contiguous.
    • Best For: Mixed workloads and consumers that rely on strict ordering or scanning by position, such as position-ordered projectors or catch-up subscriptions.
    • Performance: Under high concurrency, all transactions serialize on the single $all document write lock, which can become a bottleneck.

    GlobalPositionOutsideTX (Opt-in)

    • Position Guarantees: Gaps in the global position sequence are possible; cross-aggregate ordering is not strictly guaranteed.
    • Behavior on Abort: The position is reserved via an atomic operation before the transaction opens. If the transaction aborts, the reserved position is lost, creating a permanent gap in the sequence.
    • Best For: High-concurrency or many-aggregate workloads where consumers are event-bus reactive (e.g., eventhandler/saga, eventhandler/projector, or outboxes wired via WithEventHandler). These consumers react to individual events as they are published and do not scan the global position.
    • Performance: Significantly higher throughput under concurrency because the transaction no longer holds a lock on the $all document, preventing serialization across different aggregates.
  3. Benchmark MongoDB v2 Save Strategies

    main

    You can reproduce the performance characteristics of the different global position strategies using the built-in benchmarks. This is useful for determining the 'contention knee' where InTX latency begins to grow non-linearly compared to OutsideTX.

    To run the benchmark matrix:

    go test -bench=BenchmarkSaveStrategiesMatrix -benchtime=3s -run=^$ -timeout=15m ./eventstore/mongodb_v2/...

    To perform a statistical comparison across multiple runs using benchstat:

    go test -bench=BenchmarkSaveStrategiesMatrix -benchtime=3s -count=5 -run=^$ -timeout=30m ./eventstore/mongodb_v2/... | tee bench.txt
    benchstat -col /strategy bench.txt
  4. Run the TodoMVC example with Docker

    main

    The fastest way to run the full TodoMVC example, including the Elm frontend and the backend services, is using Docker. This command will handle the compilation and startup of all necessary components.

    Once running:

    • Access the TodoMVC app at: http://localhost:8080
    • View the event traces at: http://localhost:16686
    make run
  5. Run tests for Event Horizon

    main

    To develop Event Horizon, you must have Docker and Docker Compose installed. Use the following commands to run different test suites.

    Unit Tests

    Run all unit tests using:

    make test

    Integration Tests

    To run integration tests, you must first start the required services, run the tests, and then stop the services.

    make run
    make test_integration
    make stop

    Docker-based Testing

    You can also run tests within Docker environments:

    make test_docker
    make test_integration_docker
  6. Configure the MongoDB v2 Global Position Strategy

    main

    The mongodb_v2 event store allows you to choose how the global event position (the $all stream document) is incremented. This choice impacts the trade-off between strict ordering guarantees and write throughput under high concurrency.

    Use mongodb_v2.WithGlobalPositionStrategy(...) during the initialization of your event store to set the desired strategy.

    store, err := mongodb_v2.NewEventStore(uri, dbName,
        mongodb_v2.WithGlobalPositionStrategy(mongodb_v2.GlobalPositionOutsideTX),
    )
  7. Run integration tests using Docker Compose

    main

    The docker-compose.yml file provides a complete environment for running integration tests. It orchestrates the eventhorizon-test service along with several infrastructure dependencies including MongoDB, Google Cloud Pub/Sub emulator, Kafka, Redis, and NATS.

    To run the tests, ensure Docker and Docker Compose are installed, then execute the following command in the repository root:

  8. Available Event Bus Implementations

    main

    Event Horizon provides several implementations for publishing and handling events across different systems.

    Official Implementations

    • GCP Cloud Pub/Sub: Uses one topic with multiple subscribers.
    • NATS: Utilizes Jetstream features.
    • Kafka: Uses one topic with multiple consumer groups.
    • Local: Best for testing and experimentation.
    • Redis: Uses Redis streams.
    • Tracing: Middleware that adds OpenTracing distributed tracing support to event publishing and handling.

    3rd Party Implementations

    • Kafka: https://github.com/Kistler-Group/eh-kafka
    • NATS Streaming: https://github.com/v0id3r/eh-nats
  9. Available Repository Implementations

    main

    Repository implementations are used for managing projected entities.

    Official Implementations

    • Memory: Best for testing and experimentation.
    • MongoDB: Stores one document per projected entity.
    • Version: Adds support for reading a specific version of an entity from an underlying repository.
    • Cache: Adds in-memory caching support for an underlying repository.
    • Tracing: Adds OpenTracing distributed tracing support to repository operations.