Author: Amin Boulouma, Software Engineer Github source code: https://github.com/aminblm/ai_systems_design_from_scratch Engineering Blog: https://aminblm.github.io/ai_systems_design_from_scratch/blog/
From Scripts to Services: The Architect’s Evolution
We all start the same way: a .py file on a laptop, a cron job, and a prayer. But what happens when that script becomes the backbone of a production workflow? Scripts are built for execution; services are built for availability.
Moving from a script to a service is not just about deployment; it is a fundamental shift in how you handle state, failure, and observability.
The Problem: The “Script” Mindset
Scripts are linear. They have a start, a middle, and an end. If they fail, they die. In a production environment, this is unacceptable. A service must be long-running, re-entrant, and self-monitoring.
Glossary for Beginners
- Service: A persistent program designed to run continuously and respond to events or requests.
- Observability: The ability to understand the internal state of your system by analyzing its external outputs (logs, metrics, traces).
- Idempotency: A property of a system where performing the same operation multiple times results in the same outcome as performing it once.
- Configuration Drift: When a system’s live state diverges from its expected state due to unmanaged manual changes.
Architectural Pivot: Why We Move to Services
We choose the service-based architecture over raw scripts because scripts lack lifecycle management. A service allows us to implement Graceful Shutdowns, Health Checks, and State Persistence.
Implementation: Simple Script vs. Resilient Service
The Script (Brittle):
# script.py
def process():
data = load_data()
# If this crashes here, all progress is lost
result = transform(data)
save(result)
process()
The Service (Resilient):
import time
import logging
class TaskService:
"""
Production-grade service loop with error handling and state management.
"""
def __init__(self):
self.running = True
def run(self):
logging.info("Service started")
while self.running:
try:
# Always wrap the core loop in a try/except
self.process_cycle()
except Exception as e:
logging.error(f"Cycle failed: {e}")
# Exponential backoff would be implemented here
time.sleep(60) # Interval control
def process_cycle(self):
# Implementation of idempotent logic
print("Processing...")
service = TaskService()
# service.run()
Quick Reference: Script vs. Service
| Feature | Local Script | Enterprise Service |
|---|---|---|
| Lifecycle | Runs once/dies | Persistent loop |
| Error Handling | Crash and burn | Catch, log, and recover |
| Configuration | Hardcoded values | Environment variables/TOML |
| State | Filesystem/Memory | Databases/Distributed cache |
Developer Checklist: Is your service ready for production?
- Idempotency: Can I run this process twice without duplicating data?
- Observability: Does it log errors to a centralized location?
- Config: Are secrets and configuration externalized from the code?
- Signal Handling: Does it handle
SIGTERMto shut down gracefully? - Health Checks: Is there a way for the orchestrator to know it is still “alive”?
Takeaway
A service is just a script that respects the environment it lives in. By decoupling your business logic from the execution loop, adding error recovery, and externalizing your configuration, you transform a fragile, one-off automation into a foundational pillar of your production infrastructure.