Abstract Factory di Go: Pola Creational dengan Implementasi Clean/Hexagonal

3/2/2026

Design Pattern
Golang
Creational
Clean Architecture
Hexagonal
Thumbnail Abstract Factory di Go: Pola Creational dengan Implementasi Clean/Hexagonal

Overview

Abstract Factory adalah design pattern creational yang digunakan untuk membuat keluarga objek terkait lewat satu kontrak pembuatan yang konsisten. Di backend nyata, pattern ini membantu tim menjaga modul tetap terpisah sambil tetap cepat mengirim fitur.

Problem It Solves

Tanpa Abstract Factory, tim sering mencampur orkestrasi, integrasi eksternal, dan aturan domain dalam satu tempat. Dampaknya adalah coupling tinggi, testing sulit, dan refactor mahal.

5W + 1H

What

Abstract Factory 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 Abstract Factory 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

go
package ports

import "context"

type AbstractFactoryInput struct {
	RequestID string
	Payload   map[string]any
}

type AbstractFactoryOutput struct {
	Status string
	Data   map[string]any
}

type AbstractFactoryPort interface {
	Execute(ctx context.Context, in AbstractFactoryInput) (AbstractFactoryOutput, error)
}

2) Adapter / implementasi

go
package adapters

import (
	"context"
	"fmt"
	"myapp/internal/ports"
)

type AbstractFactoryAdapter struct {
	providerName string
}

func NewAbstractFactoryAdapter(providerName string) *AbstractFactoryAdapter {
	return &AbstractFactoryAdapter{providerName: providerName}
}

func (a *AbstractFactoryAdapter) Execute(ctx context.Context, in ports.AbstractFactoryInput) (ports.AbstractFactoryOutput, error) {
	if in.RequestID == "" {
		return ports.AbstractFactoryOutput{}, fmt.Errorf("request id is required")
	}

	return ports.AbstractFactoryOutput{
		Status: "ok",
		Data: map[string]any{
			"pattern": "Abstract Factory",
			"provider": a.providerName,
		},
	}, nil
}

3) Use-case / wiring di main

go
package main

import (
	"context"
	"fmt"
	"log"
	"myapp/internal/adapters"
	"myapp/internal/ports"
)

func run(ctx context.Context, svc ports.AbstractFactoryPort) error {
	out, err := svc.Execute(ctx, ports.AbstractFactoryInput{
		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.NewAbstractFactoryAdapter("default-provider")
	if err := run(ctx, service); err != nil {
		log.Fatal(err)
	}
}

Suggested Project Structure

text
cmd/
  api/
    main.go
internal/
  domain/
    abstract-factory-go/
      model.go
      policy.go
  application/
    abstract-factory-go/
      usecase.go
  ports/
    abstract-factory-go_port.go
  adapters/
    inbound/
      http/
        abstract-factory-go_handler.go
    outbound/
      abstract-factory-go_adapter.go
  infrastructure/
    config/
      loader.go
    persistence/
      repository.go

Clean/Hexagonal Placement

Boundary Rules

  1. Dependensi mengarah ke dalam: adapters/infrastructure bergantung pada ports/domain, bukan sebaliknya.
  2. Domain harus framework-agnostic (tanpa import HTTP, driver DB, atau vendor SDK).
  3. Layer application sebaiknya mengetahui interface, bukan adapter konkret.
  4. Infrastructure boleh tahu detail teknis, tetapi tidak boleh menyimpan policy bisnis.

Real-World Use Case

Dalam implementasi produksi, Abstract Factory efektif untuk berpindah antar keluarga provider payment (client, signer, validator). Use case bisa tetap stabil walau provider atau implementasi transport diganti.

Benefits & Tradeoffs

Benefits

Tradeoffs

Common Pitfalls

  1. Menaruh domain rules di layer adapter.
  2. Payload vendor bocor langsung ke model domain.
  3. Membuat abstraksi berlebihan tanpa kebutuhan perubahan nyata.
  4. Tidak ada contract test untuk perilaku port.

When NOT to use

Conclusion

Abstract Factory akan paling efektif jika dipasangkan dengan boundary Clean/Hexagonal. Pattern memberi struktur, sementara arsitektur menjaga arah dependensi tetap sehat saat codebase bertumbuh.