Overview
Factory Method is a creational design pattern used to centralize object creation and avoid constructor leakage in service logic. In real backend services, this pattern helps teams keep modules decoupled while still shipping features quickly.
Problem It Solves
Without Factory Method, teams often mix orchestration, external integration, and domain rules in one place. That leads to high coupling, difficult testing, and expensive refactoring.
5W + 1H
What
Factory Method is a pattern that structures collaboration between components to solve a recurring design problem.
Why
To increase maintainability, isolate change, and enforce clear boundaries between policy and implementation detail.
Who
Backend engineers, platform engineers, and API teams working with integrations and evolving business logic.
When
Use Factory Method when your service complexity is growing and one responsibility starts bleeding into unrelated modules.
Where
Apply it in service boundaries such as application use cases, integration adapters, workflow orchestration, and domain policies.
How
Define stable ports first, then implement adapters and wire them through use-case orchestration.
Go Implementation (Step-by-step)
1) Port / interface definitions
package ports
import "context"
type FactoryMethodInput struct {
RequestID string
Payload map[string]any
}
type FactoryMethodOutput struct {
Status string
Data map[string]any
}
type FactoryMethodPort interface {
Execute(ctx context.Context, in FactoryMethodInput) (FactoryMethodOutput, error)
}2) Adapter / implementation
package adapters
import (
"context"
"fmt"
"myapp/internal/ports"
)
type FactoryMethodAdapter struct {
providerName string
}
func NewFactoryMethodAdapter(providerName string) *FactoryMethodAdapter {
return &FactoryMethodAdapter{providerName: providerName}
}
func (a *FactoryMethodAdapter) Execute(ctx context.Context, in ports.FactoryMethodInput) (ports.FactoryMethodOutput, error) {
if in.RequestID == "" {
return ports.FactoryMethodOutput{}, fmt.Errorf("request id is required")
}
return ports.FactoryMethodOutput{
Status: "ok",
Data: map[string]any{
"pattern": "Factory Method",
"provider": a.providerName,
},
}, nil
}3) Use-case / main wiring
package main
import (
"context"
"fmt"
"log"
"myapp/internal/adapters"
"myapp/internal/ports"
)
func run(ctx context.Context, svc ports.FactoryMethodPort) error {
out, err := svc.Execute(ctx, ports.FactoryMethodInput{
RequestID: "REQ-1001",
Payload: map[string]any{"source": "api"},
})
if err != nil {
return err
}
fmt.Println("status:", out.Status, "pattern:", out.Data["pattern"])
return nil
}
func main() {
ctx := context.Background()
service := adapters.NewFactoryMethodAdapter("default-provider")
if err := run(ctx, service); err != nil {
log.Fatal(err)
}
}Suggested Project Structure
cmd/
api/
main.go
internal/
domain/
factory-method-go/
model.go
policy.go
application/
factory-method-go/
usecase.go
ports/
factory-method-go_port.go
adapters/
inbound/
http/
factory-method-go_handler.go
outbound/
factory-method-go_adapter.go
infrastructure/
config/
loader.go
persistence/
repository.goClean/Hexagonal Placement
- Domain: keeps pure business rules and entities related to Factory Method usage.
- Application (Use Case): orchestrates request flow and coordinates domain + ports.
- Ports: defines stable contracts (
FactoryMethodPort) that core logic depends on. - Adapters: implements port behavior (HTTP, gRPC, vendor API, queue consumer).
- Infrastructure: framework-specific and provider-specific setup.
Boundary Rules
- Dependencies point inward: adapters/infrastructure depend on ports/domain, not the reverse.
- Domain must stay framework-agnostic (no HTTP, DB driver, or vendor SDK import).
- Application layer should know interfaces, not concrete adapters.
- Infrastructure may know everything technical, but should not hold business policy.
Real-World Use Case
In production, Factory Method is useful to select notifier implementation by config (email, slack, webhook). You can keep your use case stable while replacing providers or transport implementations with minimal changes.
Benefits & Tradeoffs
Benefits
- Better modularity and maintainability.
- Easier testability with mocked ports.
- Safer refactoring because boundaries are explicit.
Tradeoffs
- More files and abstractions to maintain.
- Initial learning curve for team members unfamiliar with layered architecture.
Common Pitfalls
- Putting domain rules in adapters.
- Leaking vendor-specific payload directly into domain models.
- Creating too many abstractions without concrete change pressure.
- Skipping contract tests for port behavior.
When NOT to use
- Service is still very small and not expected to grow.
- Team needs a quick prototype and architecture overhead would block delivery.
- There is only one stable integration with no foreseeable variation.
Conclusion
Factory Method is most effective when paired with Clean/Hexagonal boundaries. The pattern gives structure, while architecture keeps dependencies under control as the codebase evolves.