monorepo/cloud/maplepress-backend/docs/Architecture/BACKEND_BLUEPRINT.md

83 KiB

Golang Backend Blueprint: Clean Architecture with Wire DI & Cassandra

Version: 1.0 Last Updated: November 2025 Based on: maplepress-backend architecture

This document provides a step-by-step guide to building a new Golang backend using the same architecture, authentication system, and reusable components from the MaplePress backend. Use this as a reference when creating new backend projects from scratch.


Table of Contents

  1. Architecture Overview
  2. Project Initialization
  3. Directory Structure
  4. Core Dependencies
  5. Configuration System
  6. Dependency Injection with Wire
  7. Reusable pkg/ Components
  8. Authentication System
  9. Clean Architecture Layers
  10. Database Setup (Cassandra)
  11. Middleware Implementation
  12. HTTP Server Setup
  13. Docker & Infrastructure
  14. CLI Commands (Cobra)
  15. Development Workflow
  16. Testing Strategy
  17. Production Deployment

1. Architecture Overview

Core Principles

Our backend architecture follows these fundamental principles:

  1. Clean Architecture - Separation of concerns with clear dependency direction
  2. Dependency Injection - Using Google Wire for compile-time DI
  3. Multi-tenancy - Tenant isolation at the application layer
  4. Security-first - CWE-compliant security measures throughout
  5. Code Reuse - Extensive use of shared pkg/ components

Architecture Layers

┌─────────────────────────────────────────────────────────────┐
│                    Interface Layer (HTTP)                    │
│  • Handlers (HTTP endpoints)                                 │
│  • DTOs (Data Transfer Objects)                              │
│  • Middleware (JWT, API Key, Rate Limiting, CORS)           │
└──────────────────────┬──────────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────────┐
│                      Service Layer                           │
│  • Orchestration logic                                       │
│  • Transaction management (SAGA pattern)                     │
│  • Cross-use-case coordination                               │
└──────────────────────┬──────────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────────┐
│                      Use Case Layer                          │
│  • Focused, single-responsibility operations                 │
│  • Business logic encapsulation                              │
│  • Validation and domain rules                               │
└──────────────────────┬──────────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────────┐
│                    Repository Layer                          │
│  • Data access implementations                               │
│  • Database queries (Cassandra CQL)                          │
│  • Cache operations (Redis)                                  │
└──────────────────────┬──────────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────────┐
│                      Domain Layer                            │
│  • Entities (domain models)                                  │
│  • Repository interfaces                                     │
│  • Domain errors                                             │
└─────────────────────────────────────────────────────────────┘

              Shared Infrastructure (pkg/)
┌─────────────────────────────────────────────────────────────┐
│  • Logger (Zap)          • Security (JWT, Password, API Key)│
│  • Database (Cassandra)  • Cache (Redis, Two-Tier)          │
│  • Storage (S3)          • Rate Limiting                     │
│  • Email (Mailgun)       • Search (Meilisearch)             │
│  • Distributed Mutex     • Validation                        │
└─────────────────────────────────────────────────────────────┘

Dependency Direction

Critical Rule: Dependencies flow INWARD only (outer layers depend on inner layers).

Interface → Service → Use Case → Repository → Domain
    ↓           ↓         ↓           ↓
    └───────────┴─────────┴───────────┴──→ pkg/ (Infrastructure)

2. Project Initialization

Step 1: Create Project Structure

# Create new project directory
mkdir -p cloud/your-backend-name
cd cloud/your-backend-name

# Initialize Go module
go mod init codeberg.org/mapleopentech/monorepo/cloud/your-backend-name

# Create initial directory structure
mkdir -p {app,cmd,config,internal,pkg,migrations,static,docs}

Step 2: Initialize Go Workspace (if using monorepo)

# From monorepo root
cd /path/to/monorepo
go work use ./cloud/your-backend-name

Step 3: Add Core Dependencies

# Core dependencies
go get github.com/google/wire@v0.7.0
go get github.com/spf13/cobra@v1.10.1
go get go.uber.org/zap@v1.27.0

# Database & Cache
go get github.com/gocql/gocql@v1.7.0
go get github.com/redis/go-redis/v9@v9.16.0
go get github.com/golang-migrate/migrate/v4@v4.19.0

# Security
go get github.com/golang-jwt/jwt/v5@v5.3.0
go get golang.org/x/crypto@v0.41.0
go get github.com/awnumar/memguard@v0.23.0

# HTTP & Utilities
go get github.com/google/uuid@v1.6.0

# Install Wire CLI
go install github.com/google/wire/cmd/wire@latest

3. Directory Structure

Complete Directory Layout

your-backend-name/
├── app/                          # Dependency injection (Wire)
│   ├── wire.go                   # Wire dependency providers
│   ├── wire_gen.go               # Generated by Wire (gitignore)
│   └── app.go                    # Application bootstrapper
│
├── cmd/                          # CLI commands (Cobra)
│   ├── root.go                   # Root command
│   ├── daemon/
│   │   └── daemon.go             # Main server command
│   ├── migrate/
│   │   └── migrate.go            # Database migrations
│   └── version/
│       └── version.go            # Version command
│
├── config/                       # Configuration
│   ├── config.go                 # Config loader
│   └── constants/
│       ├── constants.go          # Global constants
│       └── session.go            # Session context keys
│
├── internal/                     # Private application code
│   ├── domain/                   # Domain entities & interfaces
│   │   ├── user/
│   │   │   ├── entity.go         # User domain entity
│   │   │   └── repository.go    # User repository interface
│   │   └── tenant/
│   │       ├── entity.go         # Tenant domain entity
│   │       └── repository.go    # Tenant repository interface
│   │
│   ├── repository/               # Repository implementations
│   │   ├── user/
│   │   │   ├── impl.go           # Repository struct
│   │   │   ├── create.go         # Create operations
│   │   │   ├── get.go            # Read operations
│   │   │   ├── update.go         # Update operations
│   │   │   ├── delete.go         # Delete operations
│   │   │   └── models/
│   │   │       └── user.go       # Database models
│   │   └── tenant/
│   │       └── ...               # Similar structure
│   │
│   ├── usecase/                  # Use cases (focused operations)
│   │   ├── user/
│   │   │   ├── create_user_entity.go
│   │   │   ├── save_user_to_repo.go
│   │   │   ├── validate_user_email_unique.go
│   │   │   └── get.go
│   │   └── gateway/
│   │       ├── login.go
│   │       ├── hash_password.go
│   │       └── verify_password.go
│   │
│   ├── service/                  # Service layer (orchestration)
│   │   ├── provider.go           # Service providers for Wire
│   │   ├── session.go            # Session management service
│   │   ├── gateway/
│   │   │   ├── login.go          # Login orchestration
│   │   │   ├── register.go       # Registration orchestration
│   │   │   └── provider.go       # Gateway service providers
│   │   └── user/
│   │       ├── create.go         # User creation orchestration
│   │       └── provider.go       # User service providers
│   │
│   ├── interface/http/           # HTTP interface layer
│   │   ├── server.go             # HTTP server & routing
│   │   ├── handler/              # HTTP handlers
│   │   │   ├── healthcheck/
│   │   │   │   └── healthcheck_handler.go
│   │   │   ├── gateway/
│   │   │   │   ├── login_handler.go
│   │   │   │   ├── register_handler.go
│   │   │   │   └── refresh_handler.go
│   │   │   └── user/
│   │   │       ├── create_handler.go
│   │   │       └── get_handler.go
│   │   ├── dto/                  # Data Transfer Objects
│   │   │   ├── gateway/
│   │   │   │   ├── login_dto.go
│   │   │   │   └── register_dto.go
│   │   │   └── user/
│   │   │       ├── create_dto.go
│   │   │       └── get_dto.go
│   │   └── middleware/
│   │       └── tenant.go         # Tenant context middleware
│   │
│   ├── http/middleware/          # Shared HTTP middleware
│   │   ├── jwt.go                # JWT authentication
│   │   ├── apikey.go             # API key authentication
│   │   ├── ratelimit.go          # Rate limiting
│   │   ├── security_headers.go   # Security headers (CORS, CSP)
│   │   ├── request_size_limit.go # Request size limits
│   │   └── provider.go           # Middleware providers
│   │
│   └── scheduler/                # Background schedulers (cron)
│       ├── quota_reset.go        # Monthly quota reset
│       └── ip_cleanup.go         # GDPR IP cleanup
│
├── pkg/                          # Reusable packages (copy from maplepress-backend)
│   ├── logger/                   # Structured logging (Zap)
│   │   ├── logger.go
│   │   └── sanitizer.go          # PII sanitization
│   ├── security/                 # Security utilities
│   │   ├── jwt/                  # JWT provider
│   │   ├── password/             # Password hashing & validation
│   │   ├── apikey/               # API key generation & hashing
│   │   ├── clientip/             # IP extraction & validation
│   │   ├── ipcrypt/              # IP encryption (GDPR)
│   │   └── validator/            # Credential validation
│   ├── storage/
│   │   ├── database/             # Cassandra client
│   │   │   ├── cassandra.go
│   │   │   └── migrator.go
│   │   ├── cache/                # Redis client
│   │   │   └── redis.go
│   │   └── object/               # S3-compatible storage
│   │       └── s3.go
│   ├── cache/                    # Two-tier caching
│   │   ├── redis.go
│   │   ├── cassandra.go
│   │   └── twotier.go
│   ├── ratelimit/                # Rate limiting
│   │   ├── ratelimiter.go
│   │   └── login_ratelimiter.go
│   ├── distributedmutex/         # Distributed locking
│   │   └── distributedmutex.go
│   ├── validation/               # Input validation
│   │   └── validator.go
│   ├── httperror/                # HTTP error handling
│   │   └── error.go
│   ├── httpresponse/             # HTTP response helpers
│   │   └── response.go
│   └── transaction/              # SAGA pattern
│       └── saga.go
│
├── migrations/                   # Cassandra migrations (CQL)
│   ├── 000001_create_users.up.cql
│   ├── 000001_create_users.down.cql
│   ├── 000002_create_tenants.up.cql
│   └── 000002_create_tenants.down.cql
│
├── static/                       # Static files (if needed)
├── docs/                         # Documentation
├── .env.sample                   # Sample environment variables
├── .env                          # Local environment (gitignored)
├── .gitignore                    # Git ignore rules
├── docker-compose.dev.yml        # Development docker compose
├── Dockerfile                    # Production dockerfile
├── dev.Dockerfile                # Development dockerfile
├── Taskfile.yml                  # Task runner configuration
├── go.mod                        # Go module definition
├── go.sum                        # Go module checksums
├── main.go                       # Application entry point
└── README.md                     # Project documentation

