Open-source project
shijuvar/go-distributed-sys avatar
shijuvar/go-distributed-sys

go-distributed-sys: a worked example of CQRS over JetStream and gRPC

Guidance for building event-driven distributed systems and microservices in Go with NATS JetStream, gRPC and CockroachDB

664 stars119 forksGoApache-2.0

At a glance

What is it?
go-distributed-sys is guidance rather than a framework: an event-driven order system in Go where NATS JetStream carries the events, an immutable event store is reached over gRPC, CockroachDB holds the query model, and Go kit runs the HTTP service. It is worth reading as a worked example, and worth reading critically, because the saga coordinator in the example is still marked to be implemented.
Who is it for?
go-distributed-sys fits a Go developer who wants to see event sourcing and CQRS wired end to end, with the real dependency set rather than a diagram, and who is prepared to run a three node CockroachDB cluster and a JetStream server locally to follow it. It does not fit a team looking for a production ready starter kit, since the saga coordinator in the worked example is explicitly still to be implemented and the project is published as guidance with no releases.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Guidance, not a framework, and that shapes how you read it

The repository is titled as guidance for building distributed systems and microservices in Go, and the description names the stack: event-driven distributed systems and microservices in Go with NATS JetStream, gRPC and CockroachDB. The Articles section links to a long-form Medium post on building event-driven distributed systems in Go with gRPC, NATS JetStream and CockroachDB, so the repository is the code that accompanies the writing rather than a library with an API. The technologies list is explicit: Go, NATS with JetStream and Micro, gRPC, CockroachDB, Go kit, and Zipkin for distributed tracing. The licence is Apache-2.0, the default branch is master, and there are no GitHub releases, which is consistent with a body of example code and commentary rather than a versioned distribution. The last push to the repository was on 2026-10-18, so the work is current.

The event store is the write side, CockroachDB the read side

The example, which lives in the eventstream directory, is a CQRS arrangement with an immutable log at its centre. The README describes the Event Store as an immutable log of events of domain events, reached over its own gRPC API by a service called eventstoresvc. The Event Store executes a command and then publishes the resulting domain event to NATS JetStream so other services learn about it. Separately, a query model lives in CockroachDB, and the two databases are created with different names in the setup, eventstoredb and ordersdb, which makes the split visible from the first command.

The gRPC surface is generated from protobuf files in the eventstore directory, and the compile step is a single protoc invocation.

