Real-World Chain Examples
HTTP Request Pipeline
typescript1interface HttpContext {2 request: Request;3 response: Response;4 user?: User;5 data: Map<string, any>;6}78abstract class HttpMiddleware {9 protected next: HttpMiddleware | null = null;1011 setNext(middleware: HttpMiddleware): HttpMiddleware {12 this.next = middleware;13 return middleware;14 }1516 abstract handle(context: HttpContext): Promise<void>;1718 protected async callNext(context: HttpContext): Promise<void> {19 if (this.next) {20 await this.next.handle(context);21 }22 }23}2425class AuthenticationMiddleware extends HttpMiddleware {26 async handle(context: HttpContext): Promise<void> {27 const token = context.request.headers.get('Authorization');2829 if (token) {30 context.user = await this.validateToken(token);31 }3233 await this.callNext(context);34 }35}3637class AuthorizationMiddleware extends HttpMiddleware {38 async handle(context: HttpContext): Promise<void> {39 if (!context.user) {40 context.response.status = 401;41 return;42 }4344 if (!this.hasPermission(context.user, context.request.path)) {45 context.response.status = 403;46 return;47 }4849 await this.callNext(context);50 }51}5253class RateLimitMiddleware extends HttpMiddleware {54 async handle(context: HttpContext): Promise<void> {55 const key = context.user?.id ?? context.request.ip;5657 if (await this.isRateLimited(key)) {58 context.response.status = 429;59 return;60 }6162 await this.callNext(context);63 }64}
Support Ticket Escalation
typescript1interface Ticket {2 id: string;3 priority: 'low' | 'medium' | 'high' | 'critical';4 category: string;5 description: string;6 assignee?: string;7}89abstract class SupportHandler {10 protected next: SupportHandler | null = null;1112 setNext(handler: SupportHandler): SupportHandler {13 this.next = handler;14 return handler;15 }1617 handle(ticket: Ticket): Ticket {18 if (this.canHandle(ticket)) {19 return this.process(ticket);20 }2122 if (this.next) {23 return this.next.handle(ticket);24 }2526 return { ...ticket, assignee: 'unassigned' };27 }2829 protected abstract canHandle(ticket: Ticket): boolean;30 protected abstract process(ticket: Ticket): Ticket;31}3233class Tier1Support extends SupportHandler {34 protected canHandle(ticket: Ticket): boolean {35 return ticket.priority === 'low' &&36 ['billing', 'account'].includes(ticket.category);37 }3839 protected process(ticket: Ticket): Ticket {40 return { ...ticket, assignee: 'tier1-team' };41 }42}4344class Tier2Support extends SupportHandler {45 protected canHandle(ticket: Ticket): boolean {46 return ticket.priority === 'medium' ||47 ticket.category === 'technical';48 }4950 protected process(ticket: Ticket): Ticket {51 return { ...ticket, assignee: 'tier2-team' };52 }53}5455class EngineeringSupport extends SupportHandler {56 protected canHandle(ticket: Ticket): boolean {57 return ticket.priority === 'critical' ||58 ticket.category === 'bug';59 }6061 protected process(ticket: Ticket): Ticket {62 return { ...ticket, assignee: 'engineering-team' };63 }64}
Discount Calculator
typescript1interface Order {2 items: OrderItem[];3 customer: Customer;4 subtotal: number;5 discounts: Discount[];6}78abstract class DiscountHandler {9 protected next: DiscountHandler | null = null;1011 setNext(handler: DiscountHandler): DiscountHandler {12 this.next = handler;13 return handler;14 }1516 apply(order: Order): Order {17 const discount = this.calculate(order);1819 if (discount) {20 order = {21 ...order,22 discounts: [...order.discounts, discount]23 };24 }2526 if (this.next) {27 return this.next.apply(order);28 }2930 return order;31 }3233 protected abstract calculate(order: Order): Discount | null;34}3536class LoyaltyDiscount extends DiscountHandler {37 protected calculate(order: Order): Discount | null {38 if (order.customer.loyaltyTier === 'gold') {39 return { type: 'loyalty', amount: order.subtotal * 0.1, description: '10% Gold Member' };40 }41 if (order.customer.loyaltyTier === 'silver') {42 return { type: 'loyalty', amount: order.subtotal * 0.05, description: '5% Silver Member' };43 }44 return null;45 }46}4748class VolumeDiscount extends DiscountHandler {49 protected calculate(order: Order): Discount | null {50 if (order.subtotal >= 500) {51 return { type: 'volume', amount: order.subtotal * 0.15, description: '15% Volume Discount' };52 }53 if (order.subtotal >= 200) {54 return { type: 'volume', amount: order.subtotal * 0.08, description: '8% Volume Discount' };55 }56 return null;57 }58}5960class PromotionalDiscount extends DiscountHandler {61 constructor(private activeCodes: Map<string, number>) {62 super();63 }6465 protected calculate(order: Order): Discount | null {66 const code = order.customer.promoCode;67 if (code && this.activeCodes.has(code)) {68 const percent = this.activeCodes.get(code)!;69 return { type: 'promo', amount: order.subtotal * percent, description: `${percent * 100}% Promo` };70 }71 return null;72 }73}
Testing Chains
typescript1describe('DiscountChain', () => {2 let chain: DiscountHandler;34 beforeEach(() => {5 const loyalty = new LoyaltyDiscount();6 const volume = new VolumeDiscount();7 const promo = new PromotionalDiscount(new Map([['SAVE10', 0.10]]));89 loyalty.setNext(volume).setNext(promo);10 chain = loyalty;11 });1213 it('applies all applicable discounts', () => {14 const order = {15 subtotal: 500,16 customer: { loyaltyTier: 'gold', promoCode: 'SAVE10' },17 discounts: []18 };1920 const result = chain.apply(order);2122 expect(result.discounts).toHaveLength(3); // loyalty + volume + promo23 });2425 it('skips inapplicable discounts', () => {26 const order = {27 subtotal: 50,28 customer: { loyaltyTier: 'none' },29 discounts: []30 };3132 const result = chain.apply(order);3334 expect(result.discounts).toHaveLength(0);35 });36});
Summary
Chain of Responsibility is common in:
- HTTP middleware pipelines
- Support ticket routing
- Discount/pricing calculations
- Validation workflows
- Event handling systems