Adapter Pattern di Go

Keunggulan Adapter Pattern:

  1. Interoperability - Memungkinkan integrasi dengan library/API third-party yang memiliki interface berbeda

  2. Consistency - Menyediakan interface yang konsisten untuk berbagai implementasi

  3. Maintainability - Mudah mengganti provider tanpa mengubah business logic

  4. Testability - Interface yang seragam memudahkan unit testing dan mocking

Real-world Use Cases:

  1. Database Abstraction - Menggabungkan MySQL, PostgreSQL, MongoDB dengan interface seragam

  2. Payment Gateways - Integrasi Stripe, PayPal, Midtrans, dll dengan API yang sama

  3. Logging Libraries - Menggunakan Logrus, Zap, atau standard log dengan interface konsisten

  4. Cloud Storage - AWS S3, Google Cloud Storage, Azure Blob dengan API unified

  5. Message Queues - RabbitMQ, Kafka, Redis Pub/Sub dengan interface sama

Best Practices:

  1. Interface Segregation - Buat interface yang focused dan tidak terlalu besar

  2. Error Handling - Standardkan error handling di adapter layer

  3. Configuration - Pisahkan konfigurasi dari business logic

  4. Testing - Buat mock adapter untuk unit testing

  5. Documentation - Dokumentasikan mapping antara target interface dan adaptee methods

Pattern ini sangat berguna ketika harus mengintegrasikan multiple third-party services dengan interface yang berbeda-beda, tapi ingin mempertahankan konsistensi dalam application layer.

package main

import (
	"fmt"
	"log"
	"strings"
)

// =============================================================================
// Example 1: Database Adapter - Adapting different database clients
// =============================================================================

// Target interface yang diharapkan aplikasi
type DatabaseInterface interface {
	Connect() error
	Query(sql string) ([]map[string]interface{}, error)
	Close() error
}

// Legacy MySQL client (third-party library yang tidak bisa diubah)
type MySQLLegacyClient struct {
	host     string
	username string
	password string
}

func (m *MySQLLegacyClient) EstablishConnection(host, user, pass string) error {
	m.host = host
	m.username = user
	m.password = pass
	fmt.Printf("MySQL: Connected to %s\n", host)
	return nil
}

func (m *MySQLLegacyClient) ExecuteSQL(query string) []string {
	fmt.Printf("MySQL: Executing query: %s\n", query)
	return []string{"row1", "row2", "row3"}
}

func (m *MySQLLegacyClient) Disconnect() error {
	fmt.Println("MySQL: Connection closed")
	return nil
}

// PostgreSQL client (third-party library dengan interface berbeda)
type PostgreSQLClient struct {
	connectionString string
}

func (p *PostgreSQLClient) OpenConnection(connStr string) error {
	p.connectionString = connStr
	fmt.Printf("PostgreSQL: Connected with %s\n", connStr)
	return nil
}

func (p *PostgreSQLClient) RunQuery(sql string) map[string][]string {
	fmt.Printf("PostgreSQL: Running query: %s\n", sql)
	return map[string][]string{
		"users": {"john", "jane", "bob"},
	}
}

func (p *PostgreSQLClient) CloseConnection() error {
	fmt.Println("PostgreSQL: Connection closed")
	return nil
}

// Adapter untuk MySQL
type MySQLAdapter struct {
	client *MySQLLegacyClient
}

func NewMySQLAdapter() *MySQLAdapter {
	return &MySQLAdapter{
		client: &MySQLLegacyClient{},
	}
}

func (m *MySQLAdapter) Connect() error {
	return m.client.EstablishConnection("localhost:3306", "root", "password")
}

func (m *MySQLAdapter) Query(sql string) ([]map[string]interface{}, error) {
	results := m.client.ExecuteSQL(sql)
	
	// Convert ke format yang diharapkan
	var formattedResults []map[string]interface{}
	for i, row := range results {
		formattedResults = append(formattedResults, map[string]interface{}{
			"id":   i + 1,
			"data": row,
		})
	}
	
	return formattedResults, nil
}

func (m *MySQLAdapter) Close() error {
	return m.client.Disconnect()
}

// Adapter untuk PostgreSQL
type PostgreSQLAdapter struct {
	client *PostgreSQLClient
}

func NewPostgreSQLAdapter() *PostgreSQLAdapter {
	return &PostgreSQLAdapter{
		client: &PostgreSQLClient{},
	}
}

func (p *PostgreSQLAdapter) Connect() error {
	return p.client.OpenConnection("postgresql://localhost:5432/mydb")
}

func (p *PostgreSQLAdapter) Query(sql string) ([]map[string]interface{}, error) {
	results := p.client.RunQuery(sql)
	
	// Convert ke format yang diharapkan
	var formattedResults []map[string]interface{}
	for table, rows := range results {
		for i, row := range rows {
			formattedResults = append(formattedResults, map[string]interface{}{
				"id":    i + 1,
				"table": table,
				"data":  row,
			})
		}
	}
	
	return formattedResults, nil
}

