Skip to content

Latest commit

 

History

History
335 lines (269 loc) · 10.1 KB

File metadata and controls

335 lines (269 loc) · 10.1 KB

Architecture Diagrams

Visual documentation for the API Template Gin project. All diagrams use Mermaid syntax and render natively on GitHub.


Table of Contents


1. Clean Architecture Overview

Hexagonal architecture with three layers. Dependencies always point inward — adapters depend on application, application depends on domain. External systems are accessed only through port interfaces.

flowchart TB
    subgraph External["External Systems"]
        Client([HTTP Client])
        Terminal([Terminal / CLI])
        PG[(PostgreSQL)]
    end

    subgraph Adapters["Adapters Layer"]
        direction TB

        subgraph HTTP["HTTP Adapter"]
            Handlers["handlers/\nGin HTTP Handlers"]
            DTO["dto/\nSuccessResponse & ErrorResponse"]
            Infra["infrastructure/\nGin Engine, Routes, Middleware"]
        end

        subgraph CLI["CLI Adapter"]
            CobraCLI["cli/\nCobra Subcommand"]
        end

        subgraph Repo["Repository Adapter"]
            GormRepo["repository/\nGORM + sync.Once Singleton"]
        end
    end

    subgraph Application["Application Layer"]
        Services["system_services/\nHealth Service"]
        Ports["ports/\nStore Interface"]
    end

    subgraph Domain["Domain Layer"]
        Entities["entities & rules\n(ready for expansion)"]
    end

    subgraph Pkg["Shared Packages"]
        Config["config/\ngodotenv + pflag"]
        Logger["log/\nLogrus wrapper"]
    end

    Client -->|HTTP| Infra
    Infra --> Handlers
    Handlers --> DTO
    Handlers --> Services
    Terminal -->|CLI| CobraCLI
    CobraCLI --> Services
    Services --> Ports
    Ports -.-|implements| GormRepo
    GormRepo -->|SQL| PG
    Services --> Entities
    Config -.->|used by| Infra
    Config -.->|used by| GormRepo
    Logger -.->|used by| Handlers
    Logger -.->|used by| GormRepo

    classDef external fill:#64748b,stroke:#475569,color:#fff
    classDef adapters fill:#3b82f6,stroke:#2563eb,color:#fff
    classDef application fill:#8b5cf6,stroke:#7c3aed,color:#fff
    classDef domain fill:#10b981,stroke:#059669,color:#fff
    classDef shared fill:#f59e0b,stroke:#d97706,color:#fff

    class Client,Terminal,PG external
    class Handlers,DTO,Infra,CobraCLI,GormRepo adapters
    class Services,Ports application
    class Entities domain
    class Config,Logger shared
Loading

2. HTTP Request Flow

End-to-end flow of a GET /ping request through all architectural layers.

sequenceDiagram
    actor Client
    participant Gin as Gin Engine
    participant Router as RegisterHandlersWithOptions
    participant Handler as Handler.Ping()
    participant DTO as dto.OK()

    Client->>+Gin: GET /ping
    Gin->>Gin: Logger & Recovery middleware
    Gin->>+Router: Match route (public group)
    Router->>+Handler: Invoke Ping(c *gin.Context)
    Handler->>DTO: dto.OK(c, gin.H{"ping": "pong"})
    DTO-->>Handler: SuccessResponse{Data, Meta} written
    Handler-->>-Router: Response sent
    Router-->>-Gin: Response written
    Gin-->>-Client: 200 {"data": {"ping": "pong"}, "meta": {"timestamp": "..."}}
Loading

3. Authentication Middleware Flow

How the Basic Auth middleware validates requests on protected routes. Public routes bypass this entirely.

sequenceDiagram
    actor Client
    participant Gin as Gin Engine
    participant Router as Route Groups
    participant Auth as basicAuthMiddleware
    participant Handler as Protected Handler
    participant Config as config.GetAuthenticationKey()

    Client->>+Gin: Request to protected route

    alt Public Route (/ping, /metrics)
        Gin->>+Router: Match public group
        Router->>Handler: Direct handler call (no auth)
        Handler-->>Router: Response
        Router-->>-Gin: 200 OK
    else Protected Route
        Gin->>+Router: Match protected group
        Router->>+Auth: Execute middleware chain
        Auth->>Config: Get AUTH_SECRET
        Config-->>Auth: Secret value

        alt Missing or invalid Authorization header
            Auth-->>Router: dto.Unauthorized(c, "Invalid or missing auth token")
            Router-->>Gin: 401 {"error": {"code": "UNAUTHORIZED", "message": "..."}}
        else Valid "Basic <base64(secret)>" header
            Auth->>Auth: Compare token == "Basic " + base64(secret)
            Auth-->>-Router: c.Next()
            Router->>+Handler: Invoke handler
            Handler-->>-Router: Response
            Router-->>Gin: 200 OK
        end
        Router-->>-Gin: Response sent
    end

    Gin-->>-Client: HTTP Response
Loading

4. Server Startup Sequence

Complete initialization flow from main.go to a running HTTP server with graceful shutdown.

