Unlock Go’s Structural & Architectural Patterns with Real-World Code

This guide explores key Go structural and architectural design patterns—Adapter, Decorator, Proxy, Bridge, Composite, Flyweight, Facade, Pipeline & Filter, Event Bus, Microkernel, and Dependency Injection—providing definitions, core ideas, Go implementations, and practical use cases to build scalable, maintainable systems.

Code Wrench
Code Wrench
Code Wrench
Unlock Go’s Structural & Architectural Patterns with Real-World Code

Adapter Pattern — Compatibility with Legacy Interfaces

Definition: Convert an existing interface into one required by the client, allowing incompatible components to collaborate.

Core Idea: Introduce an interface‑conversion layer.

type Target interface {
    Request()
}

type Adaptee struct{}

func (a *Adaptee) SpecificRequest() { fmt.Println("Adaptee special request") }

type Adapter struct {
    adaptee *Adaptee
}

func (ad *Adapter) Request() {
    ad.adaptee.SpecificRequest()
}

Typical use cases: wrapping old third‑party libraries, normalising data formats across services.

Adapter is often the first choice for system integration because it avoids large‑scale refactoring.

Decorator Pattern — Dynamically Adding Behaviour

Definition: Attach additional responsibilities to an object without altering its structure.

Core Idea: Prefer composition over inheritance.

type Notifier interface { Send(msg string) }

type EmailNotifier struct{}

func (e *EmailNotifier) Send(msg string) { fmt.Println("Send email:", msg) }

type SMSDecorator struct { wrappee Notifier }

func (s *SMSDecorator) Send(msg string) {
    s.wrappee.Send(msg)
    fmt.Println("Send SMS:", msg)
}

Use case: a notification pipeline that sends email, then SMS, then push notifications.

Features can be stacked indefinitely, providing flexible extension.

Proxy Pattern — Controlling Access and Adding Enhancements

Definition: Provide a surrogate object that controls access to the real subject.

Core Idea: Intercept calls without modifying the original object.

type Service interface { Request() }

type RealService struct{}

func (r *RealService) Request() { fmt.Println("Real service request") }

type Proxy struct { real *RealService }

func (p *Proxy) Request() {
    fmt.Println("Proxy checks permission")
    p.real.Request()
}

Typical scenarios: RPC wrappers, caching proxies, security checks, lazy loading.

Commonly used to add logging or caching layers to gRPC calls.

Bridge Pattern — Decoupling Abstraction from Implementation

Definition: Separate an abstraction from its concrete implementation so both can evolve independently.

Core Idea: Two orthogonal dimensions of extension.

type MessageSender interface { Send(content string) }

type EmailSender struct{}

func (e *EmailSender) Send(content string) { fmt.Println("Send email:", content) }

type SMSSender struct{}

func (s *SMSSender) Send(content string) { fmt.Println("Send SMS:", content) }

type Message struct { sender MessageSender }

func (m *Message) Send(content string) { m.sender.Send(content) }

Application: notification systems where message types (alert, marketing) and channels (email, SMS, webhook) can be extended independently.

Bridge enables unlimited dimensional expansion.

Composite Pattern — Managing Tree‑Like Structures

Definition: Compose objects into tree structures to represent part‑whole hierarchies.

Core Idea: Uniform interface for leaf and container nodes.

type Component interface { Operation() }

type Leaf struct { name string }

func (l *Leaf) Operation() { fmt.Println("Leaf:", l.name) }

type Composite struct { children []Component }

func (c *Composite) Add(child Component) { c.children = append(c.children, child) }

func (c *Composite) Operation() {
    for _, child := range c.children {
        child.Operation()
    }
}

Use cases: file‑system directories, organisational charts, menu systems.

Recursion provides a simple way to treat individual elements and whole structures uniformly.

Flyweight Pattern — Sharing Objects to Reduce Memory Usage

Definition: Use sharing to support large numbers of fine‑grained objects efficiently.

Core Idea: Combine an object pool with internal state sharing.

