From harness-claude
Implements GOF Facade pattern in TypeScript to provide simplified interfaces over complex subsystems like video encoders/uploaders or backend services with Prisma/Redis/Stripe. Use to reduce client coupling.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Provide a simplified interface to a complex subsystem to reduce coupling for clients.
Guides JavaScript Facade pattern to simplify complex subsystems with unified high-level APIs. Use for grouping frequent related calls and hiding implementation details.
Generates Facade pattern classes and unit tests for PHP 8.4 to simplify interfaces for complex subsystems like order processing, notifications, or legacy APIs.
Designs microservices architectures using patterns for service boundaries, synchronous/asynchronous communication, data management, and resilience. For decomposing monoliths and building distributed systems.
Share bugs, ideas, or general feedback.
Provide a simplified interface to a complex subsystem to reduce coupling for clients.
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 } });
}
}
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:
Testability: The facade is the right seam for integration tests. Test the facade's contract. Test the individual subsystem components in isolation.
refactoring.guru/design-patterns/facade