4. Core Dependencies

Essential go.mod Dependencies

module codeberg.org/mapleopentech/monorepo/cloud/your-backend-name

go 1.24.4

require (
	// Dependency Injection
	github.com/google/wire v0.7.0

	// CLI Framework
	github.com/spf13/cobra v1.10.1

	// Logging
	go.uber.org/zap v1.27.0

	// Database & Cache
	github.com/gocql/gocql v1.7.0
	github.com/redis/go-redis/v9 v9.16.0
	github.com/golang-migrate/migrate/v4 v4.19.0

	// Security
	github.com/golang-jwt/jwt/v5 v5.3.0
	golang.org/x/crypto v0.41.0
	github.com/awnumar/memguard v0.23.0  // Secure memory handling

	// HTTP & Utilities
	github.com/google/uuid v1.6.0

	// Distributed Locking
	github.com/bsm/redislock v0.9.4

	// Background Jobs (optional)
	github.com/robfig/cron/v3 v3.0.1

	// Optional: Email (Mailgun)
	github.com/mailgun/mailgun-go/v4 v4.23.0

	// Optional: Search (Meilisearch)
	github.com/meilisearch/meilisearch-go v0.34.1

	// Optional: GeoIP (for IP country blocking)
	github.com/oschwald/geoip2-golang v1.13.0

	// Optional: AWS S3
	github.com/aws/aws-sdk-go-v2 v1.36.3
	github.com/aws/aws-sdk-go-v2/config v1.29.14
	github.com/aws/aws-sdk-go-v2/credentials v1.17.67
	github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0
)

5. Configuration System

config/config.go

Create a centralized configuration system that loads from environment variables:

package config

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

// Config holds all application configuration
type Config struct {
	App      AppConfig
	Server   ServerConfig
	HTTP     HTTPConfig
	Security SecurityConfig
	Database DatabaseConfig
	Cache    CacheConfig
	Logger   LoggerConfig
	// Add more config sections as needed
}

// AppConfig holds application-level configuration
type AppConfig struct {
	Environment string // development, staging, production
	Version     string
	JWTSecret   string
}

// ServerConfig holds HTTP server configuration
type ServerConfig struct {
	Host string
	Port int
}

// HTTPConfig holds HTTP request handling configuration
type HTTPConfig struct {
	MaxRequestBodySize int64         // Maximum request body size in bytes
	ReadTimeout        time.Duration // Maximum duration for reading the entire request
	WriteTimeout       time.Duration // Maximum duration before timing out writes
	IdleTimeout        time.Duration // Maximum amount of time to wait for the next request
}

// SecurityConfig holds security-related configuration
type SecurityConfig struct {
	TrustedProxies  []string // CIDR blocks of trusted reverse proxies
	IPEncryptionKey string   // 32-character hex key for IP encryption (GDPR)
	AllowedOrigins  []string // CORS allowed origins
}

// DatabaseConfig holds Cassandra database configuration
type DatabaseConfig struct {
	Hosts          []string
	Keyspace       string
	Consistency    string
	Replication    int
	MigrationsPath string
}

// CacheConfig holds Redis cache configuration
type CacheConfig struct {
	Host     string
	Port     int
	Password string
	DB       int
}

// LoggerConfig holds logging configuration
type LoggerConfig struct {
	Level  string // debug, info, warn, error
	Format string // json, console
}

// Load loads configuration from environment variables
func Load() (*Config, error) {
	cfg := &Config{
		App: AppConfig{
			Environment: getEnv("APP_ENVIRONMENT", "development"),
			Version:     getEnv("APP_VERSION", "0.1.0"),
			JWTSecret:   getEnv("APP_JWT_SECRET", "change-me-in-production"),
		},
		Server: ServerConfig{
			Host: getEnv("SERVER_HOST", "0.0.0.0"),
			Port: getEnvAsInt("SERVER_PORT", 8000),
		},
		HTTP: HTTPConfig{
			MaxRequestBodySize: getEnvAsInt64("HTTP_MAX_REQUEST_BODY_SIZE", 10*1024*1024), // 10 MB
			ReadTimeout:        getEnvAsDuration("HTTP_READ_TIMEOUT", 30*time.Second),
			WriteTimeout:       getEnvAsDuration("HTTP_WRITE_TIMEOUT", 30*time.Second),
			IdleTimeout:        getEnvAsDuration("HTTP_IDLE_TIMEOUT", 60*time.Second),
		},
		Security: SecurityConfig{
			TrustedProxies:  getEnvAsSlice("SECURITY_TRUSTED_PROXIES", []string{}),
			IPEncryptionKey: getEnv("SECURITY_IP_ENCRYPTION_KEY", "00112233445566778899aabbccddeeff"),
			AllowedOrigins:  getEnvAsSlice("SECURITY_CORS_ALLOWED_ORIGINS", []string{}),
		},
		Database: DatabaseConfig{
			Hosts:          getEnvAsSlice("DATABASE_HOSTS", []string{"localhost"}),
			Keyspace:       getEnv("DATABASE_KEYSPACE", "your_keyspace"),
			Consistency:    getEnv("DATABASE_CONSISTENCY", "QUORUM"),
			Replication:    getEnvAsInt("DATABASE_REPLICATION", 3),
			MigrationsPath: getEnv("DATABASE_MIGRATIONS_PATH", "file://migrations"),
		},
		Cache: CacheConfig{
			Host:     getEnv("CACHE_HOST", "localhost"),
			Port:     getEnvAsInt("CACHE_PORT", 6379),
			Password: getEnv("CACHE_PASSWORD", ""),
			DB:       getEnvAsInt("CACHE_DB", 0),
		},
		Logger: LoggerConfig{
			Level:  getEnv("LOGGER_LEVEL", "info"),
			Format: getEnv("LOGGER_FORMAT", "json"),
		},
	}

	// Validate configuration
	if err := cfg.validate(); err != nil {
		return nil, fmt.Errorf("invalid configuration: %w", err)
	}

	return cfg, nil
}

// validate checks if the configuration is valid
func (c *Config) validate() error {
	if c.Server.Port < 1 || c.Server.Port > 65535 {
		return fmt.Errorf("invalid server port: %d", c.Server.Port)
	}

	if c.Database.Keyspace == "" {
		return fmt.Errorf("database keyspace is required")
	}

	if len(c.Database.Hosts) == 0 {
		return fmt.Errorf("at least one database host is required")
	}

	if c.App.JWTSecret == "" {
		return fmt.Errorf("APP_JWT_SECRET is required")
	}

	// Production security checks
	if c.App.Environment == "production" {
		if strings.Contains(strings.ToLower(c.App.JWTSecret), "change-me") {
			return fmt.Errorf("SECURITY ERROR: JWT secret is using default value in production")
		}

		if len(c.App.JWTSecret) < 32 {
			return fmt.Errorf("SECURITY ERROR: JWT secret is too short for production (minimum 32 characters)")
		}
	}

	return nil
}

// Helper functions

func getEnv(key, defaultValue string) string {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}
	return value
}

func getEnvAsInt(key string, defaultValue int) int {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := strconv.Atoi(valueStr)
	if err != nil {
		return defaultValue
	}

	return value
}

func getEnvAsInt64(key string, defaultValue int64) int64 {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := strconv.ParseInt(valueStr, 10, 64)
	if err != nil {
		return defaultValue
	}

	return value
}

func getEnvAsSlice(key string, defaultValue []string) []string {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	// Simple comma-separated parsing
	var result []string
	for _, item := range strings.Split(valueStr, ",") {
		trimmed := strings.TrimSpace(item)
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}

	if len(result) == 0 {
		return defaultValue
	}

	return result
}

func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := time.ParseDuration(valueStr)
	if err != nil {
		return defaultValue
	}

	return value
}

config/constants/constants.go

Define global constants:

package constants

// Context keys for request context
type ContextKey string

const (
	// Session context keys
	SessionIsAuthorized ContextKey = "session_is_authorized"
	SessionID           ContextKey = "session_id"
	SessionUserID       ContextKey = "session_user_id"
	SessionUserUUID     ContextKey = "session_user_uuid"
	SessionUserEmail    ContextKey = "session_user_email"
	SessionUserName     ContextKey = "session_user_name"
	SessionUserRole     ContextKey = "session_user_role"
	SessionTenantID     ContextKey = "session_tenant_id"

	// API Key context keys
	APIKeyIsAuthorized ContextKey = "apikey_is_authorized"
	APIKeySiteID       ContextKey = "apikey_site_id"
	APIKeyTenantID     ContextKey = "apikey_tenant_id"
)

// User roles
const (
	RoleAdmin  = "admin"
	RoleUser   = "user"
	RoleGuest  = "guest"
)

