omnix-sopiga/.claude/SKILL.md

1374 lines
30 KiB
Markdown
Raw Normal View History

2026-08-07 12:21:42 +07:00
# 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