bash
protoc eventstore/*.proto \
		--go_out=. \
		--go-grpc_out=. \
		--go_opt=paths=source_relative \
		--go-grpc_opt=paths=source_relative \
		--proto_path=.

The paths=source_relative options keep the generated files next to the proto definitions rather than in a nested module path, and the flags are worth copying exactly because the layout of the generated code determines the import paths in the rest of the example.

Eight steps from a posted order to an approved one

The Basic Workflow section in the README is the clearest description of the design, and it is a numbered trace of one order. A client app posts an order to an HTTP API, ordersvc, which is the Go kit service. The service executes a command onto the Event Store to create an event, and the Event Store API publishes ORDERS.created to JetStream. A payment worker subscribes to ORDERS.created, makes the payment, and creates ORDERS.paymentdebited through the same Event Store API. The Event Store publishes that event too. A query synchronising worker, querymodelworker, subscribes to ORDERS.created and synchronises the query data model so the aggregates have state for the query views. A review worker subscribes to ORDERS.paymentdebited and finally approves the order, creating ORDERS.approved.

Two things stand out. The query model is not updated by the write path; it is built by a worker consuming the same event that the payment worker consumes, which is what makes the read side independent. And the last line of the workflow, a saga coordinator that manages the distributed transactions and makes void transactions on failures, is marked to be implemented. The README is explicit about that, and it is the one part of the distributed transaction story the example does not finish.

A three node CockroachDB cluster on one machine

The database setup is a local three node cluster, which is unusual to see in a README and turns out to be the reason CockroachDB is in the stack. The three cockroach start commands differ only in their store name, listen address and http address, and all three join the same set of addresses.

bash
cockroach start \
--insecure \
--store=orders-1 \
--listen-addr=localhost:26257 \
--http-addr=localhost:8080 \
--join=localhost:26257,localhost:26258,localhost:26259 \
--background

The cluster is initialised once, and then a SQL shell and a user are created through the cockroach command.

bash
cockroach init --insecure --host=localhost:26257
bash
cockroach sql --insecure --host=localhost:26257
bash
cockroach user set shijuvar --insecure

The two databases and the grants follow in the same insecure shell.

bash
cockroach sql --insecure -e 'CREATE DATABASE eventstoredb'
bash
cockroach sql --insecure -e 'GRANT ALL ON DATABASE eventstoredb TO shijuvar'

The three node arrangement is what makes the replication visible in a local setup, and it is also the reason the instructions point at the CockroachDB documentation for securing a cluster and creating users, since the insecure mode used here is for local work only.

JetStream with an explicit memory and file budget

The message broker is NATS with JetStream enabled, and the config file in the README sets the limits that matter for a log like this.

bash
nats-server -js
bash
nats-server -c js.conf

The js.conf file sets a store directory, a maximum memory store of 1073741824 bytes for 1GB, and a maximum file store of 10737418240 bytes for 10GB, with the HTTP port on 8222.

text
jetstream {
    // jetstream data will be in /data/jetstream-server/jetstream
    store_dir: "/data/jetstream-server"

    // 1GB
    max_memory_store: 1073741824

    // 10GB
    max_file_store: 10737418240
}

http_port: 8222

The explicit caps are the point. JetStream will keep an event log on disk, and without a file store limit a long running system fills the disk. The 8222 HTTP port is the monitoring endpoint, which is how you see the store filling up.

What has to be running before the demo does anything

The Prerequisites section lists what the eventstream demo needs, and it is a useful checklist because nothing works until all of it is up. The NATS JetStream server has to be running. CockroachDB has to be running. The bootstrapper app, at eventstream/bootstrapper, has to be run as a one time activity, and it is what creates the stream in JetStream and the two databases in CockroachDB. And for distributed tracing with gokit-ordersvc, the order service built with Go kit, a Zipkin distributed tracing system has to be running.

The bootstrapper step is the one that is easy to miss, because a fresh clone gives you an empty JetStream and an empty database, and the example does not create either by itself. Tracing is optional in the sense that it is only needed for the tracing demonstration, but the dependency list in go.mod shows what it costs: the OpenCensus Zipkin exporter, the openzipkin Go client, and go.opencensus.io itself, which is the older tracing API rather than the OpenTelemetry one.

The dependency list tells you what the design really uses

The go.mod is short enough to read as a design document. The module is github.com/shijuvar/go-distributed-sys and it targets Go 1.27.1. The direct dependencies are the go-kit/kit service framework, google.golang.org/grpc with protobuf, nats-io/nats.go for the client side of JetStream, gorm with the postgres driver alongside lib/pq and the two cockroachdb Go client packages, gorilla/mux for routing, google/uuid, and the OpenCensus and openzipkin pair for tracing. gorm being there with both a postgres driver and the CockroachDB client is worth noting, because CockroachDB speaks the PostgreSQL wire protocol, so the same ORM layer serves both the event store and the query model.

The repository layout also shows that the example is not the only thing here. There is an eventstream/ directory for the order example, a jetstream-demo/ directory, a pkg/ directory, and an examples/ directory with a dozen focused samples: nats-basics, nats-micro, nats-micro-group, jetstream, js-kv, cockroachdb, grpcv1, grpcv2, grpcv3 and grpc-alts. The three gRPC versions side by side are the useful part, since they show the same service contract expressed at different levels of the protocol's maturity.

Where the example stops short, and what to use instead

The limitations are documented rather than hidden, and two of them are structural. The saga coordinator is not implemented, so the example shows the happy path of a distributed transaction and tells you nothing about compensating actions beyond the sentence describing what a coordinator would do. And the query model is synchronised by a worker rather than maintained transactionally, which means the read side is eventually consistent, so a reader can observe a window where an order exists in the write model and not yet in the query view.

The alternative to copy here is a broker-agnostic design. You can run the same CQRS shape on Kafka or Pulsar by replacing the JetStream publish and subscribe calls, and you can run the event store on a database you already have rather than CockroachDB, since the important boundary in this example is the gRPC command interface and the immutable log, not the storage engine. What does not transfer is the local three node setup, which is a convenience for trying the example and not a production topology.

The honest framing is that this repository is a teaching artefact with a real dependency set, useful for seeing the wiring of a saga-friendly event store, JetStream fan out and a read-model worker in one place, and useless as a starting point if you need the saga to work on day one.

Editorial conclusion

go-distributed-sys fits a Go developer who wants to see event sourcing and CQRS wired end to end, with the real dependency set rather than a diagram, and who is prepared to run a three node CockroachDB cluster and a JetStream server locally to follow it. It does not fit a team looking for a production ready starter kit, since the saga coordinator in the worked example is explicitly still to be implemented and the project is published as guidance with no releases. Before you build on the pattern, read the eventstream example rather than the README, because the README documents the setup commands while the event flow, the workers and the boundaries between the write model and the query model live in the code, and note that the module targets Go 1.27.1.

Frequently asked questions

What does go-distributed-sys contain?

It is guidance for building event-driven distributed systems and microservices in Go, with an eventstream example of an order system, a jetstream-demo directory, a pkg directory and an examples folder with focused samples. The technologies it uses are Go, NATS with JetStream and Micro, gRPC, CockroachDB, Go kit and Zipkin for distributed tracing.

How does the order example in go-distributed-sys work?

A client posts an order to an HTTP API, ordersvc, which executes a command on the immutable Event Store over gRPC. The Event Store publishes ORDERS.created to NATS JetStream, a payment worker consumes it and creates ORDERS.paymentdebited, a query synchronising worker updates the query model, and a review worker consumes the payment event and creates ORDERS.approved. A saga coordinator for void transactions is described but marked as to be implemented.

How do I compile the protobuf files for the event store?

Run protoc from the eventstream directory with the eventstore protos, using --go_out, --go-grpc_out, paths=source_relative for both, and --proto_path=., as the README shows.

What has to be running before the eventstream demo works?

The NATS JetStream server, CockroachDB, and the bootstrapper app at eventstream/bootstrapper, which is run once to create the stream in JetStream and the two databases in CockroachDB. Zipkin is also needed to use distributed tracing with gokit-ordersvc.

Does go-distributed-sys use a message broker other than NATS?

The broker is NATS with JetStream, and the go.mod depends on nats-io/nats.go for it. The repository also includes a jetstream-demo directory and an examples folder covering nats-basics, nats-micro, nats-micro-group, jetstream and js-kv, alongside gRPC and CockroachDB samples.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. shijuvar/go-distributed-sys on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shijuvar-go-distributed-sys.svg)](https://hysenlabs.com/projects/shijuvar-go-distributed-sys)

Was this useful?