Shipment tracking service. Manages the lifecycle of delivery orders from creation to completion.
Imagine: a customer orders shipment delivery from point A to point B. What happens next?
Customer creates order
↓
[PENDING] ← order waiting for driver assignment
↓
Dispatcher assigns driver
↓
[ASSIGNED] ← driver heading to pickup
↓
Driver picked up shipment
↓
[PICKED_UP] → [IN_TRANSIT] → [ARRIVED]
↓
Driver at destination, waiting for customer
↓
[DELIVERED] ← shipment delivered, awaiting confirmation
↓
┌───┴───┐
↓ ↓
[COMPLETED] [RETURNED]
customer customer didn't
picked up pick up within 72h
At any point before final status, order can be cancelled → CANCELLED.
Why this flow? In real logistics, shipment physically moves through space. You can't skip stages. You can't deliver what wasn't picked up. You can't complete an order until customer confirms receipt.
- Reference number — human-readable ID (
SHP-20260319-A7B3C2) - Origin/Destination — pickup and delivery addresses
- Driver and vehicle — assigned by dispatcher
- Cost — what customer pays and what driver earns
- Change history — who changed what and when
Business logic shouldn't depend on how we store data or which protocol we use. Tomorrow we might want to swap MongoDB for PostgreSQL — we only change the storage layer, service keeps working.
┌─────────────────────────────────────────────────┐
│ Transport │
│ (gRPC) │
└─────────────────────┬───────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Service │
│ (orchestration, transactions) │
└─────────────────────┬───────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Domain │
│ (Shipment entity, status transitions, │
│ validation, history tracking) │
└─────────────────────┬───────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Storage │
│ (MongoDB, Redis cache) │
└─────────────────────────────────────────────────┘
Domain — the core. This is where the Shipment entity lives with all status transition logic. It knows nothing about MongoDB or gRPC.
Service — orchestration. Receives "update status" command, fetches shipment from DB, calls entity method, saves result, invalidates cache.
Transport — adapter. Transforms gRPC requests into service calls and back.
When order status changes, we need to send an event to Kafka. But what if Kafka is down? We can't lose events.
Solution: save the event in the same transaction as the order change. Background worker periodically picks up unprocessed events and publishes to Kafka.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Service │────▶│ MongoDB │────▶│ Worker │────▶ Kafka
│ │ │ (shipment │ │ (outbox │
│ updateStatus│ │ + outbox) │ │ processor) │
└──────────────┘ └──────────────┘ └──────────────┘
│
└── single transaction
Read often, write rarely. Redis as first-level cache:
GetShipment(id)→ check Redis- If found → return
- If not → fetch from MongoDB, store in Redis
- On any write → delete from Redis
Why delete instead of update? Simpler and more reliable. Cache invalidation is one of two hard problems in CS.
If customer doesn't pick up shipment within 72 hours — order automatically transitions to RETURNED. Background worker checks delivered_at and marks expired ones.
Idempotency. Currently, duplicate status change request fails if status already changed. Real system needs idempotency keys.
Multi-tenancy. Service is single-tenant. SaaS would need data isolation by organization.
Saga pattern. If dependent services appear (billing, notifications), we'd need distributed transaction coordination.
# Infrastructure
docker compose up -d
# Service
go run ./cmd/app
# Tests
make test # unit
make integration # integration (spins up Docker)| Variable | Default | Description |
|---|---|---|
GRPC_PORT |
50051 | gRPC server port |
MONGODB_URI |
mongodb://localhost:27017 | MongoDB |
REDIS_ADDR |
localhost:6379 | Redis |
KAFKA_BROKERS |
localhost:9092 | Kafka |
SHIPMENT_PICKUP_TIMEOUT |
72h | Pickup timeout |
- Go 1.24
- gRPC + Protocol Buffers
- MongoDB (replica set for transactions)
- Redis (cache)
- Kafka (events)