Fundamentals 13 min read

Applying the Adapter Pattern in Go: From Payment Systems to Multi‑Cloud Management and Model Training Platforms

This article explains the Adapter design pattern, illustrates its real‑world analogy, demonstrates a complete Go implementation for payment processing, and shows how the pattern can be used in production scenarios such as a multi‑cloud management platform and a model‑training service, highlighting its role in unifying incompatible interfaces.

Go Programming World
Go Programming World
Go Programming World
Applying the Adapter Pattern in Go: From Payment Systems to Multi‑Cloud Management and Model Training Platforms

The Adapter pattern is one of the 23 classic design patterns and belongs to the behavioral category; it enables incompatible interfaces to work together by introducing an adapter class that wraps the legacy interface and presents a compatible one.

Adapter Pattern

According to the book *Design Patterns: Elements of Reusable Object‑Oriented Software*, the intent of the Adapter is to convert the interface of a class into another interface that clients expect, allowing classes that could not otherwise collaborate to do so.

Real‑world analogy

Power adapters for phones and computers convert high‑voltage AC electricity (e.g., 220 V) into low‑voltage DC (e.g., 9 V 3 A) so that devices can be charged safely, illustrating the essence of the Adapter pattern.

Adapter pattern in Go

Consider a payment system with a unified PaymentProcessor interface:

type PaymentProcessor interface {
    Pay(amount float64)
}

An existing OldPaymentSystem implements Pay directly:

type OldPaymentSystem struct{}

func (ops *OldPaymentSystem) Pay(amount float64) {
    fmt.Printf("Processing payment of %.2f using old payment system\n", amount)
}

When a new payment system NewPaymentSystem provides a method MakePayment instead of Pay , it does not satisfy PaymentProcessor . Direct assignment results in a compilation error.

To bridge the gap, an adapter NewPaymentAdapter is introduced:

type NewPaymentAdapter struct {
    NewSystem *NewPaymentSystem
}

func (npa *NewPaymentAdapter) Pay(amount float64) {
    npa.NewSystem.MakePayment(amount)
}

Now the client can use the new system through the same processor.Pay(...) call, demonstrating how the adapter translates the incompatible interface.

Production practice

In a multi‑cloud management platform written in Go, different cloud providers (Aliyun, Tencent, etc.) expose distinct SDKs. An interface Provider is defined to unify operations such as RunInstance . Each provider implements this interface via an adapter (e.g., AliCloudProvider , TencentCloudProvider ), allowing the platform to manage instances across clouds with a single code path.

type Provider interface {
    Type() ProviderType
    RunInstance(r *RunInstanceRequest) (*RunInstanceResponse, error)
    // ... other methods
}

A factory function NewProvider returns the appropriate adapter based on ProviderType , and client code simply calls p.RunInstance(...) regardless of the underlying cloud SDK.

Another example is a model‑training platform that originally deployed inference services via Kubernetes Deployment resources. To switch to the OpenPAI platform without rewriting existing logic, a Predictor interface is defined and an OpenPAIAdapter implements it, converting Kubernetes deployment calls into OpenPAI RESTful API requests.

type Predictor interface {
    Deploy(deployment *appsv1.Deployment) error
    Scale(namespace, name string, replicas int) error
    Delete(namespace, name string) error
    // ...
}

The adapter pattern thus enables minimal code changes while supporting alternative back‑ends, and the same approach can be extended to other platforms by creating additional adapters.

Summary

The Adapter pattern serves as a post‑hoc compensation mechanism that unifies disparate interfaces, making it valuable for integrating third‑party services, handling multi‑cloud environments, or swapping underlying systems with limited impact on existing code.

Typical scenarios include adapting legacy payment APIs, consolidating cloud provider SDKs, and abstracting model‑deployment pipelines, all of which are illustrated in the examples above.

design patternssoftware architectureCloud NativeKubernetesGoAdapter Pattern
Go Programming World
Written by

Go Programming World

Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.