git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/gof-facade-pattern" ~/.claude/skills/intense-visions-harness-engineering-gof-facade-pattern && rm -rf "$T"
agents/skills/claude-code/gof-facade-pattern/SKILL.mdGOF Facade Pattern
Provide a simplified interface to a complex subsystem to reduce coupling for clients.
When to Use
- A subsystem has grown complex and clients only use a fraction of its features
- You want to layer a clean public API over a messy legacy system
- You need to reduce coupling between clients and subsystem internals
- You're integrating multiple third-party libraries and want one coherent interface
Instructions
Classic facade hiding multiple subsystems:
// Complex subsystems — lots of moving parts class VideoEncoder { encode(filePath: string, format: 'mp4' | 'webm'): Promise<string> { console.log(`Encoding ${filePath} to ${format}`); return Promise.resolve(`${filePath}.${format}`); } } class ThumbnailExtractor { extract(videoPath: string, atSeconds: number): Promise<string> { console.log(`Extracting thumbnail at ${atSeconds}s from ${videoPath}`); return Promise.resolve(`${videoPath}-thumb.jpg`); } } class CDNUploader { upload(filePath: string, bucket: string): Promise<string> { console.log(`Uploading ${filePath} to ${bucket}`); return Promise.resolve(`https://cdn.example.com/${bucket}/${filePath}`); } } class MetadataStore { save(videoId: string, meta: Record<string, unknown>): Promise<void> { console.log(`Saving metadata for ${videoId}`); return Promise.resolve(); } } // Facade — single coherent interface over the subsystem class VideoProcessingFacade { private encoder = new VideoEncoder(); private thumbExtractor = new ThumbnailExtractor(); private uploader = new CDNUploader(); private metaStore = new MetadataStore(); async processUpload( rawFilePath: string, videoId: string, options: { format: 'mp4' | 'webm'; thumbnailAt: number } = { format: 'mp4', thumbnailAt: 5 } ): Promise<{ videoUrl: string; thumbnailUrl: string }> { // Orchestrate subsystems — client doesn't need to know any of this const encodedPath = await this.encoder.encode(rawFilePath, options.format); const thumbPath = await this.thumbExtractor.extract(encodedPath, options.thumbnailAt); const [videoUrl, thumbnailUrl] = await Promise.all([ this.uploader.upload(encodedPath, 'videos'), this.uploader.upload(thumbPath, 'thumbnails'), ]); await this.metaStore.save(videoId, { videoUrl, thumbnailUrl, processedAt: new Date() }); return { videoUrl, thumbnailUrl }; } } // Client code — clean, simple const videoFacade = new VideoProcessingFacade(); const result = await videoFacade.processUpload('/tmp/raw-upload.mov', 'vid-123'); console.log(result.videoUrl);
API service facade (common in backend apps):
// Instead of clients knowing about Prisma, Redis, and Stripe separately class UserAccountFacade { constructor( private readonly db: PrismaClient, private readonly cache: RedisClient, private readonly payments: Stripe, private readonly mailer: Mailer ) {} async createAccount(data: CreateAccountInput): Promise<UserAccount> { // Transaction across multiple systems const user = await this.db.user.create({ data: { email: data.email } }); const customer = await this.payments.customers.create({ email: data.email }); await this.db.user.update({ where: { id: user.id }, data: { stripeId: customer.id } }); await this.cache.set(`user:${user.id}`, JSON.stringify(user), 'EX', 300); await this.mailer.sendWelcome(user.email); return user; } async getUser(userId: string): Promise<UserAccount | null> { const cached = await this.cache.get(`user:${userId}`); if (cached) return JSON.parse(cached); return this.db.user.findUnique({ where: { id: userId } }); } }
Details
Facade vs. Adapter: Facade simplifies an interface — it's for you, the client. Adapter translates one interface to another — it's about compatibility. A Facade can internally use Adapters to talk to subsystems.
Facade vs. API Gateway: In microservices, an API Gateway is a Facade at the network level. It provides a unified entry point over multiple backend services. Same pattern, different layer.
Facade does not prevent direct subsystem access: The facade provides a simpler path but doesn't lock out direct use. Clients who need fine-grained control can still access subsystems directly. Document when the facade is insufficient.
Anti-patterns:
- God facade that exposes everything — a facade exposing 40 methods is just a bad subsystem with extra steps
- Facade that performs no orchestration — if it's just forwarding one call, the abstraction adds no value
- Facade that leaks subsystem types in its public API — clients shouldn't need to import subsystem classes to use the facade
Testability: The facade is the right seam for integration tests. Test the facade's contract. Test the individual subsystem components in isolation.
Source
refactoring.guru/design-patterns/facade
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.