// Tenant status
const (
	TenantStatusActive   = "active"
	TenantStatusInactive = "inactive"
	TenantStatusSuspended = "suspended"
)

.env.sample

Create a sample environment file:

# Application Configuration
APP_ENVIRONMENT=development
APP_VERSION=0.1.0
APP_JWT_SECRET=change-me-in-production-use-openssl-rand-base64-64

# HTTP Server Configuration
SERVER_HOST=0.0.0.0
SERVER_PORT=8000

# HTTP Timeouts & Limits
HTTP_MAX_REQUEST_BODY_SIZE=10485760  # 10 MB
HTTP_READ_TIMEOUT=30s
HTTP_WRITE_TIMEOUT=30s
HTTP_IDLE_TIMEOUT=60s

# Security Configuration
# Trusted proxies for X-Forwarded-For validation (comma-separated CIDR)
SECURITY_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
# IP encryption key for GDPR compliance (32 hex characters)
SECURITY_IP_ENCRYPTION_KEY=00112233445566778899aabbccddeeff
# CORS allowed origins (comma-separated)
SECURITY_CORS_ALLOWED_ORIGINS=https://yourdomain.com

# Cassandra Database Configuration
DATABASE_HOSTS=localhost:9042
DATABASE_KEYSPACE=your_keyspace
DATABASE_CONSISTENCY=QUORUM
DATABASE_REPLICATION=3
DATABASE_MIGRATIONS_PATH=file://migrations

# Redis Cache Configuration
CACHE_HOST=localhost
CACHE_PORT=6379
CACHE_PASSWORD=
CACHE_DB=0

# Logger Configuration
LOGGER_LEVEL=info
LOGGER_FORMAT=json

6. Dependency Injection with Wire

Overview

We use Google Wire for compile-time dependency injection. Wire generates code at build time, eliminating runtime reflection and providing type safety.

app/wire.go

This file defines all providers for Wire:

//go:build wireinject
// +build wireinject

package app

import (
	"github.com/google/wire"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/http/middleware"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/handler/gateway"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/handler/healthcheck"
	userrepo "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/repository/user"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/service"
	gatewaysvc "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/service/gateway"
	gatewayuc "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/usecase/gateway"
	userusecase "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/cache"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/logger"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/security"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/security/password"
	rediscache "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/storage/cache"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/storage/database"
)

// InitializeApplication wires up all dependencies
func InitializeApplication(cfg *config.Config) (*Application, error) {
	wire.Build(
		// Infrastructure layer (pkg/)
		logger.ProvideLogger,
		database.ProvideCassandraSession,

		// Cache layer
		rediscache.ProvideRedisClient,
		cache.ProvideRedisCache,
		cache.ProvideCassandraCache,
		cache.ProvideTwoTierCache,

		// Security layer
		security.ProvideJWTProvider,
		password.NewPasswordProvider,
		password.NewPasswordValidator,
		password.NewBreachChecker,
		security.ProvideClientIPExtractor,

		// Repository layer
		userrepo.ProvideRepository,

		// Use case layer
		userusecase.ProvideCreateUserEntityUseCase,
		userusecase.ProvideSaveUserToRepoUseCase,
		userusecase.ProvideGetUserUseCase,
		gatewayuc.ProvideLoginUseCase,
		gatewayuc.ProvideHashPasswordUseCase,
		gatewayuc.ProvideVerifyPasswordUseCase,

		// Service layer
		service.ProvideSessionService,
		gatewaysvc.ProvideLoginService,

		// Middleware layer
		middleware.ProvideJWTMiddleware,
		middleware.ProvideSecurityHeadersMiddleware,
		middleware.ProvideRequestSizeLimitMiddleware,

		// Handler layer
		healthcheck.ProvideHealthCheckHandler,
		gateway.ProvideLoginHandler,

		// HTTP server
		http.ProvideServer,

		// Application
		ProvideApplication,
	)

	return nil, nil
}

app/app.go

Application bootstrapper:

package app

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http"
)

// Application represents the main application
type Application struct {
	config     *config.Config
	httpServer *http.Server
	logger     *zap.Logger
}

// ProvideApplication creates the application instance
func ProvideApplication(
	cfg *config.Config,
	httpServer *http.Server,
	logger *zap.Logger,
) *Application {
	return &Application{
		config:     cfg,
		httpServer: httpServer,
		logger:     logger,
	}
}

// Start starts the application
func (app *Application) Start() error {
	app.logger.Info("Starting application",
		zap.String("environment", app.config.App.Environment),
		zap.String("version", app.config.App.Version))

	// Start HTTP server in goroutine
	go func() {
		if err := app.httpServer.Start(); err != nil {
			app.logger.Fatal("HTTP server failed", zap.Error(err))
		}
	}()

	// Wait for interrupt signal
	app.waitForShutdown()

	return nil
}

// waitForShutdown waits for interrupt signal and performs graceful shutdown
func (app *Application) waitForShutdown() {
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	<-quit
	app.logger.Info("Shutting down application...")

	// Create context with timeout for graceful shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Shutdown HTTP server
	if err := app.httpServer.Shutdown(ctx); err != nil {
		app.logger.Error("HTTP server shutdown failed", zap.Error(err))
	}

	app.logger.Info("Application shutdown complete")
}

Generate Wire Code

# Navigate to app directory
cd app

# Run wire to generate wire_gen.go
wire

# You should see: wire_gen.go created

Add to .gitignore

app/wire_gen.go

7. Reusable pkg/ Components

The pkg/ directory contains infrastructure components that can be copied directly from maplepress-backend and reused across projects.

Components to Copy

7.1 Logger (pkg/logger/)

Copy from: cloud/maplepress-backend/pkg/logger/

Purpose: Structured logging with Zap, including PII sanitization

Key files:

  • logger.go - Logger provider and configuration
  • sanitizer.go - Email and sensitive data sanitization

Usage:

logger := logger.ProvideLogger(cfg)
logger.Info("User logged in", zap.String("user_id", userID))
logger.Error("Operation failed", zap.Error(err))

7.2 Security (pkg/security/)

Copy from: cloud/maplepress-backend/pkg/security/

Purpose: Comprehensive security utilities

Subpackages:

JWT (pkg/security/jwt/)
// Generate JWT tokens
jwtProvider := security.ProvideJWTProvider(cfg)
accessToken, accessExpiry, err := jwtProvider.GenerateToken(sessionID, 15*time.Minute)

// Validate JWT
sessionID, err := jwtProvider.ValidateToken(tokenString)
Password (pkg/security/password/)
// Hash password
passwordProvider := password.NewPasswordProvider()
hashedPassword, err := passwordProvider.HashPassword("user-password")

// Verify password
isValid, err := passwordProvider.VerifyPassword("user-password", hashedPassword)

// Check for breached passwords (CWE-521)
breachChecker := password.NewBreachChecker()
isBreached, err := breachChecker.CheckPassword("user-password")
API Key (pkg/security/apikey/)
// Generate API key
generator := apikey.ProvideGenerator()
apiKey := generator.Generate()  // Returns: "mp_live_abc123..."

// Hash API key for storage
hasher := apikey.ProvideHasher()
hashedKey, err := hasher.Hash(apiKey)

// Verify API key
isValid, err := hasher.Verify(apiKey, hashedKey)
Client IP (pkg/security/clientip/)
// Extract client IP with X-Forwarded-For validation (CWE-348)
extractor := security.ProvideClientIPExtractor(cfg)
clientIP, err := extractor.ExtractIP(r)
IP Encryption (pkg/security/ipcrypt/)
// Encrypt IP for GDPR compliance (CWE-359)
encryptor := ipcrypt.ProvideIPEncryptor(cfg)
encryptedIP, err := encryptor.Encrypt("192.168.1.1")
decryptedIP, err := encryptor.Decrypt(encryptedIP)

7.3 Database (pkg/storage/database/)

Copy from: cloud/maplepress-backend/pkg/storage/database/

Purpose: Cassandra connection and migration management

// Connect to Cassandra
session, err := database.ProvideCassandraSession(cfg, logger)

// Run migrations
migrator := database.NewMigrator(cfg, logger)
err := migrator.Up()

7.4 Cache (pkg/cache/ and pkg/storage/cache/)

Copy from:

  • cloud/maplepress-backend/pkg/cache/
  • cloud/maplepress-backend/pkg/storage/cache/

Purpose: Redis client and two-tier caching (Redis + Cassandra)

// Two-tier cache (fast Redis + persistent Cassandra)
cache := cache.ProvideTwoTierCache(redisCache, cassandraCache, logger)

// Set with TTL
err := cache.Set(ctx, "key", value, 1*time.Hour)

// Get
value, err := cache.Get(ctx, "key")

// Delete
err := cache.Delete(ctx, "key")

7.5 Rate Limiting (pkg/ratelimit/)

Copy from: cloud/maplepress-backend/pkg/ratelimit/

Purpose: Redis-based rate limiting with configurable limits

// Create rate limiter
limiter := ratelimit.NewRateLimiter(redisClient, logger)

// Check rate limit
allowed, err := limiter.Allow(ctx, "user:123", 100, time.Hour)
if !allowed {
	// Rate limit exceeded
}

7.6 Distributed Mutex (pkg/distributedmutex/)

Copy from: cloud/maplepress-backend/pkg/distributedmutex/

Purpose: Redis-based distributed locking to prevent race conditions

// Acquire lock
mutex := distributedmutex.ProvideDistributedMutexAdapter(redisClient, logger)
lock, err := mutex.Lock(ctx, "resource:123", 30*time.Second)
if err != nil {
	// Failed to acquire lock
}
defer lock.Unlock(ctx)

// Critical section protected by lock
// ...