type Flyweight struct { name string }

type FlyweightFactory struct { pool map[string]*Flyweight }

func (f *FlyweightFactory) Get(name string) *Flyweight {
    if v, ok := f.pool[name]; ok {
        return v
    }
    fw := &Flyweight{name: name}
    f.pool[name] = fw
    return fw
}

Typical examples: bullet pools in games, connection pools, cache pools.

Go's sync.Pool can implement the flyweight mechanism, reducing GC pressure.

Facade Pattern — Providing a Unified Interface

Definition: Offer a single, simplified interface to a complex subsystem.

Core Idea: Encapsulate subsystem complexity.

type Auth struct{}

func (a *Auth) Check() { fmt.Println("Validate identity") }

type Logger struct{}

func (l *Logger) Log() { fmt.Println("Record log") }

type API struct { auth *Auth; logger *Logger }

func (a *API) Serve() {
    a.auth.Check()
    a.logger.Log()
    fmt.Println("Business logic completed")
}

Applications: microservice gateways, SDKs with a simplified entry point.

Pipeline & Filter Pattern — Concurrent Stream Processing in Go

Definition: Split data processing into independent stages, each running in its own goroutine and linked by channels.

Core Idea: Stream processing combined with high‑concurrency decoupling.

func Filter(input <-chan int, fn func(int) bool) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for v := range input {
            if fn(v) {
                out <- v
            }
        }
    }()
    return out
}

Use cases: log analysis, data cleaning, ETL pipelines, real‑time monitoring streams.

Go's channels make this pattern a powerful tool for building data‑flow systems.

Event Bus Pattern — Decoupled Component Communication

Definition: Event‑driven publish‑subscribe mechanism that lets modules communicate without direct references.

Core Idea: Pub‑sub + asynchronous dispatch.

type EventBus struct { subs map[string][]func(interface{}) }

func (bus *EventBus) Subscribe(topic string, fn func(interface{})) {
    bus.subs[topic] = append(bus.subs[topic], fn)
}

func (bus *EventBus) Publish(topic string, data interface{}) {
    for _, fn := range bus.subs[topic] {
        go fn(data)
    }
}

Applications: event‑driven microservices, game event systems, plugin communication.

Can be backed by channels, Kafka, or other message queues for stronger guarantees.

Microkernel Architecture — Plugin‑Based Core System

Definition: Separate a stable core from extensible plugin modules.

Core Idea: Core stability combined with plugin decoupling.

Core module defines interfaces and provides the runtime framework.

Plugins implement those interfaces and are loaded dynamically via registration.

type Plugin interface { Name() string; Execute() }

type Kernel struct { plugins []Plugin }

func (k *Kernel) Register(p Plugin) { k.plugins = append(k.plugins, p) }

func (k *Kernel) RunAll() { for _, p := range k.plugins { p.Execute() } }

Use cases: IDE plugin systems, monitoring agents, API gateway extensions.

Go's interfaces and reflection make it well‑suited for building microkernel architectures.

Dependency Injection — Decoupling Dependencies

Definition: Objects receive required collaborators from an external container rather than constructing them themselves.

Core Idea: Inversion of Control (IoC).

type Repository interface { Save(data string) }

type FileRepo struct{}

func (r *FileRepo) Save(data string) { fmt.Println("Save to file:", data) }

type Service struct { repo Repository }

func NewService(repo Repository) *Service { return &Service{repo: repo} }

Typical scenario: web services injecting repositories, configuration, and logging components.

Popular Go DI libraries include wire , fx , and dig .

Summary

Structural patterns (Adapter, Decorator, Proxy, Bridge, Composite, Flyweight, Facade) provide decoupling, reuse, and memory efficiency through interfaces, composition, and embedding.

Architectural patterns (Pipeline & Filter, Event Bus, Microkernel, Dependency Injection) enable extensibility and modularity using goroutines, channels, plugin mechanisms, and IoC containers.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsGoStructural Patternsarchitectural patterns
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

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.