# Service Catalog Module: Reusable Abstraction ## Overview The Service Catalog system (implemented in syntaxis) can be **extracted and reused by any application** that needs to manage services, presets, and provisioning integration. This document describes how to: 1. **Understand the abstraction** - What makes it reusable 2. **Implement in your app** - How to adapt it to other projects 3. **Integrate with provisioners** - How to connect with project-provisioning or similar systems --- ## What Is a Service Catalog? A **Service Catalog** is a **standardized TOML configuration** that completely describes: 1. **Available Services**: What can be installed/deployed 2. **Service Metadata**: How to run, health check, port, dependencies 3. **Presets**: Combinations of services for different contexts 4. **Patterns**: Common deployment topologies 5. **Networking**: Port assignments and protocols **Key Principle**: One source of truth for service definitions that both local and remote systems can read independently. --- ## The Abstraction ### Core Components ``` Service Catalog System ├── Configuration Schema (TOML) │ ├── Service definitions │ ├── Preset compositions │ ├── Service groups/patterns │ └── Port mappings ├── Discovery Tool │ ├── List services │ ├── Show usage examples │ ├── View preset compositions │ └── Export for provisioners └── Integration Bridge ├── Export presets as YAML ├── Generate provisioning configs └── Validate deployments ``` ### TOML Schema (Simplified) ```toml [version] catalog_version = "1.0.0" [service.my-service] name = "my-service" display_name = "My Service" description = "Service description" type = "server" # or "cli", "database", "web", etc. [service.my-service.server] default_host = "127.0.0.1" default_port = 3000 scheme = "http" [service.my-service.usage] basic_command = "my-service --bind 127.0.0.1:3000" health_check_endpoint = "/health" examples = [...] [service.my-service.dependencies] requires = [] conflicts = [] [preset.my-preset] name = "My Preset" description = "Preset description" database_backend = "sqlite" [preset.my-preset.services] my_service = { enabled = true, auto_start = true, port = 3000 } ``` --- ## Implementation Steps ### Step 1: Create the Catalog Schema Create `configs/services-catalog.toml` in your project with this structure: ```toml [version] catalog_version = "1.0.0" app_version = "1.0.0" # Define all services [service.app-api] name = "app-api" display_name = "API Server" description = "REST API for the application" type = "server" required = false [service.app-api.server] default_host = "127.0.0.1" default_port = 3000 scheme = "http" [service.app-api.usage] basic_command = "app-api --bind 127.0.0.1:3000" health_check_endpoint = "/health" examples = ["app-api --bind 127.0.0.1:3000"] [service.app-api.dependencies] requires = [] conflicts = [] [service.app-api.metadata] platform_support = ["linux", "macos"] min_memory_mb = 256 min_disk_space_mb = 100 # Define presets [preset.local] name = "Local Development" description = "Single-machine development" [preset.local.services] app_api = { enabled = true, auto_start = false, port = 3000 } [preset.production] name = "Production" description = "Production deployment" [preset.production.services] app_api = { enabled = true, auto_start = true, port = 3000 } ``` ### Step 2: Create Discovery Tool Create a simple script to help users explore the catalog: **Option A: Simple Bash** ```bash #!/bin/bash # scripts/service-catalog.sh echo "Available Services:" grep '^\[service\.' configs/services-catalog.toml | sed 's/\[service\.//' | sed 's/\]//' echo "" echo "Available Presets:" grep '^\[preset\.' configs/services-catalog.toml | sed 's/\[preset\.//' | sed 's/\]//' ``` **Option B: NuShell** (See syntax-catalog.nu for examples) ### Step 3: Integrate with Your Installation Update your installation script to read from the catalog: ```bash # scripts/install.sh CATALOG=$(cat configs/services-catalog.toml) PRESET=${1:-local} # Extract services from preset SERVICES=$(grep -A 10 "^\[preset.$PRESET.services\]" configs/services-catalog.toml) # Install each enabled service echo "$SERVICES" | while read line; do if [[ $line == *"enabled = true"* ]]; then SERVICE=$(echo $line | cut -d= -f1) echo "Installing $SERVICE..." # ... installation logic fi done ``` ### Step 4: Export for Provisioning Create an export function that converts the catalog to YAML for provisioners: ```bash # scripts/export-catalog.sh PRESET=$1 cat > /tmp/export.yaml << EOF preset: $PRESET services: EOF grep -A 20 "^\[preset.$PRESET.services\]" configs/services-catalog.toml | \ while read line; do if [[ $line == *"enabled = true"* ]]; then echo " - $(echo $line | cut -d= -f1)" >> /tmp/export.yaml fi done cat /tmp/export.yaml ``` --- ## Using with Provisioners ### Integration Pattern 1: Direct File Reading ```bash # provisioner reads catalog directly provisioner \ --catalog myapp/configs/services-catalog.toml \ --preset production \ --output terraform/main.tf ``` ### Integration Pattern 2: Export and Read ```bash # export from app ./scripts/export-catalog.sh production > /tmp/myapp-services.yaml # provisioner reads export provisioner \ --config /tmp/myapp-services.yaml \ --backend kubernetes \ --output k8s/deployment.yaml ``` ### Integration Pattern 3: API/Bridge ```bash # Create a simple HTTP bridge python -m http.server --directory configs/ # Provisioner fetches curl http://localhost:8000/services-catalog.toml ``` --- ## Reusing in Other Applications ### Example 1: Microservices App ```toml [service.auth-service] name = "auth-service" type = "server" [service.auth-service.server] default_port = 4000 [service.user-service] name = "user-service" type = "server" [service.user-service.server] default_port = 4001 [service.data-service] name = "data-service" type = "database" [service.data-service.server] default_port = 5432 [preset.dev] [preset.dev.services] auth_service = { enabled = true } user_service = { enabled = true } data_service = { enabled = true } [preset.production] [preset.production.services] auth_service = { enabled = true, replicas = 3 } user_service = { enabled = true, replicas = 2 } data_service = { enabled = true, replicas = 1 } ``` ### Example 2: Data Pipeline ```toml [service.data-ingress] name = "data-ingress" type = "server" [service.data-ingress.server] default_port = 8000 [service.processor] name = "processor" type = "worker" [service.storage] name = "storage" type = "database" [preset.local-dev] [preset.local-dev.services] data_ingress = { enabled = true } processor = { enabled = true } storage = { enabled = true } [preset.cloud] [preset.cloud.services] data_ingress = { enabled = true, scale = "auto" } processor = { enabled = true, scale = "auto" } storage = { enabled = true, backup = true } ``` --- ## Key Design Principles ### 1. Configuration-First Everything is configuration, no hardcoding: - Service definitions in TOML - Preset combinations in TOML - Ports, health checks, dependencies all declarative ### 2. Single Source of Truth One file (`services-catalog.toml`) defines everything: - Local tools read it for discovery - Remote provisioners read it for deployment - No duplication between systems ### 3. Minimal Tooling Required Works with standard tools: - TOML is text-based (git-friendly) - Can be read by bash, Python, Go, NuShell, etc. - No special infrastructure needed ### 4. Self-Documenting The catalog is both specification and documentation: - Service definitions include examples - Health checks documented - Dependencies explicit - Usage patterns clear --- ## Extending the Module ### Adding Custom Fields You can extend the schema for your specific needs: ```toml [service.my-service] # Standard fields name = "my-service" type = "server" # Custom fields for your app custom_field_1 = "value" tags = ["tag1", "tag2"] compliance = "hipaa" cost_per_month = 100 ``` ### Adding Custom Patterns Create application-specific deployment patterns: ```toml [pattern.ml-training] name = "ML Training Pipeline" description = "For batch ML training jobs" services = ["data-ingress", "preprocessor", "trainer", "model-storage"] [pattern.ml-serving] name = "ML Model Serving" description = "For ML inference in production" services = ["model-service", "feature-store", "metrics-collector"] ``` ### Adding Custom Validators Extend validation to your needs: ```bash # scripts/validate-catalog.sh validate_compliance_tags() { grep '^\[service\.' configs/services-catalog.toml | while read svc; do if ! grep -A 10 "$svc" configs/services-catalog.toml | grep -q "compliance ="; then echo "❌ $svc missing compliance tag" fi done } ``` --- ## Best Practices ### 1. Keep It Maintainable - One service = one [service.xxx] block - Group related fields together - Use consistent naming ### 2. Document Services ```toml [service.my-service] # Always include usage examples [service.my-service.usage] examples = [ "Command to run locally", "With config file: my-service --config config.toml", "In Docker: docker run myapp/my-service", ] ``` ### 3. Version Your Catalog ```toml [version] catalog_version = "1.0.0" # schema version app_version = "1.0.0" # your app version updated_at = "2025-11-19" ``` ### 4. Make It Testable ```bash # scripts/test-catalog.sh test_valid_toml() { nu -c "open configs/services-catalog.toml" > /dev/null } test_required_services() { # Your app might require certain services grep -q "app-api" configs/services-catalog.toml } test_valid_ports() { # Check no duplicate ports grep "default_port" configs/services-catalog.toml | sort | uniq -d | [ -z "$(cat)" ] } ``` --- ## Integration Checklist - [ ] Create `configs/services-catalog.toml` - [ ] Define all services with metadata - [ ] Define presets for different contexts - [ ] Create simple discovery tool (bash/NuShell) - [ ] Update installation script to read from catalog - [ ] Add export function for provisioners - [ ] Document how to extend the catalog - [ ] Add validation tests - [ ] Version control the catalog - [ ] Document in your project README --- ## Real-World Example: Using with project-provisioning ### Step 1: Create Catalog (myapp/configs/) ```toml [service.my-api] display_name = "My API" type = "server" [service.my-api.server] default_port = 3000 [preset.production] [preset.production.services] my_api = { enabled = true } ``` ### Step 2: Export (myapp/scripts/) ```bash #!/bin/bash nu -c "open configs/services-catalog.toml | get preset | get production" ``` ### Step 3: Integrate (project-provisioning/scripts/) ```bash #!/bin/bash # Read from myapp SERVICES=$(myapp/scripts/export-preset.sh production) # Generate infrastructure echo "$SERVICES" | while read service; do # Create terraform resource cat >> terraform/main.tf << EOF resource "docker_container" "$service" { name = "$service" image = "myapp/$service:latest" ports = [...] # extracted from catalog } EOF done ``` --- ## Conclusion The Service Catalog abstraction is **language-agnostic** and **tool-agnostic**. It's a **simple but powerful pattern** for: - Centralizing service definitions - Enabling local discovery - Integrating with remote provisioners - Documenting infrastructure - Maintaining single source of truth Use it in any application where you need to manage multiple services or deployment contexts. --- **Document Version**: 1.0.0 **Last Updated**: 2025-11-19 **Status**: Ready for implementation in external projects