flowchart TD
    Start([main.go]) --> LogStart["log.Info('Starting application...')"]
    LogStart --> Execute["app.Execute()"]
    Execute --> Init["Initialize()"]

    Init --> LoadEnv["config.LoadConfiguration()"]
    LoadEnv --> DotEnv["godotenv.Load(.env)"]
    DotEnv --> PFlags["pflag.Parse()"]
    PFlags --> MapEnv["loadEnvVariables()\nmap ENV → pflag keys"]
    MapEnv --> SetLog["log.SetLogLevel()"]
    SetLog --> SetEnvName["config.SetEnvironment()"]

    SetEnvName --> Cobra["rootCmd.Execute()"]
    Cobra --> CmdChoice{Subcommand?}

    CmdChoice -->|server| StartServer["StartServer()"]
    CmdChoice -->|cli -f test| CLIRun["cli.RunCliCmd()"]
    CmdChoice -->|none| Help["Show help"]

    StartServer --> NewServer["infrastructure.NewServer()"]
    NewServer --> Validate["serverConfig.Validate()"]
    Validate --> GinMode{"Mode?"}
    GinMode -->|debug| DebugMode["gin.SetMode(debug)\nconsole output"]
    GinMode -->|release| ReleaseMode["gin.SetMode(release)\nlog to file"]
    DebugMode --> CreateRouter["gin.Default()"]
    ReleaseMode --> CreateRouter
    CreateRouter --> Metrics["setMetrics(/metrics)"]
    Metrics --> Register["RegisterHandlersWithOptions()\npublic + protected groups"]
    Register --> LoadHandlers["loadHandlers()\nNewRestHandler()"]
    LoadHandlers --> ConfigLogger["log.ConfigureLogger()"]
    ConfigLogger --> ListenServe["srv.ListenAndServe()\n(goroutine)"]

    ListenServe --> Running([Server Running])
    Running --> WaitSignal["signal.Notify(SIGINT)"]
    WaitSignal --> Shutdown["srv.Shutdown(ctx)\n10s timeout"]
    Shutdown --> Stopped([Server Stopped])

    CLIRun --> HealthSvc["HealthService()"]
    HealthSvc --> RepoInit["NewRepository()\nsync.Once singleton"]
    RepoInit --> TestDB["healthService.TestDb()"]
    TestDB --> DBResult{Success?}
    DBResult -->|Yes| CLISuccess(["Print success"])
    DBResult -->|No| CLIError(["Print error"])

    classDef startEnd fill:#64748b,stroke:#475569,color:#fff
    classDef config fill:#f59e0b,stroke:#d97706,color:#fff
    classDef server fill:#3b82f6,stroke:#2563eb,color:#fff
    classDef cli fill:#8b5cf6,stroke:#7c3aed,color:#fff
    classDef decision fill:#f97316,stroke:#ea580c,color:#fff

    class Start,Running,Stopped,CLISuccess,CLIError,Help startEnd
    class LoadEnv,DotEnv,PFlags,MapEnv,SetLog,SetEnvName config
    class NewServer,Validate,DebugMode,ReleaseMode,CreateRouter,Metrics,Register,LoadHandlers,ConfigLogger,ListenServe,WaitSignal,Shutdown server
    class CLIRun,HealthSvc,RepoInit,TestDB cli
    class CmdChoice,GinMode,DBResult decision
Loading

5. Ports & Adapters (Class Diagram)

Interfaces (ports) and their concrete implementations (adapters). Shows the Dependency Inversion principle — application layer defines the contracts, adapter layer implements them.

classDiagram
    direction LR

    namespace Ports {
        class Store {
            <<interface>>
            +TestDb() error
        }

        class Health {
            <<interface>>
            +TestDb() error
        }

        class ServerInterface {
            <<interface>>
            +Ping(c *gin.Context)
        }
    }

    namespace Adapters {
        class repository {
            -db *gorm.DB
            +TestDb() error
        }

        class Handler {
            +Ping(c *gin.Context)
        }
    }

    namespace Application {
        class healthImp {
            -r Store
            +TestDb() error
        }

        class HealthService {
            <<factory>>
            +HealthService() (Health, error)
        }
    }

    namespace Infrastructure {
        class NewRepository {
            <<factory>>
            -once sync.Once
            -instance *repository
            +NewRepository() (Store, error)
            +NewConnection(dsn DBConfig) (Store, error)
        }

        class GinServer {
            +NewGinServer(handler ServerInterface) *gin.Engine
            +NewServer() *gin.Engine
            +RegisterHandlersWithOptions()
        }

        class GinServerOptions {
            +BaseURL string
            +Middlewares []gin.HandlerFunc
        }
    }

    namespace DTO {
        class SuccessResponse {
            +Data any
            +Meta Meta
        }

        class ErrorResponse {
            +Error ErrorDetail
        }

        class ErrorDetail {
            +Code ErrorCode
            +Message string
        }

        class Meta {
            +Timestamp string
        }
    }

    Store <|.. repository : implements
    Health <|.. healthImp : implements
    ServerInterface <|.. Handler : implements
    healthImp --> Store : depends on
    HealthService --> healthImp : creates
    HealthService --> NewRepository : uses
    Handler --> SuccessResponse : returns
    ErrorResponse --> ErrorDetail : contains
    GinServer --> ServerInterface : receives
    GinServer --> GinServerOptions : configures
    NewRepository --> repository : creates (singleton)
Loading

Rendering

These diagrams render natively on:

For local editing, use the Mermaid Live Editor.