# Go Data Warehouse Framework - SKILL.md > **Engineering Guide - Go Microservices + PostgreSQL** > Version: 1.0 > Stack: Go 1.21+, PostgreSQL, Kafka, Redis, Prometheus, ELK > Last Updated: 2026-07-24 --- ## 1. Purpose Dokumen ini merupakan panduan resmi implementasi **Go Data Warehouse Framework** untuk standardisasi development di 30-person engineering team. Tujuan: - Standardisasi architecture untuk semua data warehouse services - Consistency di seluruh codebase - Production-ready patterns dan best practices - Reference untuk AI Coding Assistant - Maintain performance dan reliability --- ## 2. Stack Overview ``` Data Sources ↓ Services (Go + Gin) ├── Ingestion Workers (batch via Kafka) ├── Query API └── Stream Processors ↓ PostgreSQL (Data Warehouse) ↓ Redis (Query Cache) ↓ Prometheus + Grafana (Metrics) ELK Stack (Logs) Jaeger (Tracing - optional) ↓ Clients ``` --- ## 3. Business Capabilities Data Warehouse Platform provides: 1. **Data Ingestion** - Accept data from multiple sources (batch, streaming) 2. **Data Storage** - PostgreSQL as primary warehouse 3. **Data Query** - API layer untuk access warehouse 4. **Data Quality** - Validation, deduplication, audit trail 5. **Observability** - Metrics, logs, traces --- ## 4. Folder Structure ### Project Level ``` go-dw-platform/ ├── framework/ # Shared library (Go module) │ ├── go.mod │ ├── ingestion/ │ │ ├── batcher.go │ │ ├── deduplicator.go │ │ └── retry.go │ ├── query/ │ │ ├── cache.go │ │ └── analyzer.go │ ├── db/ │ │ ├── pool.go │ │ ├── batch.go │ │ └── tx.go │ ├── metrics/ │ │ └── prometheus.go │ ├── logger/ │ │ └── structured.go │ ├── middleware/ │ │ ├── auth.go │ │ ├── tracing.go │ │ └── logging.go │ └── config/ │ └── loader.go │ ├── services/ │ ├── ingestion-{domain}/ # Per-domain ingestion service │ │ ├── main.go │ │ ├── handler/ │ │ ├── service/ │ │ ├── repository/ │ │ ├── transformer/ │ │ ├── domain/ │ │ ├── entity/ │ │ ├── dto/ │ │ ├── validator/ │ │ ├── migrations/ │ │ ├── tests/ │ │ ├── go.mod │ │ ├── config.go │ │ └── README.md │ │ │ ├── query-api/ # Unified query API │ │ ├── (same structure) │ │ │ └── template-service/ # Boilerplate untuk new service │ ├── deployments/ │ ├── docker/ │ │ ├── Dockerfile.base │ │ └── Dockerfile.service │ ├── k8s/ # (if upgrade to K8s later) │ └── docker-compose.yml │ ├── docs/ │ ├── ARCHITECTURE.md │ ├── DATABASE.md │ ├── PERFORMANCE.md │ ├── API.md │ └── DEPLOYMENT.md │ ├── scripts/ │ ├── new-service.sh │ ├── migrate.sh │ └── benchmark.sh │ ├── go.work # Go workspace ├── Makefile └── README.md ``` ### Service Level (Ingestion Example) ``` services/ingestion-orders/ ├── go.mod ├── main.go # Entry point ├── config.go # Configuration │ ├── handler/ # HTTP + Kafka handlers │ ├── ingest.go # POST /ingest endpoint │ └── health.go # Health check │ ├── service/ # Business logic │ └── orders.go # Order ingestion logic │ ├── repository/ # Database access │ └── orders.go # CRUD + batch operations │ ├── transformer/ # Data mapping │ └── orders.go # Raw → Domain → Entity │ ├── domain/ # Business objects │ └── order.go # Order domain model │ ├── entity/ # Database entities │ └── order.go # DB table mapping │ ├── dto/ # Request/Response │ ├── request.go │ └── response.go │ ├── validator/ # Input validation │ └── orders.go │ ├── migrations/ # Database schema │ ├── 001_create_orders.up.sql │ └── 001_create_orders.down.sql │ ├── tests/ # Unit & integration tests │ ├── service_test.go │ ├── repository_test.go │ ├── handler_test.go │ └── fixtures.go │ └── README.md ``` --- ## 5. Architecture Layers ``` HTTP/Kafka Request ↓ ┌─────────────────┐ │ HANDLER │ ← Receive request, auth, validation └─────────────────┘ ↓ ┌─────────────────┐ │ TRANSFORMER │ ← Map data, convert types └─────────────────┘ ↓ ┌─────────────────┐ │ SERVICE │ ← Business logic, batching, dedup └─────────────────┘ ↓ ┌─────────────────┐ │ REPOSITORY │ ← Database operations └─────────────────┘ ↓ PostgreSQL ``` All requests MUST follow this flow. **No shortcuts allowed.** --- ## 6. Layer Responsibilities ### 6.1 Handler (HTTP/Kafka) **Responsibilities:** - Receive request (HTTP POST atau Kafka message) - Authentication & Authorization - Request validation (format, required fields) - Call Service layer - Return HTTP Response atau handle errors - Metrics: request count, latency **DO ✅** ```go - Bind request to DTO - Validate request format - Extract auth token - Call service.Process() - Return standardized response - Log request_id + action ``` **DON'T ❌** ```go - Business logic - SQL queries - Database transactions - Data transformation logic - Direct repository access ``` **Example:** ```go func (h *Handler) IngestOrders(c *gin.Context) { ctx := c.Request.Context() requestID := c.GetString("request_id") var req dto.IngestRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Error("validation failed", "request_id", requestID, "error", err) c.JSON(400, errorResponse(err)) return } result, err := h.service.Process(ctx, &req) if err != nil { c.JSON(500, errorResponse(err)) return } c.JSON(200, successResponse(result)) } ``` ### 6.2 Transformer **Responsibilities:** - Map data between layers - Type conversion - Normalization (trim, lowercase, etc) - Timestamp handling **Transformations:** ``` Request DTO → Domain Model Domain Model → Entity (for DB) Entity → Response DTO ``` **DO ✅** ```go - Map Request → Domain - Convert types - Format dates/timestamps - Validate data format - Handle null values ``` **DON'T ❌** ```go - Business rules/logic - Database access - HTTP operations ``` **Example:** ```go func (t *Transformer) RequestToDomain(req *dto.IngestRequest) (*domain.Order, error) { order := &domain.Order{ OrderID: req.OrderID, CustomerID: req.CustomerID, Amount: req.Amount, CreatedAt: time.Now(), } // Validation if err := order.Validate(); err != nil { return nil, fmt.Errorf("transform: %w", err) } return order, nil } func (t *Transformer) DomainToEntity(domain *domain.Order) *entity.Order { return &entity.Order{ OrderID: domain.OrderID, CustomerID: domain.CustomerID, Amount: domain.Amount, CreatedAt: domain.CreatedAt, } } ``` ### 6.3 Service (Business Logic) **Responsibilities:** - All business rules - Batching strategy - Deduplication logic - Transaction control - Retry logic - Metrics calculation **DO ✅** ```go - Implement business rules - Control transactions - Call repository methods - Validate business constraints - Handle retries - Log business events ``` **DON'T ❌** ```go - JSON parsing (use Handler) - HTTP operations - SQL queries (use Repository) - Data mapping (use Transformer) ``` **Example:** ```go func (s *Service) Process(ctx context.Context, req *dto.IngestRequest) (*dto.ProcessResult, error) { // Transform domain, err := s.transformer.RequestToDomain(req) if err != nil { return nil, fmt.Errorf("process: %w", err) } // Business logic: deduplication exists, err := s.repository.ExistsByKey(ctx, domain.OrderID) if err != nil { return nil, fmt.Errorf("check duplicate: %w", err) } if exists { return nil, ErrDuplicate } // Accumulate to batch s.batcher.Add(domain) // Auto-flush on size or timeout if s.batcher.IsFull() { return s.FlushBatch(ctx) } return &dto.ProcessResult{Status: "queued"}, nil } func (s *Service) FlushBatch(ctx context.Context) (*dto.ProcessResult, error) { batch := s.batcher.Get() // Transform to entities entities := make([]*entity.Order, len(batch)) for i, d := range batch { entities[i] = s.transformer.DomainToEntity(d) } // Insert with retry rows, err := s.retrier.Do(ctx, func() (int64, error) { return s.repository.InsertBatch(ctx, entities) }) if err != nil { return nil, fmt.Errorf("flush batch: %w", err) } // Metrics s.metrics.RecordBatchInsert(len(batch), time.Since(start)) s.batcher.Clear() return &dto.ProcessResult{ Status: "success", RowsInserted: rows, }, nil } ``` ### 6.4 Repository (Data Access) **Responsibilities:** - CRUD operations - Query execution - Batch operations - Connection management - Transaction handling (initiated by Service) **DO ✅** ```go - CRUD operations - Execute queries - Manage batch inserts - Use prepared statements - Log slow queries - Handle connection errors ``` **DON'T ❌** ```go - Business logic - Validation (beyond schema) - Response mapping - HTTP operations ``` **Example:** ```go func (r *Repository) InsertBatch(ctx context.Context, entities []*entity.Order) (int64, error) { const batchSize = 5000 tx, err := r.pool.Begin(ctx) if err != nil { return 0, fmt.Errorf("begin tx: %w", err) } defer tx.Rollback(ctx) stmt := `INSERT INTO orders (order_id, customer_id, amount, created_at) VALUES ($1, $2, $3, $4)` batch := &pgx.Batch{} for _, e := range entities { batch.Queue(stmt, e.OrderID, e.CustomerID, e.Amount, e.CreatedAt) } results := tx.SendBatch(ctx, batch) defer results.Close() var rowsInserted int64 for i := 0; i < len(entities); i++ { tag, err := results.Exec() if err != nil { return 0, fmt.Errorf("exec batch: %w", err) } rowsInserted += tag.RowsAffected() } if err := tx.Commit(ctx); err != nil { return 0, fmt.Errorf("commit: %w", err) } return rowsInserted, nil } func (r *Repository) FindByFilters(ctx context.Context, filters *repository.Filter) ([]*entity.Order, error) { ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() query := `SELECT order_id, customer_id, amount, created_at FROM orders WHERE created_at >= $1 AND created_at <= $2 LIMIT $3` rows, err := r.pool.Query(ctx, query, filters.StartDate, filters.EndDate, filters.Limit) if err != nil { return nil, fmt.Errorf("query: %w", err) } defer rows.Close() var orders []*entity.Order for rows.Next() { var o entity.Order if err := rows.Scan(&o.OrderID, &o.CustomerID, &o.Amount, &o.CreatedAt); err != nil { return nil, fmt.Errorf("scan: %w", err) } orders = append(orders, &o) } return orders, rows.Err() } ``` --- ## 7. Dependency Rules **Allowed:** ``` Handler ↓ Transformer ↓ Service ↓ Repository ↓ PostgreSQL ``` **FORBIDDEN (will be caught in code review):** - Handler → Repository (bypass Service) - Repository → Service (circular) - Any layer → Handler (upward dependency) - Handler → Database (direct SQL) --- ## 8. Data Warehouse Specific Rules ### 8.1 Batching ```go // Service layer manages batching batcher := ingestion.NewBatcher(config.BatchSize) for _, record := range records { batcher.Add(record) if batcher.IsFull() || batcher.IsExpired() { err := s.flush(ctx) } } ``` **Configuration (per service in config.go):** ```go type BatchConfig struct { Size int // 5000 rows TimeoutSec int // 30 seconds MaxRetries int // 3 RetryDelay time.Duration // exponential backoff } ``` ### 8.2 Deduplication **Natural Key:** ```go // In domain/order.go func (o *Order) NaturalKey() string { return fmt.Sprintf("%s_%s_%d", o.OrderID, o.CustomerID, o.CreatedAt.Unix()) } ``` **In Service:** ```go exists, err := s.repository.ExistsByKey(ctx, order.NaturalKey()) if exists { // Skip atau return error based on business rule } ``` ### 8.3 Kafka Integration **Handler untuk Kafka:** ```go type KafkaHandler struct { service Service logger Logger } func (h *KafkaHandler) Handle(ctx context.Context, msg *kafka.Message) error { var dto dto.IngestRequest if err := json.Unmarshal(msg.Value, &dto); err != nil { h.logger.Error("unmarshal failed", "error", err) return err // will be retried } _, err := h.service.Process(ctx, &dto) return err } ``` **Configuration (in config.go):** ```go type KafkaConfig struct { Brokers []string Topic string ConsumerGroup string MaxConcurrency int } ``` ### 8.4 Redis Caching (Query API) **In Service:** ```go func (s *QueryService) Execute(ctx context.Context, q *repository.Query) ([]Result, error) { // Check cache cacheKey := q.CacheKey() if cached, err := s.cache.Get(ctx, cacheKey); err == nil { return cached, nil } // Query database results, err := s.repository.Query(ctx, q) if err != nil { return nil, err } // Store in cache (TTL: 5-60 minutes) s.cache.Set(ctx, cacheKey, results, 5*time.Minute) return results, nil } ``` **Cache Strategy:** - Hot queries: 60 minutes - Moderate queries: 15 minutes - Cold queries: 5 minutes - Invalidate on data update --- ## 9. Naming Conventions ### Package ```go github.com/yourorg/go-dw-platform/framework github.com/yourorg/go-dw-platform/services/ingestion-orders ``` ### Files - Lowercase, underscore separated - `orders.go`, `orders_test.go`, `orders_integration_test.go` ### Types ```go type OrderService struct{} // PascalCase type IngestRequest struct{} type ErrOrderNotFound struct{} ``` ### Functions/Methods ```go func (s *Service) Process() error // PascalCase (exported) func (s *Service) process() error // camelCase (private) func (s *Service) FindByID() error func (s *Service) CreateOrder() error ``` ### Constants ```go const ( DefaultBatchSize = 5000 MaxRetries = 3 ) ``` ### Errors ```go var ( ErrOrderNotFound = errors.New("order not found") ErrDuplicateOrder = errors.New("duplicate order") ErrInvalidBatchSize = errors.New("invalid batch size") ) ``` **Error wrapping (always):** ```go if err != nil { return fmt.Errorf("process order: %w", err) } ``` --- ## 10. Error Handling Standard **Pattern:** ```go // Always wrap with context if err != nil { return fmt.Errorf("insert batch: %w", err) } // Custom errors for business logic if !order.IsValid() { return ErrInvalidOrder } // Never panic in production code // Only panic if application cannot continue panic("database pool initialization failed") ``` **Error Response Format:** ```json { "success": false, "message": "Failed to process order", "error_code": "DUPLICATE_ORDER", "request_id": "req-12345" } ``` --- ## 11. Logging Standard **Structured Logging (JSON):** ```go logger.Info("order processed", "request_id", "req-12345", "user_id", "user-456", "order_id", "ord-789", "duration_ms", 125, "batch_size", 100, ) ``` **Minimal Fields (required):** - `request_id` - Trace requests - `module` - Service name - `action` - Operation (insert, query, etc) - `duration_ms` - Timing - `error` - Error message (if applicable) **Log Levels:** - `ERROR` - Errors that need attention - `WARN` - Warnings (retries, slow queries) - `INFO` - Business events (batch inserted, dedup) - `DEBUG` - Detailed debugging (not in production) **NEVER log:** - Passwords, tokens, API keys - PII (passwords, emails in logs) - Full database records (only IDs) - Sensitive configuration ```go // ❌ Wrong logger.Info("user login", "password", "secret123") // ✅ Correct logger.Info("user login", "user_id", "user-123") ``` --- ## 12. Validation Standard **Layer:** - **Handler** - Format validation (required fields, JSON format) - **Transformer** - Type validation (dates, numbers) - **Service** - Business validation (business rules) **Example:** ```go // Handler - format if req.OrderID == "" { return ErrMissingOrderID } // Transformer - type orderID, err := strconv.ParseInt(req.OrderID, 10, 64) if err != nil { return fmt.Errorf("invalid order ID: %w", err) } // Service - business rule if !s.IsValidCustomer(ctx, order.CustomerID) { return ErrInvalidCustomer } ``` --- ## 13. Transaction Standard **Rule:** Transactions ONLY in Service layer. **Repository accepts context, Service creates tx:** ```go // ❌ Wrong - Repository creates tx func (r *Repository) Process(ctx context.Context) error { tx, _ := r.pool.Begin(ctx) // ... } // ✅ Correct - Service creates tx func (s *Service) Process(ctx context.Context) error { tx, err := s.pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) // Call repository with tx context err = s.repository.Insert(ctx, entity) if err != nil { return err } return tx.Commit(ctx) } ``` --- ## 14. Database Rules **Connection Pooling (in framework/db/pool.go):** ```go type Config struct { MaxConns int32 // 25 (tune per load) MinConns int32 // 5 MaxConnLifetime time.Duration // 15 minutes MaxConnIdleTime time.Duration // 5 minutes } ``` **All SQL in Repository:** ```go // ❌ Wrong - SQL di Handler func (h *Handler) GetOrder(c *gin.Context) { rows, _ := db.Query("SELECT * FROM orders") } // ✅ Correct - SQL di Repository func (r *Repository) FindByID(ctx context.Context, id string) (*Order, error) { row := r.pool.QueryRow(ctx, "SELECT * FROM orders WHERE id = $1", id) // ... } ``` **Prepared Statements (always for batch):** ```go stmt := `INSERT INTO orders (id, customer_id, amount) VALUES ($1, $2, $3)` batch := &pgx.Batch{} for _, order := range orders { batch.Queue(stmt, order.ID, order.CustomerID, order.Amount) } ``` **Query Timeout:** ```go ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() rows, err := r.pool.Query(ctx, query, args...) ``` --- ## 15. API Response Format **Success:** ```json { "success": true, "message": "Data retrieved successfully", "data": { "orders": [...], "total": 100, "page": 1 }, "request_id": "req-12345" } ``` **Error:** ```json { "success": false, "message": "Validation failed", "error_code": "VALIDATION_ERROR", "details": { "field": "order_id", "error": "required" }, "request_id": "req-12345" } ``` **Pagination:** ```json { "data": [...], "pagination": { "page": 1, "limit": 10, "total": 150, "pages": 15 } } ``` --- ## 16. Security Rules **MANDATORY:** 1. **Authentication** - JWT verification ```go func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { c.JSON(401, errorResponse("missing token")) c.Abort() return } // Verify token } } ``` 2. **Authorization** - Role-based access ```go func RoleMiddleware(required []string) gin.HandlerFunc { return func(c *gin.Context) { role := c.GetString("user_role") // Check if role in required } } ``` 3. **Input Validation** - Prevent injection ```go // Always use parameterized queries stmt := "SELECT * FROM users WHERE id = $1" // ✅ stmt := "SELECT * FROM users WHERE id = " + id // ❌ ``` 4. **Secret Management** - Use Vault ```go type Config struct { DBPassword string `vault:"db.password"` // Loaded from Vault } ``` 5. **Audit Trail** - Log sensitive operations ```go logger.Info("data accessed", "user_id", userID, "action", "export", "records", 1000, "timestamp", time.Now(), ) ``` **NEVER:** - Hardcode credentials - Log passwords/tokens - Store plaintext secrets - Skip validation --- ## 17. Performance Rules ### Pagination (always) ```go const DefaultLimit = 100 const MaxLimit = 10000 func (h *Handler) List(c *gin.Context) { limit := c.DefaultQuery("limit", "100") page := c.DefaultQuery("page", "1") // Validate if limit > MaxLimit { limit = MaxLimit } } ``` ### Context & Timeout ```go // Always use context with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() rows, err := r.pool.Query(ctx, query) ``` ### Batch Processing ```go // For large datasets, use batching const BatchSize = 5000 for i := 0; i < len(records); i += BatchSize { batch := records[i:min(i+BatchSize, len(records))] r.InsertBatch(ctx, batch) } ``` ### Query Optimization ```go // ✅ Good - With index SELECT * FROM orders WHERE customer_id = $1 AND created_at > $2 LIMIT 1000 // ❌ Bad - Full table scan SELECT * FROM orders WHERE amount * quantity > 1000 // Index needed on (customer_id, created_at) ``` ### Avoid N+1 ```go // ❌ Wrong for _, order := range orders { customer := r.GetCustomer(order.CustomerID) // N queries } // ✅ Correct customerIDs := extract(orders, "CustomerID") customers := r.GetCustomersBatch(customerIDs) // 1 query ``` ### Connection Pooling ```go // Pool configured in framework pool, _ := pgxpool.New(ctx, dsn) defer pool.Close() // Connection automatically managed row := pool.QueryRow(ctx, query) ``` --- ## 18. Testing Standard **Minimum Coverage: 80%** **Layers to Test:** - ✅ Service (business logic) - ✅ Repository (database access) - ✅ Transformer (data mapping) - ⚠️ Handler (integration test, if needed) **Testing Framework: testify** ### Service Test ```go func TestOrderService_Process(t *testing.T) { // Arrange mockRepo := &MockRepository{} service := NewService(mockRepo) req := &dto.IngestRequest{OrderID: "ord-123"} // Act result, err := service.Process(context.Background(), req) // Assert assert.NoError(t, err) assert.Equal(t, "success", result.Status) assert.Equal(t, 1, mockRepo.InsertCallCount) } ``` ### Repository Test ```go func TestOrderRepository_InsertBatch(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewRepository(db) entities := []*entity.Order{ {OrderID: "1", CustomerID: "cust-1", Amount: 100}, {OrderID: "2", CustomerID: "cust-2", Amount: 200}, } rows, err := repo.InsertBatch(context.Background(), entities) assert.NoError(t, err) assert.Equal(t, int64(2), rows) } ``` ### Integration Test ```go func TestOrderHandler_Ingest(t *testing.T) { router := setupTestRouter() db := setupTestDB(t) payload := `{"order_id": "123", "customer_id": "cust-1", "amount": 100}` req, _ := http.NewRequest("POST", "/api/ingest", strings.NewReader(payload)) req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) assert.Equal(t, 200, resp.Code) } ``` **Test File Naming:** ``` orders.go → orders_test.go orders_handler.go → orders_handler_test.go ``` **Run Tests:** ```bash go test ./... -cover # All tests go test ./service -cover # Specific package go test -run TestOrderService -v # Specific test ``` --- ## 19. Observability ### 19.1 Metrics (Prometheus) **Framework provides:** ```go metrics.RecordHTTPRequest(method, endpoint, statusCode, duration) metrics.RecordDatabaseQuery(operation, duration) metrics.RecordBatchInsert(rowCount, duration) metrics.RecordCacheHit(key, duration) ``` **In your code:** ```go start := time.Now() rows, err := r.InsertBatch(ctx, entities) metrics.RecordBatchInsert(len(entities), time.Since(start)) ``` **Prometheus Queries:** ``` rate(http_request_duration_seconds[5m]) histogram_quantile(0.95, rate(db_query_duration_seconds[5m])) ``` ### 19.2 Logging (ELK) **Framework provides structured logger:** ```go logger.Info("batch inserted", "module", "ingestion-orders", "batch_size", 5000, "duration_ms", 250, ) ``` **Kibana can parse JSON:** ```json { "timestamp": "2026-07-24T10:30:45Z", "level": "INFO", "message": "batch inserted", "module": "ingestion-orders", "batch_size": 5000, "duration_ms": 250, "request_id": "req-12345" } ``` ### 19.3 Tracing (Optional - Jaeger) **Add trace context:** ```go ctx, span := tracer.Start(ctx, "ProcessOrder") defer span.End() span.SetAttributes( attribute.String("order_id", order.ID), attribute.Int64("customer_id", order.CustomerID), ) ``` --- ## 20. CI/CD & Deployment ### GitHub Actions ```yaml on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: go-version: '1.21' - run: go test ./... -cover - run: golangci-lint run ``` ### Docker Build ```dockerfile FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN go build -o service . FROM alpine:latest COPY --from=builder /app/service . ENTRYPOINT ["./service"] ``` ### Deployment Checklist - [ ] All tests pass - [ ] Code review approved - [ ] Linting passes - [ ] No vulnerabilities (gosec) - [ ] Database migrations ready - [ ] Configuration set - [ ] Monitoring configured - [ ] Rollback plan defined --- ## 21. Common Patterns ### Pattern: Graceful Shutdown ```go func main() { server := gin.Default() go func() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-sigChan ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() server.Shutdown(ctx) }() server.Run(":8080") } ``` ### Pattern: Health Check ```go func (h *Handler) Health(c *gin.Context) { health := map[string]interface{}{ "status": "healthy", "database": h.db.Ping(c.Request.Context()), "cache": h.cache.Ping(c.Request.Context()), } c.JSON(200, health) } ``` ### Pattern: Middleware Chain ```go router := gin.New() router.Use(middleware.Logger()) router.Use(middleware.Recovery()) router.Use(middleware.Auth()) router.Use(middleware.Tracing()) router.POST("/api/ingest", handler.Ingest) ``` --- ## 22. Code Review Checklist Before submitting PR: - [ ] Follows layer architecture (Handler → Transformer → Service → Repository) - [ ] No business logic in Handler - [ ] Mapping in Transformer - [ ] Business logic in Service - [ ] SQL only in Repository - [ ] All tests pass (≥80% coverage) - [ ] Logging complete (request_id, action, duration) - [ ] Error handling correct (wrapped) - [ ] Context used in all I/O - [ ] No hardcoded values - [ ] Passes linting (`golangci-lint run`) - [ ] No SQL injection vulnerabilities - [ ] No secrets in code - [ ] Performance acceptable (EXPLAIN ANALYZE for queries) - [ ] Metrics added - [ ] README updated (if new feature) --- ## 23. Definition of Done Service is production-ready when: - ✅ All unit tests pass (≥80% coverage) - ✅ Code review approved - ✅ Linting passes (golangci-lint) - ✅ No security vulnerabilities (gosec) - ✅ Database migrations tested - ✅ Configuration documented (.env.example) - ✅ API documented (OpenAPI/Swagger) - ✅ Logging configured (ELK integration) - ✅ Metrics configured (Prometheus) - ✅ Performance benchmarked - ✅ Deployment tested (Docker build succeeds) - ✅ Rollback plan documented - ✅ README complete --- ## 24. Project Structure Script **New Service Generation:** ```bash ./scripts/new-service.sh ingestion-payments # Generated: # services/ingestion-payments/ # ├── go.mod # ├── main.go # ├── config.go # ├── handler/ # ├── service/ # ├── repository/ # ├── transformer/ # ├── domain/ # ├── entity/ # ├── dto/ # ├── migrations/ # ├── tests/ # └── README.md ``` --- ## 25. References - **Go Best Practices**: https://golang.org/doc/effective_go - **PostgreSQL**: https://www.postgresql.org/docs/ - **pgx Documentation**: https://github.com/jackc/pgx - **Gin Web Framework**: https://gin-gonic.com/ - **Prometheus**: https://prometheus.io/docs/ - **Clean Architecture**: Robert C. Martin - **Domain-Driven Design**: Eric Evans - **Microservices Patterns**: Chris Richardson --- ## 26. Change History | Version | Date | Changes | |---------|------------|---------| | 1.0 | 2026-07-24 | Initial Version - Go DW Framework | --- **Last Updated:** 2026-07-24 **Owner:** Engineering Team **Next Review:** 2026-10-24