Schema

YAML schema definitions and validation for deployment configurations

Overview

The schema package provides type definitions for YAML deployment configurations and embeds the JSON schema for validation. It defines the structure for all deployment-related configuration including services, routers, middleware, and deployment topology.

Import Path

import "github.com/primadi/lokstra/core/deploy/schema"

Core Types

DeployConfig

Root configuration structure for YAML files.

Definition:

type DeployConfig struct {
    Configs                    map[string]any
    MiddlewareDefinitions      map[string]*MiddlewareDef
    ServiceDefinitions         map[string]*ServiceDef
    RouterDefinitions          map[string]*RouterDef           // Renamed from "Routers"
    Deployments                map[string]*DeploymentDefMap
    // RouterOverrides removed - overrides are now inline in RouterDef
}

YAML Example:

configs:
  app.name: "MyApp"
  app.version: "1.0.0"

service-definitions:
  user-service:
    type: user-service-factory

router-definitions:
  user-router:
    convention: rest
    resource: user

deployments:
  production:
    servers:
      api-server:
        base-url: https://api.example.com

Service Definitions

ServiceDef

Defines a service instance.

Definition:

type ServiceDef struct {
    Name      string
    Type      string         // Factory type
    DependsOn []string       // Dependencies
    Config    map[string]any // Optional config
}

YAML Example:

service-definitions:
  user-service:
    type: user-service-factory
    depends-on:
      - db-service
      - cache-service
    config:
      max_connections: 100
      timeout: 30s

Dependency Syntax:

# Simple dependency (uses service name as key)
depends-on:
  - db-service

# Explicit mapping (custom key)
depends-on:
  - userRepo:user-repository
  - paymentSvc:payment-service

---

## Router Definitions

### RouterDef
Defines a router auto-generated from a service, or middleware overrides for manual routers.

**Router Naming Convention:**
- Router name determines the service name using pattern `{service-name}-router`
- Example: `user-service-router` β†’ service is `user-service`

**Manual routers** (registered via `RegisterRouter()`) can also use `router-definitions` for:
- Path prefix overrides
- Middleware injection
- Route-level customization

