Skip to main content

Service Generation Guide

Complete guide for using Saga's service generation system to quickly bootstrap services with automatic registration.

Table of Contents​

Overview​

Saga's service generation system (saga generate service) automatically creates service registration code for different programming languages. This eliminates boilerplate and ensures consistent service registration patterns across your microservices.

What Gets Generated​

For each language, the generator creates:

  • Configuration management - Environment-based config with sensible defaults
  • Service registration - Automatic registration with Saga on startup
  • Heartbeat management - Automatic TTL refresh to keep registrations alive
  • Graceful shutdown - Automatic unregistration on service shutdown
  • Project structure - Language-specific project files and dependencies

Quick Start​

Generate a service in any supported language:

# Generate Rust service
saga generate service payment-service --language rust --port 8001

# Generate Python service
saga generate service auth-service --language python --port 8002

# Generate Elixir service
saga generate service notification --language elixir --port 8003

# Generate Node.js service
saga generate service gateway --language node --port 8000

This creates a complete service skeleton with Saga integration ready to use.

Supported Languages​

  • Rust (rust) - Full async/await with tokio
  • Python (python) - Async with asyncio, FastAPI-ready
  • Elixir (elixir) - GenServer-based with Phoenix-ready
  • Node.js (node) - Async/await with Express-ready

Basic Usage​

Command Syntax​

saga generate service <NAME> [options]

Required Arguments​

  • NAME - Service name (unique identifier)

