In this lesson, you will learn how to protect your core business logic from infrastructure volatility by implementing the Ports and Adapters pattern (also known as Hexagonal Architecture).
The Problem: Infrastructure Leakage
When your business logic directly references infrastructure—such as a specific database context, a cloud-native SDK, or an external API client—your domain becomes tightly coupled to the outside world. This makes testing difficult: you end up writing integration tests for simple logic because you cannot easily swap out the "heavy" infrastructure dependencies.
The Solution: Ports and Adapters
The goal is to invert the dependency. Instead of your logic calling an infrastructure component, the logic defines a Port (an interface). The infrastructure layer then implements an Adapter that satisfies that port.
Think of it like a USB hub. Your computer (the core logic) doesn't care if you plug in a printer or a hard drive (the infrastructure), as long as they speak the USB protocol (the interface).
Consider this example where we decouple a PaymentService:
// The Port: Defines what the business needs, not how it's done
public interface IPaymentGateway
{
Task<bool> ChargeAsync(decimal amount);
}
// The Business Logic: Depends only on the abstraction
public class CheckoutService
{
private readonly IPaymentGateway _gateway;
public CheckoutService(IPaymentGateway gateway) => _gateway = gateway;
public async Task ProcessOrder(decimal amount)
{
// Business rules here are agnostic to Stripe/PayPal/etc.
await _gateway.ChargeAsync(amount);
}
}
By defining IPaymentGateway, the CheckoutService remains pure. When testing, you don't need a real payment provider; you simply inject a mock implementation of the port. The infrastructure code lives in a separate project or assembly, where it implements the adapter:
// The Adapter: Infrastructure-specific implementation
public class StripeAdapter : IPaymentGateway
{
public async Task<bool> ChargeAsync(decimal amount)
{
// Stripe-specific SDK calls go here
return true;
}
}
By moving infrastructure to the edge of your system, your core domain becomes highly testable, technology-agnostic, and significantly easier to refactor as infrastructure requirements change.
Key Takeaway: By defining Ports as interfaces and implementing them via Infrastructure Adapters, you isolate your business domain from external volatility, ensuring your logic is testable without invoking real-world IO.