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.