Skip to content

// Design Patterns · 06

Adapter

Incompatible interfaceleads toTranslate at the seam

Make one interface look like the one your code expects.

How it fits together

Adapter structure«client»OrderServicePlaceOrderAsync()«interface»PaymentGatewayChargeAsync(request)VendorPaymentAdaptermaps requestsmaps errors«external»Vendor SDKChargeCard(...)translates
uses / callsimplements
  • OrderService uses / calls PaymentGateway
  • VendorPaymentAdapter implements PaymentGateway
  • VendorPaymentAdapter uses / calls Vendor SDK (translates)

Spot it

  • Vendor SDK types leak into domain code
  • Swapping a supplier means a big rewrite
  • Mapping code duplicated at every call site

It costs

Another mapping layer to keep in sync with the thing it wraps.

In code

public sealed record ChargeRequest(decimal Amount, string Currency, string OrderId);​public sealed record ChargeResult(bool Approved, string? DeclineCode);​public interface IPaymentGateway{    Task<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct);}​public sealed class VendorPaymentAdapter(IVendorClient vendor) : IPaymentGateway{    public async Task<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct)    {        try        {            var charge = new VendorCharge(request.Amount, request.Currency, request.OrderId);            var response = await vendor.ChargeCardAsync(charge, ct);            return new ChargeResult(response.Approved, response.DeclineCode);        }        catch (VendorException ex)        {            return new ChargeResult(false, ex.Code);        }    }}
Go deeperThe full explanation and what to ask for · 1 min readExpand

An adapter converts one interface into another that the caller already expects. In object-oriented languages it usually holds the adaptee and translates calls, types and errors.

A leaky adapter that passes vendor exceptions or types straight through has not done its job. One that starts making business decisions has become a service in disguise.

Ports and adapters (hexagonal architecture) is this pattern at the boundary of the app; an anti-corruption layer is the same idea between bounded contexts. Facade simplifies many interfaces; adapter converts one.

Ask for it

  • adapter
  • target interface
  • adaptee
  • port
  • anti-corruption layer

Our domain calls the payment vendor SDK directly. Define a PaymentGateway port with our own request and result types, implement a VendorPaymentAdapter in the infrastructure layer that maps to the SDK and its errors, and register it in the DI container.