Skip to main content

System Design & Architecture

Dokumentasi lengkap arsitektur sistem MStore Backend dari high-level design hingga implementation details.
πŸ’‘ Working with Diagrams:
  • Zoom: Cmd/Ctrl + Scroll atau klik kanan β†’ β€œOpen Image in New Tab”
  • Download: Copy code diagram β†’ paste ke Mermaid Live Editor β†’ Download PNG/SVG
  • Full Guide: Lihat Mermaid Guide untuk tutorial lengkap

🎯 Architecture Principles

MStore Backend dibangun dengan prinsip:
  • βœ… Clean Architecture: Separation of concerns (handler β†’ service β†’ repository)
  • βœ… Domain-Driven Design: Business logic organized by domain
  • βœ… Multi-Tenant: Merchant-scoped data isolation
  • βœ… Microservices-Ready: Modular design untuk future decomposition
  • βœ… Event-Driven: Message broker integration (NATS, RabbitMQ, Kafka)
  • βœ… API-First: RESTful API dengan OpenAPI/Swagger documentation
  • βœ… Observability: LGTM stack (Loki, Grafana, Tempo, Mimir)

πŸ—οΈ High-Level Architecture


πŸ“¦ Clean Architecture Layers

1. Handler Layer (Presentation)

Responsibility: HTTP request/response handling, validation, authentication
// internal/domains/transaction/transaction_handler.go
type TransactionHandler struct {
    service TransactionService
}

func (h *TransactionHandler) CreateTransaction(c *fiber.Ctx) error {
    // 1. Parse & validate request
    var req CreateTransactionRequest
    if err := c.BodyParser(&req); err != nil {
        return response.BadRequest(c, "invalid request")
    }
    
    // 2. Get user context
    userID := c.Locals("user_id").(string)
    
    // 3. Call service
    tx, err := h.service.CreateTransaction(c.Context(), req, userID)
    if err != nil {
        return response.InternalError(c, err.Error())
    }
    
    // 4. Return response
    return response.Success(c, tx)
}

2. Service Layer (Business Logic)

Responsibility: Business rules, orchestration, transaction management
// internal/domains/transaction/transaction_service.go
type TransactionService interface {
    CreateTransaction(ctx context.Context, req CreateTransactionRequest, userID string) (*Transaction, error)
    VerifyPayment(ctx context.Context, txID int64, req VerifyPaymentRequest) (*Transaction, error)
    SyncTransaction(ctx context.Context, txID int64) (*Transaction, error)
}

type transactionService struct {
    repo           TransactionRepository
    inventoryRepo  InventoryRepository
    accountingRepo AccountingRepository
    paymentClient  PaymentClient
}

func (s *transactionService) CreateTransaction(ctx context.Context, req CreateTransactionRequest, userID string) (*Transaction, error) {
    // 1. Validate business rules
    if err := s.validateTransaction(req); err != nil {
        return nil, err
    }
    
    // 2. Begin transaction
    return s.repo.Transaction(ctx, func(tx *gorm.DB) (*Transaction, error) {
        // 3. Create transaction
        transaction := &Transaction{
            BranchCode:    req.BranchCode,
            CashierID:     userID,
            Status:        "draft",
            Items:         req.Items,
            GrandTotal:    calculateTotal(req.Items),
        }
        
        if err := s.repo.Create(ctx, transaction); err != nil {
            return nil, err
        }
        
        // 4. Generate transaction code
        transaction.TransactionCode = s.generateTransactionCode(transaction)
        
        return transaction, nil
    })
}

3. Repository Layer (Data Access)

Responsibility: Database operations, query building
// internal/domains/transaction/transaction_repository.go
type TransactionRepository interface {
    Create(ctx context.Context, tx *Transaction) error
    GetByID(ctx context.Context, id int64) (*Transaction, error)
    Update(ctx context.Context, id int64, updates map[string]interface{}) error
    Transaction(ctx context.Context, fn func(*gorm.DB) error) error
}

type transactionRepository struct {
    db *gorm.DB
}

func (r *transactionRepository) Create(ctx context.Context, tx *Transaction) error {
    return r.db.WithContext(ctx).Create(tx).Error
}

func (r *transactionRepository) GetByID(ctx context.Context, id int64) (*Transaction, error) {
    var tx Transaction
    err := r.db.WithContext(ctx).
        Preload("Items").
        Preload("Items.Product").
        First(&tx, id).Error
    return &tx, err
}

