specwright/templates/skills/platform/system-integration-patterns/SKILL.md
# [SKILL_NAME] - System Integration Patterns > **Role:** Platform Integration Architect > **Domain:** Multi-Module Communication & Integration > **Created:** [CURRENT_DATE] ## Purpose Design and implement integration patterns for multi-module platforms. Focus on how modules communicate, share data, and maintain loose coupling while ensuring reliability and performance. ## When to Activate **Use this skill for:** - Designing module-to-module communication - Choosing integration patterns (RES
npx skillsauth add michsindlinger/specwright specwright/templates/skills/platform/system-integration-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Role: Platform Integration Architect Domain: Multi-Module Communication & Integration Created: [CURRENT_DATE]
Design and implement integration patterns for multi-module platforms. Focus on how modules communicate, share data, and maintain loose coupling while ensuring reliability and performance.
Use this skill for:
Do NOT use for:
When to Use:
Architecture:
sequenceDiagram
participant ModuleA
participant APIGateway
participant ModuleB
ModuleA->>APIGateway: GET /api/v1/resource
APIGateway->>ModuleB: Forward request
ModuleB-->>APIGateway: Response
APIGateway-->>ModuleA: Response
Implementation Example (Node.js/Express):
// Module B - API Provider
import express from 'express';
interface ResourceResponse {
id: string;
data: any;
version: string;
}
// API Contract
class ResourceAPI {
private router = express.Router();
constructor(private resourceService: ResourceService) {
this.setupRoutes();
}
private setupRoutes() {
// Versioned endpoint
this.router.get('/v1/resources/:id', async (req, res) => {
try {
const resource = await this.resourceService.findById(req.params.id);
if (!resource) {
return res.status(404).json({
error: 'Resource not found',
version: 'v1'
});
}
const response: ResourceResponse = {
id: resource.id,
data: resource.data,
version: 'v1'
};
res.json(response);
} catch (error) {
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
}
getRouter() {
return this.router;
}
}
// Module A - API Consumer
class ResourceClient {
constructor(private baseUrl: string) {}
async getResource(id: string): Promise<ResourceResponse> {
const response = await fetch(`${this.baseUrl}/v1/resources/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch resource: ${response.statusText}`);
}
return response.json();
}
}
Trade-offs:
When to Use:
Architecture:
graph LR
ModuleA[Module A] -->|Publish Event| EventBus[Event Bus]
EventBus -->|Subscribe| ModuleB[Module B]
EventBus -->|Subscribe| ModuleC[Module C]
EventBus -->|Subscribe| ModuleD[Module D]
Implementation Example (RabbitMQ):
// Event Schema
interface DomainEvent {
eventId: string;
eventType: string;
occurredAt: Date;
payload: any;
version: number;
}
interface OrderCreatedEvent extends DomainEvent {
eventType: 'OrderCreated';
payload: {
orderId: string;
userId: string;
items: Array<{ productId: string; quantity: number }>;
totalAmount: number;
};
}
// Module A - Event Publisher
import amqp from 'amqplib';
class EventPublisher {
private connection: amqp.Connection;
private channel: amqp.Channel;
async connect() {
this.connection = await amqp.connect('amqp://localhost');
this.channel = await this.connection.createChannel();
}
async publishEvent(event: DomainEvent) {
const exchange = 'platform.events';
await this.channel.assertExchange(exchange, 'topic', { durable: true });
this.channel.publish(
exchange,
event.eventType,
Buffer.from(JSON.stringify(event)),
{ persistent: true }
);
console.log(`Published event: ${event.eventType}`);
}
}
// Module B, C, D - Event Subscribers
class OrderEventSubscriber {
private connection: amqp.Connection;
private channel: amqp.Channel;
async connect() {
this.connection = await amqp.connect('amqp://localhost');
this.channel = await this.connection.createChannel();
}
async subscribe(handler: (event: OrderCreatedEvent) => Promise<void>) {
const exchange = 'platform.events';
const queue = 'module-b.order-events';
await this.channel.assertExchange(exchange, 'topic', { durable: true });
await this.channel.assertQueue(queue, { durable: true });
await this.channel.bindQueue(queue, exchange, 'OrderCreated');
this.channel.consume(queue, async (msg) => {
if (msg) {
const event = JSON.parse(msg.content.toString()) as OrderCreatedEvent;
try {
await handler(event);
this.channel.ack(msg);
} catch (error) {
console.error('Failed to process event:', error);
// Requeue or dead-letter
this.channel.nack(msg, false, false);
}
}
});
}
}
// Usage
const publisher = new EventPublisher();
await publisher.connect();
const event: OrderCreatedEvent = {
eventId: crypto.randomUUID(),
eventType: 'OrderCreated',
occurredAt: new Date(),
version: 1,
payload: {
orderId: '123',
userId: 'user-456',
items: [{ productId: 'prod-789', quantity: 2 }],
totalAmount: 99.99
}
};
await publisher.publishEvent(event);
Trade-offs:
When to Use:
Architecture:
graph TB
Client[Clients] --> Gateway[API Gateway]
Gateway -->|Route| ModuleA[Module A]
Gateway -->|Route| ModuleB[Module B]
Gateway -->|Route| ModuleC[Module C]
Gateway --> Auth[Auth Service]
Gateway --> RateLimit[Rate Limiter]
Implementation Example (Kong/Express):
// API Gateway
import express from 'express';
import httpProxy from 'http-proxy-middleware';
class APIGateway {
private app = express();
constructor(
private authService: AuthService,
private rateLimiter: RateLimiter
) {
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware() {
// Authentication
this.app.use(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const user = await this.authService.verifyToken(token);
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
// Rate Limiting
this.app.use(async (req, res, next) => {
const userId = req.user?.id;
const allowed = await this.rateLimiter.checkLimit(userId);
if (!allowed) {
return res.status(429).json({ error: 'Too many requests' });
}
next();
});
}
private setupRoutes() {
// Route to Module A
this.app.use('/api/users', httpProxy.createProxyMiddleware({
target: 'http://module-a:3001',
changeOrigin: true,
pathRewrite: { '^/api/users': '/v1/users' }
}));
// Route to Module B
this.app.use('/api/orders', httpProxy.createProxyMiddleware({
target: 'http://module-b:3002',
changeOrigin: true,
pathRewrite: { '^/api/orders': '/v1/orders' }
}));
// Route to Module C
this.app.use('/api/products', httpProxy.createProxyMiddleware({
target: 'http://module-c:3003',
changeOrigin: true,
pathRewrite: { '^/api/products': '/v1/products' }
}));
}
listen(port: number) {
this.app.listen(port, () => {
console.log(`API Gateway listening on port ${port}`);
});
}
}
Trade-offs:
When to Use:
Architecture:
graph TB
Client[Client]
Client -->|Commands| WriteModel[Write Model]
Client -->|Queries| ReadModel[Read Model]
WriteModel -->|Events| EventStore[Event Store]
EventStore -->|Project| ReadModel
Implementation Example:
// Command Side
interface CreateOrderCommand {
userId: string;
items: Array<{ productId: string; quantity: number }>;
}
class OrderCommandHandler {
constructor(
private orderRepository: OrderRepository,
private eventPublisher: EventPublisher
) {}
async handleCreateOrder(command: CreateOrderCommand): Promise<string> {
// Validation
if (!command.items.length) {
throw new Error('Order must have items');
}
// Create aggregate
const order = Order.create(command.userId, command.items);
// Persist
await this.orderRepository.save(order);
// Publish events
const events = order.getDomainEvents();
for (const event of events) {
await this.eventPublisher.publishEvent(event);
}
return order.id;
}
}
// Query Side
interface OrderView {
orderId: string;
userId: string;
status: string;
totalAmount: number;
itemCount: number;
createdAt: Date;
}
class OrderQueryHandler {
constructor(private readDatabase: ReadDatabase) {}
async getOrderById(orderId: string): Promise<OrderView | null> {
// Query optimized read model
const result = await this.readDatabase.query(`
SELECT
order_id,
user_id,
status,
total_amount,
item_count,
created_at
FROM order_views
WHERE order_id = $1
`, [orderId]);
return result.rows[0] || null;
}
async getOrdersByUser(userId: string): Promise<OrderView[]> {
const result = await this.readDatabase.query(`
SELECT *
FROM order_views
WHERE user_id = $1
ORDER BY created_at DESC
`, [userId]);
return result.rows;
}
}
// Read Model Projection
class OrderViewProjection {
constructor(
private readDatabase: ReadDatabase,
private eventSubscriber: EventSubscriber
) {
this.subscribeToEvents();
}
private subscribeToEvents() {
this.eventSubscriber.subscribe('OrderCreated', async (event) => {
await this.readDatabase.query(`
INSERT INTO order_views (order_id, user_id, status, total_amount, item_count, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
`, [
event.payload.orderId,
event.payload.userId,
'pending',
event.payload.totalAmount,
event.payload.items.length,
event.occurredAt
]);
});
this.eventSubscriber.subscribe('OrderStatusChanged', async (event) => {
await this.readDatabase.query(`
UPDATE order_views
SET status = $1
WHERE order_id = $2
`, [event.payload.newStatus, event.payload.orderId]);
});
}
}
Trade-offs:
Prevent cascading failures:
class CircuitBreaker {
private failureCount = 0;
private lastFailureTime: Date | null = null;
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (this.shouldAttemptReset()) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
private onFailure() {
this.failureCount++;
this.lastFailureTime = new Date();
if (this.failureCount >= 5) {
this.state = 'OPEN';
}
}
private shouldAttemptReset(): boolean {
if (!this.lastFailureTime) return false;
const timeSinceLastFailure = Date.now() - this.lastFailureTime.getTime();
return timeSinceLastFailure > 60000; // 1 minute
}
}
async function retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
Manage distributed transactions:
class OrderSaga {
async execute(order: Order) {
try {
// Step 1: Reserve inventory
await this.inventoryService.reserve(order.items);
// Step 2: Process payment
await this.paymentService.charge(order.userId, order.total);
// Step 3: Confirm order
await this.orderService.confirm(order.id);
} catch (error) {
// Compensating transactions (rollback)
await this.inventoryService.release(order.items);
await this.paymentService.refund(order.userId, order.total);
await this.orderService.cancel(order.id);
throw error;
}
}
}
Remember: Integration patterns should prioritize loose coupling, resilience, and observability. Choose patterns based on actual requirements, not trends.
tools
Session Handoff: Erstellt eine vollständige Zusammenfassung der aktuellen Session für einen sauberen Kontextwechsel. NUR bei explizitem Aufruf (/session-handoff). NICHT automatisch auslösen. Geeignet wenn der User die Session resetten will, den Kontext aufräumen will, oder bei ~120k Tokens angelangt ist.
development
Pre-Mortem Risk Analysis: Strukturierte Prospective-Hindsight-Übung um launch-blocking Risiken vor Commitment aufzudecken. Team stellt sich vor, das Produkt sei 14 Tage nach Launch gefloppt, und arbeitet rückwärts. Klassifiziert Risiken in Tigers (echt), Paper Tigers (hypothetisch), Elephants (unausgesprochen). Nutze diesen Skill vor Build-Commitment, bei zu hoher Stakeholder-Confidence, vor Major-Releases, oder wenn das Team vage Sorgen nicht artikulieren kann. Trigger: /pre-mortem, 'pre-mortem', 'risk analysis', 'was könnte schiefgehen', 'risiken vor launch'.
testing
Six-Sigma Atomicity Validator for create-spec stories
tools
UX pattern definition guidance for navigation, user flows, interactions, and accessibility