Course contentsShow
← C#
Lesson 1709 of 1,90815. Software Quality and TestingFree lesson

Decoupling Infrastructure via Ports and Adapters

Structure projects to ensure core business logic remains independent of IO.

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.