A production-ready, event-driven microservices architecture built with Go, demonstrating distributed systems patterns, real-time location tracking, and financial transaction consistency.
Status: โ
Production-Ready MVP | Portfolio Project
Author: Dwiki Kusuma | Date: December 2025
- Overview
- Architecture Highlights
- Service Mesh Overview
- Core Services
- System Flow
- Design Patterns & Learning Journey
- Technology Stack
- Getting Started
- API Documentation
- Key Implementation Details
- Future Enhancements
Atlas is a fully functional ride-hailing platform backend that demonstrates modern microservices architecture principles. Built as a solo learning project, it showcases enterprise-grade patterns including event-driven architecture, CQRS, saga patterns, and distributed transaction management.
- โ Microservices Architecture with 5+ independent services
- โ Event-Driven Communication using Apache Kafka
- โ Real-time Geospatial Operations with Redis
- โ ACID Transactions in distributed systems
- โ gRPC for high-performance service-to-service communication
- โ RESTful API Gateway pattern
- โ Domain-Driven Design with clean architecture
- โ Graceful Shutdown handling with WaitGroups
- โ Test-Driven Development with mocks
The system follows an event-driven architecture where services communicate asynchronously through Kafka topics, enabling:
- Loose Coupling: Services are independent and can evolve separately
- Scalability: Each service can be scaled horizontally based on load
- Resilience: Failures in one service don't cascade to others
- Eventual Consistency: Data synchronization through event streams
graph TB
subgraph "Client Layer"
CUSTOMER["๐ค Customer App<br/>Mobile/Web"]
DRIVER["๐ Driver App<br/>Mobile"]
end
subgraph "API Gateway Layer"
GATEWAY["๐ HTTP Gateway :8085<br/>- REST API<br/>- Request Routing<br/>- Load Balancing"]
end
subgraph "Service Layer (gRPC)"
TRACKER["๐ Tracker Service :50051<br/>- Location Updates<br/>- Geospatial Queries<br/>- Driver Availability"]
DISPATCH["๐ฏ Dispatch Service :50053<br/>- Driver Matching<br/>- Ride Requests<br/>- Event Publishing"]
ORDER["๐ Order Service :50052<br/>- Ride Lifecycle<br/>- State Machine<br/>- Order Management"]
WALLET["๐ฆ Wallet Service :50054<br/>- Balance Management<br/>- Transactions<br/>- Payment Processing"]
end
subgraph "Event Bus"
KAFKA["๐จ Apache Kafka<br/>Topics:<br/>โข driver-gps<br/>โข ride-dispatch<br/>โข wallet-transactions"]
end
subgraph "Data Layer"
REDIS["๐ Redis<br/>- Geospatial Index<br/>- GEOADD/GEORADIUS<br/>- Driver Locations"]
POSTGRES["๐๏ธ PostgreSQL<br/>- Orders DB<br/>- Wallet DB<br/>- ACID Transactions"]
end
CUSTOMER -->|HTTPS| GATEWAY
DRIVER -->|HTTPS| GATEWAY
GATEWAY -->|gRPC| TRACKER
GATEWAY -->|gRPC| DISPATCH
GATEWAY -->|gRPC| ORDER
GATEWAY -->|gRPC| WALLET
DISPATCH -->|gRPC Query| TRACKER
TRACKER -->|Publish Events| KAFKA
DISPATCH -->|Publish Events| KAFKA
ORDER -->|Publish Events| KAFKA
TRACKER -->|Consume Events| KAFKA
ORDER -->|Consume Events| KAFKA
WALLET -->|Consume Events| KAFKA
TRACKER -->|GEOADD<br/>GEORADIUS| REDIS
ORDER -->|CRUD| POSTGRES
WALLET -->|ACID Tx| POSTGRES
style GATEWAY fill:#e1f5ff
style KAFKA fill:#fff3e0
style REDIS fill:#ffebee
style POSTGRES fill:#f3e5f5
The platform implements multiple data flow patterns:
- Synchronous (gRPC): Gateway โ Services for immediate responses
- Asynchronous (Kafka): Services โ Services for eventual consistency
- Pub/Sub: Location updates, ride matching, payment triggers
- Request-Response: API calls requiring immediate feedback
Purpose: Single entry point for all client applications, providing a RESTful HTTP API.
Key Responsibilities:
- Request routing to appropriate backend services
- Protocol translation (HTTP โ gRPC)
- Response aggregation from multiple services
- Authentication/Authorization (future enhancement)
Implementation Highlights:
// Concurrent service calls using WaitGroup
type CustomerHandler struct {
order order.OrderServiceClient
dispatch dispatch.DispatchServiceClient
}
// Graceful JSON response handling
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}API Endpoints:
POST /customer/order- Create ride orderPOST /customer/ride/request- Request driverGET /customer/order- Get order statusPOST /driver/location- Update driver locationPUT /driver/order/status- Update ride status
Pattern: API Gateway + Aggregator
Purpose: High-throughput location tracking with geospatial queries.
Architecture Pattern: CQRS (Command Query Responsibility Segregation)
Write Path (Command):
func (s *Server) UpdateLocation(ctx context.Context, req *UpdateLocationRequest) {
// Publish to Kafka for async processing
event := model.LocationEvent{...}
s.producer.Publish(ctx, "driver-gps", userId, eventBytes)
}Read Path (Query):
func (s *Server) GetNearbyDrivers(ctx context.Context, req *GetNearbyDriverRequest) {
// Direct Redis query for low latency
drivers := s.repo.GetNearbyDrivers(ctx, lat, lon, radius)
}Worker (Background Processing):
func (w *IngestionWorker) Run(ctx context.Context) {
for {
msg := w.consumer.FetchMessage(ctx)
event := unmarshal(msg.Value)
// Persist to Redis with geospatial indexing
w.repo.UpdatePosition(ctx, event.UserID, event.Lat, event.Lon)
w.consumer.CommitMessages(ctx, msg)
}
}Key Technologies:
- Redis GEOADD: O(log(N)) insertion into sorted set
- Redis GEORADIUS: Efficient spatial queries
- Kafka Consumer: Asynchronous location persistence
Why This Architecture?
- Write Scalability: Kafka absorbs write spikes
- Read Performance: Redis provides sub-millisecond queries
- Decoupling: gRPC handler doesn't block on Redis writes
Purpose: Intelligent driver-customer matching engine.
Matching Algorithm:
func (s *DispatchService) RequestRide(ctx context.Context, req *RequestRideRequest) {
// 1. Query nearby drivers from Tracker
drivers := s.trackerClient.GetNearbyDrivers(ctx, &tracker.GetNearbyDriverRequest{
Latitude: req.PickupLat,
Longitude: req.PickupLong,
Radius: 5.0, // 5km radius
})
// 2. Select closest available driver
if len(drivers) > 0 {
selectedDriver := drivers[0] // Already sorted by distance
// 3. Publish RideDispatchedEvent to Kafka
event := RideDispatchedEvent{
RideID: req.PassengerId,
DriverID: selectedDriver.Id,
...
}
s.producer.Publish(ctx, "ride-dispatch", event)
}
}Pattern: Orchestrator (Saga Pattern)
Responsibilities:
- Coordinates driver discovery
- Implements matching logic
- Publishes dispatch events
- Returns immediate response to customer
Purpose: Ride lifecycle state machine and order management.
State Machine:
CREATED โ MATCHED โ STARTED โ FINISHED
Implementation:
// Synchronous order creation
func (s *Service) CreateOrder(ctx context.Context, req *CreateOrderRequest) {
order := s.store.CreateOrder(ctx, db.CreateOrderParams{
PassengerID: req.UserId,
Status: "CREATED",
Price: calculatePrice(req), // Haversine formula
})
return &CreateOrderResponse{OrderId: order.ID}
}
// Asynchronous driver assignment
type OrderWorker struct {
consumer kafka.EventConsumer
store db.Querier
}
func (w *OrderWorker) Start(ctx context.Context) {
for {
msg := w.consumer.FetchMessage(ctx)
event := unmarshal(msg.Value) // RideDispatchedEvent
// Update order with assigned driver
w.store.UpdateOrderDriver(ctx, db.UpdateOrderDriverParams{
ID: event.RideID,
DriverID: event.DriverID,
})
w.consumer.CommitMessages(ctx, msg)
}
}Payment Flow (Async Decoupling):
func (s *Service) UpdateOrderStatus(ctx context.Context, req *UpdateOrderStatusRequest) {
// Update ride status
s.store.UpdateOrderStatus(ctx, req.OrderId, req.Status)
// If ride finished, trigger payment asynchronously
if req.Status == "FINISHED" {
order := s.store.GetOrder(ctx, req.OrderId)
debitEvent := DebitBalanceEvent{
UserID: order.PassengerID,
Amount: order.Price,
Reference: order.ID,
}
// Publish to Kafka - don't block on payment
s.producer.Publish(ctx, "wallet-transactions", debitEvent)
}
}Pattern: Event Sourcing + Transactional Outbox
Key Features:
- State Validation: Prevents invalid transitions
- Idempotency: Kafka message keys prevent duplicates
- Async Payment: Ride completion doesn't wait for payment
Purpose: Financial ledger with ACID guarantees.
Double-Entry Ledger Architecture:
-- Snapshot table (current state)
CREATE TABLE wallets (
user_id VARCHAR(50) PRIMARY KEY,
balance DOUBLE PRECISION NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- Append-only log (audit trail)
CREATE TABLE transactions (
id UUID PRIMARY KEY,
wallet_id VARCHAR(50) REFERENCES wallets(user_id),
amount DOUBLE PRECISION NOT NULL, -- negative = debit
description TEXT,
reference_id VARCHAR(50), -- links to order
created_at TIMESTAMPTZ NOT NULL
);ACID Transaction Implementation:
func (s *PostgresWalletService) DebitBalance(ctx context.Context, req *DebitBalanceRequest) {
var newBalance float64
// Start transaction
err := s.execTx(ctx, func(q *db.Queries) error {
// 1. Verify wallet exists
wallet := q.GetWallet(ctx, req.UserId)
// 2. Insert transaction record (audit trail)
q.CreateTransaction(ctx, db.CreateTransactionParams{
WalletID: req.UserId,
Amount: -req.Amount, // Negative for debit
Description: "DEBIT",
ReferenceID: req.ReferenceId,
})
// 3. Update balance atomically with row-level lock
wallet = q.AddWalletBalance(ctx, db.AddWalletBalanceParams{
UserID: req.UserId,
Amount: -req.Amount,
})
newBalance = wallet.Balance
return nil // Commit
})
return &BalanceResponse{NewBalance: newBalance}
}Payment Worker (Kafka Consumer):
func (w *WalletWorker) Start(ctx context.Context) {
for {
msg := w.consumer.FetchMessage(ctx)
event := unmarshal(msg.Value) // DebitBalanceEvent
// Validate event data
if event.UserID == "" || event.Amount <= 0 {
// Poison pill protection - commit bad message
w.consumer.CommitMessages(ctx, msg)
continue
}
// Process payment with timeout
debitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
_, err := w.service.DebitBalance(debitCtx, &wallet.DebitBalanceRequest{
UserId: event.UserID,
Amount: event.Amount,
ReferenceId: event.Reference,
})
cancel()
if err != nil {
// Don't commit - allow retry on restart
log.Printf("Payment failed: %v", err)
continue
}
// Success - commit offset
w.consumer.CommitMessages(ctx, msg)
}
}Pattern: Ledger + ACID Transactions + Event-Driven Processing
Concurrency Control:
- Row-Level Locking:
UPDATE ... RETURNINGin Postgres - Optimistic Concurrency: Balance check before debit
- Idempotency: Reference ID prevents duplicate charges
Poison Pill Handling:
- Malformed JSON โ Commit and skip
- Invalid data โ Commit and skip
- Database errors โ Don't commit, allow retry
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Phase 1: Customer Requests Ride โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Customer App
โ
โ POST /customer/order
โผ
HTTP Gateway :8085
โ
โ gRPC CreateOrder()
โผ
Order Service :50052
โ
โ INSERT INTO orders (status='CREATED', price=calculated)
โผ
PostgreSQL
โ
โโโโ Response: {order_id, status, price}
โ
Customer App
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Phase 2: Driver Matching (Async) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Customer App
โ
โ POST /customer/ride/request
โผ
HTTP Gateway :8085
โ
โ gRPC RequestRide()
โผ
Dispatch Service :50053
โ
โ gRPC GetNearbyDrivers(lat, lon, radius=5km)
โผ
Tracker Service :50051
โ
โ GEORADIUS atlas:tracker:positions 5 km ASC
โผ
Redis
โ
โโโโ [{driver_1, distance: 0.5km}, {driver_2, distance: 1.2km}]
โ
Dispatch Service :50053
โ
โ SELECT driver_1 (closest)
โ Publish RideDispatchedEvent to Kafka
โผ
Kafka Topic: ride-dispatch
โ
โ [Background Worker Consuming]
โผ
Order Service Worker
โ
โ UPDATE orders SET driver_id='driver_1', status='MATCHED'
โผ
PostgreSQL
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Phase 3: Driver Updates Location (Real-time) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Driver App (every 5 seconds)
โ
โ POST /driver/location {lat, lon}
โผ
HTTP Gateway :8085
โ
โ gRPC UpdateLocation()
โผ
Tracker Service :50051
โ
โ Publish LocationEvent to Kafka
โผ
Kafka Topic: driver-gps
โ
โ [Background Worker Consuming]
โผ
Tracker Ingestion Worker
โ
โ GEOADD atlas:tracker:positions lon lat driver_id
โผ
Redis (Geospatial Index Updated)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Phase 4: Ride Lifecycle Updates โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Driver App
โ
โ PUT /driver/order/status {status='STARTED'}
โผ
Order Service :50052
โ
โ UPDATE orders SET status='STARTED'
โผ
PostgreSQL
... (ride in progress) ...
Driver App
โ
โ PUT /driver/order/status {status='FINISHED'}
โผ
Order Service :50052
โ
โ UPDATE orders SET status='FINISHED'
โ Calculate final fare
โ Publish DebitBalanceEvent to Kafka
โผ
Kafka Topic: wallet-transactions
โ
โ [Background Worker Consuming]
โผ
Wallet Service Worker
โ
โ BEGIN TRANSACTION
โ INSERT INTO transactions (amount=-fare, ref=order_id)
โ UPDATE wallets SET balance = balance - fare
โ COMMIT TRANSACTION
โผ
PostgreSQL
This project was built as a structured learning path through distributed systems patterns. Each service demonstrates specific architectural concepts:
| Service | Design Pattern | Go Fundamentals | Key Learning |
|---|---|---|---|
| Tracker | CQRS + Event Sourcing | Goroutines, Channels | Separating read/write paths for scalability |
| Order | Saga Pattern + State Machine | Context, WaitGroups | Managing distributed transactions |
| Dispatch | Orchestrator + Pub/Sub | gRPC clients, Error handling | Service coordination without tight coupling |
| Gateway | API Gateway + Aggregator | HTTP handlers, JSON marshaling | Single entry point pattern |
| Wallet | Ledger + ACID Transactions | Postgres transactions, Row locking | Financial consistency in distributed systems |
Every service implements proper shutdown handling:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
// Start worker
wg.Add(1)
go func() {
defer wg.Done()
worker.Start(ctx) // Respects ctx.Done()
}()
// Start gRPC server
wg.Add(1)
go func() {
defer wg.Done()
grpcServer.Serve(listener)
}()
// Wait for SIGTERM/SIGINT
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
// Cancel context (stops workers)
cancel()
// Graceful gRPC shutdown with timeout
stopped := make(chan struct{})
go func() {
grpcServer.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
log.Println("โ
Shutdown complete")
case <-time.After(10 * time.Second):
grpcServer.Stop() // Force stop
}
// Wait for all goroutines
wg.Wait()
}Why This Matters:
- Messages in-flight are completed before shutdown
- Database connections close cleanly
- No data loss during deployments
At-Least-Once Delivery:
for {
msg := consumer.FetchMessage(ctx)
// Process message
err := processMessage(msg)
if isPermanentError(err) {
// Poison pill - commit to skip
consumer.CommitMessages(ctx, msg)
} else if err != nil {
// Transient error - don't commit, will retry
continue
}
// Success - commit offset
consumer.CommitMessages(ctx, msg)
}Poison Pill Protection:
// Invalid JSON - will never succeed
if json.Unmarshal(msg.Value, &event) != nil {
consumer.CommitMessages(ctx, msg) // Skip it
continue
}
// Invalid data - will never succeed
if event.Amount <= 0 {
consumer.CommitMessages(ctx, msg) // Skip it
continue
}
// Database timeout - might succeed later
if isDatabaseError(err) {
// DON'T commit - allow retry
continue
}// Worker context (respects shutdown)
func (w *WalletWorker) Start(ctx context.Context) {
for {
select {
case <-ctx.Done():
return // Clean shutdown
default:
}
msg := w.consumer.FetchMessage(ctx)
// Operation context (independent timeout)
opCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
_, err := w.service.DebitBalance(opCtx, &req)
cancel() // Always cleanup
if ctx.Err() != nil {
return // Parent cancelled during operation
}
}
}// Every financial operation creates TWO entries
func DebitBalance(amount float64) error {
tx.Begin()
// Entry 1: Transaction log (immutable audit trail)
tx.CreateTransaction(TransactionParams{
Amount: -amount,
Description: "DEBIT",
ReferenceID: orderID,
})
// Entry 2: Balance update (mutable snapshot)
tx.UpdateBalance(BalanceParams{
UserID: userID,
Amount: -amount, // balance = balance - amount
})
tx.Commit()
}Benefits:
- Auditability: Every transaction is recorded
- Reconciliation: Can rebuild balances from transaction log
- Debugging: Full history of all financial operations
| Category | Technology | Purpose |
|---|---|---|
| Language | Go 1.25 | High-performance, concurrent services |
| Service Communication | gRPC | Type-safe, efficient RPC |
| HTTP Server | Go net/http | RESTful API gateway |
| Message Queue | Apache Kafka | Event streaming, async communication |
| RDBMS | PostgreSQL 15 | ACID transactions, orders & wallet |
| Cache/Geospatial | Redis 7 | Sub-ms location queries, GEOADD/GEORADIUS |
| Code Generation | protoc, SQLC | Type-safe gRPC & database code |
// Database
"github.com/jackc/pgx/v5" // PostgreSQL driver
"github.com/redis/go-redis/v9" // Redis client
// Messaging
"github.com/segmentio/kafka-go" // Kafka producer/consumer
// gRPC
"google.golang.org/grpc" // gRPC framework
"google.golang.org/protobuf" // Protocol buffers
// Testing
"github.com/stretchr/testify" // Test assertions & mocks- Docker Compose: Local development environment
- PostgreSQL: Orders and wallet data
- Redis: Driver location index
- Kafka (KRaft mode): Event streaming without ZooKeeper
- Kafka UI: Message inspection and debugging
# Required
- Go 1.25+
- Docker & Docker Compose
- Make (optional, for shortcuts)
# Optional
- protoc (for regenerating gRPC code)
- sqlc (for regenerating database code)# 1. Clone repository
git clone https://github.com/dwikikusuma/atlas.git
cd atlas
# 2. Start infrastructure
make up
# OR
docker-compose up -d
# 3. Run database migrations
make migrate-up service=order
make migrate-up service=wallet
# 4. Start services (in separate terminals)
go run cmd/tracker/main.go # :50051
go run cmd/order/main.go # :50052
go run cmd/dispatch/main.go # :50053
go run cmd/wallet/main.go # :50054
go run cmd/gateway/main.go # :8085
# 5. Access Kafka UI
open http://localhost:8080atlas/
โโโ cmd/ # Service entry points
โ โโโ gateway/ # HTTP API gateway
โ โโโ tracker/ # Location service
โ โโโ order/ # Order management
โ โโโ dispatch/ # Driver matching
โ โโโ wallet/ # Payment service
โโโ internal/ # Private application code
โ โโโ gateway/
โ โ โโโ handler.go # HTTP handlers
โ โ โโโ passenger.go # Customer endpoints
โ โ โโโ driver.go # Driver endpoints
โ โโโ tracker/
โ โ โโโ domain/ # Repository interfaces
โ โ โโโ repository/ # Redis implementation
โ โ โโโ service/ # gRPC server & worker
โ โโโ order/
โ โ โโโ db/ # SQLC generated code
โ โ โโโ service/ # Business logic & worker
โ โโโ dispatch/
โ โ โโโ model/ # Event models
โ โ โโโ service/ # Matching logic
โ โโโ wallet/
โ โโโ db/ # SQLC generated code
โ โโโ service/ # Ledger implementation
โโโ pkg/ # Shared libraries
โ โโโ kafka/ # Producer/consumer wrappers
โ โโโ database/ # Postgres & Redis clients
โ โโโ pb/ # Generated protobuf code
โ โโโ model/ # Shared event models
โโโ api/proto/ # gRPC service definitions
โโโ docker-compose.yml # Infrastructure setup
โโโ Makefile # Build & migration commands
POST http://localhost:8085/customer/order
Content-Type: application/json
{
"user_id": "customer-123",
"pickup_long": 106.8456,
"pickup_lat": -6.2088,
"dropoff_long": 106.8650,
"dropoff_lat": -6.2300
}
Response:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "CREATED",
"price": 25000
}POST http://localhost:8085/customer/ride/request
Content-Type: application/json
{
"passenger_id": "customer-123",
"pickup_long": 106.8456,
"pickup_lat": -6.2088,
"dropoff_long": 106.8650,
"dropoff_lat": -6.2300,
"vehicle_type": "go-ride"
}
Response:
{
"ride_id": "customer-123",
"status": "DRIVERS_FOUND",
"driver_id": "driver-456"
}GET http://localhost:8085/customer/order?id=550e8400-e29b-41d4-a716-446655440000
Response:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"passenger_id": "customer-123",
"driver_id": "driver-456",
"status": "MATCHED",
"price": 25000,
"created_at": "2025-12-15T10:30:00Z"
}POST http://localhost:8085/driver/location
Content-Type: application/json
{
"user_id": "driver-456",
"latitude": -6.2088,
"longitude": 106.8456,
"timestamp": "2025-12-15T10:30:00Z"
}
Response:
{
"success": true
}PUT http://localhost:8085/driver/order/status
Content-Type: application/json
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "STARTED"
}
Response:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "STARTED",
"updated_at": "2025-12-15T10:35:00Z"
}-
History Service (MongoDB)
- Archive GPS logs and ride history
- Worker pool pattern with buffered channels
- Time-series data storage
-
Observability Stack
- Prometheus metrics
- Grafana dashboards
- Distributed tracing with Jaeger
-
**Driver Credit System