7.7 Validation (pkg/validation/)

Copy from: cloud/maplepress-backend/pkg/validation/

Purpose: Input validation utilities

validator := validation.NewValidator()

// Validate email
if !validator.IsValidEmail("user@example.com") {
	// Invalid email
}

// Validate UUID
if !validator.IsValidUUID("123e4567-e89b-12d3-a456-426614174000") {
	// Invalid UUID
}

7.8 HTTP Utilities (pkg/httperror/ and pkg/httpresponse/)

Copy from:

  • cloud/maplepress-backend/pkg/httperror/
  • cloud/maplepress-backend/pkg/httpresponse/

Purpose: Consistent HTTP error and response handling

// Send error response
httperror.SendError(w, http.StatusBadRequest, "Invalid input", err)

// Send JSON response
httpresponse.SendJSON(w, http.StatusOK, map[string]interface{}{
	"message": "Success",
	"data": data,
})

7.9 Transaction SAGA (pkg/transaction/)

Copy from: cloud/maplepress-backend/pkg/transaction/

Purpose: SAGA pattern for distributed transactions

// Create SAGA
saga := transaction.NewSaga(logger)

// Add compensation steps
saga.AddStep(
	func(ctx context.Context) error {
		// Forward operation
		return createUser(ctx, user)
	},
	func(ctx context.Context) error {
		// Compensation (rollback)
		return deleteUser(ctx, user.ID)
	},
)

// Execute SAGA
err := saga.Execute(ctx)
if err != nil {
	// SAGA failed and all compensations were executed
}

Copy Script

Create a script to copy all reusable components:

#!/bin/bash
# copy-pkg.sh - Copy reusable pkg/ components from maplepress-backend

SOURCE_DIR="../maplepress-backend/pkg"
DEST_DIR="./pkg"

# Create destination directory
mkdir -p "$DEST_DIR"

# Copy all pkg components
cp -r "$SOURCE_DIR/logger" "$DEST_DIR/"
cp -r "$SOURCE_DIR/security" "$DEST_DIR/"
cp -r "$SOURCE_DIR/storage" "$DEST_DIR/"
cp -r "$SOURCE_DIR/cache" "$DEST_DIR/"
cp -r "$SOURCE_DIR/ratelimit" "$DEST_DIR/"
cp -r "$SOURCE_DIR/distributedmutex" "$DEST_DIR/"
cp -r "$SOURCE_DIR/validation" "$DEST_DIR/"
cp -r "$SOURCE_DIR/httperror" "$DEST_DIR/"
cp -r "$SOURCE_DIR/httpresponse" "$DEST_DIR/"
cp -r "$SOURCE_DIR/httpvalidation" "$DEST_DIR/"
cp -r "$SOURCE_DIR/transaction" "$DEST_DIR/"

# Optional components (copy if needed)
# cp -r "$SOURCE_DIR/emailer" "$DEST_DIR/"
# cp -r "$SOURCE_DIR/search" "$DEST_DIR/"
# cp -r "$SOURCE_DIR/dns" "$DEST_DIR/"

echo "✅ All pkg/ components copied successfully"

# Update import paths
echo "⚠️  Remember to update import paths in copied files from:"
echo "   codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend"
echo "   to:"
echo "   codeberg.org/mapleopentech/monorepo/cloud/your-backend-name"

Run the script:

chmod +x copy-pkg.sh
./copy-pkg.sh

Update Import Paths

After copying, you'll need to update import paths in all copied files:

# Find and replace import paths
find ./pkg -type f -name "*.go" -exec sed -i '' \
  's|codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend|codeberg.org/mapleopentech/monorepo/cloud/your-backend-name|g' \
  {} +

8. Authentication System

Our authentication system uses JWT tokens with session management stored in a two-tier cache (Redis + Cassandra).

8.1 Authentication Flow

┌──────────┐
│  Client  │
└─────┬────┘
      │ 1. POST /api/v1/login
      │    {email, password}
      ↓
┌─────────────────────────┐
│   Login Handler         │
└────────┬────────────────┘
         │ 2. Validate input
         ↓
┌─────────────────────────┐
│   Login Service         │
└────────┬────────────────┘
         │ 3. Verify credentials
         │ 4. Create session
         │ 5. Generate JWT tokens
         ↓
┌─────────────────────────┐
│   Session Service       │
│   (Two-Tier Cache)      │
└────────┬────────────────┘
         │ 6. Store in Redis (fast)
         │ 7. Store in Cassandra (persistent)
         ↓
┌─────────────────────────┐
│   Response              │
│   - access_token        │
│   - refresh_token       │
│   - user_info           │
└─────────────────────────┘

8.2 Session Service Implementation

internal/service/session.go:

package service

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/domain"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/cache"
)

const (
	SessionTTL = 24 * time.Hour // Session expires after 24 hours
)

// SessionService handles session management
type SessionService interface {
	CreateSession(ctx context.Context, userID uint64, userUUID uuid.UUID, email, name, role string, tenantID uuid.UUID) (*domain.Session, error)
	GetSession(ctx context.Context, sessionID string) (*domain.Session, error)
	DeleteSession(ctx context.Context, sessionID string) error
	InvalidateUserSessions(ctx context.Context, userUUID uuid.UUID) error
}

type sessionService struct {
	cache  cache.TwoTierCacher
	logger *zap.Logger
}

// ProvideSessionService provides a session service instance
func ProvideSessionService(cache cache.TwoTierCacher, logger *zap.Logger) SessionService {
	return &sessionService{
		cache:  cache,
		logger: logger.Named("session-service"),
	}
}

// CreateSession creates a new session and stores it in cache
func (s *sessionService) CreateSession(
	ctx context.Context,
	userID uint64,
	userUUID uuid.UUID,
	email, name, role string,
	tenantID uuid.UUID,
) (*domain.Session, error) {
	// Generate unique session ID
	sessionID := uuid.New().String()

	// Create session object
	session := &domain.Session{
		ID:        sessionID,
		UserID:    userID,
		UserUUID:  userUUID,
		UserEmail: email,
		UserName:  name,
		UserRole:  role,
		TenantID:  tenantID,
		CreatedAt: time.Now().UTC(),
		ExpiresAt: time.Now().UTC().Add(SessionTTL),
	}

	// Serialize to JSON
	sessionJSON, err := json.Marshal(session)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal session: %w", err)
	}

	// Store in two-tier cache (Redis + Cassandra)
	cacheKey := fmt.Sprintf("session:%s", sessionID)
	if err := s.cache.Set(ctx, cacheKey, sessionJSON, SessionTTL); err != nil {
		return nil, fmt.Errorf("failed to store session: %w", err)
	}

	s.logger.Info("Session created",
		zap.String("session_id", sessionID),
		zap.String("user_uuid", userUUID.String()))

	return session, nil
}

// GetSession retrieves a session from cache
func (s *sessionService) GetSession(ctx context.Context, sessionID string) (*domain.Session, error) {
	cacheKey := fmt.Sprintf("session:%s", sessionID)

	// Get from two-tier cache (tries Redis first, falls back to Cassandra)
	sessionJSON, err := s.cache.Get(ctx, cacheKey)
	if err != nil {
		return nil, fmt.Errorf("session not found: %w", err)
	}

	// Deserialize from JSON
	var session domain.Session
	if err := json.Unmarshal(sessionJSON, &session); err != nil {
		return nil, fmt.Errorf("failed to unmarshal session: %w", err)
	}

	// Check if session is expired
	if time.Now().UTC().After(session.ExpiresAt) {
		// Delete expired session
		_ = s.DeleteSession(ctx, sessionID)
		return nil, fmt.Errorf("session expired")
	}

	return &session, nil
}

// DeleteSession removes a session from cache
func (s *sessionService) DeleteSession(ctx context.Context, sessionID string) error {
	cacheKey := fmt.Sprintf("session:%s", sessionID)
	return s.cache.Delete(ctx, cacheKey)
}

// InvalidateUserSessions invalidates all sessions for a user (CWE-384: Session Fixation Prevention)
func (s *sessionService) InvalidateUserSessions(ctx context.Context, userUUID uuid.UUID) error {
	// Note: This is a simplified implementation
	// In production, you should maintain a user->sessions mapping in cache
	// For now, sessions will naturally expire after SessionTTL
	s.logger.Info("Invalidating user sessions",
		zap.String("user_uuid", userUUID.String()))
	return nil
}

8.3 Domain Session Entity

internal/domain/session.go:

package domain

import (
	"time"

	"github.com/google/uuid"
)

// Session represents a user session
type Session struct {
	ID        string    `json:"id"`
	UserID    uint64    `json:"user_id"`
	UserUUID  uuid.UUID `json:"user_uuid"`
	UserEmail string    `json:"user_email"`
	UserName  string    `json:"user_name"`
	UserRole  string    `json:"user_role"`
	TenantID  uuid.UUID `json:"tenant_id"`
	CreatedAt time.Time `json:"created_at"`
	ExpiresAt time.Time `json:"expires_at"`
}

8.4 JWT Middleware

internal/http/middleware/jwt.go:

package middleware

import (
	"context"
	"net/http"
	"strings"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/service"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/security/jwt"
)

// JWTMiddleware validates JWT tokens and populates session context
type JWTMiddleware struct {
	jwtProvider    jwt.Provider
	sessionService service.SessionService
	logger         *zap.Logger
}

// NewJWTMiddleware creates a new JWT middleware
func NewJWTMiddleware(
	jwtProvider jwt.Provider,
	sessionService service.SessionService,
	logger *zap.Logger,
) *JWTMiddleware {
	return &JWTMiddleware{
		jwtProvider:    jwtProvider,
		sessionService: sessionService,
		logger:         logger.Named("jwt-middleware"),
	}
}

