The Problem
Real systems often need to integrate with libraries or vendor SDKs that expose awkward method names, odd units, or transport-shaped return types. If that external shape leaks through the application, every caller becomes coupled to it and replacement gets expensive.
The Solution
Adapter translates one interface into another. In Go, adapters are usually small structs that wrap an external dependency and implement the narrow application interface the rest of the code already expects. That keeps the translation boundary in one place.
Structure
- Target interface:
ShippingEstimatoris what the application wants to depend on. - Adaptee:
LegacyCarrierClientexposes an incompatible pricing API. - Adapter:
CarrierAdapterconverts between the vendor API and the app-facing interface. - Client:
CheckoutServiceworks only with the target interface.
Implementation
This example adapts a legacy shipping client that prices shipments directly in floating-point dollars. The checkout service receives a stable ShippingEstimator interface and does not know anything about the vendor-specific method names.
package main
type LegacyCarrierClient struct{}
func (LegacyCarrierClient) QuoteShipment(destination string, weightGrams int) float64 {
base := 3.50
if destination != "local" {
base = 9.00
}
return base + (float64(weightGrams) / 1000.0 * 1.20)
} Best Practices
- Hide translation logic at the boundary instead of spreading it across callers.
- Define the target interface from your application needs, not from the external SDK surface.
- Validate and normalize odd external units inside the adapter.
- Keep adapters thin. If orchestration grows large, you may want a Facade or dedicated service instead.
- Test the adapter at the boundary because translation bugs often look small but break entire flows.
When to Use
- You need to integrate a dependency with an interface that does not match your application.
- You want to keep external library details from leaking through the codebase.
- Replacement or mocking becomes easier if the boundary is normalized first.
When NOT to Use
- The external API already matches the shape you need.
- You are only renaming methods without reducing any real coupling.
- The adapter would hide a poor domain model instead of fixing the boundary.