**Definition:**
```go
type RouterDef struct {
    // Service name is derived from router name pattern: "{service-name}-router"
    // Example: "user-service-router" β†’ service is "user-service"
    
    Convention     string           // Convention type (rest, rpc, graphql) - for auto-generated only
    Resource       string           // Singular form (e.g., "user") - for auto-generated only
    ResourcePlural string           // Plural form (e.g., "users") - for auto-generated only
    
    // Inline overrides (works for both auto-generated AND manual routers)
    PathPrefix   string           // Path prefix override
    PathRewrites []PathRewriteDef // Regex-based path rewrite rules
    Middlewares  []string         // Router-level middleware names
    Hidden       []string         // Methods to hide (auto-generated only)
    Custom       []RouteDef       // Custom route definitions
}

type PathRewriteDef struct {
    Pattern     string // Regex pattern to match (e.g., "^/api/v1/(.*)$")
    Replacement string // Replacement string (e.g., "/api/v2/$1")
}

YAML Example (Auto-Generated Router):

router-definitions:
  user-service-router:  # Service derived: "user-service"
    # Inline overrides
    path-prefix: /api/v1
    middlewares:
      - auth
      - logger
    custom:
      - name: Login
        method: POST
        path: /auth/login
        middlewares:
          - rate-limiter

YAML Example (Manual Router Overrides):

# Code: Manual router registration
# r := router.New("")
# r.GET("/dashboard", handler.ShowDashboard, route.WithNameOption("showDashboard"))
# r.GET("/users", handler.ListUsers)  # Auto-name: "GET_/users"
# lokstra_registry.RegisterRouter("admin-router", r)

router-definitions:
  admin-router:  # Manual router (already registered in code)
    # Supported overrides for manual routers
    path-prefix: /api/v1/admin  # Change path prefix
    middlewares:
      - admin-auth
      - audit-log
    
    # Route-level overrides (by route name)
    custom:
      - name: showDashboard  # Update named route
        method: POST  # Change method from GET to POST
        path: /admin/main  # Change path
        middlewares:
          - extra-logging
      
      - name: GET_/users  # Update auto-named route
        middlewares:
          - rate-limiter  # Just add middlewares
    
    # Note: convention, resource, hidden are ignored for manual routers

Router Naming Convention:

  • Format: {service-name}-router
  • Service name: Remove -router suffix
  • Examples:
    • user-service-router β†’ user-service
    • order-service-router β†’ order-service
    • payment-router β†’ payment
    • admin-router β†’ manual router (no auto-generation)

Use Cases for Manual Router Overrides:

  • Apply environment-specific middlewares (auth only in prod)
  • Add monitoring/logging per deployment
  • Inject rate limiting from YAML configuration
  • Change path prefix per environment (API versioning)
  • Rewrite paths using regex patterns (flexible transformations)
  • Update specific route methods or paths per environment
  • Add route-specific middlewares without changing code
  • Keep router logic in code, configuration in YAML

Path Rewrites

Path rewrites allow regex-based transformation of route paths at build time. This is more flexible than path-prefix for complex URL transformations.

When to use:

  • path-prefix: Simple prefix addition (e.g., add /api/v1 to all routes)
  • path-rewrites: Complex transformations (e.g., change /api/v1/ to /api/v2/, add tenant IDs, etc.)

Example: API Versioning

router-definitions:
  user-router:
    path-rewrites:
      - pattern: "^/api/v1/(.*)$"
        replacement: "/api/v2/$1"

Result:

Code:         r.GET("/api/v1/users", ...)
After rewrite: GET /api/v2/users
Internal name: GET_/api/v1/users (unchanged)

Example: Multi-Tenant Paths

router-definitions:
  tenant-router:
    path-rewrites:
      - pattern: "^/(.*)$"
        replacement: "/tenant/${TENANT_ID}/$1"

Result:

Code:         r.GET("/users", ...)
After rewrite: GET /tenant/acme-corp/users

Example: Multiple Rules

router-definitions:
  api-router:
    path-rewrites:
      - pattern: "^/v1/(.*)$"
        replacement: "/api/v2/$1"
      - pattern: "^/legacy/(.*)$"
        replacement: "/api/v2/compat/$1"

Rules:

  • Applied in order (first match wins)
  • Uses Go’s regexp package
  • Supports capture groups: $1, $2, etc.
  • Applied at router build time (zero runtime overhead)
  • Route names unchanged (only HTTP paths affected)

Combining with path-prefix:

router-definitions:
  api-router:
    path-prefix: /v2       # Applied first
    path-rewrites:         # Applied after prefix
      - pattern: "^/v2/old/(.*)$"
        replacement: "/v2/new/$1"

RouteDef

Defines a single route override.

Definition:

type RouteDef struct {
    Name        string   // Route name (manual or auto-generated)
    Method      string   // HTTP method override
    Path        string   // Path override
    Middlewares []string // Route-level middleware names
}

YAML Example:

custom:
  - name: Login
    method: POST
    path: /auth/login
    middlewares:  # Route-level middlewares
      - rate-limiter-strict
  - name: Logout
    method: POST
    path: /auth/logout
  - name: AdminReset
    method: POST
    path: /admin/reset
    middlewares:
      - admin-auth
      - audit-log

Middleware Definitions

MiddlewareDef

Defines a middleware instance.

Definition:

type MiddlewareDef struct {
    Name   string
    Type   string         // Factory type
    Config map[string]any // Optional config
}

YAML Example:

middleware-definitions:
  logger-debug:
    type: logger
    config:
      level: DEBUG
      colorize: true
      
  cors-dev:
    type: cors
    config:
      allow_origin: "*"
      allow_methods: "*"
      allow_headers: "*"
      
  rate-limiter-strict:
    type: rate-limiter
    config:
      requests_per_minute: 10
      burst: 5

Configuration

ConfigDef

Defines a configuration value.

Definition:

type ConfigDef struct {
    Name  string
    Value any // Can be string or ${...} reference
}

YAML Example:

configs:
  app.name: "MyApp"
  app.port: 8080
  app.env: "${APP_ENV:development}"
  db.dsn: "${DATABASE_URL}"
  log.level: "INFO"

Deployment Topology

DeploymentDefMap

Deployment using map structure.

Definition:

type DeploymentDefMap struct {
    ConfigOverrides map[string]any
    Servers         map[string]*ServerDefMap
}

YAML Example:

deployments:
  production:
    config-overrides:
      log.level: INFO
      db.pool_size: 100
    servers:
      api-server-1:
        # ...
      api-server-2:
        # ...

ServerDefMap

Server using map structure.

Definition:

type ServerDefMap struct {
    BaseURL string
    Apps    []*AppDefMap
    
    // Helper fields (shorthand for single app)
    HelperAddr              string
    HelperRouters           []string
    HelperPublishedServices []string
}

YAML Example (Full):

servers:
  api-server:
    base-url: https://api.example.com
    apps:
      - addr: ":443"
        routers:
          - user-router
          - order-router
      - addr: ":8080"
        routers:
          - admin-router

YAML Example (Shorthand):

servers:
  api-server:
    base-url: https://api.example.com
    # Shorthand for single app
    addr: ":443"
    routers:
      - user-router
      - order-router

AppDefMap

App using map structure.

Definition:

type AppDefMap struct {
    Addr              string
    Routers           []string
    PublishedServices []string // Auto-generate routers for these services
    ReverseProxies    []*ReverseProxyDef
    MountSpa          []*MountSpaDef
    MountStatic       []*MountStaticDef
}

YAML Example:

apps:
  - addr: ":8080"
    routers:
      - user-router
      - order-router
      
  - addr: ":9090"
    # Auto-generate routers from services
    published-services:
      - user-service
      - order-service
      
  - addr: ":3000"
    # Handler configurations
    reverse-proxies:
      - prefix: /api/v1
        target: http://backend:8080
        strip-prefix: true
    mount-spa:
      - prefix: /admin
        dir: ./dist/admin
    mount-static:
      - prefix: /assets
        dir: ./public/assets

Schema Validation

GetSchemaBytes

Returns the embedded JSON schema for validation.

Signature:

func GetSchemaBytes() []byte

Example:

schemaBytes := schema.GetSchemaBytes()
// Use with JSON schema validator

Complete Examples

Minimal Configuration

service-definitions:
  user-service:
    type: user-service-factory

router-definitions:
  user-service-router:  # Service derived: "user-service"
    convention: rest

deployments:
  production:
    servers:
      api-server:
        base-url: https://api.example.com
        addr: ":443"
        routers:
          - user-service-router

# Configuration values
configs:
  app.name: "MyApp"
  app.version: "1.0.0"
  app.env: "${APP_ENV:production}"
  db.dsn: "${DATABASE_URL}"
  db.pool_size: 100

# Middleware definitions
middleware-definitions:
  logger:
    type: logger
    config:
      level: INFO
      
  auth:
    type: jwt-auth
    config:
      secret: "${JWT_SECRET}"
      
  rate-limiter:
    type: rate-limiter
    config:
      requests_per_minute: 60
      burst: 10

# Service definitions
service-definitions:
  db-service:
    type: postgres-factory
    config:
      dsn: "${@cfg:db.dsn}"
      pool_size: "${@cfg:db.pool_size}"
      
  cache-service:
    type: redis-factory
    config:
      addr: "${REDIS_URL:localhost:6379}"
      
  user-service:
    type: user-service-factory
    depends-on:
      - db-service
      - cache-service
    config:
      max_users: 10000
      
  order-service:
    type: order-service-factory
    depends-on:
      - db-service
      - user-service
    config:
      max_orders: 50000

# Router definitions
router-definitions:
  user-service-router:  # Service derived: "user-service"
    # Inline overrides
    path-prefix: /api/v1
    middlewares:
      - auth
      - logger
    custom:
      - name: Login
        method: POST
        path: /auth/login
        middlewares:
          - rate-limiter-strict
      - name: Logout
        method: POST
        path: /auth/logout

# Deployments
deployments:
  production:
    config-overrides:
      log.level: INFO
      db.pool_size: 100
    servers:
      api-server-1:
        base-url: https://api-1.example.com
        apps:
          - addr: ":443"
            routers:
              - user-service-router
              - order-service-router
      
      api-server-2:
        base-url: https://api-2.example.com
        apps:
          - addr: ":443"
            routers:
              - user-service-router
              - order-service-router

Multi-Environment Configuration

# Base configuration (shared)
service-definitions:
  user-service:
    type: user-service-factory

router-definitions:
  user-service-router:
    convention: rest

# Development deployment
deployments:
  development:
    config-overrides:
      log.level: DEBUG
      db.pool_size: 10
    servers:
      dev-server:
        base-url: http://localhost:8080
        addr: ":8080"
        routers:
          - user-service-router

# Staging deployment
deployments:
  staging:
    config-overrides:
      log.level: INFO
      db.pool_size: 50
    servers:
      staging-server:
        base-url: https://staging.example.com
        addr: ":443"
        routers:
          - user-service-router

# Production deployment
deployments:
  production:
    config-overrides:
      log.level: WARN
      db.pool_size: 100
    servers:
      api-server-1:
        base-url: https://api-1.example.com
        addr: ":443"
        routers:
          - user-service-router
      api-server-2:
        base-url: https://api-2.example.com
        addr: ":443"
        routers:
          - user-service-router

Microservices Architecture

# User service deployment
service-definitions:
  user-service:
    type: user-service-factory

deployments:
  user-deployment:
    servers:
      user-server:
        base-url: https://user-api.example.com
        addr: ":443"
        published-services:
          - user-service

---

# Order service deployment (separate file)
service-definitions:
  order-service:
    type: order-service-factory

# External user service
external-service-definitions:
  user-service:
    url: https://user-api.example.com
    type: user-service-factory

deployments:
  order-deployment:
    servers:
      order-server:
        base-url: https://order-api.example.com
        addr: ":443"
        published-services:
          - order-service

Handler Configurations

# Full example with reverse proxies, SPAs, and static files
service-definitions:
  api-service:
    type: api-service-factory

deployments:
  production:
    servers:
      web-server:
        base-url: https://example.com
        apps:
          # Backend API
          - addr: ":8080"
            routers:
              - api-service-router
          
          # Frontend + Gateway
          - addr: ":3000"
            # Reverse proxy to backend API
            reverse-proxies:
              - prefix: /api
                target: http://localhost:8080
                strip-prefix: false
              
              # Proxy to legacy system
              - prefix: /legacy
                target: http://legacy-system:9000
                strip-prefix: true
                rewrite:
                  path-pattern: "^/legacy/(.*)$"
                  path-replacement: "/v1/$1"
            
            # SPA applications
            mount-spa:
              - prefix: /admin
                dir: ./dist/admin
              - prefix: /
                dir: ./dist/app
            
            # Static assets
            mount-static:
              - prefix: /assets
                dir: ./public/assets
              - prefix: /uploads
                dir: ./storage/uploads

ReverseProxyDef

HTTP reverse proxy configuration.

Definition:

type ReverseProxyDef struct {
    Prefix      string
    StripPrefix bool
    Target      string
    Rewrite     *ReverseProxyRewriteDef
}

type ReverseProxyRewriteDef struct {
    PathPattern      string
    PathReplacement  string
    HostPattern      string
    HostReplacement  string
}

YAML Example:

reverse-proxies:
  # Simple proxy
  - prefix: /api/v1
    target: http://backend:8080
    strip-prefix: true
    
  # Advanced rewrite
  - prefix: /api/v2
    target: http://new-backend:8080
    rewrite:
      path-pattern: "^/api/v2/(.*)$"
      path-replacement: "/v3/$1"
      host-pattern: "^api\\.(.*)$"
      host-replacement: "new-api.$1"

Use Cases:

  • Proxy to backend services (microservices architecture)
  • API gateway patterns
  • Legacy system integration
  • Dynamic path/host rewriting

MountSpaDef

Single Page Application mounting configuration.

Definition:

type MountSpaDef struct {
    Prefix string
    Dir    string
}

YAML Example:

mount-spa:
  # Admin dashboard
  - prefix: /admin
    dir: ./dist/admin
    
  # Main application
  - prefix: /
    dir: ./dist/app

Use Cases:

  • Serve React/Vue/Angular applications
  • Multiple SPA deployments on one server
  • Admin panels and dashboards
  • Fallback to index.html for client-side routing

MountStaticDef

Static file serving configuration.

Definition:

type MountStaticDef struct {
    Prefix string
    Dir    string
}

YAML Example:

mount-static:
  # Static assets
  - prefix: /assets
    dir: ./public/assets
    
  # User uploads
  - prefix: /uploads
    dir: ./storage/uploads
    
  # Documentation
  - prefix: /docs
    dir: ./public/docs

Use Cases:

  • Serve CSS, JavaScript, images
  • User-generated content (uploads)
  • Documentation files
  • Static resources

Schema Validation Rules

Required Fields

ServiceDef:

  • βœ… type - Service factory type must be specified
  • ❌ config - Optional
  • ❌ depends-on - Optional

RouterDef:

  • ❌ service - REMOVED (derived from router name pattern)
  • ❌ convention - Optional (defaults to β€œrest”)
  • ❌ resource - Optional (auto-detected from service name)

DeploymentDefMap:

  • βœ… servers - At least one server required
  • ❌ config-overrides - Optional

ServerDefMap:

  • βœ… base-url - Base URL required
  • βœ… apps or helper fields (addr + routers/published-services)

Field Types

String Fields:

  • type, name, service, convention, resource, url, addr, path-prefix

String Array Fields:

  • depends-on, middlewares, routers, hidden, published-services

Map Fields:

  • config, configs, config-overrides

Object Fields:

  • service-definitions, routers, router-overrides, external-service-definitions, deployments

Best Practices

1. Use Meaningful Names

# βœ… Good: Descriptive names
service-definitions:
  user-management-service:
    type: user-service-factory
  
  product-catalog-service:
    type: catalog-service-factory

# 🚫 Avoid: Cryptic names
service-definitions:
  svc1:
    type: user-service-factory
  svc2:
    type: catalog-service-factory

# βœ… Good: Logical grouping
configs:
  # Database settings
  db.host: "localhost"
  db.port: 5432
  db.name: "myapp"
  
  # Cache settings
  cache.host: "localhost"
  cache.port: 6379
  
  # App settings
  app.name: "MyApp"
  app.version: "1.0.0"

# 🚫 Avoid: Random order
configs:
  app.name: "MyApp"
  db.host: "localhost"
  cache.port: 6379
  db.port: 5432

3. Use Config References

# βœ… Good: DRY with config references
configs:
  api.version: "v1"
  api.prefix: "/api/${@cfg:api.version}"

router-definitions:
  user-service-router:
    path-prefix: "${@cfg:api.prefix}"

# 🚫 Avoid: Duplication
router-definitions:
  user-service-router:
    path-prefix: "/api/v1"
  order-service-router:
    path-prefix: "/api/v1"

4. Document Dependencies Clearly

# βœ… Good: Clear dependencies
service-definitions:
  order-service:
    type: order-service-factory
    depends-on:
      - db-service       # Database access
      - user-service     # User validation
      - payment-service  # Payment processing
      - inventory-service # Stock management

# 🚫 Avoid: Undocumented dependencies
service-definitions:
  order-service:
    type: order-service-factory
    # Hidden dependencies!

5. Use Inline Router Overrides When Needed

# βœ… Good: Custom routes documented
router-definitions:
  user-service-router:
    custom:
      - name: Login
        method: POST
        path: /auth/login
      - name: Logout
        method: POST
        path: /auth/logout
      - name: RefreshToken
        method: POST
        path: /auth/refresh

# 🚫 Avoid: Overriding standard CRUD
router-definitions:
  user-service-router:
    custom:
      - name: List
        method: GET
        path: /users  # Unnecessary override

6. Organize Handler Configurations by Purpose

# βœ… Good: Clear separation of concerns
apps:
  # API backend
  - addr: ":8080"
    routers:
      - api-router
  
  # Frontend gateway
  - addr: ":3000"
    reverse-proxies:
      - prefix: /api
        target: http://localhost:8080
    mount-spa:
      - prefix: /
        dir: ./dist/app
    mount-static:
      - prefix: /assets
        dir: ./public/assets

# 🚫 Avoid: Mixing everything in one app
apps:
  - addr: ":8080"
    routers:
      - api-router
    reverse-proxies:
      - prefix: /other-api
        target: http://other:9000
    mount-spa:
      - prefix: /admin
        dir: ./dist/admin
    # Too many responsibilities!

7. Use Reverse Proxy for Backend Integration

# βœ… Good: Proxy pattern for microservices
reverse-proxies:
  - prefix: /api/users
    target: http://user-service:8080
    strip-prefix: false
  - prefix: /api/orders
    target: http://order-service:8080
    strip-prefix: false

# βœ… Good: Strip prefix for clean routing
reverse-proxies:
  - prefix: /api/v1
    target: http://backend:8080
    strip-prefix: true  # /api/v1/users -> /users

# 🚫 Avoid: No strip-prefix when needed
reverse-proxies:
  - prefix: /api/v1
    target: http://backend:8080
    # Backend receives /api/v1/users instead of /users

8. Validate Directory Paths for Static Handlers

# βœ… Good: Relative paths from project root
mount-spa:
  - prefix: /admin
    dir: ./dist/admin    # Clear relative path
  
mount-static:
  - prefix: /assets
    dir: ./public/assets # Clear relative path

# ⚠️ Caution: Absolute paths (environment-specific)
mount-spa:
  - prefix: /admin
    dir: /var/www/admin  # Only works in production

# 🚫 Avoid: Non-existent paths
mount-static:
  - prefix: /assets
    dir: ./assets  # Typo: should be ./public/assets

See Also