// ProvideJWTMiddleware provides JWT middleware for Wire
func ProvideJWTMiddleware(
	jwtProvider jwt.Provider,
	sessionService service.SessionService,
	logger *zap.Logger,
) *JWTMiddleware {
	return NewJWTMiddleware(jwtProvider, sessionService, logger)
}

// Handler validates JWT and populates context
func (m *JWTMiddleware) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get Authorization header
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			ctx := context.WithValue(r.Context(), constants.SessionIsAuthorized, false)
			next.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		// Expected format: "JWT <token>"
		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 || parts[0] != "JWT" {
			ctx := context.WithValue(r.Context(), constants.SessionIsAuthorized, false)
			next.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		token := parts[1]

		// Validate token
		sessionID, err := m.jwtProvider.ValidateToken(token)
		if err != nil {
			ctx := context.WithValue(r.Context(), constants.SessionIsAuthorized, false)
			next.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		// Get session from cache
		session, err := m.sessionService.GetSession(r.Context(), sessionID)
		if err != nil {
			ctx := context.WithValue(r.Context(), constants.SessionIsAuthorized, false)
			next.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		// Populate context with session data
		ctx := r.Context()
		ctx = context.WithValue(ctx, constants.SessionIsAuthorized, true)
		ctx = context.WithValue(ctx, constants.SessionID, session.ID)
		ctx = context.WithValue(ctx, constants.SessionUserUUID, session.UserUUID.String())
		ctx = context.WithValue(ctx, constants.SessionUserEmail, session.UserEmail)
		ctx = context.WithValue(ctx, constants.SessionUserName, session.UserName)
		ctx = context.WithValue(ctx, constants.SessionUserRole, session.UserRole)
		ctx = context.WithValue(ctx, constants.SessionTenantID, session.TenantID.String())

		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// RequireAuth ensures the request is authenticated
func (m *JWTMiddleware) RequireAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		isAuthorized, ok := r.Context().Value(constants.SessionIsAuthorized).(bool)
		if !ok || !isAuthorized {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

8.5 Login Handler Example

internal/interface/http/handler/gateway/login_handler.go:

package gateway

import (
	"encoding/json"
	"net/http"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/dto/gateway"
	gatewaysvc "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/service/gateway"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/httpresponse"
)

// LoginHandler handles user login
type LoginHandler struct {
	loginService gatewaysvc.LoginService
	logger       *zap.Logger
}

// ProvideLoginHandler provides a login handler for Wire
func ProvideLoginHandler(
	loginService gatewaysvc.LoginService,
	logger *zap.Logger,
) *LoginHandler {
	return &LoginHandler{
		loginService: loginService,
		logger:       logger.Named("login-handler"),
	}
}

// Handle processes login requests
func (h *LoginHandler) Handle(w http.ResponseWriter, r *http.Request) {
	// Parse request
	var req gateway.LoginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httperror.SendError(w, http.StatusBadRequest, "Invalid request body", err)
		return
	}

	// Validate input
	if req.Email == "" || req.Password == "" {
		httperror.SendError(w, http.StatusBadRequest, "Email and password are required", nil)
		return
	}

	// Execute login
	response, err := h.loginService.Login(r.Context(), &gatewaysvc.LoginInput{
		Email:    req.Email,
		Password: req.Password,
	})
	if err != nil {
		h.logger.Error("Login failed", zap.Error(err))
		httperror.SendError(w, http.StatusUnauthorized, "Invalid credentials", err)
		return
	}

	// Send response
	httpresponse.SendJSON(w, http.StatusOK, response)
}

9. Clean Architecture Layers

Layer Structure

9.1 Domain Layer (internal/domain/)

Purpose: Core business entities and repository interfaces

Example: User Entity

// internal/domain/user/entity.go
package user

import (
	"time"

	"github.com/google/uuid"
)

// User represents a user entity
type User struct {
	ID        uuid.UUID
	TenantID  uuid.UUID
	Email     string
	Name      string
	Role      string
	Password  string // Hashed
	Status    string
	CreatedAt time.Time
	UpdatedAt time.Time
}

Example: User Repository Interface

// internal/domain/user/repository.go
package user

import (
	"context"

	"github.com/google/uuid"
)

// Repository defines user data access interface
type Repository interface {
	Create(ctx context.Context, user *User) error
	GetByID(ctx context.Context, id uuid.UUID) (*User, error)
	GetByEmail(ctx context.Context, email string) (*User, error)
	Update(ctx context.Context, user *User) error
	Delete(ctx context.Context, id uuid.UUID) error
}

9.2 Repository Layer (internal/repository/)

Purpose: Data access implementations

Example: User Repository Implementation

// internal/repository/user/impl.go
package user

import (
	"context"

	"github.com/gocql/gocql"
	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/domain/user"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/cache"
)

type repository struct {
	session *gocql.Session
	cache   cache.TwoTierCacher
	logger  *zap.Logger
}

// ProvideRepository provides a user repository for Wire
func ProvideRepository(
	session *gocql.Session,
	cache cache.TwoTierCacher,
	logger *zap.Logger,
) user.Repository {
	return &repository{
		session: session,
		cache:   cache,
		logger:  logger.Named("user-repository"),
	}
}

// Create creates a new user
func (r *repository) Create(ctx context.Context, user *user.User) error {
	query := `
		INSERT INTO users (id, tenant_id, email, name, role, password, status, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`

	return r.session.Query(query,
		user.ID,
		user.TenantID,
		user.Email,
		user.Name,
		user.Role,
		user.Password,
		user.Status,
		user.CreatedAt,
		user.UpdatedAt,
	).WithContext(ctx).Exec()
}

// GetByID retrieves a user by ID
func (r *repository) GetByID(ctx context.Context, id uuid.UUID) (*user.User, error) {
	// Try cache first
	cacheKey := fmt.Sprintf("user:id:%s", id.String())
	if cachedData, err := r.cache.Get(ctx, cacheKey); err == nil {
		var u user.User
		if err := json.Unmarshal(cachedData, &u); err == nil {
			return &u, nil
		}
	}

	// Query database
	query := `
		SELECT id, tenant_id, email, name, role, password, status, created_at, updated_at
		FROM users
		WHERE id = ?
	`

	var u user.User
	err := r.session.Query(query, id).
		WithContext(ctx).
		Scan(&u.ID, &u.TenantID, &u.Email, &u.Name, &u.Role, &u.Password, &u.Status, &u.CreatedAt, &u.UpdatedAt)

	if err != nil {
		return nil, err
	}

	// Cache result
	if data, err := json.Marshal(u); err == nil {
		_ = r.cache.Set(ctx, cacheKey, data, 15*time.Minute)
	}

	return &u, nil
}

9.3 Use Case Layer (internal/usecase/)

Purpose: Focused, single-responsibility business operations

Example: Create User Entity Use Case

// internal/usecase/user/create_user_entity.go
package user

import (
	"context"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/domain/user"
)

// CreateUserEntityUseCase creates a user entity from input
type CreateUserEntityUseCase struct {
	logger *zap.Logger
}

// ProvideCreateUserEntityUseCase provides the use case for Wire
func ProvideCreateUserEntityUseCase(logger *zap.Logger) *CreateUserEntityUseCase {
	return &CreateUserEntityUseCase{
		logger: logger.Named("create-user-entity-uc"),
	}
}

// CreateUserEntityInput represents the input
type CreateUserEntityInput struct {
	TenantID       uuid.UUID
	Email          string
	Name           string
	Role           string
	HashedPassword string
}

// Execute creates a user entity
func (uc *CreateUserEntityUseCase) Execute(
	ctx context.Context,
	input *CreateUserEntityInput,
) (*user.User, error) {
	now := time.Now().UTC()

	entity := &user.User{
		ID:        uuid.New(),
		TenantID:  input.TenantID,
		Email:     input.Email,
		Name:      input.Name,
		Role:      input.Role,
		Password:  input.HashedPassword,
		Status:    constants.TenantStatusActive,
		CreatedAt: now,
		UpdatedAt: now,
	}

	uc.logger.Info("User entity created",
		zap.String("user_id", entity.ID.String()),
		zap.String("email", entity.Email))

	return entity, nil
}

Example: Save User to Repo Use Case

// internal/usecase/user/save_user_to_repo.go
package user

import (
	"context"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/domain/user"
)

// SaveUserToRepoUseCase saves a user to repository
type SaveUserToRepoUseCase struct {
	repo   user.Repository
	logger *zap.Logger
}

// ProvideSaveUserToRepoUseCase provides the use case for Wire
func ProvideSaveUserToRepoUseCase(
	repo user.Repository,
	logger *zap.Logger,
) *SaveUserToRepoUseCase {
	return &SaveUserToRepoUseCase{
		repo:   repo,
		logger: logger.Named("save-user-to-repo-uc"),
	}
}

// Execute saves the user
func (uc *SaveUserToRepoUseCase) Execute(
	ctx context.Context,
	user *user.User,
) error {
	if err := uc.repo.Create(ctx, user); err != nil {
		uc.logger.Error("Failed to save user",
			zap.String("user_id", user.ID.String()),
			zap.Error(err))
		return err
	}

	uc.logger.Info("User saved to repository",
		zap.String("user_id", user.ID.String()))

	return nil
}

9.4 Service Layer (internal/service/)

Purpose: Orchestration logic, transaction management (SAGA)

Example: Create User Service

// internal/service/user/create.go
package user

import (
	"context"
	"fmt"

	"github.com/google/uuid"
	"go.uber.org/zap"

	userusecase "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/security/password"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/transaction"
)

// CreateUserService handles user creation orchestration
type CreateUserService interface {
	Create(ctx context.Context, input *CreateUserInput) (*CreateUserResponse, error)
}

type createUserService struct {
	validateEmailUC userusecase.ValidateUserEmailUniqueUseCase
	createEntityUC  *userusecase.CreateUserEntityUseCase
	saveToRepoUC    *userusecase.SaveUserToRepoUseCase
	passwordProvider password.Provider
	logger          *zap.Logger
}

// ProvideCreateUserService provides the service for Wire
func ProvideCreateUserService(
	validateEmailUC *userusecase.ValidateUserEmailUniqueUseCase,
	createEntityUC *userusecase.CreateUserEntityUseCase,
	saveToRepoUC *userusecase.SaveUserToRepoUseCase,
	passwordProvider password.Provider,
	logger *zap.Logger,
) CreateUserService {
	return &createUserService{
		validateEmailUC: validateEmailUC,
		createEntityUC:  createEntityUC,
		saveToRepoUC:    saveToRepoUC,
		passwordProvider: passwordProvider,
		logger:          logger.Named("create-user-service"),
	}
}

type CreateUserInput struct {
	TenantID uuid.UUID
	Email    string
	Name     string
	Role     string
	Password string
}

type CreateUserResponse struct {
	UserID string
	Email  string
	Name   string
	Role   string
}

// Create orchestrates user creation with SAGA pattern
func (s *createUserService) Create(
	ctx context.Context,
	input *CreateUserInput,
) (*CreateUserResponse, error) {
	// Validate email uniqueness
	if err := s.validateEmailUC.Execute(ctx, input.Email); err != nil {
		return nil, fmt.Errorf("email validation failed: %w", err)
	}

	// Hash password
	hashedPassword, err := s.passwordProvider.HashPassword(input.Password)
	if err != nil {
		return nil, fmt.Errorf("password hashing failed: %w", err)
	}

	// Create user entity
	userEntity, err := s.createEntityUC.Execute(ctx, &userusecase.CreateUserEntityInput{
		TenantID:       input.TenantID,
		Email:          input.Email,
		Name:           input.Name,
		Role:           input.Role,
		HashedPassword: hashedPassword,
	})
	if err != nil {
		return nil, fmt.Errorf("entity creation failed: %w", err)
	}

	// Use SAGA pattern for transaction management
	saga := transaction.NewSaga(s.logger)

	// Step 1: Save user to repository
	saga.AddStep(
		func(ctx context.Context) error {
			return s.saveToRepoUC.Execute(ctx, userEntity)
		},
		func(ctx context.Context) error {
			// Compensation: Delete user if subsequent steps fail
			// (implement delete use case)
			s.logger.Warn("Compensating: deleting user", zap.String("user_id", userEntity.ID.String()))
			return nil
		},
	)

	// Execute SAGA
	if err := saga.Execute(ctx); err != nil {
		return nil, fmt.Errorf("user creation failed: %w", err)
	}

	return &CreateUserResponse{
		UserID: userEntity.ID.String(),
		Email:  userEntity.Email,
		Name:   userEntity.Name,
		Role:   userEntity.Role,
	}, nil
}

9.5 Interface Layer (internal/interface/http/)

Purpose: HTTP handlers and DTOs

Example: Create User Handler

// internal/interface/http/handler/user/create_handler.go
package user

import (
	"encoding/json"
	"net/http"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/dto/user"
	usersvc "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/service/user"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/pkg/httpresponse"
)

// CreateHandler handles user creation
type CreateHandler struct {
	createService usersvc.CreateUserService
	logger        *zap.Logger
}

// ProvideCreateHandler provides the handler for Wire
func ProvideCreateHandler(
	createService usersvc.CreateUserService,
	logger *zap.Logger,
) *CreateHandler {
	return &CreateHandler{
		createService: createService,
		logger:        logger.Named("create-user-handler"),
	}
}

// Handle processes user creation requests
func (h *CreateHandler) Handle(w http.ResponseWriter, r *http.Request) {
	// Get tenant ID from context (populated by JWT middleware)
	tenantIDStr, ok := r.Context().Value(constants.SessionTenantID).(string)
	if !ok {
		httperror.SendError(w, http.StatusUnauthorized, "Tenant ID not found", nil)
		return
	}

	tenantID, err := uuid.Parse(tenantIDStr)
	if err != nil {
		httperror.SendError(w, http.StatusBadRequest, "Invalid tenant ID", err)
		return
	}

	// Parse request
	var req user.CreateUserRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httperror.SendError(w, http.StatusBadRequest, "Invalid request body", err)
		return
	}

	// Validate input
	if req.Email == "" || req.Password == "" || req.Name == "" {
		httperror.SendError(w, http.StatusBadRequest, "Missing required fields", nil)
		return
	}

	// Create user
	response, err := h.createService.Create(r.Context(), &usersvc.CreateUserInput{
		TenantID: tenantID,
		Email:    req.Email,
		Name:     req.Name,
		Role:     req.Role,
		Password: req.Password,
	})
	if err != nil {
		h.logger.Error("User creation failed", zap.Error(err))
		httperror.SendError(w, http.StatusInternalServerError, "Failed to create user", err)
		return
	}

	// Send response
	httpresponse.SendJSON(w, http.StatusCreated, response)
}

10. Database Setup (Cassandra)

10.1 Cassandra Schema Design

Design Principles:

  1. Query-driven modeling - Design tables based on query patterns
  2. Denormalization - Duplicate data to avoid joins
  3. Partition keys - Choose keys that distribute data evenly
  4. Clustering keys - Define sort order within partitions

Example Migration:

-- migrations/000001_create_users.up.cql

-- Users table (by ID)
CREATE TABLE IF NOT EXISTS users (
    id uuid,
    tenant_id uuid,
    email text,
    name text,
    role text,
    password text,
    status text,
    created_at timestamp,
    updated_at timestamp,
    PRIMARY KEY (id)
);

-- Users by email (for login lookups)
CREATE TABLE IF NOT EXISTS users_by_email (
    email text,
    tenant_id uuid,
    user_id uuid,
    PRIMARY KEY (email)
);

-- Users by tenant (for listing users in a tenant)
CREATE TABLE IF NOT EXISTS users_by_tenant (
    tenant_id uuid,
    user_id uuid,
    email text,
    name text,
    role text,
    status text,
    created_at timestamp,
    PRIMARY KEY (tenant_id, created_at, user_id)
) WITH CLUSTERING ORDER BY (created_at DESC, user_id ASC);

-- Create indexes
CREATE INDEX IF NOT EXISTS users_status_idx ON users (status);
CREATE INDEX IF NOT EXISTS users_tenant_idx ON users (tenant_id);
-- migrations/000001_create_users.down.cql

DROP TABLE IF EXISTS users_by_tenant;
DROP TABLE IF EXISTS users_by_email;
DROP INDEX IF EXISTS users_tenant_idx;
DROP INDEX IF EXISTS users_status_idx;
DROP TABLE IF EXISTS users;

10.2 Migration Management

pkg/storage/database/migrator.go:

package database

import (
	"fmt"

	"github.com/golang-migrate/migrate/v4"
	_ "github.com/golang-migrate/migrate/v4/database/cassandra"
	_ "github.com/golang-migrate/migrate/v4/source/file"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
)

// Migrator handles database migrations
type Migrator struct {
	migrate *migrate.Migrate
	logger  *zap.Logger
}

// NewMigrator creates a new migrator
func NewMigrator(cfg *config.Config, logger *zap.Logger) *Migrator {
	// Build Cassandra connection string
	dbURL := fmt.Sprintf("cassandra://%s/%s?consistency=%s",
		cfg.Database.Hosts[0],
		cfg.Database.Keyspace,
		cfg.Database.Consistency,
	)

	// Create migrate instance
	m, err := migrate.New(cfg.Database.MigrationsPath, dbURL)
	if err != nil {
		logger.Fatal("Failed to create migrator", zap.Error(err))
	}

	return &Migrator{
		migrate: m,
		logger:  logger.Named("migrator"),
	}
}

// Up runs all pending migrations
func (m *Migrator) Up() error {
	m.logger.Info("Running migrations...")
	if err := m.migrate.Up(); err != nil && err != migrate.ErrNoChange {
		return fmt.Errorf("migration failed: %w", err)
	}

	version, _, _ := m.migrate.Version()
	m.logger.Info("Migrations completed",
		zap.Uint("version", uint(version)))

	return nil
}

// Down rolls back the last migration
func (m *Migrator) Down() error {
	m.logger.Info("Rolling back last migration...")
	if err := m.migrate.Steps(-1); err != nil {
		return fmt.Errorf("rollback failed: %w", err)
	}

	version, _, _ := m.migrate.Version()
	m.logger.Info("Rollback completed",
		zap.Uint("version", uint(version)))

	return nil
}

// Version returns current migration version
func (m *Migrator) Version() (uint, bool, error) {
	return m.migrate.Version()
}

// ForceVersion forces migration to specific version
func (m *Migrator) ForceVersion(version int) error {
	m.logger.Warn("Forcing migration version",
		zap.Int("version", version))
	return m.migrate.Force(version)
}

11. Middleware Implementation

11.1 Security Headers Middleware

internal/http/middleware/security_headers.go:

package middleware

import (
	"net/http"
	"strings"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
)

// SecurityHeadersMiddleware adds security headers to responses
type SecurityHeadersMiddleware struct {
	config *config.Config
	logger *zap.Logger
}

// ProvideSecurityHeadersMiddleware provides the middleware for Wire
func ProvideSecurityHeadersMiddleware(
	cfg *config.Config,
	logger *zap.Logger,
) *SecurityHeadersMiddleware {
	return &SecurityHeadersMiddleware{
		config: cfg,
		logger: logger.Named("security-headers-middleware"),
	}
}

// Handler applies security headers
func (m *SecurityHeadersMiddleware) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// CWE-693: Apply security headers

		// CORS headers
		origin := r.Header.Get("Origin")
		if m.isAllowedOrigin(origin) {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Credentials", "true")
		}

		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Tenant-ID")
		w.Header().Set("Access-Control-Max-Age", "86400") // 24 hours

		// Handle preflight requests
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusNoContent)
			return
		}

		// Security headers (CWE-693: Protection Mechanism Failure)
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("X-XSS-Protection", "1; mode=block")
		w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
		w.Header().Set("Content-Security-Policy", "default-src 'self'")
		w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")

		next.ServeHTTP(w, r)
	})
}