func (p *PostgreSQLAdapter) Close() error {
	return p.client.CloseConnection()
}

// =============================================================================
// Example 2: Payment Gateway Adapter - Integrating multiple payment providers
// =============================================================================

// Target interface untuk payment processing
type PaymentProcessor interface {
	ProcessPayment(amount float64, currency string, cardToken string) (*PaymentResult, error)
	RefundPayment(transactionID string, amount float64) error
}

type PaymentResult struct {
	TransactionID string
	Status        string
	Message       string
}

// Stripe API (third-party)
type StripeAPI struct{}

func (s *StripeAPI) ChargeCard(amountCents int, curr string, token string) map[string]string {
	fmt.Printf("Stripe: Charging %d cents in %s\n", amountCents, curr)
	return map[string]string{
		"id":     "stripe_tx_123",
		"status": "succeeded",
		"msg":    "Payment successful",
	}
}

func (s *StripeAPI) CreateRefund(txID string, amountCents int) bool {
	fmt.Printf("Stripe: Refunding %d cents for transaction %s\n", amountCents, txID)
	return true
}

// PayPal API (third-party)
type PayPalAPI struct{}

func (p *PayPalAPI) MakePayment(dollars float64, currency string, paymentMethod string) (string, bool, string) {
	fmt.Printf("PayPal: Processing $%.2f %s payment\n", dollars, currency)
	return "paypal_tx_456", true, "Payment completed successfully"
}

func (p *PayPalAPI) ProcessRefund(transactionRef string, refundAmount float64) error {
	fmt.Printf("PayPal: Processing refund of $%.2f for %s\n", refundAmount, transactionRef)
	return nil
}

// Stripe Adapter
type StripeAdapter struct {
	api *StripeAPI
}

func NewStripeAdapter() *StripeAdapter {
	return &StripeAdapter{api: &StripeAPI{}}
}

func (s *StripeAdapter) ProcessPayment(amount float64, currency string, cardToken string) (*PaymentResult, error) {
	// Stripe menggunakan cents, bukan dollars
	amountCents := int(amount * 100)
	
	result := s.api.ChargeCard(amountCents, currency, cardToken)
	
	return &PaymentResult{
		TransactionID: result["id"],
		Status:        result["status"],
		Message:       result["msg"],
	}, nil
}

func (s *StripeAdapter) RefundPayment(transactionID string, amount float64) error {
	amountCents := int(amount * 100)
	success := s.api.CreateRefund(transactionID, amountCents)
	
	if !success {
		return fmt.Errorf("stripe refund failed")
	}
	return nil
}

// PayPal Adapter
type PayPalAdapter struct {
	api *PayPalAPI
}

func NewPayPalAdapter() *PayPalAdapter {
	return &PayPalAdapter{api: &PayPalAPI{}}
}

func (p *PayPalAdapter) ProcessPayment(amount float64, currency string, cardToken string) (*PaymentResult, error) {
	txID, success, message := p.api.MakePayment(amount, currency, cardToken)
	
	status := "failed"
	if success {
		status = "succeeded"
	}
	
	return &PaymentResult{
		TransactionID: txID,
		Status:        status,
		Message:       message,
	}, nil
}

func (p *PayPalAdapter) RefundPayment(transactionID string, amount float64) error {
	return p.api.ProcessRefund(transactionID, amount)
}

// =============================================================================
// Example 3: Logger Adapter - Adapting different logging libraries
// =============================================================================

// Target interface
type Logger interface {
	Info(message string)
	Error(message string)
	Debug(message string)
}

// Third-party logger 1 (Logrus-like)
type LogrusLogger struct{}

func (l *LogrusLogger) WithFields(fields map[string]interface{}) *LogrusLogger {
	return l
}

func (l *LogrusLogger) Infoln(args ...interface{}) {
	fmt.Print("LOGRUS INFO: ")
	fmt.Println(args...)
}

func (l *LogrusLogger) Errorln(args ...interface{}) {
	fmt.Print("LOGRUS ERROR: ")
	fmt.Println(args...)
}

func (l *LogrusLogger) Debugln(args ...interface{}) {
	fmt.Print("LOGRUS DEBUG: ")
	fmt.Println(args...)
}

// Third-party logger 2 (Zap-like)
type ZapLogger struct{}

func (z *ZapLogger) LogInfo(msg string, fields ...interface{}) {
	fmt.Printf("ZAP INFO: %s %v\n", msg, fields)
}

func (z *ZapLogger) LogError(msg string, err error) {
	fmt.Printf("ZAP ERROR: %s - %v\n", msg, err)
}

func (z *ZapLogger) LogDebug(msg string) {
	fmt.Printf("ZAP DEBUG: %s\n", msg)
}

