Open Source Framework

goes

Type-safe aggregates, commands, and projections - from prototyping to production.

158 ·Apache-2.0· Go ·17 forks
goes Gopher
Scroll
Overview

What is goes?

goes is an event sourcing framework for Go that provides the building blocks for distributed, event-driven applications. Type-safe aggregates, commands, and projections - with swappable backends for every scale.

Unlike many event sourcing libraries that are either too academic or too opinionated, goes embraces idiomatic Go. No code generation, no magic annotations - just interfaces, generics, and composition. You define your aggregates as plain Go structs, register event handlers via typed functions, and the framework takes care of persistence, replay, and concurrency.

goes is built for Go teams that want to integrate event sourcing and CQRS into existing or new systems without locking themselves into a specific infrastructure setup. Whether you start with a simple in-memory prototype or run a distributed system with NATS, MongoDB, and PostgreSQL, your domain logic stays identical.

See It In Action
order.go
// Define your aggregate
type Order struct {
    *aggregate.Base

    Items  []LineItem
    Status OrderStatus
}

// Register typed event handlers
func NewOrder(id uuid.UUID) *Order {
    o := &Order{Base: aggregate.New("order", id)}

    event.ApplyWith(o, o.placed, "order.placed")
    event.ApplyWith(o, o.paid, "order.paid")

    return o
}

// Produce events from commands
func (o *Order) Place(items []LineItem) error {
    aggregate.Next(o, "order.placed",
        OrderPlaced{Items: items},
    )
    return nil
}

// Rebuild state from events
func (o *Order) placed(e event.Of[OrderPlaced]) {
    o.Items = e.Data().Items
    o.Status = Placed
}

No code generation, no annotations - just Go interfaces, generics, and composition. Your domain logic stays clean and portable.

Motivation

Why Event Sourcing?

The Problem

Traditional CRUD systems only store the current state. When something goes wrong or an audit is needed, the history is gone. Data gets overwritten, context is lost. Who changed what, when, and why? A plain UPDATE statement can't answer those questions.

The Approach

Event sourcing stores every state change as an event. Current state is reconstructed from the event history. Nothing is lost: full auditability, time-travel debugging, and the ability to build new read models retroactively from existing events. Your event log becomes the single source of truth.

What goes does differently

goes takes the complexity out of event sourcing in Go. No boilerplate, no DSLs - just Go interfaces and generics. You start with in-memory backends and switch to MongoDB, PostgreSQL, or NATS without changing a single line of domain code. The framework handles event routing, aggregate hydration, snapshot management, and projection scheduling, so you can focus on your domain.

How It Works

The Event Pipeline

Every state change flows through a well-defined pipeline. Commands trigger aggregate logic, which produces events. Events are persisted and projected into read models.

1
Command

A typed intent is dispatched and validated through the middleware chain.

2
Aggregate

The aggregate enforces invariants and produces events from commands.

3
Event

An immutable fact describing a state change. The single source of truth.

4
Event Store

Events are persisted append-only and distributed to subscribers.

5
Projection

Read models are built from the event stream, live or on-demand.

Architecture

Core Concepts

Event-Sourced Aggregates

Embed the base type, register typed event handlers. The framework handles versioning, persistence, and replay. Aggregates are plain Go structs with an embedded aggregate.Base. Event handlers are registered via generic functions and called automatically during hydration. Soft deletes, aggregate versioning, and optimistic concurrency control are built in.

Distributed Events

Publish and subscribe to events via NATS JetStream. MongoDB and PostgreSQL as event stores. Swap backends without code changes. The event bus abstracts transport and delivery. Your code works with typed events regardless of whether they're local, in-process, or distributed across a cluster.

Type-Safe Commands

Dispatch and handle commands with full generic type safety. Synchronous or asynchronous, depending on the use case. The command bus supports middleware chains, context propagation, and error handling. Async commands are dispatched over the event bus, sync commands are executed directly in-process.

Projection Toolkit

Build read models with continuous or periodic schedules. Automatic progress tracking, debouncing, and startup catch-up. Projections can read from any event store and write to any read database. The scheduler handles error retry, backoff, and guaranteed processing.

Codec Registry

