Introduction to Proxy Pattern
Proxy provides a surrogate or placeholder for another object to control access to it.
The Problem
Direct access to external APIs can be problematic:
typescript1// Direct API calls without control2class ShippingClient {3 async getRates(request: RateRequest): Promise<Rate[]> {4 // Every call hits the external API5 // No rate limiting6 // No caching7 // No logging8 // No access control9 return await fetch(`${API_URL}/rates`, { body: JSON.stringify(request) });10 }11}1213// Issues:14// - API overload leads to rate limiting/bans15// - Redundant calls for same data16// - No visibility into usage17// - No protection from abuse
The Solution: Proxy
Add a layer of control:
typescript1interface ShippingService {2 getRates(request: RateRequest): Promise<Rate[]>;3}45// Real subject6class CarrierAPI implements ShippingService {7 async getRates(request: RateRequest): Promise<Rate[]> {8 return await fetch(`${API_URL}/rates`, { body: JSON.stringify(request) });9 }10}1112// Proxy with rate limiting13class RateLimitedProxy implements ShippingService {14 private lastCall = 0;15 private minInterval = 1000; // 1 second between calls1617 constructor(private real: ShippingService) {}1819 async getRates(request: RateRequest): Promise<Rate[]> {20 const now = Date.now();21 const elapsed = now - this.lastCall;2223 if (elapsed < this.minInterval) {24 await this.sleep(this.minInterval - elapsed);25 }2627 this.lastCall = Date.now();28 return this.real.getRates(request);29 }3031 private sleep(ms: number): Promise<void> {32 return new Promise(resolve => setTimeout(resolve, ms));33 }34}3536// Usage - transparent to client37const service: ShippingService = new RateLimitedProxy(new CarrierAPI());38const rates = await service.getRates(request);
Pattern Structure
1┌─────────────────────┐2│ Client │3└──────────┬──────────┘4 │5 ▼6┌─────────────────────┐7│ Subject │8│ (interface) │9│─────────────────────│10│ + request() │11└──────────┬──────────┘12 │13 ┌─────┴─────┐14 │ │15┌────▼────┐ ┌────▼────┐16│ Proxy │ │ Real │17│─────────│ │ Subject │18│- real │─│─────────│19│+request()│ │+request()│20└─────────┘ └─────────┘
Types of Proxies
Virtual Proxy (Lazy Loading)
typescript1class LazyCarrierProxy implements ShippingService {2 private real: CarrierAPI | null = null;34 async getRates(request: RateRequest): Promise<Rate[]> {5 if (!this.real) {6 // Expensive initialization only when needed7 this.real = await this.initializeCarrier();8 }9 return this.real.getRates(request);10 }1112 private async initializeCarrier(): Promise<CarrierAPI> {13 // Load credentials, establish connection, etc.14 return new CarrierAPI();15 }16}
Protection Proxy (Access Control)
typescript1class SecuredCarrierProxy implements ShippingService {2 constructor(3 private real: ShippingService,4 private authService: AuthService5 ) {}67 async getRates(request: RateRequest): Promise<Rate[]> {8 const user = await this.authService.getCurrentUser();910 if (!user.hasPermission('shipping:read')) {11 throw new UnauthorizedError('Cannot access shipping rates');12 }1314 return this.real.getRates(request);15 }16}
Caching Proxy
typescript1class CachingCarrierProxy implements ShippingService {2 private cache = new Map<string, { data: Rate[]; expires: number }>();34 constructor(5 private real: ShippingService,6 private ttl: number = 300000 // 5 minutes7 ) {}89 async getRates(request: RateRequest): Promise<Rate[]> {10 const key = this.getCacheKey(request);11 const cached = this.cache.get(key);1213 if (cached && cached.expires > Date.now()) {14 return cached.data;15 }1617 const rates = await this.real.getRates(request);18 this.cache.set(key, { data: rates, expires: Date.now() + this.ttl });19 return rates;20 }2122 private getCacheKey(request: RateRequest): string {23 return JSON.stringify(request);24 }25}
Logging Proxy
typescript1class LoggingCarrierProxy implements ShippingService {2 constructor(3 private real: ShippingService,4 private logger: Logger5 ) {}67 async getRates(request: RateRequest): Promise<Rate[]> {8 const start = Date.now();9 this.logger.info('Getting rates', { request });1011 try {12 const rates = await this.real.getRates(request);13 this.logger.info('Rates received', {14 duration: Date.now() - start,15 rateCount: rates.length16 });17 return rates;18 } catch (error) {19 this.logger.error('Rate lookup failed', { error, duration: Date.now() - start });20 throw error;21 }22 }23}
When to Use Proxy
| Type | Use Case |
|---|---|
| Virtual | Expensive object creation |
| Protection | Access control |
| Caching | Repeated expensive operations |
| Logging | Audit trail, debugging |
| Rate Limiting | API protection |
| Remote | Network transparency |
Summary
Proxy controls access to an object without clients knowing. It's transparent - same interface as the real object - but adds valuable behaviors like caching, logging, and protection.