// isAllowedOrigin checks if origin is in allowed list
func (m *SecurityHeadersMiddleware) isAllowedOrigin(origin string) bool {
	if origin == "" {
		return false
	}

	// In development, allow localhost
	if m.config.App.Environment == "development" {
		if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") {
			return true
		}
	}

	// Check against configured allowed origins
	for _, allowed := range m.config.Security.AllowedOrigins {
		if origin == allowed {
			return true
		}
	}

	return false
}

11.2 Request Size Limit Middleware

internal/http/middleware/request_size_limit.go:

package middleware

import (
	"net/http"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
)

// RequestSizeLimitMiddleware limits request body size
type RequestSizeLimitMiddleware struct {
	config *config.Config
	logger *zap.Logger
}

// ProvideRequestSizeLimitMiddleware provides the middleware for Wire
func ProvideRequestSizeLimitMiddleware(
	cfg *config.Config,
	logger *zap.Logger,
) *RequestSizeLimitMiddleware {
	return &RequestSizeLimitMiddleware{
		config: cfg,
		logger: logger.Named("request-size-limit-middleware"),
	}
}

// LimitSmall applies 1MB limit (for auth endpoints)
func (m *RequestSizeLimitMiddleware) LimitSmall() func(http.Handler) http.Handler {
	return m.limitWithSize(1 * 1024 * 1024) // 1 MB
}