Map event names to Go types for automatic serialization. JSON by default, MessagePack or Protobuf as a one-line swap. The codec registry is central to type-safe event handling. It ensures events are correctly serialized, deserialized, and versioned across your system.

Snapshots

Capture aggregate state at a point in time. Only replay recent events instead of the full history. Snapshots are created automatically at configurable intervals. Supported stores include MongoDB, PostgreSQL, and in-memory, using the same pluggable backend architecture as the event store.

Testable by Design

Fluent test assertions for aggregates, in-memory backends for integration tests, and conformance suites for custom implementations. The testing package provides expect-style assertions: given events, when command, then events. Conformance suites verify that custom event store implementations behave correctly.

Modular Design

Use only what you need - event system, commands, projections - and add more components as required. Each package has clearly defined interfaces and minimal dependencies. You can integrate goes incrementally into an existing project without adopting everything at once.

Backends

Production-Ready Backends

goes follows the principle of swappable backends: every event store, snapshot store, and event bus implements a shared interface. You develop against abstractions, not implementations. This makes it possible to test locally with in-memory backends, use MongoDB in staging, and run PostgreSQL with NATS in production, without touching a single line of domain code.

MongoDB

Document-based event store with native change stream support for real-time event subscriptions. Ideal for event-driven architectures where projections need to react to new events in real time. Also supports snapshots and optimistic concurrency via document versioning.

PostgreSQL

Relational event store with ACID transactions and optimistic concurrency control. Perfect for teams that already have a PostgreSQL setup and don't want to introduce additional infrastructure. Serializable isolation guarantees consistent event streams.

NATS JetStream

Distributed event bus with at-least-once delivery, consumer groups, and horizontal scaling. NATS is particularly well suited for microservice architectures where events need to be distributed between services. Persistence via JetStream, replay via consumer offsets.

In-Memory

Zero-config backend for prototyping and testing. Same API as production backends, so your tests and prototypes use identical code to your production environment. No setup, no dependencies, ready to go.

Interactive 3D

Microservice Architecture

goes provides the backbone for event-driven microservice systems. Each service communicates through the central event bus. Scroll to explore each component.

Aggregates1/8

Domain entities that encapsulate business logic and enforce invariants. The aggregate is the consistency boundary for your events.

Technology

Tech Stack

Go NATS JetStream MongoDB PostgreSQL Protocol Buffers gRPC Docker
GitHub
158

GitHub Stars

License

Apache-2.0

Language

Go

Forks

17

Contributors

5+

View on GitHub

FAQ

Frequently asked questions about goes

Yes. goes is used in multiple production environments and actively maintained. The framework has a stable API and is used internally at modernice for client projects. Projects like Crovillas, DepositDirect, and Prestige Cars have been running on goes since 2021.

goes supports MongoDB, PostgreSQL, and NATS JetStream as production backends. For testing and prototyping, there are complete in-memory implementations. All backends implement the same interfaces, making it possible to switch without code changes.

Basic Go knowledge is sufficient. The getting-started guide and the 12-chapter tutorial walk you through all concepts step by step. goes is designed so you can learn and apply event sourcing patterns incrementally.

Yes. goes is modular by design. You can use individual components like the event system or commands independently and integrate more as needed. There's no all-or-nothing dependency.

goes is designed for high-throughput scenarios. Snapshots drastically reduce replay time, projections run asynchronously and in parallel, and the event store supports batching. In production environments, millions of events are processed without noticeable latency.

EventStoreDB is a standalone database, while goes uses your existing infrastructure (MongoDB, PostgreSQL, NATS). Axon is Java-based and significantly more opinionated. goes gives you the building blocks and lets you decide how to assemble them. No runtime dependencies beyond your chosen backend.

goes requires Go 1.23 or newer. The framework makes extensive use of Go generics for type safety across commands, events, and aggregates. We recommend always using the latest stable Go version.

Contributions are welcome. The best starting point is the GitHub repository. Issues labeled 'good first issue' are a great way to get started. PRs are reviewed and merged in a timely manner. For larger changes, we recommend opening an issue first to align on the approach.

Get in Touch

Now that you've learned about our process, why not take the next step and let us help you take your online presence to the next level? Don't wait – schedule a call with us today and let's discuss how we can give your business the recognition it deserves.