πŸ”€ Domain Organization

internal/domains/
β”œβ”€β”€ auth/                   # Authentication & Authorization
β”‚   β”œβ”€β”€ auth_handler.go
β”‚   β”œβ”€β”€ auth_service.go
β”‚   β”œβ”€β”€ auth_repository.go
β”‚   β”œβ”€β”€ auth_payload.go
β”‚   └── auth_response.go
β”œβ”€β”€ transaction/            # POS Transactions
β”‚   β”œβ”€β”€ transaction_handler.go
β”‚   β”œβ”€β”€ transaction_service.go
β”‚   β”œβ”€β”€ transaction_repository.go
β”‚   β”œβ”€β”€ transaction_state.go      # State machine
β”‚   └── transaction_journal_mapping.go
β”œβ”€β”€ inventory/              # Inventory & Warehouse
β”‚   β”œβ”€β”€ inventory_handler.go
β”‚   β”œβ”€β”€ inventory_service.go
β”‚   └── inventory_repository.go
β”œβ”€β”€ payments/               # Payment Gateway Integration
β”‚   β”œβ”€β”€ payment_service.go
β”‚   β”œβ”€β”€ xendit/
β”‚   β”‚   β”œβ”€β”€ xendit_service.go
β”‚   β”‚   └── xendit_payload.go
β”‚   └── midtrans/
β”œβ”€β”€ pos/                    # POS Operations
β”‚   β”œβ”€β”€ pos_handler.go
β”‚   β”œβ”€β”€ pos_service.go
β”‚   └── offline/            # Offline-first support
β”œβ”€β”€ approvals/              # Multi-level Approval
β”‚   β”œβ”€β”€ approval_handler.go
β”‚   β”œβ”€β”€ approval_service.go
β”‚   └── approval_repository.go
β”œβ”€β”€ accounting/             # Journal & Financial
β”‚   β”œβ”€β”€ accounting_service.go
β”‚   └── journal_repository.go
β”œβ”€β”€ product/                # Product & Bundle
β”œβ”€β”€ promotions/             # Promotions & Price Lists
β”œβ”€β”€ merchant/               # Merchant Management
β”œβ”€β”€ branches/               # Branch Management
β”œβ”€β”€ user/                   # User Management
└── index.go                # Domain registry

πŸ”„ Request Flow

Typical API Request Flow


πŸ—„οΈ Database Architecture

Multi-Database Strategy

MySQL (Primary - Transactional)
  • Transactions
  • Inventory & Warehouse
  • Approvals
  • Accounting & Journals
  • Master data (Products, Branches, Users)
MongoDB (Secondary - Flexible Schema)
  • POS offline transactions (temporary)
  • Audit logs
  • Analytics data
  • Unstructured documents
Redis (Cache & Session)
  • Session storage
  • API rate limiting
  • Real-time data cache
  • Pub/Sub for real-time updates

Multi-Tenant Data Isolation

-- Every table has merchant_id for isolation
CREATE TABLE transactions (
    id BIGINT UNSIGNED PRIMARY KEY,
    merchant_id BIGINT UNSIGNED NOT NULL,
    branch_id BIGINT UNSIGNED,
    transaction_code VARCHAR(255),
    -- ... other columns
    
    INDEX idx_merchant (merchant_id),
    FOREIGN KEY fk_merchant (merchant_id) 
        REFERENCES merchants(id)
);

-- Application-level filtering
SELECT * FROM transactions 
WHERE merchant_id = ? 
  AND branch_id = ?
  AND deleted_at IS NULL;

πŸ” Security Architecture

Authentication Flow


Authorization (RBAC)

// Middleware for role-based access
func RequireRole(roles ...string) fiber.Handler {
    return func(c *fiber.Ctx) error {
        userRole := c.Locals("user_role").(string)
        
        for _, role := range roles {
            if userRole == role {
                return c.Next()
            }
        }
        
        return response.Forbidden(c, "insufficient permissions")
    }
}

// Usage in routes
app.Post("/api/v1/transactions/:id/void", 
    middleware.Auth(),
    middleware.RequireRole("MANAGER", "ADMIN"),
    handler.VoidTransaction,
)

πŸ“‘ Event-Driven Architecture

Message Broker Integration