// LimitMedium applies 5MB limit (for typical API endpoints)
func (m *RequestSizeLimitMiddleware) LimitMedium() func(http.Handler) http.Handler {
	return m.limitWithSize(5 * 1024 * 1024) // 5 MB
}

// LimitLarge applies 50MB limit (for file uploads)
func (m *RequestSizeLimitMiddleware) LimitLarge() func(http.Handler) http.Handler {
	return m.limitWithSize(50 * 1024 * 1024) // 50 MB
}

// limitWithSize creates a middleware with specific size limit
func (m *RequestSizeLimitMiddleware) limitWithSize(maxBytes int64) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Limit request body size (CWE-770: Resource Exhaustion)
			r.Body = http.MaxBytesReader(w, r.Body, maxBytes)

			next.ServeHTTP(w, r)
		})
	}
}

12. HTTP Server Setup

internal/interface/http/server.go

package http

import (
	"context"
	"fmt"
	"net/http"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
	httpmw "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/http/middleware"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/handler/gateway"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/handler/healthcheck"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/internal/interface/http/middleware"
)

// Server represents the HTTP server
type Server struct {
	server                    *http.Server
	logger                    *zap.Logger
	jwtMiddleware             *httpmw.JWTMiddleware
	securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware
	requestSizeLimitMw        *httpmw.RequestSizeLimitMiddleware
	config                    *config.Config
	healthHandler             *healthcheck.Handler
	loginHandler              *gateway.LoginHandler
}

// ProvideServer creates a new HTTP server
func ProvideServer(
	cfg *config.Config,
	logger *zap.Logger,
	jwtMiddleware *httpmw.JWTMiddleware,
	securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware,
	requestSizeLimitMw *httpmw.RequestSizeLimitMiddleware,
	healthHandler *healthcheck.Handler,
	loginHandler *gateway.LoginHandler,
) *Server {
	mux := http.NewServeMux()

	s := &Server{
		logger:                    logger,
		jwtMiddleware:             jwtMiddleware,
		securityHeadersMiddleware: securityHeadersMiddleware,
		requestSizeLimitMw:        requestSizeLimitMw,
		config:                    cfg,
		healthHandler:             healthHandler,
		loginHandler:              loginHandler,
	}

	// Register routes
	s.registerRoutes(mux)

	// Create HTTP server
	s.server = &http.Server{
		Addr:         fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
		Handler:      s.applyMiddleware(mux),
		ReadTimeout:  cfg.HTTP.ReadTimeout,
		WriteTimeout: cfg.HTTP.WriteTimeout,
		IdleTimeout:  cfg.HTTP.IdleTimeout,
	}

	logger.Info("✓ HTTP server configured",
		zap.String("address", s.server.Addr),
		zap.Duration("read_timeout", cfg.HTTP.ReadTimeout),
		zap.Duration("write_timeout", cfg.HTTP.WriteTimeout))

	return s
}

// registerRoutes registers all HTTP routes
func (s *Server) registerRoutes(mux *http.ServeMux) {
	// ===== PUBLIC ROUTES =====
	mux.HandleFunc("GET /health", s.healthHandler.Handle)

	// Authentication routes
	mux.HandleFunc("POST /api/v1/login",
		s.requestSizeLimitMw.LimitSmall()(
			http.HandlerFunc(s.loginHandler.Handle),
		).ServeHTTP)

	// ===== AUTHENTICATED ROUTES =====
	// Add your authenticated routes here with JWT middleware
	// Example:
	// mux.HandleFunc("GET /api/v1/me", s.applyAuthOnly(s.meHandler.Handle))
}

// applyMiddleware applies global middleware to all routes
func (s *Server) applyMiddleware(handler http.Handler) http.Handler {
	// Apply middleware in order
	handler = middleware.LoggerMiddleware(s.logger)(handler)
	handler = s.securityHeadersMiddleware.Handler(handler)
	return handler
}

// applyAuthOnly applies only JWT authentication middleware
func (s *Server) applyAuthOnly(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.jwtMiddleware.Handler(
			s.jwtMiddleware.RequireAuth(
				http.HandlerFunc(handler),
			),
		).ServeHTTP(w, r)
	}
}

// Start starts the HTTP server
func (s *Server) Start() error {
	s.logger.Info("")
	s.logger.Info("🚀 Backend is ready!")
	s.logger.Info("",
		zap.String("address", s.server.Addr),
		zap.String("url", fmt.Sprintf("http://localhost:%d", s.config.Server.Port)))
	s.logger.Info("")

	if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		return fmt.Errorf("failed to start server: %w", err)
	}

	return nil
}

// Shutdown gracefully shuts down the HTTP server
func (s *Server) Shutdown(ctx context.Context) error {
	s.logger.Info("shutting down HTTP server")

	if err := s.server.Shutdown(ctx); err != nil {
		return fmt.Errorf("failed to shutdown server: %w", err)
	}

	s.logger.Info("HTTP server shut down successfully")
	return nil
}

13. Docker & Infrastructure

13.1 Development Dockerfile

dev.Dockerfile:

FROM golang:1.24-alpine

# Install development tools
RUN apk add --no-cache git curl bash

# Set working directory
WORKDIR /go/src/codeberg.org/mapleopentech/monorepo/cloud/your-backend-name

# Install Wire
RUN go install github.com/google/wire/cmd/wire@latest

# Copy go.mod and go.sum
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY . .

# Generate Wire code
RUN cd app && wire

# Build the application
RUN go build -o app-dev .

# Expose port
EXPOSE 8000

# Run the application
CMD ["./app-dev", "daemon"]

13.2 Production Dockerfile

Dockerfile:

###
### Build Stage
###

FROM golang:1.24-alpine AS build-env

# Create app directory
RUN mkdir /app
WORKDIR /app

# Copy dependency list
COPY go.mod go.sum ./

# Install dependencies
RUN go mod download

# Copy all files
COPY . .

# Install Wire
RUN go install github.com/google/wire/cmd/wire@latest

# Generate Wire code
RUN cd app && wire

# Build for Linux AMD64
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-backend .

###
### Run Stage
###

FROM alpine:latest

WORKDIR /app

# Copy executable
COPY --from=build-env /app/app-backend .

# Copy migrations
COPY --from=build-env /app/migrations ./migrations

# Copy static files (if any)
COPY --from=build-env /app/static ./static

EXPOSE 8000

# Run the server
CMD ["/app/app-backend", "daemon"]

13.3 Docker Compose (Development)

docker-compose.dev.yml:

# Use external network from shared infrastructure
networks:
  maple-dev:
    external: true

services:
  app:
    container_name: your-backend-dev
    stdin_open: true
    build:
      context: .
      dockerfile: ./dev.Dockerfile
    ports:
      - "${SERVER_PORT:-8000}:${SERVER_PORT:-8000}"
    env_file:
      - .env
    environment:
      # Application
      APP_ENVIRONMENT: ${APP_ENVIRONMENT:-development}
      APP_VERSION: ${APP_VERSION:-0.1.0-dev}
      APP_JWT_SECRET: ${APP_JWT_SECRET:-dev-secret}

      # Server
      SERVER_HOST: ${SERVER_HOST:-0.0.0.0}
      SERVER_PORT: ${SERVER_PORT:-8000}

      # Cassandra (connect to shared infrastructure)
      DATABASE_HOSTS: ${DATABASE_HOSTS:-cassandra-1:9042,cassandra-2:9042,cassandra-3:9042}
      DATABASE_KEYSPACE: ${DATABASE_KEYSPACE:-your_keyspace}
      DATABASE_CONSISTENCY: ${DATABASE_CONSISTENCY:-ONE}
      DATABASE_REPLICATION: ${DATABASE_REPLICATION:-3}
      DATABASE_MIGRATIONS_PATH: ${DATABASE_MIGRATIONS_PATH:-file://migrations}

      # Redis (connect to shared infrastructure)
      CACHE_HOST: ${CACHE_HOST:-redis}
      CACHE_PORT: ${CACHE_PORT:-6379}
      CACHE_PASSWORD: ${CACHE_PASSWORD:-}
      CACHE_DB: ${CACHE_DB:-0}

      # Logger
      LOGGER_LEVEL: ${LOGGER_LEVEL:-debug}
      LOGGER_FORMAT: ${LOGGER_FORMAT:-console}

    volumes:
      - ./:/go/src/codeberg.org/mapleopentech/monorepo/cloud/your-backend-name
    networks:
      - maple-dev
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT:-8000}/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

