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"
    ExternalServiceDefinitions map[string]*RemoteServiceSimple
    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

RemoteServiceSimple

Defines an external service (outside this deployment).

Definition:

type RemoteServiceSimple struct {
    URL            string
    Type           string         // Factory type (auto-creates wrapper)
    Resource       string         // Resource name (singular)
    ResourcePlural string         // Resource name (plural)
    Convention     string         // Convention type (rest, rpc, graphql)
    // Inline overrides (no more references)
    PathPrefix     string
    PathRewrites   []PathRewriteDef // Regex-based path rewrite rules
    Middlewares    []string
    Hidden         []string
    Custom         []RouteDef
    Config         map[string]any // Additional factory config
}

YAML Example:

external-service-definitions:
  payment-service:
    url: https://payment-api.example.com
    type: payment-service-factory
    resource: payment
    resource-plural: payments
    convention: rest
    path-prefix: /api/v1
    path-rewrites:
      - pattern: "^/api/v1/(.*)$"
        replacement: "/v2/$1"
    middlewares:
      - auth
      - rate-limiter
    config:
      api_key: "${PAYMENT_API_KEY}"
      timeout: 10s

Use Cases:

  • Third-party APIs (Stripe, SendGrid, etc.)
  • Microservices in other deployments
  • External REST/RPC services
  • Legacy system integration

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:

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"
    convention: rest
    resource: user
    resource-plural: users
    # Inline overrides
    path-prefix: /api/v1
    middlewares:
      - auth
      - logger
    hidden:
      - InternalHelper
    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
}

YAML Example:

apps:
  - addr: ":8080"
    routers:
      - user-router
      - order-router
      
  - addr: ":9090"
    # Auto-generate routers from services
    published-services:
      - user-service
      - order-service

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"
    convention: rest
    resource: user
    resource-plural: users
    # Inline overrides
    path-prefix: /api/v1
    middlewares:
      - auth
      - logger
    hidden:
      - InternalHelper
    custom:
      - name: Login
        method: POST
        path: /auth/login
        middlewares:
          - rate-limiter-strict
      - name: Logout
        method: POST
        path: /auth/logout
    
  order-service-router:  # Service derived: "order-service"
    convention: rest
    resource: order
    resource-plural: orders

# External services
external-service-definitions:
  payment-service:
    url: https://payment-api.example.com
    type: payment-client-factory
    resource: payment
    resource-plural: payments
    config:
      api_key: "${PAYMENT_API_KEY}"

# 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

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

See Also