Structural Design Patterns: Adapters, Proxies, Facades & Trees
Structural patterns solve the friction of connecting incompatible interfaces, managing expensive resources, and representing hierarchical systems. This article covers the Adapter as an anti-corruption layer against vendor SDK churn, ES native Proxy for transparent interception, Facade for subsystem simplification, and Composite for recursive fee calculation trees.
In TypeScript 5+, OOP is an architectural contract, not an inheritance tree: enforce domain invariants at compile time, encapsulate mutation strictly within aggregates, and invert dependencies so high-level business policy never couples to execution details. Structural patterns enforce the last phrase at a practical level — they are the anti-corruption layers, transparent interceptors, and simplified facades that insulate high-level policy from vendor SDK churn, cross-cutting infrastructure concerns, and deeply recursive domain structures.
When Stripe updates their API (they did with payment_method_configuration in 2024), every OrderFulfillmentService, RefundService, SubscriptionService, and every other class that imported Stripe directly must be audited and updated. The domain is coupled to the vendor's version lifecycle.
Anti-Corruption Layer (ACL) is the DDD term for this pattern. In large systems with multiple bounded contexts, the ACL is a dedicated module (directory) that owns all vendor-specific translation logic. No file outside the ACL directory imports vendor SDKs.
TypeScript inherits ECMAScript's Proxy API — a mechanism to wrap any object with custom traps that intercept get, set, has, deleteProperty, and 10 other fundamental operations. This is fundamentally different from a wrapping Decorator pattern: a Proxy intercepts at the object model level, not at the method level.
// ✅ Transparent logging proxy — wraps any gateway without changing its interfacefunction createLoggingProxy<T extendsobject>(target: T, label: string): T {
returnnewProxy(target, {
get(obj: T, prop: string): unknown {
const value = (obj asRecord<string, unknown>)[prop];
if (typeof value !== 'function') return value;
// Wrap the method call with timing and loggingreturnasyncfunction (this: unknown, ...args: unknown[]) {
const start = Date.now();
console.log(`[${label}] ${String(prop)} called`);
try {
const result = await (value asFunction).apply(obj, args);
console.log(`[${label}] ${String(prop)} completed in ${Date.now() - start}ms`);
return result;
} catch (err) {
console.error(`[${label}] ${String(prop)} failed after ${Date.now() - start}ms:`, err);
throw err;
}
};
},
});
}
constgateway: IPaymentGateway = newStripeAdapter(process.env.STRIPE_KEY!);
const loggedGateway = createLoggingProxy(gateway, 'StripeAdapter');
// All calls through loggedGateway are automatically logged — zero change to StripeAdapter
Crucial Requirement
Native Proxy cannot be polyfilled. It requires a runtime that supports the ES2015 Proxy global natively — all Node.js versions ≥ 6 and all modern browsers satisfy this requirement. But in interview sandboxes targeting very old environments, use a decorator-style wrapper class instead.
A Facade provides a simplified interface to a complex subsystem. In the fintech domain, order fulfillment involves a payment gateway, inventory service, shipping service, and notification service — coordinating all four in a caller is complex. A FulfillmentFacade simplifies this to a single method.
Two-panel structural pattern map. LEFT panel labeled 'Without Facade: Direct Subsystem Coupling' in red: shows 'OrderController' box with four separate red a…
The Composite pattern lets clients treat individual objects and groups of objects uniformly through a common interface. In fintech, pricing rules are naturally hierarchical: a CompoundFeeRule contains multiple child rules; each child may itself be a compound rule. The total fee is the recursive sum.
In Part 10, we cover the behavioral patterns — how objects communicate, delegate, and respond to events. Part 10: Behavioral Patterns: Strategy, Observer & Command builds a type-safe event bus with generic discriminated union listeners, a Strategy registry that enforces exhaustive provider coverage, and a Command pattern with undo/redo queues for financial ledger operations.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.