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:
- Debugging requires learning each team's unique setup
- Security patches must be applied differently for each approach
- New team members face steep learning curves
- Knowledge sharing becomes difficult
- Platform improvements cannot be rolled out uniformly
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
- Standardization through templates reduces inconsistency, accelerates development, and ensures best practices are followed uniformly across teams
- Service templates provide complete starting points including application structure, configuration management, testing setup, and deployment pipelines
- Infrastructure templates encapsulate proven patterns for common resources like databases, caches, and networking with security and compliance built in
- Template generation tools like Cookiecutter or custom CLIs automate the creation of new services from templates, reducing manual setup work
- Template versioning and automated update propagation ensure existing services benefit from platform improvements over time
- Effective templates balance standardization with flexibility, providing sensible defaults while allowing customization when needed
- Regular template maintenance based on team feedback ensures templates stay relevant and incorporate emerging best practices