// Logrus Adapter
type LogrusAdapter struct {
	logger *LogrusLogger
}

func NewLogrusAdapter() *LogrusAdapter {
	return &LogrusAdapter{logger: &LogrusLogger{}}
}

func (l *LogrusAdapter) Info(message string) {
	l.logger.Infoln(message)
}

func (l *LogrusAdapter) Error(message string) {
	l.logger.Errorln(message)
}

func (l *LogrusAdapter) Debug(message string) {
	l.logger.Debugln(message)
}

// Zap Adapter
type ZapAdapter struct {
	logger *ZapLogger
}

func NewZapAdapter() *ZapAdapter {
	return &ZapAdapter{logger: &ZapLogger{}}
}

func (z *ZapAdapter) Info(message string) {
	z.logger.LogInfo(message)
}

func (z *ZapAdapter) Error(message string) {
	z.logger.LogError(message, fmt.Errorf(message))
}

func (z *ZapAdapter) Debug(message string) {
	z.logger.LogDebug(message)
}

// =============================================================================
// Service Layer yang menggunakan adapter pattern
// =============================================================================

type UserService struct {
	db     DatabaseInterface
	payment PaymentProcessor
	logger Logger
}

func NewUserService(db DatabaseInterface, payment PaymentProcessor, logger Logger) *UserService {
	return &UserService{
		db:      db,
		payment: payment,
		logger:  logger,
	}
}

func (u *UserService) ProcessUserPayment(userID string, amount float64) error {
	u.logger.Info(fmt.Sprintf("Processing payment for user %s", userID))
	
	// Connect to database
	if err := u.db.Connect(); err != nil {
		u.logger.Error("Failed to connect to database")
		return err
	}
	defer u.db.Close()
	
	// Query user data
	users, err := u.db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", userID))
	if err != nil {
		u.logger.Error("Failed to query user data")
		return err
	}
	
	if len(users) == 0 {
		u.logger.Error("User not found")
		return fmt.Errorf("user not found")
	}
	
	// Process payment
	result, err := u.payment.ProcessPayment(amount, "USD", "card_token_123")
	if err != nil {
		u.logger.Error("Payment processing failed")
		return err
	}
	
	if result.Status == "succeeded" {
		u.logger.Info(fmt.Sprintf("Payment successful: %s", result.TransactionID))
	} else {
		u.logger.Error(fmt.Sprintf("Payment failed: %s", result.Message))
		return fmt.Errorf("payment failed: %s", result.Message)
	}
	
	return nil
}

// =============================================================================
// Demo function
// =============================================================================

func main() {
	fmt.Println("=== Adapter Pattern Examples ===\n")
	
	// Example 1: Database adapters
	fmt.Println("1. Database Adapter Example:")
	fmt.Println(strings.Repeat("-", 40))
	
	var databases []DatabaseInterface
	databases = append(databases, NewMySQLAdapter())
	databases = append(databases, NewPostgreSQLAdapter())
	
	for i, db := range databases {
		fmt.Printf("\nDatabase %d:\n", i+1)
		db.Connect()
		results, _ := db.Query("SELECT * FROM users")
		fmt.Printf("Query results: %+v\n", results)
		db.Close()
	}
	
	// Example 2: Payment gateway adapters
	fmt.Println("\n\n2. Payment Gateway Adapter Example:")
	fmt.Println(strings.Repeat("-", 40))
	
	var payments []PaymentProcessor
	payments = append(payments, NewStripeAdapter())
	payments = append(payments, NewPayPalAdapter())
	
	for i, payment := range payments {
		fmt.Printf("\nPayment Gateway %d:\n", i+1)
		result, _ := payment.ProcessPayment(99.99, "USD", "token_123")
		fmt.Printf("Payment result: %+v\n", result)
		payment.RefundPayment(result.TransactionID, 50.00)
	}
	
	// Example 3: Logger adapters
	fmt.Println("\n\n3. Logger Adapter Example:")
	fmt.Println(strings.Repeat("-", 40))
	
	var loggers []Logger
	loggers = append(loggers, NewLogrusAdapter())
	loggers = append(loggers, NewZapAdapter())
	
	for i, logger := range loggers {
		fmt.Printf("\nLogger %d:\n", i+1)
		logger.Info("This is an info message")
		logger.Error("This is an error message")
		logger.Debug("This is a debug message")
	}
	
	// Example 4: Complete service integration
	fmt.Println("\n\n4. Complete Service Integration:")
	fmt.Println(strings.Repeat("-", 40))
	
	userService := NewUserService(
		NewMySQLAdapter(),
		NewStripeAdapter(),
		NewLogrusAdapter(),
	)
	
	if err := userService.ProcessUserPayment("user_123", 199.99); err != nil {
		log.Printf("Service error: %v", err)
	}
}

Last updated