Skip to content

Multi-Service Integration

What You’ll Build: A complete e-commerce checkout workflow that integrates user, order, inventory, and notification services.

This tutorial teaches you to build workflows that span multiple microservices. You’ll integrate services through the message bus, handle failures gracefully, and implement distributed business processes.

1. User places order (Order Service)
2. Reserve inventory (Inventory Service)
3. Process payment (Payment Service)
4. Send confirmation (Notification Service)
5. Update user profile (User Service)

By the end of this tutorial, you will be able to:

  • Design multi-service workflows
  • Implement service-to-service communication
  • Create typed service clients
  • Handle compensating actions
  • Test distributed workflows
  • Debug cross-service issues

Before starting this tutorial, you should:

  • Complete beginner tutorials
  • Have multiple services running
  • Understand message-based architecture

Order Service:

  • Creates orders
  • Manages order lifecycle
  • Emits OrderPlaced event

Inventory Service:

  • Tracks product stock
  • Reserves inventory
  • Subscribes to OrderPlaced

Payment Service:

  • Processes payments
  • Emits PaymentProcessed event

Notification Service:

  • Sends emails/SMS
  • Subscribes to OrderPlaced, PaymentProcessed

User Service:

  • Manages user data
  • Tracks purchase history

Services communicate exclusively through RabbitMQ:

Order Service → OrderPlaced Event → RabbitMQ
[Inventory, Payment, Notification]
Payment Service → PaymentProcessed → RabbitMQ
[Notification]

In Inventory Service, create a client to query Order Service:

inventory-service/src/clients/OrderServiceClient.ts
import { MessageBusClient } from '@banyanai/platform-message-bus-client';
export class OrderServiceClient {
private messageBus: MessageBusClient;
constructor() {
this.messageBus = new MessageBusClient({
url: process.env.MESSAGE_BUS_URL || 'amqp://localhost:5672',
exchange: 'platform',
});
}
async getOrder(orderId: string) {
return await this.messageBus.sendQuery(
'OrderService.Queries.GetOrder',
{ orderId }
);
}
}

Create event handlers in each service:

inventory-service/src/events/OrderPlacedHandler.ts
import { EventHandler, EventHandlerDecorator } from '@banyanai/platform-base-service';
import { Logger } from '@banyanai/platform-telemetry';
export class OrderPlaced {
constructor(
public orderId: string,
public items: Array<{ productId: string; quantity: number }>,
public customerId: string
) {}
}
@EventHandlerDecorator(OrderPlaced)
export class OrderPlacedHandler extends EventHandler<OrderPlaced> {
async handle(event: OrderPlaced): Promise<void> {
Logger.info('Order placed, reserving inventory', { orderId: event.orderId });
for (const item of event.items) {
await this.reserveInventory(item.productId, item.quantity);
}
Logger.info('Inventory reserved', { orderId: event.orderId });
}
private async reserveInventory(productId: string, quantity: number): Promise<void> {
// Implementation
}
}

If payment fails after inventory is reserved:

payment-service/src/commands/ProcessPaymentHandler.ts
async handle(command: ProcessPaymentCommand): Promise<ProcessPaymentResult> {
try {
// Process payment
const result = await this.processPayment(command);
if (!result.success) {
// Emit failure event
await this.publishEvent(new PaymentFailed(command.orderId, result.error));
}
return result;
} catch (error) {
// Critical failure - emit event for compensation
await this.publishEvent(new PaymentFailed(command.orderId, error.message));
throw error;
}
}

Handle compensation in Inventory Service:

inventory-service/src/events/PaymentFailedHandler.ts
@EventHandlerDecorator(PaymentFailed)
export class PaymentFailedHandler extends EventHandler<PaymentFailed> {
async handle(event: PaymentFailed): Promise<void> {
Logger.info('Payment failed, releasing inventory', { orderId: event.orderId });
// Load order to get items
const order = await this.orderClient.getOrder(event.orderId);
// Release reserved inventory
for (const item of order.items) {
await this.releaseInventory(item.productId, item.quantity);
}
}
}
describe('Checkout Workflow', () => {
it('should complete full checkout process', async () => {
// 1. Create order
const orderResult = await orderClient.createOrder({
customerId: 'customer-123',
items: [{ productId: 'product-1', quantity: 2 }]
});
// 2. Wait for inventory reservation
await waitForEvent('InventoryReserved', { orderId: orderResult.orderId });
// 3. Process payment
const paymentResult = await paymentClient.processPayment({
orderId: orderResult.orderId,
amount: 100.00
});
expect(paymentResult.success).toBe(true);
// 4. Wait for notification
await waitForEvent('NotificationSent', { orderId: orderResult.orderId });
// 5. Verify order status
const order = await orderClient.getOrder(orderResult.orderId);
expect(order.status).toBe('completed');
});
it('should rollback on payment failure', async () => {
// Create order
const orderResult = await orderClient.createOrder({
customerId: 'customer-123',
items: [{ productId: 'product-1', quantity: 2 }]
});
// Wait for inventory reservation
await waitForEvent('InventoryReserved', { orderId: orderResult.orderId });
// Simulate payment failure
const paymentResult = await paymentClient.processPayment({
orderId: orderResult.orderId,
amount: 999999.99 // Amount that will fail
});
expect(paymentResult.success).toBe(false);
// Wait for inventory release
await waitForEvent('InventoryReleased', { orderId: orderResult.orderId });
// Verify inventory was released
const inventory = await inventoryClient.getInventory('product-1');
expect(inventory.reserved).toBe(0);
});
});
  1. Event-Driven Communication: Services react to events
  2. Service Clients: Type-safe inter-service queries
  3. Compensating Actions: Undo operations on failure
  4. Eventual Consistency: Services sync asynchronously
  5. Idempotency: Event handlers can be called multiple times
  • Loose Coupling: Services don’t know about each other’s internals
  • Resilience: Failures are isolated
  • Scalability: Services can be scaled independently
  • Flexibility: Easy to add new services to workflow
  • Debugging: Trace requests across services
  • Testing: Need integration tests
  • Consistency: Eventual consistency requires careful design
  • Ordering: Event order may not be guaranteed