All posts
Engineering 2026-01-12 · 6 min read

One Codebase, Twenty Sites: The Factory Pattern in Go

One Codebase, Twenty Sites: The Factory Pattern in Go

At W3 Engineers I inherited a project that needed to power 20+ white-label sites from one codebase. Each site had its own theme, payment provider, and content rules. The original code handled this with a growing thicket of conditionals:

if site == "alpha" {
    // ...
} else if site == "beta" {
    // ...
} // and on, and on

Every new site meant editing a dozen functions. That doesn't scale. The Factory pattern fixed it.

Define the contract

Start with an interface that captures what every site must do:

type SiteProvider interface {
    Theme() ThemeConfig
    Payment() PaymentGateway
    Render(page string) ([]byte, error)
}

Build the factory

A registry maps a site key to a constructor. New sites register themselves — no central switch statement to edit:

var registry = map[string]func() SiteProvider{}

func Register(key string, ctor func() SiteProvider) {
    registry[key] = ctor
}

func New(key string) (SiteProvider, error) {
    ctor, ok := registry[key]
    if !ok {
        return nil, fmt.Errorf("unknown site: %q", key)
    }
    return ctor(), nil
}

Each site lives in its own file and registers in init():

func init() {
    Register("alpha", func() SiteProvider { return &AlphaSite{} })
}

Why this won

Before After
Conditionals everywhere One interface, many impls
Edit core for each new site Drop in a file, call Register
Hard to unit test Mock the interface trivially

The result was a ~30% drop in production bugs once paired with a proper Go test suite — because each provider could be tested in isolation:

func TestAlphaPayment(t *testing.T) {
    site, _ := New("alpha")
    if site.Payment() == nil {
        t.Fatal("expected a payment gateway")
    }
}

The best abstractions don't just remove duplication — they remove the fear of adding the 21st site.

When not to reach for it

If you genuinely have two cases that will never grow, a plain switch is clearer than a registry. The Factory earns its keep when the set of variants is open and expected to expand.

More posts