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 }}