# 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](#architecture-overview) 2. [Project Initialization](#project-initialization) 3. [Directory Structure](#directory-structure) 4. [Core Dependencies](#core-dependencies) 5. [Configuration System](#configuration-system) 6. [Dependency Injection with Wire](#dependency-injection-with-wire) 7. [Reusable pkg/ Components](#reusable-pkg-components) 8. [Authentication System](#authentication-system) 9. [Clean Architecture Layers](#clean-architecture-layers) 10. [Database Setup (Cassandra)](#database-setup-cassandra) 11. [Middleware Implementation](#middleware-implementation) 12. [HTTP Server Setup](#http-server-setup) 13. [Docker & Infrastructure](#docker--infrastructure) 14. [CLI Commands (Cobra)](#cli-commands-cobra) 15. [Development Workflow](#development-workflow) 16. [Testing Strategy](#testing-strategy) 17. [Production Deployment](#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 ```bash # 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) ```bash # From monorepo root cd /path/to/monorepo go work use ./cloud/your-backend-name ``` ### Step 3: Add Core Dependencies ```bash # 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 ```go 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: ```go 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: ```go 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: ```bash # 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 //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: ```go 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 ```bash # 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:** ```go 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/) ```go // 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/) ```go // 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/) ```go // 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/) ```go // Extract client IP with X-Forwarded-For validation (CWE-348) extractor := security.ProvideClientIPExtractor(cfg) clientIP, err := extractor.ExtractIP(r) ``` ##### IP Encryption (pkg/security/ipcrypt/) ```go // 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 ```go // 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) ```go // 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 ```go // 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 ```go // 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 ```go 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 ```go // 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 ```go // 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: ```bash #!/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: ```bash chmod +x copy-pkg.sh ./copy-pkg.sh ``` ### Update Import Paths After copying, you'll need to update import paths in all copied files: ```bash # 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:** ```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:** ```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:** ```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 " 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:** ```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** ```go // 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** ```go // 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** ```go // 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** ```go // 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** ```go // 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** ```go // 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** ```go // 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:** ```cql -- 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); ``` ```cql -- 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:** ```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:** ```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:** ```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 ```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:** ```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:** ```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:** ```yaml # Use external network from shared infrastructure networks: mapleopentech-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: - mapleopentech-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:** ```yaml 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:** ```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:** ```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:** ```go package main import ( "codeberg.org/mapleopentech/monorepo/cloud/your-backend-name/cmd" ) func main() { cmd.Execute() } ``` --- ## 15. Development Workflow ### Daily Development Flow ```bash # 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 ```bash # 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** ```go // 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: ```go // 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 ```bash # Build for Linux AMD64 task deploy ``` ### 17.2 Environment Variables (Production) ```bash # .env (production) APP_ENVIRONMENT=production APP_VERSION=1.0.0 APP_JWT_SECRET= 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= CACHE_DB=0 SECURITY_IP_ENCRYPTION_KEY= 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:** ```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.