Standardization & Templates

Standardization through templates provides consistent starting points for common development tasks, reducing decision fatigue and ensuring best practices. Well-designed templates accelerate development while maintaining quality and compliance standards.

The Need for Standardization

Without standards, teams create inconsistent solutions that increase maintenance burden and cognitive load.

The Inconsistency Problem

Each team implements differently:

Without Standards: ├── Team A: Python Service │ ├── Flask framework │ ├── Custom logging format │ ├── Prometheus metrics │ ├── Manual Dockerfile │ └── Custom CI/CD scripts ├── Team B: Python Service │ ├── FastAPI framework │ ├── Different logging library │ ├── StatsD metrics │ ├── Different base image │ └── Different CI/CD approach └── Team C: Python Service ├── Django framework ├── No structured logging ├── No metrics ├── Outdated base image └── Manual deployments

Problems this creates:

Standardization Benefits

Consistent patterns across all teams:

graph TB A[Golden Template] --> B[Team A Service] A --> C[Team B Service] A --> D[Team C Service] B --> E[Same logging format] C --> E D --> E B --> F[Same metrics approach] C --> F D --> F B --> G[Same CI/CD pipeline] C --> G D --> G style A fill:#e1f5ff style E fill:#d4f1d4 style F fill:#d4f1d4 style G fill:#d4f1d4

Template Categories

Different template types serve different purposes.

Service Templates

Starting points for microservices:

service-template-python/ ├── .github/ │ └── workflows/ │ ├── ci.yml # Automated testing │ ├── security-scan.yml # Dependency scanning │ └── deploy.yml # Deployment automation ├── src/ │ ├── __init__.py │ ├── main.py # Application entry point │ ├── api/ │ │ ├── __init__.py │ │ ├── routes.py # API endpoints │ │ └── models.py # Request/response models │ ├── service/ │ │ ├── __init__.py │ │ └── business_logic.py # Core logic │ └── repository/ │ ├── __init__.py │ └── database.py # Data access ├── tests/ │ ├── unit/ │ ├── integration/ │ └── conftest.py ├── k8s/ │ ├── deployment.yaml │ ├── service.yaml │ ├── configmap.yaml │ └── secrets.yaml ├── Dockerfile ├── requirements.txt ├── pyproject.toml ├── README.md └── catalog-info.yaml # Service catalog metadata

Core application structure:

# src/main.py - Standardized entry point from fastapi import FastAPI from prometheus_fastapi_instrumentator import Instrumentator import structlog import uvicorn from .api.routes import router from .middleware import setup_middleware from .config import get_settings # Standard structured logging structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.JSONRenderer() ], wrapper_class=structlog.stdlib.BoundLogger, logger_factory=structlog.stdlib.LoggerFactory(), ) logger = structlog.get_logger() def create_app() -> FastAPI: settings = get_settings() app = FastAPI( title=settings.service_name, version=settings.version, docs_url="/api/docs", openapi_url="/api/openapi.json" ) # Standard middleware setup_middleware(app) # Standard metrics Instrumentator().instrument(app).expose(app, endpoint="/metrics") # Health checks @app.get("/health/live") async def liveness(): return {"status": "alive"} @app.get("/health/ready") async def readiness(): # Check dependencies return {"status": "ready"} # Include API routes app.include_router(router, prefix="/api/v1") logger.info("application_started", service=settings.service_name) return app if __name__ == "__main__": uvicorn.run( "main:create_app", host="0.0.0.0", port=8000, factory=True )

Standard configuration management:

# src/config.py - Environment-based configuration from pydantic import BaseSettings from functools import lru_cache class Settings(BaseSettings): # Service identification service_name: str = "example-service" version: str = "1.0.0" environment: str = "development" # Database database_url: str database_pool_size: int = 10 # Cache redis_url: str redis_ttl: int = 300 # External APIs external_api_url: str external_api_timeout: int = 30 # Observability log_level: str = "INFO" tracing_enabled: bool = True tracing_endpoint: str = "" class Config: env_file = ".env" case_sensitive = False @lru_cache() def get_settings() -> Settings: return Settings()

Infrastructure Templates

Terraform modules for common patterns:

