Placing an order involves coordinating inventory, payment, shipping, and notifications. Direct communication between subsystems creates a web of dependencies where each service must know about every other service it interacts with. Adding a new subsystem requires modifying all existing ones.
The mediator coordinates a multi-step order placement workflow: Inventory reservation -> Payment processing -> Shipping creation -> Notification sending. If payment fails, the mediator handles rollback by releasing the inventory reservation.
mediator.interface.ts colleague.interface.ts inventory.colleague.ts payment.colleague.ts shipping.colleague.ts notification.colleague.ts order-mediator.service.ts
export interface Mediator {
notify ( sender : string , event : string , data ? : any ): any ;
} import { Mediator } from './mediator.interface' ;
export abstract class Colleague {
protected mediator !: Mediator ;
setMediator ( mediator : Mediator ): void {
this . mediator = mediator ;
}
abstract getName (): string ;
} import { Colleague } from './colleague.interface' ;
export class InventoryColleague extends Colleague {
getName (): string {
return 'Inventory' ;
}
reserveItems ( items : { name : string ; qty : number }[]): {
success : boolean ;
reserved : { name : string ; qty : number }[];
message : string ;
} {
const reserved = items . map (( item ) => ({
name : item . name ,
qty : item . qty ,
}));
const result = {
success : true ,
reserved ,
message : `Reserved ${ reserved . length } item(s) in inventory` ,
};
this . mediator . notify ( this . getName (), 'inventory.reserved' , result );
return result ;
}
releaseItems ( items : { name : string ; qty : number }[]): {
success : boolean ;
message : string ;
} {
return {
success : true ,
message : `Released ${ items . length } item(s) back to inventory` ,
};
}
} import { Colleague } from './colleague.interface' ;
export class PaymentColleague extends Colleague {
getName (): string {
return 'Payment' ;
}
processPayment ( paymentMethod : string , amount : number ): {
success : boolean ;
transactionId : string ;
message : string ;
} {
const transactionId = `txn_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). substring ( 2 , 8 ) } ` ;
const result = {
success : true ,
transactionId ,
message : `Payment of $ ${ amount . toFixed ( 2 ) } processed via ${ paymentMethod } ` ,
};
this . mediator . notify ( this . getName (), 'payment.processed' , result );
return result ;
}
refundPayment ( transactionId : string ): {
success : boolean ;
message : string ;
} {
return {
success : true ,
message : `Refund processed for transaction ${ transactionId } ` ,
};
}
} import { Colleague } from './colleague.interface' ;
export class ShippingColleague extends Colleague {
getName (): string {
return 'Shipping' ;
}
createShipment ( address : Record < string , string >, items : { name : string ; qty : number }[]): {
success : boolean ;
trackingNumber : string ;
estimatedDelivery : string ;
message : string ;
} {
const trackingNumber = `SHIP_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). substring ( 2 , 8 ) } ` ;
const deliveryDate = new Date ();
deliveryDate . setDate ( deliveryDate . getDate () + 5 );
const result = {
success : true ,
trackingNumber ,
estimatedDelivery : deliveryDate . toISOString (). split ( 'T' )[ 0 ],
message : `Shipment created for ${ items . length } item(s) to ${ address . city } , ${ address . country } ` ,
};
this . mediator . notify ( this . getName (), 'shipping.created' , result );
return result ;
}
} import { Colleague } from './colleague.interface' ;
export class NotificationColleague extends Colleague {
getName (): string {
return 'Notification' ;
}
sendOrderConfirmation ( email : string , orderId : string , trackingNumber : string ): {
success : boolean ;
message : string ;
} {
const result = {
success : true ,
message : `Order confirmation sent to ${ email } for order ${ orderId } (tracking: ${ trackingNumber } )` ,
};
this . mediator . notify ( this . getName (), 'notification.sent' , result );
return result ;
}
sendPaymentReceipt ( email : string , transactionId : string , amount : number ): {
success : boolean ;
message : string ;
} {
return {
success : true ,
message : `Payment receipt for $ ${ amount . toFixed ( 2 ) } sent to ${ email } (txn: ${ transactionId } )` ,
};
}
} import { Injectable } from '@nestjs/common' ;
import { Mediator } from './mediator.interface' ;
import { InventoryColleague } from './inventory.colleague' ;
import { PaymentColleague } from './payment.colleague' ;
import { ShippingColleague } from './shipping.colleague' ;
import { NotificationColleague } from './notification.colleague' ;
interface OrderData {
items : { name : string ; qty : number ; price : number }[];
paymentMethod : string ;
shippingAddress : Record < string , string >;
customerEmail : string ;
}
export interface EventLogEntry {
sender : string ;
event : string ;
data : any ;
timestamp : string ;
}
@ Injectable ()
export class OrderMediatorService implements Mediator {
private readonly inventory : InventoryColleague ;
private readonly payment : PaymentColleague ;
private readonly shipping : ShippingColleague ;
private readonly notification : NotificationColleague ;
// Per-request event log, set during placeOrder() execution
private currentLog : EventLogEntry [] | null = null ;
constructor () {
this . inventory = new InventoryColleague ();
this . payment = new PaymentColleague ();
this . shipping = new ShippingColleague ();
this . notification = new NotificationColleague ();
this . inventory . setMediator ( this );
this . payment . setMediator ( this );
this . shipping . setMediator ( this );
this . notification . setMediator ( this );
}
notify ( sender : string , event : string , data ? : any ): any {
if ( this . currentLog ) {
this . currentLog . push ({
sender ,
event ,
data ,
timestamp : new Date (). toISOString (),
});
}
}
placeOrder ( orderData : OrderData ) {
const orderId = `ORD_ ${ Date . now () } _ ${ Math . random (). toString ( 36 ). substring ( 2 , 8 ) } ` ;
const eventLog : EventLogEntry [] = [];
this . currentLog = eventLog ;
try {
// Step 1: Reserve inventory
const inventoryResult = this . inventory . reserveItems ( orderData . items );
if ( ! inventoryResult . success ) {
return { success : false , orderId , message : 'Failed to reserve inventory' , steps : eventLog };
}
// Step 2: Process payment
const totalAmount = orderData . items . reduce (( sum , item ) => sum + item . price * item . qty , 0 );
const paymentResult = this . payment . processPayment ( orderData . paymentMethod , totalAmount );
if ( ! paymentResult . success ) {
this . inventory . releaseItems ( orderData . items );
return { success : false , orderId , message : 'Payment failed' , steps : eventLog };
}
// Step 3: Create shipment
const shippingResult = this . shipping . createShipment ( orderData . shippingAddress , orderData . items );
// Step 4: Send notifications
const notificationResult = this . notification . sendOrderConfirmation (
orderData . customerEmail ,
orderId ,
shippingResult . trackingNumber ,
);
this . notification . sendPaymentReceipt (
orderData . customerEmail ,
paymentResult . transactionId ,
totalAmount ,
);
return {
success : true ,
orderId ,
totalAmount ,
transactionId : paymentResult . transactionId ,
trackingNumber : shippingResult . trackingNumber ,
estimatedDelivery : shippingResult . estimatedDelivery ,
notification : notificationResult . message ,
workflow : eventLog . map (( e ) => ({
step : e . event ,
handler : e . sender ,
timestamp : e . timestamp ,
})),
};
} finally {
this . currentLog = null ;
}
}
}