// Publish event after transaction created
func (s *transactionService) CreateTransaction(ctx context.Context, req CreateTransactionRequest) (*Transaction, error) {
    // ... create transaction
    
    // Publish event
    event := TransactionCreatedEvent{
        TransactionID:   tx.ID,
        TransactionCode: tx.TransactionCode,
        MerchantID:      tx.MerchantID,
        BranchID:        tx.BranchID,
        Amount:          tx.GrandTotal,
        CreatedAt:       tx.CreatedAt,
    }
    
    if err := s.messageBroker.Publish("transaction.created", event); err != nil {
        // Log error but don't fail transaction
        log.Printf("failed to publish event: %v", err)
    }
    
    return tx, nil
}

// Subscribe to events
func (s *inventoryService) SubscribeToTransactionEvents() {
    s.messageBroker.Subscribe("transaction.created", func(msg Message) {
        var event TransactionCreatedEvent
        json.Unmarshal(msg.Data, &event)
        
        // Process inventory movement
        s.ProcessTransactionInventory(context.Background(), event)
    })
}

πŸ” Observability Architecture

LGTM Stack Integration

Loki (Logs)
// Structured logging with Zap
logger.Info("transaction created",
    zap.String("transaction_code", code),
    zap.Int64("merchant_id", merchantID),
    zap.Float64("amount", amount),
)
Tempo (Traces)
// OpenTelemetry tracing
ctx, span := tracer.Start(ctx, "CreateTransaction")
defer span.End()

span.SetAttributes(
    attribute.String("transaction.code", code),
    attribute.Int64("transaction.amount", amount),
)
Mimir (Metrics)
// Prometheus metrics
counter, _ := meter.Int64Counter("transactions.created")
counter.Add(ctx, 1, 
    metric.WithAttributes(
        attribute.String("branch", branchCode),
        attribute.String("payment_method", "CASH"),
    ),
)
Grafana (Dashboards)
  • Transaction volume & revenue
  • API latency & error rate
  • Database query performance
  • Cache hit rate
  • Message broker throughput

πŸš€ Deployment Architecture

Development Environment

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - ENV=development
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
      - nats
  
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mstore
    volumes:
      - mysql_data:/var/lib/mysql
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  
  nats:
    image: nats:latest
    ports:
      - "4222:4222"

Production Architecture


πŸ“Š Performance Characteristics

Target Metrics

MetricTargetNotes
API Latency (P95)< 100msExcluding external API calls
API Latency (P99)< 500msIncluding database queries
Throughput10k req/sPer instance
Database Query< 50msP95 for simple queries
Cache Hit Rate> 80%Redis cache
Availability99.9%~8.7 hours downtime/year
Error Rate< 0.1%4xx + 5xx errors

Scalability Strategy

Horizontal Scaling
  • Stateless application servers
  • Load balancer distribution
  • Database read replicas
  • Redis cluster for cache
Vertical Scaling
  • Database server resources
  • Cache memory allocation
  • Message broker capacity
Caching Strategy
  • L1: In-memory cache (per instance)
  • L2: Redis cluster (shared)
  • Cache invalidation via events

πŸ’‘ Best Practices

DO βœ…

  • Follow clean architecture layers
  • Use dependency injection
  • Implement proper error handling
  • Log with structured logging (Zap)
  • Use context for cancellation & timeout
  • Implement circuit breaker for external APIs
  • Use database transactions for atomic operations
  • Implement idempotency for critical operations
  • Monitor with OpenTelemetry
  • Document APIs with Swagger

DON’T ❌

  • Jangan skip input validation
  • Jangan hardcode credentials
  • Jangan ignore context cancellation
  • Jangan block goroutines indefinitely
  • Jangan skip error logging
  • Jangan expose internal errors ke client
  • Jangan skip database indexes
  • Jangan ignore connection pooling

πŸ”„ Migration Strategy

Microservices Decomposition (Future)

Decomposition Strategy:
  1. Start with bounded contexts (domains)
  2. Extract stateless services first
  3. Implement API gateway
  4. Use event bus for inter-service communication
  5. Gradual migration per domain

Tech Stack

Library dan tools yang digunakan

Service Template

Template untuk membuat service baru

API Styleguide

Panduan API design & conventions

Database Schema

ERD lengkap database

Observability

LGTM stack setup & monitoring

Need Help? Contact backend team atau check GitHub Issues