Bird By Bird
Architecting systems that scale and endure
With the fundamentals in place, we can now explore how to design systems that work in the real world.
The Architecture Stack
Modern technology is built in layers, each depending on the one below:
| Layer | Purpose | Example |
|---|---|---|
| Application | User-facing functionality | Web browsers, mobile apps |
| Framework | Reusable patterns | React, Django, Rails |
| Language | Expression of logic | JavaScript, Python, Rust |
| Runtime | Execution environment | Node.js, JVM, WASM |
| Operating System | Resource management | Linux, macOS, Windows |
| Hardware | Physical computation | CPUs, GPUs, memory |
Design Trade-offs
Every technical decision involves trade-offs. Consider this example:
// Option A: Simple but slower
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// Option B: Complex but faster (parallel processing)
func processItemsConcurrent(items []Item) []Result {
results := make([]Result, len(items))
var wg sync.WaitGroup
for i, item := range items {
wg.Add(1)
go func(idx int, it Item) {
defer wg.Done()
results[idx] = process(it)
}(i, item)
}
wg.Wait()
return results
}
The second approach is faster but introduces complexity around concurrency. Which is better depends entirely on context.
Scalability Patterns
As systems grow, they face new challenges. Here are common patterns for handling scale:
Horizontal Scaling
Instead of making one machine bigger, add more machines:
- Load balancers distribute traffic
- Stateless services can run anywhere
- Data is replicated across nodes
Caching
Store frequently accessed data closer to where it’s needed:
class CachedUserService:
def __init__(self, cache, database):
self.cache = cache
self.database = database
def get_user(self, user_id: str) -> User:
# Try cache first
cached = self.cache.get(f"user:{user_id}")
if cached:
return cached
# Fall back to database
user = self.database.find_user(user_id)
self.cache.set(f"user:{user_id}", user, ttl=3600)
return user
Summary
Good system design balances competing concerns: simplicity vs. flexibility, performance vs. maintainability, cost vs. capability.
Next: We’ll explore how these systems fail and how to build resilience.