RepoPilotOpen in app →

ThreeDotsLabs/watermill

Building event-driven applications the easy way in Go.

Healthy

Healthy across the board

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2w ago
  • 36+ active contributors
  • Distributed ownership (top contributor 39% of recent commits)
Show all 6 evidence items →
  • MIT licensed
  • CI configured
  • Tests present

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/threedotslabs/watermill)](https://repopilot.app/r/threedotslabs/watermill)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/threedotslabs/watermill on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: ThreeDotsLabs/watermill

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/ThreeDotsLabs/watermill shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across the board

  • Last commit 2w ago
  • 36+ active contributors
  • Distributed ownership (top contributor 39% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live ThreeDotsLabs/watermill repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/ThreeDotsLabs/watermill.

What it runs against: a local clone of ThreeDotsLabs/watermill — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in ThreeDotsLabs/watermill | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 47 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>ThreeDotsLabs/watermill</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of ThreeDotsLabs/watermill. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/ThreeDotsLabs/watermill.git
#   cd watermill
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of ThreeDotsLabs/watermill and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "ThreeDotsLabs/watermill(\\.git)?\\b" \\
  && ok "origin remote is ThreeDotsLabs/watermill" \\
  || miss "origin remote is not ThreeDotsLabs/watermill (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f "go.mod" \\
  && ok "go.mod" \\
  || miss "missing critical file: go.mod"
test -f "_examples/basic/1-your-first-app/main.go" \\
  && ok "_examples/basic/1-your-first-app/main.go" \\
  || miss "missing critical file: _examples/basic/1-your-first-app/main.go"
test -f "_examples/basic/3-router/main.go" \\
  && ok "_examples/basic/3-router/main.go" \\
  || miss "missing critical file: _examples/basic/3-router/main.go"
test -f "_examples/pubsubs/kafka/main.go" \\
  && ok "_examples/pubsubs/kafka/main.go" \\
  || miss "missing critical file: _examples/pubsubs/kafka/main.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 47 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~17d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/ThreeDotsLabs/watermill"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Watermill is a Go library for building event-driven applications with pluggable pub/sub transports (Kafka, RabbitMQ, PostgreSQL, HTTP, etc.). It abstracts message streaming patterns like event sourcing, CQRS, sagas, and RPC-over-messages behind a unified interface, letting developers swap transport layers without changing business logic. Monorepo structure: core Watermill logic in the root (likely pkg/ or internal/), pluggable Pub/Sub implementations in separate modules (watermill-kafka, others referenced in CONTRIBUTING.md), and _examples/ containing progressively complex runnable examples from basic usage (1-your-first-app) through real-world patterns (exactly-once-delivery-counter, synchronizing-databases).

👥Who it's for

Go backend engineers building distributed systems who need event-driven architectures without tight coupling to a specific message broker. Teams adopting CQRS, event sourcing, or async workflows who want flexibility in their messaging infrastructure.

🌱Maturity & risk

Production-ready and actively maintained. The project has reached v1.5.1 with multiple upgrade guides (UPGRADE-0.3.md through UPGRADE-1.0.md), comprehensive CI/CD pipelines in .github/workflows/, and well-documented examples. Recent GitHub Actions workflows show ongoing testing (master.yml, pr.yml, tests.yml), indicating active development and quality enforcement.

Standard open source risks apply.

Active areas of work

Active development visible in master.yml and pr.yml workflows running on all commits. The presence of four upgrade guides and v1.5.1 versioning suggests ongoing API refinement. Real-world examples directory indicates community focus on practical patterns. No specific breaking changes mentioned in snippet, but RELEASE-PROCEDURE.md exists, suggesting structured release management.

🚀Get running

Clone and run the first example: git clone https://github.com/ThreeDotsLabs/watermill.git && cd _examples/basic/1-your-first-app && go run main.go. For Kafka examples, use docker-compose: docker-compose up in the example directory, then run the Go app.

Daily commands: For basic examples: cd _examples/basic/1-your-first-app && go run main.go. For broker-dependent examples: docker-compose up -d then go run main.go or consumer/main.go + producer/main.go for multi-process examples like 2-realtime-feed. See individual example READMEs for specific setup.

🗺️Map of the codebase

  • README.md — High-level overview of Watermill's purpose, goals, and supported pub/sub implementations that frame all architectural decisions.
  • go.mod — Declares the core watermill module and its dependencies; understanding versioning and pub/sub provider integrations starts here.
  • _examples/basic/1-your-first-app/main.go — Entry point demonstrating the fundamental pub/sub pattern and router setup that all contributors should understand.
  • _examples/basic/3-router/main.go — Shows the router abstraction for message routing and handler chains, a core pattern in Watermill applications.
  • _examples/pubsubs/kafka/main.go — Demonstrates pub/sub provider integration; essential for understanding how Watermill plugs different message brokers.
  • CONTRIBUTING.md — Governance, coding standards, and contribution workflow that all developers must follow.
  • .github/workflows/tests.yml — CI/CD pipeline definition showing how tests, builds, and examples are validated across the codebase.

🛠️How to make changes

Add a New Pub/Sub Provider Example

  1. Create a new directory under _examples/pubsubs/ for your provider (e.g., _examples/pubsubs/my-provider/) (_examples/pubsubs/my-provider/)
  2. Create main.go demonstrating Publisher and Subscriber setup for your provider (_examples/pubsubs/my-provider/main.go)
  3. Add go.mod with watermill core and provider-specific dependencies (e.g., watermill-my-provider/v2) (_examples/pubsubs/my-provider/go.mod)
  4. Create docker-compose.yml to spin up your message broker for local development (_examples/pubsubs/my-provider/docker-compose.yml)
  5. Create .validate_example.yml for CI/CD to test your example (_examples/pubsubs/my-provider/.validate_example.yml)
  6. Add example to the list in .github/workflows/pr-examples.yml to ensure it's tested (.github/workflows/pr-examples.yml)

Add a New Advanced Pattern Example (CQRS, Event Sourcing, etc.)

  1. Create a new directory under _examples/basic/N-pattern-name/ following the numbering convention (_examples/basic/7-my-pattern/)
  2. Implement your domain model and message types in separate .go files (similar to activity.go, message.go in example 6) (_examples/basic/7-my-pattern/domain.go)
  3. Create main.go showing router setup and handler registration for your pattern (_examples/basic/7-my-pattern/main.go)
  4. Add docker-compose.yml to provide any external dependencies (Kafka, PostgreSQL, etc.) (_examples/basic/7-my-pattern/docker-compose.yml)
  5. Create .validate_example.yml and README.md for documentation and CI validation (_examples/basic/7-my-pattern/README.md)

Update Watermill Core or Add New Provider Integration

  1. Modify or extend the interfaces in watermill core (Publisher, Subscriber, Router, Message) as needed (README.md)
  2. Write tests following the existing test patterns and add them to the test suite (.github/workflows/tests.yml)
  3. Update UPGRADE-X.Y.md if introducing breaking changes or new features affecting users (UPGRADE-1.0.md)
  4. Follow the release procedure in RELEASE-PROCEDURE.md and update version numbers in go.mod files (RELEASE-PROCEDURE.md)
  5. Update README.md and CONTRIBUTING.md if adding new pub/sub providers or architectural patterns (CONTRIBUTING.md)

Set Up Local Development Environment

  1. Clone the repo and review CONTRIBUTING.md for development workflow and coding standards (CONTRIBUTING.md)
  2. Install Go 1.25+ (from go.mod) and dependencies declared in the root go.mod (go.mod)
  3. Run example docker-compose.yml files in _examples/ to spin up local message brokers (_examples/basic/1-your-first-app/docker-compose.yml)
  4. Execute example main.go files to verify your setup works with pub/sub providers (_examples/pubsubs/kafka/main.go)
  5. Review .github/workflows/tests.yml to understand how tests are run in CI (.github/workflows/tests.yml)

🔧Why these technologies

  • Go — High performance, concurrent message processing with goroutines; ideal for event-driven, I/O-bound systems at scale.
  • Pluggable Pub/Sub Providers (Kafka, RabbitMQ, AWS, Google Cloud, NATS, HTTP, PostgreSQL) — Decouples application logic from broker choice; enables migration between systems and supports hybrid architectures without code changes.
  • Router Pattern — Provides flexible, composable message routing and middleware chains; mirrors web framework patterns familiar to Go developers.
  • Protobuf (Optional) — Type-safe, efficient message serialization; strongly recommended for CQRS and event

🪤Traps & gotchas

Docker Compose services (Kafka, RabbitMQ, PostgreSQL) must be running for broker-dependent examples; check docker-compose.yml in each example. Examples are validated via .validate_example*.yml files (custom CI step), so changes to example code may break validation. No obvious required environment variables in snippets, but Kafka examples likely need broker URLs. Go version 1.25+ implied by go.mod; check compatibility if using older Go. Watermill-kafka/v3 is a major version (v3.1.0), indicating significant API differences from v2.

🏗️Architecture

💡Concepts to learn

  • Pub/Sub Abstraction — Core Watermill pattern: message producers and consumers are decoupled via a pluggable interface, allowing transport swapping (Kafka → PostgreSQL) without code changes
  • Message Router — Watermill's router.AddHandler() orchestrates message flow: routes topics/channels to handler functions with middleware chains, replacing manual topic-consumer bindings
  • Middleware Chain — Interceptors for cross-cutting concerns (logging, tracing, retry logic): router.AddMiddleware() applies transformations to all messages, critical for observability and resilience
  • Event Sourcing — Watermill enables storing all state changes as immutable events; domain state is derived by replaying events, providing audit trails and temporal queries
  • CQRS (Command Query Responsibility Segregation) — Separates read and write models via events; Watermill's message routing supports async read model synchronization, exemplified in _examples/basic/5-cqrs-protobuf/
  • At-Least-Once Delivery — Watermill guarantees messages aren't lost but may be delivered multiple times; idempotency handling is critical, taught in real-world-examples/exactly-once-delivery-counter
  • Saga Pattern — Distributed transactions across services via message choreography; Watermill supports saga workflows by routing compensation events on failure, key for multi-service reliability
  • confluentinc/confluent-kafka-go — Competing Kafka Go client library; Watermill abstracts away the need to choose between sarama vs confluent-kafka directly
  • streadway/amqp — AMQP protocol library often used for RabbitMQ; Watermill wraps this and similar clients behind a unified interface
  • ThreeDotsLabs/watermill-kafka — Official Kafka transport plugin for Watermill (v3.1.0), required for Kafka-based event-driven apps
  • ThreeDotsLabs/go-event-driven — Companion course/training repo by the same authors, teaches event-driven concepts and Watermill usage patterns
  • temporalio/temporal — Alternative for complex event workflows and saga orchestration; Watermill is lightweight where Temporal adds workflow engine overhead

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add integration tests for all _examples to CI workflow

The repo has 6+ example applications in _examples/ with .validate_example.yml files, but there's no dedicated CI workflow that validates all examples compile and run correctly. The current pr.yml and tests.yml don't explicitly test example validity. This prevents breaking changes to the core library from breaking examples that users rely on for learning. Each example has docker-compose.yml files suggesting they're meant to be fully testable.

  • [ ] Create new GitHub Actions workflow .github/workflows/validate-examples.yml
  • [ ] Add steps to discover all _examples/**/validate_example*.yml files
  • [ ] Add validation steps: go mod tidy, go build/run for each example
  • [ ] Ensure docker-compose services start cleanly for examples with docker-compose.yml (basic/2-realtime-feed, basic/4-metrics, basic/6-cqrs-ordered-events)
  • [ ] Add workflow trigger to pr.yml and master.yml to run on code changes

Add helper functions for testing in pkg/test or internal/testhelpers

The repo has comprehensive examples showing patterns for pub/sub, CQRS, and ordered events, but there's no visible reusable testing utilities package. Given the event-driven nature and multiple pub/sub implementations (Kafka, RabbitMQ, HTTP, etc.), contributors would benefit from helpers for mocking publishers, subscribers, and message validation to reduce boilerplate in their own tests.

  • [ ] Create pkg/test/ or internal/testhelpers/ package directory
  • [ ] Implement TestPublisher and TestSubscriber helpers that record/replay messages
  • [ ] Add message assertion helpers (e.g., AssertMessagePayload, AssertMessageMetadata)
  • [ ] Add middleware mocking utilities for testing handlers without real infrastructure
  • [ ] Document the package in CONTRIBUTING.md with examples

Add upgrade guide and deprecation docs for v1.5.1 -> v2.0 planning

The repo has UPGRADE-0.3.md, UPGRADE-0.4.md, and UPGRADE-1.0.md files, but no UPGRADE-1.5.md or v2.0 planning document. With dependencies like watermill-kafka bumped to v3 and OpenTelemetry at v1.38.0, there's likely upcoming breaking changes. This helps users and contributors understand the roadmap and prepare for major version bumps without surprises.

  • [ ] Create ROADMAP.md documenting planned v2.0 changes (if any) and deprecation timeline
  • [ ] Add UPGRADE-1.5.md for any breaking changes between 1.0 and 1.5.1
  • [ ] Document OpenTelemetry integration changes and defaults if they're new
  • [ ] Reference this in README.md's upgrade section
  • [ ] Add deprecation notices to code comments for any APIs planned for removal

🌿Good first issues

  • Add integration tests for the HTTP transport pub/sub (likely missing in _examples given only Kafka/RabbitMQ examples shown). Start in _examples/pubsubs/, create http/ subdirectory with docker-compose-free HTTP example.
  • Expand _examples/real-world-examples/ with a 'Dead-letter queue handling' example, showing how to route failed messages using router middleware—this pattern is mentioned in docs but not exemplified.
  • Add Makefile targets for running all examples in sequence with validation, making .validate_example*.yml rules explicit in CI. Currently test workflows aren't fully visible in snippet.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 48515bc — I can't see anything (#674) (boreq)
  • c9b951f — Rename variable from pub to sub in main.go (#643) (sdil)
  • 00bc74e — chore: execute goimports to format the code (#644) (xibeiyoumian)
  • 0762e01 — refactor: remove redundant variable declarations in for loops (#637) (efcking)
  • 6fe652b — chore: fix some comments (#635) (geogrego)
  • 8c07321 — docs: update discord link (#634) (m110)
  • 23a5dc5 — refactor: use maps.Copy to simplify the code (#627) (dulanting)
  • 32942cc — docs: add suport of mysql delayed messages (#630) (AdrianZajkowski)
  • 98c1107 — Refresh getting started (#628) (m110)
  • bf1892b — edit SQLite documentation and transaction example (#621) (dkotik)

🔒Security observations

  • High · Outdated Cryptography Dependencies — go.mod - golang.org/x/crypto v0.41.0. The codebase uses golang.org/x/crypto v0.41.0, which is significantly outdated. The current stable version is v0.24.0+. Older versions may contain known cryptographic vulnerabilities and weaknesses that could be exploited. Fix: Update golang.org/x/crypto to the latest stable version (v0.24.0 or newer). Run 'go get -u golang.org/x/crypto' to update.
  • High · Outdated Network Dependencies — go.mod - golang.org/x/net v0.43.0. The codebase uses golang.org/x/net v0.43.0, which is outdated and may contain known vulnerabilities in network handling, TLS implementation, and HTTP protocol handling. Fix: Update golang.org/x/net to the latest stable version. Run 'go get -u golang.org/x/net' to update.
  • Medium · Known Vulnerability in Sarama/Kafka Client — go.mod - github.com/IBM/sarama v1.46.0 (indirect). The codebase uses github.com/IBM/sarama v1.46.0 indirectly through watermill-kafka. Sarama has had historical security vulnerabilities related to SASL/TLS handling and message processing. Verify that this version is not vulnerable to CVEs. Fix: Check the Sarama security advisories and update to the latest patched version. Consider using github.com/segmentio/kafka-go as an alternative if newer versions of Sarama have unresolved issues.
  • Medium · Potential Vulnerability in Compression Libraries — go.mod - github.com/klauspost/compress v1.18.0 (indirect). The codebase uses github.com/klauspost/compress v1.18.0 (indirect through Kafka). While generally reliable, compression libraries can be susceptible to DoS attacks through malformed input. Fix: Ensure input validation and size limits are enforced before decompressing. Update to the latest version of klauspost/compress if security patches are available.
  • Medium · Kerberos Authentication Components — go.mod - github.com/jcmturner/gokrb5/v8 v8.4.4 (indirect). The codebase includes Kerberos authentication libraries (github.com/jcmturner/gokrb5/v8) through Sarama. Kerberos implementations can be complex and may have authentication bypass vulnerabilities if misconfigured. Fix: If Kerberos is not needed, disable it in Sarama configuration. Ensure proper validation of Kerberos tickets and use secure TLS connections. Keep the gokrb5 library updated.
  • Low · Missing Security Headers Documentation — _examples/pubsubs - HTTP implementation details not visible. The codebase examples using HTTP pub/sub do not show explicit security header configuration in the visible files. If HTTP is used for message transport, security headers and TLS should be enforced. Fix: Ensure all HTTP examples enforce TLS/HTTPS, set security headers (HSTS, Content-Security-Policy), and validate peer certificates. Document security best practices in HTTP transport configuration.
  • Low · No Evidence of Input Validation Documentation — Repository root - no validation package visible. The repository structure does not show explicit input validation or sanitization patterns in the visible file names. For event-driven applications, this is critical to prevent injection attacks. Fix: Implement and document input validation middleware. Validate all incoming messages, especially those from untrusted sources. Use type-safe message marshaling (Protobuf as shown in examples is good).
  • Low · Example Configurations May Use Development Settings — _examples/pubsubs/*/docker-compose.yml. The _examples directory contains docker-compose.yml files that may be configured for development/testing with relaxed security settings. Fix: Ensure development configurations are clearly marked and include comments about production hardening. Document security differences between dev and prod configurations. Consider separate docker-compose.prod.yml files.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · ThreeDotsLabs/watermill — RepoPilot