Agent Skills Framework Extension
Microservices Patterns Skill
When to Use This Skill
Use this skill when implementing microservices patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Service mesh, API gateway, service discovery, distributed tracing, and inter-service communication patterns.
Core Capabilities
- Service Decomposition - Bounded context, domain-driven design
- API Gateway - Kong, routing, rate limiting
- Service Discovery - Consul, Kubernetes DNS
- Communication - gRPC, REST, async messaging
- Saga Pattern - Distributed transaction coordination
Service Decomposition
Bounded Context Mapping
// Domain-driven design approach to service boundaries
/*
┌────────────────────────────────────────────────────────────┐
│ E-Commerce Platform │
├──────────────┬──────────────┬──────────────┬───────────────┤
│ User │ Catalog │ Order │ Payment │
│ Context │ Context │ Context │ Context │
├──────────────┼──────────────┼──────────────┼───────────────┤
│ - User │ - Product │ - Order │ - Payment │
│ - Profile │ - Category │ - OrderItem │ - Transaction │
│ - Auth │ - Inventory │ - Shipment │ - Refund │
│ - Preference │ - Price │ - Cart │ - Wallet │
└──────────────┴──────────────┴──────────────┴───────────────┘
Each context = 1 Microservice with its own:
- Database (schema isolation)
- API surface
- Domain models
- Deployment lifecycle
*/
// Anti-corruption layer between contexts
interface UserContext {
User: { id: string; email: string; name: string };
}
interface OrderContext {
// Simplified view of User for Order context
Customer: { id: string; name: string; shippingAddress: string };
}
// Adapter to translate between contexts
class UserToCustomerAdapter {
constructor(private readonly userService: UserServiceClient) {}
async getCustomer(userId: string): Promise<OrderContext['Customer']> {
const user = await this.userService.getUser(userId);
const profile = await this.userService.getProfile(userId);
return {
id: user.id,
name: user.name,
shippingAddress: profile.defaultShippingAddress,
};
}
}
API Gateway (Kong)
# kong.yml - Declarative configuration
_format_version: "3.0"
services:
- name: user-service
url: http://user-service:8080
routes:
- name: user-routes
paths:
- /api/v1/users
strip_path: false
plugins:
- name: rate-limiting
config:
minute: 100
policy: redis
redis_host: redis
- name: jwt
config:
key_claim_name: kid
- name: order-service
url: http://order-service:8080
routes:
- name: order-routes
paths:
- /api/v1/orders
plugins:
- name: rate-limiting
config:
minute: 50
- name: jwt
- name: request-transformer
config:
add:
headers:
- "X-Request-ID:$(uuid)"
- name: catalog-service
url: http://catalog-service:8080
routes:
- name: catalog-routes
paths:
- /api/v1/products
- /api/v1/categories
plugins:
- name: proxy-cache
config:
content_type:
- application/json
cache_ttl: 300
strategy: memory
plugins:
- name: correlation-id
config:
header_name: X-Correlation-ID
generator: uuid
echo_downstream: true
- name: prometheus
config:
status_code_metrics: true
latency_metrics: true
Service Discovery (Consul)
// src/discovery/consul.service.ts
import Consul from 'consul';
interface ServiceInstance {
id: string;
name: string;
address: string;
port: number;
healthy: boolean;
}
export class ServiceDiscovery {
private consul: Consul.Consul;
private registeredServices: Set<string> = new Set();
constructor(host: string, port: number) {
this.consul = new Consul({ host, port });
}
async register(service: {
name: string;
id: string;
address: string;
port: number;
healthCheckUrl: string;
}): Promise<void> {
await this.consul.agent.service.register({
id: service.id,
name: service.name,
address: service.address,
port: service.port,
check: {
http: service.healthCheckUrl,
interval: '10s',
timeout: '5s',
deregistercriticalserviceafter: '1m',
},
tags: ['api', 'v1'],
});
this.registeredServices.add(service.id);
// Graceful shutdown
process.on('SIGTERM', () => this.deregister(service.id));
process.on('SIGINT', () => this.deregister(service.id));
}
async deregister(serviceId: string): Promise<void> {
await this.consul.agent.service.deregister(serviceId);
this.registeredServices.delete(serviceId);
}
async discover(serviceName: string): Promise<ServiceInstance[]> {
const result = await this.consul.health.service({
service: serviceName,
passing: true, // Only healthy instances
});
return result.map((entry: any) => ({
id: entry.Service.ID,
name: entry.Service.Service,
address: entry.Service.Address,
port: entry.Service.Port,
healthy: entry.Checks.every((c: any) => c.Status === 'passing'),
}));
}
// Round-robin load balancer
private instanceIndex: Map<string, number> = new Map();
async getNextInstance(serviceName: string): Promise<ServiceInstance | null> {
const instances = await this.discover(serviceName);
if (instances.length === 0) return null;
const currentIndex = this.instanceIndex.get(serviceName) ?? 0;
const instance = instances[currentIndex % instances.length];
this.instanceIndex.set(serviceName, currentIndex + 1);
return instance;
}
}
gRPC Communication
// proto/order.proto
syntax = "proto3";
package order;
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (Order);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (stream Order);
rpc UpdateOrderStatus(UpdateStatusRequest) returns (Order);
}
message CreateOrderRequest {
string customer_id = 1;
repeated OrderItem items = 2;
string shipping_address = 3;
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
int64 unit_price_cents = 3;
}
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
int64 total_cents = 5;
string created_at = 6;
}
enum OrderStatus {
PENDING = 0;
CONFIRMED = 1;
SHIPPED = 2;
DELIVERED = 3;
CANCELLED = 4;
}
// src/grpc/order.client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const PROTO_PATH = 'proto/order.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const orderProto = grpc.loadPackageDefinition(packageDefinition).order as any;
export class OrderServiceClient {
private client: any;
constructor(address: string) {
this.client = new orderProto.OrderService(
address,
grpc.credentials.createInsecure()
);
}
createOrder(request: {
customerId: string;
items: Array<{ productId: string; quantity: number; unitPriceCents: number }>;
shippingAddress: string;
}): Promise<any> {
return new Promise((resolve, reject) => {
this.client.CreateOrder(
{
customer_id: request.customerId,
items: request.items.map(item => ({
product_id: item.productId,
quantity: item.quantity,
unit_price_cents: item.unitPriceCents,
})),
shipping_address: request.shippingAddress,
},
(err: Error | null, response: any) => {
if (err) reject(err);
else resolve(response);
}
);
});
}
getOrder(orderId: string): Promise<any> {
return new Promise((resolve, reject) => {
this.client.GetOrder(
{ id: orderId },
(err: Error | null, response: any) => {
if (err) reject(err);
else resolve(response);
}
);
});
}
}
Saga Pattern
// src/saga/order-saga.ts
interface SagaStep<TData> {
name: string;
execute: (data: TData) => Promise<unknown>;
compensate: (data: TData) => Promise<void>;
}
export class Saga<TData> {
private steps: SagaStep<TData>[] = [];
private completedSteps: string[] = [];
addStep(step: SagaStep<TData>): this {
this.steps.push(step);
return this;
}
async execute(data: TData): Promise<void> {
for (const step of this.steps) {
try {
console.log(`Executing step: ${step.name}`);
await step.execute(data);
this.completedSteps.push(step.name);
} catch (error) {
console.error(`Step ${step.name} failed:`, error);
await this.compensate(data);
throw error;
}
}
}
private async compensate(data: TData): Promise<void> {
// Reverse order compensation
for (let i = this.completedSteps.length - 1; i >= 0; i--) {
const stepName = this.completedSteps[i];
const step = this.steps.find(s => s.name === stepName);
if (step) {
try {
console.log(`Compensating step: ${step.name}`);
await step.compensate(data);
} catch (err) {
console.error(`Compensation failed for ${step.name}:`, err);
// Log but continue compensating other steps
}
}
}
}
}
// Order creation saga
const orderSaga = new Saga<OrderData>()
.addStep({
name: 'reserve-inventory',
execute: async (data) => {
data.reservationId = await inventoryService.reserve(data.items);
},
compensate: async (data) => {
await inventoryService.release(data.reservationId);
},
})
.addStep({
name: 'process-payment',
execute: async (data) => {
data.transactionId = await paymentService.charge(
data.customerId,
data.total
);
},
compensate: async (data) => {
await paymentService.refund(data.transactionId);
},
})
.addStep({
name: 'create-order',
execute: async (data) => {
data.orderId = await orderService.create(data);
},
compensate: async (data) => {
await orderService.cancel(data.orderId);
},
})
.addStep({
name: 'notify-customer',
execute: async (data) => {
await notificationService.sendOrderConfirmation(data);
},
compensate: async () => {
// No compensation needed for notifications
},
});
// Execute saga
await orderSaga.execute(orderData);
Usage Examples
Design Microservices Architecture
Apply microservices-patterns skill to decompose a monolith into bounded contexts with API gateway
Implement Service Discovery
Apply microservices-patterns skill to add Consul-based service discovery with health checks
Add gRPC Communication
Apply microservices-patterns skill to implement gRPC communication between order and payment services
Service Communication Decision Matrix
Choose the right communication pattern for your use case:
| Use Case | Pattern | Protocol | When to Use | When NOT to Use |
|---|---|---|---|---|
| Query data | Request-Response | REST/gRPC | Simple reads, immediate response | High-latency tolerance |
| Command with response | Request-Response | gRPC | Type-safe, low latency | Simple CRUD |
| Fire and forget | Async Messaging | RabbitMQ/Kafka | Background jobs, notifications | Immediate confirmation needed |
| Event broadcast | Pub/Sub | Kafka | Multiple consumers, event sourcing | Single consumer |
| Long-running workflow | Saga/Choreography | Events | Distributed transactions | Simple transactions |
| Bi-directional stream | Streaming | gRPC/WebSocket | Real-time updates, large datasets | Simple request/response |
Communication Selection Flowchart:
Does caller need immediate response?
│
├── Yes → Is response time-critical (<100ms)?
│ │
│ ├── Yes → gRPC (binary, efficient)
│ │
│ └── No → REST (simpler, cacheable)
│
└── No → Is ordering important?
│
├── Yes → Kafka (ordered partitions)
│
└── No → RabbitMQ (simpler, flexible routing)
Anti-Pattern Detection:
| If You See This... | It's Likely... | Fix With... |
|---|---|---|
| Service A → B → C → D (sync chain) | Cascading failures | Async messaging, circuit breakers |
| All services poll for updates | Wasted resources | Event-driven (pub/sub) |
| Services share database | Distributed monolith | Database per service |
| No retry/timeout on HTTP calls | Silent failures | Resilience patterns |
| Synchronous calls in saga steps | Blocked transactions | Async compensation |
Protocol Comparison:
| Aspect | REST | gRPC | Kafka | RabbitMQ |
|---|---|---|---|---|
| Latency | Medium | Low | Medium | Medium |
| Throughput | Medium | High | Very High | High |
| Schema | Optional (OpenAPI) | Required (Protobuf) | Optional (Avro) | Optional |
| Browser support | Native | Via proxy | N/A | Via WebSocket |
| Debugging | Easy | Harder | Harder | Medium |
Integration Points
- system-architecture-design - Overall architecture patterns
- container-orchestration - Kubernetes deployment
- monitoring-observability - Distributed tracing
- event-driven-architecture - Async communication
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: microservices-patterns
Completed:
- [x] Service decomposition with bounded contexts defined
- [x] API Gateway (Kong) configured with routing
- [x] Service discovery (Consul) implemented
- [x] gRPC communication established between services
- [x] Saga pattern for distributed transactions
Outputs:
- Service boundaries: user, catalog, order, payment contexts
- kong.yml (API gateway configuration)
- src/discovery/consul.service.ts
- proto/order.proto (gRPC schema)
- src/saga/order-saga.ts
- Services registered: 4/4 healthy in Consul
- Gateway routes: 100% coverage
Completion Checklist
Before marking this skill as complete, verify:
- Bounded contexts defined with clear separation
- API Gateway routes all services correctly
- Service discovery health checks passing
- gRPC service-to-service communication working
- Saga compensations tested (rollback scenarios)
- Anti-corruption layers between contexts implemented
- Circuit breakers configured for inter-service calls
- Distributed tracing propagates context correctly
Failure Indicators
This skill has FAILED if:
- ❌ Services share database schemas (bounded context violation)
- ❌ API Gateway routing errors or missing rate limiting
- ❌ Service discovery fails to find healthy instances
- ❌ gRPC calls fail with connection errors
- ❌ Saga compensations don't execute in reverse order
- ❌ Services have circular dependencies
- ❌ No health checks configured (blind deployments)
When NOT to Use
Do NOT use this skill when:
- Building simple CRUD application (monolith is fine)
- Team size <5 developers (microservices overhead not justified)
- Domain not well-understood (premature decomposition)
- Shared database required for transactions (consider modular monolith)
- Real-time consistency critical (distributed systems have eventual consistency)
Use alternatives:
- Modular monolith for medium-complexity apps with clear modules
- Serverless functions for event-driven, independent workloads
- Traditional n-tier architecture for simple business apps
- Peer-to-peer communication without gateway for small service counts
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Nano-services | Too many tiny services | Bounded contexts, not function-level services |
| Shared database | Tight coupling | Database per service |
| Synchronous chains | Cascading failures | Async messaging for non-critical paths |
| No circuit breakers | Failure amplification | Implement circuit breakers on all calls |
| Missing distributed tracing | Debugging nightmare | OpenTelemetry from day 1 |
| Distributed monolith | Worst of both worlds | Ensure true service independence |
| Ignoring data consistency | Data corruption | Use sagas or event sourcing |
Principles
This skill embodies:
- #2 First Principles - Domain-driven design, bounded contexts from business domains
- #4 Separation of Concerns - Each service owns its data, API, and domain logic
- #5 Eliminate Ambiguity - Clear service boundaries via bounded contexts
- #6 Clear, Understandable, Explainable - Service discovery, gateway routing documented
- #7 Validate Continuously - Health checks, circuit breakers, distributed tracing
- #8 No Assumptions - Always verify service availability before calling
- Resilience by Design - Saga compensations, retry logic, fallback mechanisms
- Independent Deployability - Services deployed without coordinating releases