# terraform/modules/web-service/main.tf terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } variable "service_name" { description = "Name of the service" type = string } variable "environment" { description = "Environment (dev, staging, prod)" type = string } variable "instance_type" { description = "Database instance type" type = string default = "db.t3.medium" } variable "backup_retention_days" { description = "Number of days to retain backups" type = number default = 7 } # RDS Database with standard configuration resource "aws_db_instance" "main" { identifier = "${var.service_name}-${var.environment}" engine = "postgres" engine_version = "15.3" instance_class = var.instance_type allocated_storage = 100 max_allocated_storage = 1000 storage_encrypted = true db_name = replace(var.service_name, "-", "_") username = "admin" password = random_password.db_password.result # Standard backup configuration backup_retention_period = var.backup_retention_days backup_window = "03:00-04:00" maintenance_window = "sun:04:00-sun:05:00" # High availability for production multi_az = var.environment == "prod" ? true : false # Monitoring enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"] monitoring_interval = 60 monitoring_role_arn = aws_iam_role.rds_monitoring.arn # Security vpc_security_group_ids = [aws_security_group.database.id] db_subnet_group_name = aws_db_subnet_group.main.name # Standard tags tags = { Name = "${var.service_name}-${var.environment}" Environment = var.environment ManagedBy = "terraform" Service = var.service_name } } # ElastiCache Redis with standard configuration resource "aws_elasticache_replication_group" "main" { replication_group_id = "${var.service_name}-${var.environment}" replication_group_description = "Redis for ${var.service_name}" engine = "redis" engine_version = "7.0" node_type = "cache.t3.medium" num_cache_clusters = var.environment == "prod" ? 3 : 1 # Encryption at_rest_encryption_enabled = true transit_encryption_enabled = true auth_token = random_password.redis_password.result # Backups snapshot_retention_limit = 5 snapshot_window = "03:00-05:00" # Security security_group_ids = [aws_security_group.cache.id] subnet_group_name = aws_elasticache_subnet_group.main.name tags = { Name = "${var.service_name}-${var.environment}" Environment = var.environment ManagedBy = "terraform" Service = var.service_name } } # Outputs for application consumption output "database_endpoint" { value = aws_db_instance.main.endpoint description = "Database connection endpoint" } output "cache_endpoint" { value = aws_elasticache_replication_group.main.primary_endpoint_address description = "Redis cache endpoint" }

Usage:

# Using the web-service module module "payment_service" { source = "../../modules/web-service" service_name = "payment-api" environment = "production" instance_type = "db.r5.large" backup_retention_days = 30 }

CI/CD Pipeline Templates

Reusable GitHub Actions workflows:

# .github/workflows/reusable-service-pipeline.yml name: Reusable Service Pipeline on: workflow_call: inputs: service_name: required: true type: string runtime: required: true type: string deploy_environment: required: false type: string default: development secrets: registry_token: required: true deploy_token: required: true jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Runtime uses: actions/setup-${{ inputs.runtime }}@v4 with: ${{ inputs.runtime }}-version: '3.11' if: inputs.runtime == 'python' - name: Install Dependencies run: | if [ "${{ inputs.runtime }}" = "python" ]; then pip install -r requirements.txt pip install pytest pytest-cov fi - name: Run Tests run: | if [ "${{ inputs.runtime }}" = "python" ]; then pytest --cov=src --cov-report=xml fi - name: Upload Coverage uses: codecov/codecov-action@v3 with: files: ./coverage.xml security-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Trivy Scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Upload to GitHub Security uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' build: needs: [test, security-scan] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.registry_token }} - name: Build and Push uses: docker/build-push-action@v5 with: context: . push: true tags: | ghcr.io/${{ github.repository }}/${{ inputs.service_name }}:${{ github.sha }} ghcr.io/${{ github.repository }}/${{ inputs.service_name }}:latest cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: build runs-on: ubuntu-latest if: inputs.deploy_environment != '' steps: - name: Deploy to Environment run: | echo "Deploying to ${{ inputs.deploy_environment }}" # ArgoCD sync or kubectl apply

Using the reusable workflow:

# .github/workflows/deploy.yml name: Deploy Payment Service on: push: branches: [main] jobs: deploy: uses: ./.github/workflows/reusable-service-pipeline.yml with: service_name: payment-service runtime: python deploy_environment: production secrets: registry_token: ${{ secrets.GITHUB_TOKEN }} deploy_token: ${{ secrets.ARGOCD_TOKEN }}

Template Generation Tools

Automating template usage reduces manual work.

Cookiecutter Templates

Project scaffolding tool:

# cookiecutter.json - Template configuration { "service_name": "example-service", "service_description": "A brief description", "team_name": "platform", "runtime": ["python", "go", "nodejs"], "database": ["postgresql", "mysql", "mongodb", "none"], "cache": ["redis", "memcached", "none"], "messaging": ["kafka", "rabbitmq", "none"], "environment": "development" }

Template structure with variables:

# {{cookiecutter.service_name}}/src/main.py from fastapi import FastAPI import structlog logger = structlog.get_logger() app = FastAPI( title="{{ cookiecutter.service_name }}", description="{{ cookiecutter.service_description }}" ) @app.get("/health") async def health(): return {"status": "healthy", "service": "{{ cookiecutter.service_name }}"} if __name__ == "__main__": logger.info( "starting_service", service="{{ cookiecutter.service_name }}", team="{{ cookiecutter.team_name }}" )

Usage:

# Generate new service from template cookiecutter gh:example-org/service-template-python # Interactive prompts: service_name [example-service]: payment-api service_description [A brief description]: Handles payment processing team_name [platform]: payments Select runtime: 1 - python 2 - go 3 - nodejs Choose from 1, 2, 3 [1]: 1 Select database: 1 - postgresql 2 - mysql 3 - mongodb 4 - none Choose from 1, 2, 3, 4 [1]: 1 # Result: payment-api/ directory with all files configured

Custom Template CLI

Platform-specific generator:

// Platform CLI for service generation package cmd import ( "fmt" "github.com/spf13/cobra" ) var createCmd = &cobra.Command{ Use: "create [service-name]", Short: "Create a new service from template", Args: cobra.ExactArgs(1), Run: createService, } func createService(cmd *cobra.Command, args []string) { serviceName := args[0] // Get configuration from flags runtime, _ := cmd.Flags().GetString("runtime") team, _ := cmd.Flags().GetString("team") database, _ := cmd.Flags().GetString("database") // Validate if err := validateServiceName(serviceName); err != nil { fmt.Printf("Invalid service name: %v\n", err) return } // Generate from template generator := &ServiceGenerator{ ServiceName: serviceName, Team: team, Runtime: runtime, Database: database, } // 1. Create repository fmt.Println("Creating GitHub repository...") repo, err := generator.CreateRepository() if err != nil { fmt.Printf("Failed to create repository: %v\n", err) return } // 2. Generate code from template fmt.Println("Generating service code...") if err := generator.GenerateCode(repo); err != nil { fmt.Printf("Failed to generate code: %v\n", err) return } // 3. Setup infrastructure fmt.Println("Provisioning infrastructure...") if err := generator.ProvisionInfrastructure(); err != nil { fmt.Printf("Failed to provision infrastructure: %v\n", err) return } // 4. Configure CI/CD fmt.Println("Setting up CI/CD pipeline...") if err := generator.SetupPipeline(repo); err != nil { fmt.Printf("Failed to setup pipeline: %v\n", err) return } // 5. Register in catalog fmt.Println("Registering in service catalog...") if err := generator.RegisterInCatalog(); err != nil { fmt.Printf("Failed to register in catalog: %v\n", err) return } fmt.Printf("\nService '%s' created successfully!\n", serviceName) fmt.Printf("Repository: %s\n", repo.URL) fmt.Printf("Pipeline: %s/actions\n", repo.URL) fmt.Printf("Documentation: https://portal.example.com/catalog/%s\n", serviceName) }

Template Maintenance

Templates require ongoing maintenance and evolution.

Version Management

Track template versions:

# template-metadata.yaml name: python-web-service version: 2.1.0 last_updated: 2024-08-15 compatibility: python: ">=3.11" kubernetes: ">=1.27" changelog: - version: 2.1.0 date: 2024-08-15 changes: - Updated Python to 3.11 - Added structured logging with structlog - Improved health check endpoints - version: 2.0.0 date: 2024-06-01 changes: - Migrated to FastAPI from Flask - Added OpenAPI documentation - Standardized metrics endpoint - version: 1.5.0 date: 2024-03-15 changes: - Added distributed tracing - Updated base Docker image - Added security scanning to CI

Template Updates

Propagate improvements to existing services:

sequenceDiagram participant P as Platform Team participant T as Template participant R as Service Repos participant D as Dev Teams P->>T: Update template (v2.1.0) T->>T: Create migration guide T->>R: Create update PRs automatically R->>D: Notify teams of updates D->>R: Review and merge PRs R->>R: Run automated tests D->>P: Report issues if any Note over P,D: Gradual rollout with monitoring

Automated update creation:

# Script to create update PRs for all services import github from jinja2 import Template class TemplateUpdater: def __init__(self, github_token: str): self.gh = github.Github(github_token) self.org = self.gh.get_organization("example-org") def update_all_services(self, template_version: str): # Find all services using the template repos = self.org.get_repos() services = [r for r in repos if self.uses_template(r)] for repo in services: try: self.create_update_pr(repo, template_version) except Exception as e: print(f"Failed to update {repo.name}: {e}") def create_update_pr(self, repo, template_version: str): # Create new branch base_branch = repo.get_branch("main") new_branch = f"template-update-{template_version}" repo.create_git_ref( ref=f"refs/heads/{new_branch}", sha=base_branch.commit.sha ) # Update files updates = self.get_template_updates(template_version) for file_path, new_content in updates.items(): repo.update_file( path=file_path, message=f"Update to template v{template_version}", content=new_content, branch=new_branch, sha=repo.get_contents(file_path, ref=new_branch).sha ) # Create pull request pr = repo.create_pull( title=f"Update to template v{template_version}", body=self.generate_pr_description(template_version), head=new_branch, base="main" ) # Add labels pr.add_to_labels("template-update", "automated") return pr

Key Takeaways