Overview
Factory Method adalah design pattern creational yang digunakan untuk memusatkan pembuatan objek agar constructor tidak bocor ke alur service. Di backend nyata, pattern ini membantu tim menjaga modul tetap terpisah sambil tetap cepat mengirim fitur.
Problem It Solves
Tanpa Factory Method, tim sering mencampur orkestrasi, integrasi eksternal, dan aturan domain dalam satu tempat. Dampaknya adalah coupling tinggi, testing sulit, dan refactor mahal.
5W + 1H
What
Factory Method adalah pola untuk menyusun kolaborasi antarkomponen agar masalah desain yang berulang bisa ditangani konsisten.
Why
Untuk meningkatkan maintainability, mengisolasi perubahan, dan menjaga batas antara policy bisnis dan detail implementasi.
Who
Backend engineer, platform engineer, dan tim API yang bekerja dengan integrasi serta business logic yang terus berkembang.
When
Gunakan Factory Method saat kompleksitas service meningkat dan satu tanggung jawab mulai bocor ke modul lain.
Where
Terapkan pada boundary service seperti use case aplikasi, adapter integrasi, orkestrasi workflow, dan domain policy.
How
Definisikan port yang stabil terlebih dahulu, lalu implementasikan adapter dan lakukan wiring lewat use-case.
Go Implementation (Step-by-step)
1) Definisi port / interface
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 / implementasi
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 / wiring di main
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: menyimpan aturan bisnis murni terkait penggunaan Factory Method.
- Application (Use Case): mengorkestrasi alur request dan koordinasi domain + ports.
- Ports: mendefinisikan kontrak stabil (
FactoryMethodPort) yang dipakai core logic. - Adapters: mengimplementasikan perilaku port (HTTP, gRPC, vendor API, queue consumer).
- Infrastructure: setup yang spesifik framework dan provider eksternal.
Boundary Rules
- Dependensi mengarah ke dalam: adapters/infrastructure bergantung pada ports/domain, bukan sebaliknya.
- Domain harus framework-agnostic (tanpa import HTTP, driver DB, atau vendor SDK).
- Layer application sebaiknya mengetahui interface, bukan adapter konkret.
- Infrastructure boleh tahu detail teknis, tetapi tidak boleh menyimpan policy bisnis.
Real-World Use Case
Dalam implementasi produksi, Factory Method efektif untuk memilih implementasi notifier berdasarkan config (email, slack, webhook). Use case bisa tetap stabil walau provider atau implementasi transport diganti.
Benefits & Tradeoffs
Benefits
- Modularitas dan maintainability lebih baik.
- Testing lebih mudah lewat mocked ports.
- Refactor lebih aman karena boundary jelas.
Tradeoffs
- Menambah jumlah file dan lapisan abstraksi.
- Butuh adaptasi tim jika belum terbiasa dengan arsitektur berlapis.
Common Pitfalls
- Menaruh domain rules di layer adapter.
- Payload vendor bocor langsung ke model domain.
- Membuat abstraksi berlebihan tanpa kebutuhan perubahan nyata.
- Tidak ada contract test untuk perilaku port.
When NOT to use
- Service masih sangat kecil dan tidak diperkirakan berkembang.
- Tim butuh prototipe cepat dan overhead arsitektur menghambat delivery.
- Hanya ada satu integrasi stabil tanpa variasi.
Conclusion
Factory Method akan paling efektif jika dipasangkan dengan boundary Clean/Hexagonal. Pattern memberi struktur, sementara arsitektur menjaga arah dependensi tetap sehat saat codebase bertumbuh.