This repository is the foundation for a tutorial series focused on observability for developers.
The goal of the series is to take a fully blind distributed system and evolve it step by step into a production-grade, developer-friendly observable system, using:
- Go
- Microservices
- OpenTelemetry
- Datadog
This project is intentionally simple in domain, but realistic in architecture.
This is a small event-driven microservice system built in Go.
It simulates a basic business workflow:
- A customer places an order
- The payment is processed
- A notification is sent to the customer
The important part is not what the system does, but how it is structured.
From Part 0 onward, the system is:
- Distributed
- Asynchronous
- Containerized
- Completely unobservable
That is intentional.
The system consists of three microservices:
- Exposes an HTTP API
- Endpoint:
POST /orders - Emits event:
order.created
- Subscribes to
order.created - Simulates payment processing
- Emits event:
payment.completed
- Subscribes to
payment.completed - Simulates sending a notification
- HTTP (synchronous) for ingress
- NATS (pub/sub) for async communication
There is no shared state and no direct service-to-service calls.
This repository intentionally starts in a bad observability state:
- Logs are unstructured
- No request IDs
- No correlation across services
- No tracing
- No metrics
Each tutorial part will introduce one observability layer at a time, clearly showing the value added by that layer.
This is not a “copy-paste observability setup”. It is a learning journey.
.
├── Dockerfile
├── docker-compose.yml
├── go.work
├── pkg/
│ └── nats/
└── services/
├── order-service/
│ ├── cmd/
│ └── internal/
├── payment-service/
│ ├── cmd/
│ └── internal/
└── notification-service/
├── cmd/
└── internal/
- Each service follows standard Go layout (
cmd/,internal/) - A single generic Dockerfile is used for all services
- Services are parameterized via build arguments
- One Dockerfile at the repository root
- Services are built using
SERVICE_NAMEas a build argument - docker-compose orchestrates the system locally
This avoids duplication and keeps the build logic explicit and teachable.
- Docker
- Docker Compose
- Go 1.23+ (optional, only for local builds)
From the repository root:
docker-compose up --buildThis will start:
- NATS
- order-service
- payment-service
- notification-service
Send a request to the order service:
curl -X POST http://localhost:8081/orders \
-H "Content-Type: application/json" \
-d '{
"customer_email": "alice@example.com",
"amount": 49.99
}'Example response:
ord_123456
You will see logs from different services, but:
- There is no correlation
- There is no request flow visibility
- There is no traceability
This is the baseline.
At this stage, the system is:
- ✅ Functional
- ✅ Distributed
- ❌ Observable
This is the starting point for the tutorial series.
The next parts of the series will introduce:
- Intentional logging
- Structured logs
- Request ID propagation
- Distributed tracing (OpenTelemetry)
- Log ↔ trace correlation
- Domain-level metrics
- Developer-oriented dashboards
Each step builds on top of this exact codebase.
This project is for developers who:
- Work with Go and microservices
- Want to understand observability from first principles
- Prefer incremental learning over copy-paste solutions
- Care about debuggability and developer experience
MIT