Structural Complexity: Medium

Proxy in Go

Control access to another object by standing in front of it to add caching, authorization, rate limiting, or lazy loading.

The Problem

Some dependencies are expensive to call or should not be accessed directly every time. Remote object storage, payment providers, and search backends often need caching, access checks, or lazy connection setup before the real operation runs.

The Solution

Proxy keeps the same outward-facing contract as the real object, but interposes extra control before delegating. In Go, a proxy is often a wrapper struct that implements the same interface and adds caching, auth, throttling, or lazy initialization.

Structure

Proxy Pattern
Step 1 of 5

The Subject Interface

BlobReader is the stable contract: Read(key) ([]byte, error). The client depends on this interface — it never knows whether the real store or a proxy is behind it.

  • Subject interface: BlobReader is the stable contract callers use.
  • Real subject: S3BlobStore performs the underlying fetch.
  • Proxy: CachedBlobStore caches repeated reads before delegating.
  • Client: Reads through the interface and does not know whether the real object or proxy is behind it.

Implementation

This example fronts a blob store with a small caching proxy. The caller still depends on the BlobReader interface, but repeated reads avoid another simulated network fetch.

package main

import "fmt"

type BlobReader interface {
	GetObject(key string) (string, error)
}

type S3BlobStore struct{}

func (S3BlobStore) GetObject(key string) (string, error) {
	fmt.Printf("fetching %s from remote storage\n", key)
	return "contents for " + key, nil
}

Real-World Analogy

Think of a receptionist in front of a secure office. Visitors still ask for the same person, but the receptionist can verify identity, reject access, or answer simple questions from a cache before letting anyone through.

Pros and Cons

ProsCons
Adds access control, caching, or lazy loading without changing callers.Can hide latency, stale data, or access behavior behind an apparently simple call.
Preserves the same interface as the real subject.Adds moving parts that make debugging request paths harder.
Centralizes boundary policies in one place.Stops being a clean proxy if it changes semantics too much.

Best Practices

  • Keep the proxy contract identical to the real subject so callers stay unaware of the wrapper.
  • Use a proxy for access control or optimization concerns that belong at the boundary.
  • Invalidate or bound caches deliberately; stale proxy data is still a bug.
  • Do not stack so many proxies that debugging the request path becomes opaque.
  • If the wrapper adds optional behavior rather than access control, consider whether Decorator is the clearer term.

When to Use

  • Access to a dependency needs caching, authorization, throttling, or lazy initialization.
  • The real subject should remain untouched while callers keep the same interface.
  • You want to enforce boundary policies consistently in one place.

When NOT to Use

  • There is no meaningful control or optimization concern to add.
  • A one-off helper around a single call site would be simpler.
  • The wrapper changes the semantics so much that it is no longer the same subject.