Integration patterns provide reusable solutions for connecting distributed systems.
Whether you’re building microservices, SaaS platforms, or cloud-native applications, you’ll often face challenges around data exchange, reliability, and scalability. Enterprise Integration Patterns (EIP) give you a toolkit to design robust communication between components.

🔌 Core Integration Patterns
1. Point-to-Point
A direct connection between two systems.
Good for simplicity, but becomes a spaghetti mess as integrations grow (n² problem).
flowchart LR
A[Service A] --> B[Service B]
🧑💻 Example (Go client calling API):
|
|
✅ Simple, fast ⚠️ Tight coupling, doesn’t scale
2. Message Queue
A producer sends messages to a queue, and consumers process them asynchronously.
flowchart LR
P[Producer] --> Q[(Queue)]
Q --> C1[Consumer 1]
Q --> C2[Consumer 2]
🧑💻 Example with RabbitMQ (using streadway/amqp):
|
|
✅ Decouples producer & consumer ✅ Smooths traffic spikes, supports retry ⚠️ Adds latency, requires broker
3. Publish–Subscribe (Pub/Sub)
A publisher emits events to a broker; multiple subscribers consume independently.
flowchart LR
P[Publisher] --> B[(Broker)]
B --> S1[Subscriber 1]
B --> S2[Subscriber 2]
B --> S3[Subscriber 3]
🧑💻 Example with NATS:
|
|
✅ Decouples producers/consumers ✅ Scales horizontally ⚠️ Delivery/order guarantees require tuning
4. Request–Reply
Classic synchronous API call. Go’s net/http or grpc are common implementations.
sequenceDiagram
Client->>Service: Request
Service-->>Client: Response
🧑💻 Example with gRPC:
|
|
✅ Familiar, widely supported ⚠️ Tight coupling, fragile if callee is down
5. Event-Driven / Event Sourcing
State changes are represented as immutable events. Consumers react asynchronously.
flowchart LR
A[Service A] -->|Event| E[(Event Log)]
B[Service B] -->|Consume Event| E
C[Service C] -->|Consume Event| E
🧑💻 Example: append events to Kafka
|
|
✅ Full audit log, replay possible ✅ Decoupled, scalable ⚠️ Requires careful schema/versioning strategy
🏗️ Advanced Architectural Patterns
1. CQRS (Command Query Responsibility Segregation) with Kafka
CQRS separates responsibilities into a write side (Commands) and a read side (Queries).
Instead of a single API serving both reads and writes, CQRS allows you to optimize each:
- Writes (Commands): exposed as REST APIs for simple, synchronous commands.
- Reads (Queries): exposed as GraphQL for flexible queries, or WebSockets for real-time updates.
In event-driven architectures, CQRS is often combined with Kafka:
- the Write side publishes events,
- the Read side consumes and projects them into optimized read models.
- Microservices remain independent, but their state converges via eventual consistency.
flowchart LR
ClientW[Client - REST Write] -->|POST /users| WriteAPI[Write Service]
WriteAPI -->|UserCreated Event| K[(Kafka Topic)]
K --> Projector[Read Model Projector]
Projector --> ReadDB[(Read Database)]
ReadDB --> GQL[GraphQL API]
ReadDB --> WS[WebSocket Stream]
ClientR1[Client - Query] -->|GraphQL Query| GQL
ClientR2[Client - Realtime UI] -->|Subscribe| WS
🧑💻 Go Example – Write Side (REST Command API)
|
|
🧑💻 Go Example – Read Side (GraphQL + WebSocket)
|
|
✅ Benefits
REST
commands are simple and predictable for writes.GraphQL
andWebSockets
make reads flexible and real-time.Microservices
keep independent state but synchronize through events.Highly scalable — read and write paths scale separately.
⚠️ Challenges
Eventual consistency: data in the read model may lag behind the write.
More moving parts: requires careful schema evolution, retries, and monitoring.
Debugging distributed state requires strong observability.
🌍 Real-world use case
Imagine a User Service with CQRS:
Writes (
REST
): POST /users creates a user and emits UserCreated.Other
microservices
(Billing, Notifications) consume that event asynchronously and update their state.Reads (GraphQL/WebSocket)
: A dashboard queries aggregated data (user + billing status) or subscribes to real-time updates without hitting the write database.
Over time, the system achieves eventual consistency: all services converge on the same user state, but they don’t have to be strongly consistent at write time.
👉 This now shows a CQRS microservices architecture where:
- Writes = REST
- Reads = GraphQL + WebSockets
- State is shared asynchronously via Kafka with eventual consistency
2. Saga Pattern (Distributed Transactions)
In a microservices world, a single business process (e.g., “place order”) may span multiple services.
A Saga coordinates these steps to ensure eventual consistency without requiring a global transaction.
- Choreography (event-based): each service listens for events and emits compensating events if something fails.
- Orchestration (central coordinator): a Saga orchestrator tells each service what to do next and how to roll back if needed.
sequenceDiagram
participant C as Customer Service
participant O as Order Service
participant P as Payment Service
participant I as Inventory Service
C->>O: Place Order
O->>P: Reserve Payment
P-->>O: Payment Reserved
O->>I: Reserve Stock
I-->>O: Stock Reserved
O-->>C: Order Confirmed
Note over O,I,P: If any step fails → compensating events (e.g., Cancel Payment)
🧑💻 Example in Go (compensating event):
|
|
✅ Benefits: Handles long-running, multi-service transactions without 2PC. ⚠️ Challenges: Requires careful design of compensating actions.
3. Circuit Breaker (Resilience)
A Circuit Breaker protects your system from cascading failures. When a dependency fails repeatedly, the breaker “opens” and stops calls until recovery is detected.
flowchart LR
A[Service A] -->|Request| B[Service B]
B -- success --> A
B -- failure x N --> CB[Circuit Breaker]
CB -.->|Fallback| A
🧑💻 Example with Go + resilience library (pseudo-code):
|
|
✅ Benefits: Prevents cascading failures, improves stability. ⚠️ Challenges: Needs good fallback strategies.
4. API Gateway / Aggregator
An API Gateway is a single entry point that routes requests to multiple microservices. Sometimes it also aggregates responses from multiple services to reduce client complexity.
flowchart LR
Client --> Gateway[API Gateway]
Gateway --> U[User Service]
Gateway --> O[Order Service]
Gateway --> P[Payment Service]
🧑💻 Example aggregator in Go (pseudo-code):
|
|
✅ Benefits: Simplifies client logic, centralizes auth/routing. ⚠️ Challenges: Gateway can become a bottleneck if overloaded.
✅ Conclusion
Integration patterns are the glue of modern distributed systems.
Choosing the right one depends on your trade-offs and the maturity of your architecture:
- Need simplicity → Point-to-Point / Request–Reply
- Need resilience → Queues / Circuit Breaker
- Need scalability → Pub/Sub / Event Sourcing
- Need consistency in distributed workflows → Saga
- Need optimized reads and writes → CQRS with Kafka
- Need simplified client access → API Gateway / Aggregator
By applying these patterns in Go — with tools like gRPC, RabbitMQ, Kafka, NATS, Envoy, and GraphQL — you can build systems that are:
- Scalable → handle traffic growth without bottlenecks
- Fault-tolerant → recover gracefully from failures
- Maintainable → clear separation of concerns and modular design
- Future-proof → adaptable to new requirements as your system evolves
Integration patterns are not silver bullets, but they provide a toolbox of proven solutions. The key is knowing when to use which pattern — and combining them wisely.
Happy integrating! 🔗🐹
🚀 Follow me on norbix.dev for more insights on Go, Python, AI, system design, and engineering wisdom.