13.4 Task Runner (Taskfile.yml)

Taskfile.yml:

version: "3"

env:
  COMPOSE_PROJECT_NAME: your-backend

vars:
  DOCKER_COMPOSE_CMD:
    sh: |
      if command -v docker-compose >/dev/null 2>&1; then
        echo "docker-compose"
      elif docker compose version >/dev/null 2>&1; then
        echo "docker compose"
      else
        echo "docker-compose"
      fi

tasks:
  # Development workflow
  dev:
    desc: Start app in development mode
    cmds:
      - "{{.DOCKER_COMPOSE_CMD}} -f docker-compose.dev.yml up --build"

  dev:down:
    desc: Stop development app
    cmds:
      - "{{.DOCKER_COMPOSE_CMD}} -f docker-compose.dev.yml down"

  dev:restart:
    desc: Quick restart
    cmds:
      - "{{.DOCKER_COMPOSE_CMD}} -f docker-compose.dev.yml restart"

  dev:logs:
    desc: View app logs
    cmds:
      - "{{.DOCKER_COMPOSE_CMD}} -f docker-compose.dev.yml logs -f"

  dev:shell:
    desc: Open shell in running container
    cmds:
      - docker exec -it your-backend-dev sh

  # Database operations
  migrate:up:
    desc: Run all migrations up
    cmds:
      - ./app-backend migrate up

  migrate:down:
    desc: Run all migrations down
    cmds:
      - ./app-backend migrate down

  # Build and test
  build:
    desc: Build the Go binary
    cmds:
      - task: wire
      - go build -o app-backend

  wire:
    desc: Generate Wire dependency injection
    cmds:
      - cd app && wire

  test:
    desc: Run tests
    cmds:
      - go test ./... -v

  lint:
    desc: Run linters
    cmds:
      - go vet ./...

  format:
    desc: Format code
    cmds:
      - go fmt ./...

  tidy:
    desc: Tidy Go modules
    cmds:
      - go mod tidy

  # Production deployment
  deploy:
    desc: Build and push production container
    cmds:
      - docker build -f Dockerfile --rm -t registry.example.com/your-org/your_backend:prod --platform linux/amd64 .
      - docker push registry.example.com/your-org/your_backend:prod

14. CLI Commands (Cobra)

14.1 Root Command

cmd/root.go:

package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/cmd/daemon"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/cmd/migrate"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/cmd/version"
)

var rootCmd = &cobra.Command{
	Use:   "your-backend",
	Short: "Your Backend Service",
	Long:  `Your Backend - Clean Architecture with Wire DI and Cassandra`,
}

// Execute runs the root command
func Execute() {
	rootCmd.AddCommand(daemon.DaemonCmd())
	rootCmd.AddCommand(migrate.MigrateCmd())
	rootCmd.AddCommand(version.VersionCmd())

	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

14.2 Daemon Command

cmd/daemon/daemon.go:

package daemon

import (
	"log"

	"github.com/spf13/cobra"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/app"
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
)

// DaemonCmd returns the daemon command
func DaemonCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "daemon",
		Short: "Start the HTTP server",
		Run: func(cmd *cobra.Command, args []string) {
			// Load configuration
			cfg, err := config.Load()
			if err != nil {
				log.Fatalf("Failed to load config: %v", err)
			}

			// Initialize application with Wire
			application, err := app.InitializeApplication(cfg)
			if err != nil {
				log.Fatalf("Failed to initialize application: %v", err)
			}

			// Start application
			if err := application.Start(); err != nil {
				log.Fatalf("Application failed: %v", err)
			}
		},
	}
}

14.3 Main Entry Point

main.go:

package main

import (
	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/cmd"
)

func main() {
	cmd.Execute()
}

15. Development Workflow

Daily Development Flow

# 1. Start shared infrastructure (first time)
cd cloud/infrastructure/development
task dev:start

# 2. Start your backend
cd cloud/your-backend-name
task dev

# 3. Run migrations (first time)
task migrate:up

# 4. Make code changes...

# 5. Quick restart (after code changes)
task dev:restart

# 6. View logs
task dev:logs

# 7. Run tests
task test

# 8. Format and lint
task format
task lint

# 9. Stop backend
task dev:down

Common Development Tasks

# Generate Wire dependencies
task wire

# Build binary locally
task build

# Run locally (without Docker)
./app-backend daemon

# Create new migration
./app-backend migrate create create_new_table

# Check migration version
./app-backend migrate version

# Reset database
task db:reset

# Open shell in container
task dev:shell

# Check application version
./app-backend version

16. Testing Strategy

16.1 Unit Testing

Example: Use Case Test

// internal/usecase/user/create_user_entity_test.go
package user

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

func TestCreateUserEntityUseCase_Execute(t *testing.T) {
	logger := zap.NewNop()
	uc := ProvideCreateUserEntityUseCase(logger)

	input := &CreateUserEntityInput{
		TenantID:       uuid.New(),
		Email:          "test@example.com",
		Name:           "Test User",
		Role:           "user",
		HashedPassword: "hashed-password",
	}

	entity, err := uc.Execute(context.Background(), input)

	if err != nil {
		t.Fatalf("Execute failed: %v", err)
	}

	if entity.Email != input.Email {
		t.Errorf("Expected email %s, got %s", input.Email, entity.Email)
	}

	if entity.ID == uuid.Nil {
		t.Error("Expected non-nil ID")
	}
}

16.2 Integration Testing

Use test containers for integration tests:

// internal/repository/user/integration_test.go
package user

import (
	"context"
	"testing"

	"github.com/gocql/gocql"
	"github.com/testcontainers/testcontainers-go"
)

func TestUserRepository_Integration(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test")
	}

	// Start Cassandra container
	ctx := context.Background()
	cassandraContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: testcontainers.ContainerRequest{
			Image:        "cassandra:4.1",
			ExposedPorts: []string{"9042/tcp"},
		},
		Started: true,
	})
	if err != nil {
		t.Fatalf("Failed to start container: %v", err)
	}
	defer cassandraContainer.Terminate(ctx)

	// Run tests...
}

17. Production Deployment

17.1 Build Production Container

# Build for Linux AMD64
task deploy

17.2 Environment Variables (Production)

# .env (production)
APP_ENVIRONMENT=production
APP_VERSION=1.0.0
APP_JWT_SECRET=<use-openssl-rand-base64-64>

SERVER_HOST=0.0.0.0
SERVER_PORT=8000

DATABASE_HOSTS=cassandra-prod-1:9042,cassandra-prod-2:9042,cassandra-prod-3:9042
DATABASE_KEYSPACE=your_keyspace_prod
DATABASE_CONSISTENCY=QUORUM
DATABASE_REPLICATION=3

CACHE_HOST=redis-prod
CACHE_PORT=6379
CACHE_PASSWORD=<strong-password>
CACHE_DB=0

SECURITY_IP_ENCRYPTION_KEY=<use-openssl-rand-hex-16>
SECURITY_CORS_ALLOWED_ORIGINS=https://yourdomain.com

LOGGER_LEVEL=info
LOGGER_FORMAT=json

17.3 Health Checks

internal/interface/http/handler/healthcheck/healthcheck_handler.go:

package healthcheck

import (
	"encoding/json"
	"net/http"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/config"
)

// Handler handles health check requests
type Handler struct {
	config *config.Config
	logger *zap.Logger
}

// ProvideHealthCheckHandler provides the handler for Wire
func ProvideHealthCheckHandler(
	cfg *config.Config,
	logger *zap.Logger,
) *Handler {
	return &Handler{
		config: cfg,
		logger: logger.Named("healthcheck-handler"),
	}
}

// Handle responds to health check requests
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) {
	response := map[string]interface{}{
		"status":      "ok",
		"timestamp":   time.Now().UTC(),
		"environment": h.config.App.Environment,
		"version":     h.config.App.Version,
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(response)
}

Summary

You now have a complete blueprint for building Golang backends with:

Clean Architecture - Proper layer separation and dependency flow Wire Dependency Injection - Compile-time, type-safe DI JWT Authentication - Secure session management with two-tier caching Cassandra Database - Query-driven schema design with migrations Reusable pkg/ Components - Copy-paste infrastructure utilities Security-First - CWE-compliant middleware and validation Docker Infrastructure - Development and production containers CLI Commands - Cobra-based command structure

Next Steps

  1. Copy this document to your new project's docs/ directory
  2. Run the copy-pkg.sh script to copy reusable components
  3. Update import paths throughout the codebase
  4. Customize domain entities, use cases, and services for your specific needs
  5. Add business logic while maintaining the architectural patterns

Reference Implementation

Always refer back to cloud/maplepress-backend for:

  • Complete working examples
  • Advanced patterns (SAGA, rate limiting, etc.)
  • Production-tested code
  • Security best practices

Questions or Issues? Review the maplepress-backend codebase or create a new issue in the repository.