Options​

  • --language <LANG> - Target language: rust, python, elixir, node (required)
  • --path <PATH> - Output path (optional, default: current directory)
  • --capabilities <CAPS> - Service capabilities as comma-separated list
  • --port <PORT> - Service port (optional, default: 8000)
  • --template <PATH> - Custom template directory (advanced)
  • --redis-url <URL> - Redis URL for generated code
  • --saga-url <URL> - Saga service URL (default: http://localhost:8030)

Examples​

# Basic service generation
saga generate service payment-service --language rust

# With custom port
saga generate service auth-service --language python --port 8002

# With capabilities
saga generate service gateway --language node \
--capabilities graphql,rest --port 8000

# Custom output path
saga generate service notification --language elixir \
--path ./services/notification

# Custom Redis and Saga URLs
saga generate service payment-service --language rust \
--redis-url redis://redis.example.com:6379 \
--saga-url http://saga.example.com:8030

Template Customization​

Using Custom Templates​

You can use custom templates by specifying the --template option:

saga generate service my-service --language rust \
--template ./custom-templates

The template directory should follow this structure:

custom-templates/
├── rust/
│ ├── Cargo.toml.template
│ ├── config.rs.template
│ ├── main.rs.template
│ └── registration.rs.template
├── python/
│ ├── config.py.template
│ ├── main.py.template
│ ├── registration.py.template
│ └── requirements.txt.template
└── ...

Template Variables​

All templates support these variables:

  • {{service_name}} - Service name (e.g., payment-service)
  • {{service_url}} - Full service URL (e.g., http://localhost:8001)
  • {{service_port}} - Service port number (e.g., 8001)
  • {{capabilities}} - Formatted capabilities list (language-specific)
  • {{redis_url}} - Redis connection URL
  • {{saga_url}} - Saga service URL

Language-Specific Variables:

  • Elixir only:
    • {{service_module_name}} - PascalCase module name (e.g., PaymentService)
    • {{ServiceName}} - Alias for module name
    • {{service_atom}} - Snake case atom (e.g., payment_service)

Template Format​

Templates use {{variable_name}} syntax for variable substitution. Example:

// In main.rs.template
info!("Starting {{service_name}} service");
let url = "{{service_url}}";
let port = {{service_port}};

Language-Specific Guides​

Rust​

Generated Files:

  • Cargo.toml - Project dependencies and metadata
  • src/config.rs - Configuration management
  • src/main.rs - Main entry point with registration
  • src/registration.rs - Service registration logic

Features:

  • Async/await with tokio
  • Automatic registration on startup
  • Background heartbeat task
  • Graceful shutdown with unregistration
  • Error handling with anyhow

Example Usage:

# Generate Rust service
saga generate service payment-service --language rust --port 8001

# Navigate to generated service
cd payment-service

# Build and run
cargo build --release
cargo run

# Or with custom Redis URL
REDIS_URL=redis://localhost:6379 cargo run

Integration:

The generated Rust service includes:

  • Configuration from environment variables
  • Automatic service registration
  • Heartbeat every 30 seconds
  • Graceful shutdown handling

Add your service logic in main.rs after registration.

Python​

Generated Files:

  • config.py - Configuration management
  • main.py - Main entry point with FastAPI-ready structure
  • registration.py - Service registration client
  • requirements.txt - Python dependencies

Features:

  • Async/await with asyncio
  • FastAPI-ready lifespan management
  • Automatic registration/unregistration
  • Background heartbeat task
  • Environment-based configuration

Example Usage:

# Generate Python service
saga generate service auth-service --language python --port 8002

# Navigate to generated service
cd auth-service

# Install dependencies
pip install -r requirements.txt

# Run service
python main.py

# Or with environment variables
REDIS_URL=redis://localhost:6379 python main.py

Integration with FastAPI:

The generated code includes a lifespan context manager ready for FastAPI:

from fastapi import FastAPI
from main import lifespan

app = FastAPI(lifespan=lifespan)

@app.get("/")
async def root():
return {"message": "Hello from auth-service"}

Elixir​

Generated Files:

  • mix.exs - Mix project configuration
  • config/config.exs - Application configuration
  • lib/{ServiceName}/application.ex - Application supervision tree
  • lib/{ServiceName}/service_registration.ex - Registration GenServer

Features:

  • GenServer-based registration
  • Supervision tree integration
  • Automatic registration on application start
  • Background heartbeat process
  • Graceful shutdown handling
  • Phoenix-ready structure

Example Usage:

# Generate Elixir service
saga generate service notification --language elixir --port 8003

# Navigate to generated service
cd Notification # Module name in PascalCase

# Install dependencies
mix deps.get

# Run service
mix run --no-halt

# Or in interactive mode
iex -S mix

Integration with Phoenix:

The generated structure follows Phoenix conventions. To add Phoenix:

# In mix.exs, add:
{:phoenix, "~> 1.7"}

# Then generate Phoenix app:
mix phx.new . --no-ecto

The service registration GenServer will start automatically with your application.

Node.js​

Generated Files:

  • package.json - Node.js project and dependencies
  • config.js - Configuration management
  • index.js - Main entry point
  • registration.js - Service registration client

Features:

  • Async/await with native Promise support
  • Express-ready structure
  • Automatic registration on startup
  • Background heartbeat interval
  • Graceful shutdown with unregistration
  • Environment-based configuration

Example Usage:

# Generate Node.js service
saga generate service gateway --language node --port 8000

# Navigate to generated service
cd gateway

# Install dependencies
npm install

# Run service
node index.js

# Or with npm scripts
npm start

# With environment variables
REDIS_URL=redis://localhost:6379 node index.js

Integration with Express:

The generated code is ready for Express integration:

const express = require('express');
const { main } = require('./index');

const app = express();

// Your routes here
app.get('/', (req, res) => {
res.json({ message: 'Hello from gateway' });
});

// Start service (includes Saga registration)
main().then(() => {
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
});
});

Integration Examples​

Complete Workflow​

  1. Generate Service:

    saga generate service payment-service --language rust \
    --port 8001 --capabilities rest
  2. Add Your Logic:

    // In src/main.rs, add your service logic:
    use actix_web::{web, App, HttpServer};

    #[actix_web::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ... existing registration code ...

    // Add your HTTP server
    HttpServer::new(|| {
    App::new()
    .route("/health", web::get().to(health))
    .route("/payments", web::post().to(create_payment))
    })
    .bind(format!("{}:{}", config.host, config.port))?
    .run()
    .await?;

    Ok(())
    }
  3. Register with Saga: The service automatically registers on startup. Verify:

    saga service list
  4. Test Service:

    # Check service health
    saga service health payment-service

    # Get service details
    saga service get payment-service

Multi-Language Service​

Generate services in different languages that all register with the same Saga instance:

