3.2 KiB
3.2 KiB
ADR-0030: Service Communication Strategy
Status
Accepted
Context
Services need to communicate with each other in a microservices architecture. All communication must go through well-defined interfaces that support network calls.
Decision
Use a service client-based communication strategy with API Gateway as the entry point:
-
API Gateway (Entry Point):
- All external traffic enters through API Gateway
- Gateway routes requests to backend services via service discovery
- Gateway handles authentication (JWT validation via Auth Service)
- Gateway handles rate limiting, CORS, request transformation
-
Service Client Interfaces (Primary for synchronous calls):
- Define interfaces in
pkg/services/for all services - All implementations are network-based:
internal/services/grpc/client/- gRPC clients (primary)internal/services/http/client/- HTTP clients (fallback)
- Gateway uses service clients to communicate with backend services
- Services use service clients for inter-service communication
- Define interfaces in
-
Event Bus (Primary for asynchronous communication):
- Distributed via Kafka
- Preferred for cross-service communication
- Event-driven architecture for loose coupling
-
Shared Infrastructure (For state):
- Redis for cache and distributed state
- PostgreSQL instance for persistent data (each service has its own schema)
- Kafka for events
Service Client Pattern
// Interface in pkg/services/
type IdentityServiceClient interface {
GetUser(ctx context.Context, id string) (*User, error)
CreateUser(ctx context.Context, user *User) (*User, error)
}
// gRPC implementation (primary)
type grpcIdentityClient struct {
conn *grpc.ClientConn
client pb.IdentityServiceClient
}
// HTTP implementation (fallback)
type httpIdentityClient struct {
baseURL string
httpClient *http.Client
}
Communication Flow
Client → API Gateway → Backend Service (via service client)
Backend Service → Other Service (via service client)
All communication goes through service clients - no direct in-process calls even in development mode.
Development Mode
For local development, services run in the same repository but as separate processes:
- Each service has its own entry point (
cmd/{service}/) - Services communicate via service clients (gRPC or HTTP) - no direct in-process calls
- Docker Compose orchestrates all services
- This ensures the architecture is consistent with production
Consequences
Positive
- Unified Interface: Consistent interface across all services
- Easy Testing: Can mock service clients
- Type Safety: gRPC provides type-safe contracts
- Clear Boundaries: Service boundaries are explicit
- Scalability: Services can be scaled independently
Negative
- Network Overhead: All calls go over network
- Interface Evolution: Changes require coordination
- Versioning: Need service versioning strategy
- Development Complexity: More setup required for local development
Implementation
- All services use gRPC clients (primary)
- HTTP clients as fallback option
- Service registry for service discovery
- Circuit breakers and retries for resilience