Adapter Pattern di Go
Keunggulan Adapter Pattern:
Interoperability - Memungkinkan integrasi dengan library/API third-party yang memiliki interface berbeda
Consistency - Menyediakan interface yang konsisten untuk berbagai implementasi
Maintainability - Mudah mengganti provider tanpa mengubah business logic
Testability - Interface yang seragam memudahkan unit testing dan mocking
Real-world Use Cases:
Database Abstraction - Menggabungkan MySQL, PostgreSQL, MongoDB dengan interface seragam
Payment Gateways - Integrasi Stripe, PayPal, Midtrans, dll dengan API yang sama
Logging Libraries - Menggunakan Logrus, Zap, atau standard log dengan interface konsisten
Cloud Storage - AWS S3, Google Cloud Storage, Azure Blob dengan API unified
Message Queues - RabbitMQ, Kafka, Redis Pub/Sub dengan interface sama
Best Practices:
Interface Segregation - Buat interface yang focused dan tidak terlalu besar
Error Handling - Standardkan error handling di adapter layer
Configuration - Pisahkan konfigurasi dari business logic
Testing - Buat mock adapter untuk unit testing
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