# Gateway (Node.js)
saga generate service gateway --language node --port 8000 \
--capabilities graphql,rest

# Payment Service (Rust)
saga generate service payment --language rust --port 8001 \
--capabilities rest

# Auth Service (Python)
saga generate service auth --language python --port 8002 \
--capabilities rest

# Notification Service (Elixir)
saga generate service notification --language elixir --port 8003 \
--capabilities rest

All services will automatically discover each other via Saga.

Custom Configuration​

Override default configuration in generated code:

Rust:

// In src/config.rs
pub struct Config {
pub redis_url: String,
pub saga_url: String,
pub host: String,
pub port: u16,
}

impl Config {
pub fn load() -> Result<Self> {
Ok(Config {
redis_url: std::env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://localhost:6379".to_string()),
saga_url: std::env::var("SAGA_URL")
.unwrap_or_else(|_| "http://localhost:8030".to_string()),
// ... other config ...
})
}
}

Python:

# In config.py
import os

class Config:
def __init__(self):
self.redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
self.saga_url = os.getenv("SAGA_URL", "http://localhost:8030")
# ... other config ...

Troubleshooting​

Template Not Found​

Error: Template not found: /path/to/template

Solution:

  • Ensure you're running saga from the correct directory
  • Check that templates exist in shared/saga/templates/
  • Use --template to specify custom template directory

Invalid Language​

Error: Invalid language: xyz

Solution:

  • Use one of: rust, python, elixir, node
  • Check spelling and case sensitivity

Service Already Exists​

Error: Directory already exists: payment-service

Solution:

  • Use --path to specify a different output directory
  • Remove existing directory first
  • Use a different service name

Registration Fails​

Error: Failed to register service: Connection refused

Solution:

  • Ensure Saga service is running: saga start
  • Check Redis connection: saga debug redis "PING"
  • Verify SAGA_URL environment variable or config

Heartbeat Not Working​

Issue: Service registration expires

Solution:

  • Check that heartbeat task/process is running
  • Verify Redis connection is stable
  • Check service logs for heartbeat errors
  • Manually trigger heartbeat: saga service heartbeat <name>

Custom Template Variables Not Working​

Issue: Variables not being substituted

Solution:

  • Use {{variable_name}} syntax (double curly braces)
  • Check variable names match exactly
  • Verify template file encoding (UTF-8)

Best Practices​

  1. Service Naming:

    • Use kebab-case: payment-service, auth-service
    • Make names descriptive and unique
    • Avoid special characters
  2. Port Management:

    • Use consistent port ranges per environment
    • Document port assignments
    • Avoid hardcoded ports in code
  3. Capabilities:

    • Be specific about capabilities
    • Update capabilities when service changes
    • Use comma-separated list format
  4. Configuration:

    • Use environment variables for sensitive data
    • Provide sensible defaults
    • Validate configuration on startup
  5. Error Handling:

    • Handle registration failures gracefully
    • Log all registration events
    • Retry failed registrations
  6. Testing:

    • Test service registration in CI/CD
    • Verify heartbeat functionality
    • Test graceful shutdown

Advanced Usage​

Custom Template Directory​

Create your own templates:

# Create template structure
mkdir -p my-templates/rust
cp shared/saga/templates/rust/* my-templates/rust/

# Modify templates as needed
vim my-templates/rust/main.rs.template

# Generate with custom templates
saga generate service my-service --language rust \
--template ./my-templates

Batch Generation​

Generate multiple services:

#!/bin/bash
# Generate all services for a microservices architecture

saga generate service api-gateway --language node --port 8000 --capabilities graphql,rest
saga generate service auth-service --language python --port 8001 --capabilities rest
saga generate service payment-service --language rust --port 8002 --capabilities rest
saga generate service notification-service --language elixir --port 8003 --capabilities rest

CI/CD Integration​

Generate services in CI/CD pipelines:

# .github/workflows/generate-services.yml
- name: Generate Service
run: |
saga generate service ${{ env.SERVICE_NAME }} \
--language ${{ env.SERVICE_LANGUAGE }} \
--port ${{ env.SERVICE_PORT }} \
--capabilities ${{ env.SERVICE_CAPABILITIES }}