Skip to main content
Clean Architecture 60 XP · 6 min

What is Architecture? Lifecycle Support

Architecture's primary purpose is to support the system's lifecycle — development, deployment, operation, and maintenance — to maximize productivity and minimize lifetime cost.

Showing
Ad (728×90)

Why this matters

Architecture is not about making a system work — even bad architecture can produce a working system. Architecture is about keeping the system easy to develop, easy to deploy, easy to operate, and easy to maintain. These four lifecycle phases are the real measure of architectural quality. The strategy is to leave as many options open as long as possible while the system demonstrates its true shape.

A team with ten members needs to work on the same codebase without constantly colliding. A system with thousands of users needs to be deployable without rebuilding everything. An operations team needs to monitor and manage the system without touching source code. And every change — whether adding a feature or fixing a bug — should be localized and contained. Architecture shapes how well the system serves all four of these needs over its entire lifetime.

The problem

All lifecycle concerns wired into one file — teams block each other, every deployment touches everything, and adding one feature requires understanding everything.

Bad

# main.py — every feature, every layer, every concern
import flask, sqlalchemy, redis, stripe, smtplib, pandas

def handle_request(request):
    user  = db.query("SELECT * FROM users WHERE token = ?", request.token)
    order = db.query("SELECT * FROM orders WHERE id = ?", request.order_id)
    discount = 0.1 if user.is_premium else 0
    total    = order.amount * (1 - discount)
    stripe.charge(user.card_token, total)
    smtp.send(user.email, f"Your order total is ${total}")
    redis.incr("orders_processed")
    return {"status": "ok", "total": total}

# Every developer edits main.py — constant merge conflicts (development).
# Any change rebuilds and redeploys everything (deployment).
# A new feature requires reading all 500 lines (maintenance).
// server.ts — all concerns in one file
import express from "express";
import { Pool } from "pg";
import Stripe from "stripe";
import nodemailer from "nodemailer";

app.post("/orders/:id/process", async (req, res) => {
  const user  = await db.query("SELECT * FROM users WHERE token = $1", [req.headers.token]);
  const order = await db.query("SELECT * FROM orders WHERE id = $1", [req.params.id]);
  const discount = user.rows[0].is_premium ? 0.1 : 0;
  const total    = order.rows[0].amount * (1 - discount);
  await stripe.charges.create({ amount: total * 100, currency: "usd" });
  await mailer.sendMail({ to: user.rows[0].email, subject: `Total: ${total}` });
  res.json({ total });
});
// One file. One deployment unit. Every team steps on every other team.

The solution

Separate modules by lifecycle concern — each layer is independently owned, testable, and deployable.

Good

# domain/orders.py — business rules (changes for business reasons)
class OrderService:
    def __init__(self, order_repo, payment_gateway, notifier):
        self._orders    = order_repo
        self._payments  = payment_gateway
        self._notifier  = notifier

    def process(self, order_id: str, user) -> Decimal:
        order    = self._orders.get(order_id)
        discount = Decimal("0.1") if user.is_premium else Decimal("0")
        total    = order.amount * (1 - discount)
        self._payments.charge(user.payment_method, total)
        self._notifier.send(user.email, total)
        return total

# api/routes.py       — HTTP boundary (changes for API consumer reasons)
# infrastructure/db.py — Persistence (changes for DB reasons)
# cli/commands.py     — CLI interface (changes for ops reasons)
# Each module is owned by one team and deployable independently.
// src/domain/OrderService.ts — business rules only
export class OrderService {
  constructor(
    private orders:   OrderRepository,
    private payments: PaymentGateway,
    private notifier: Notifier,
  ) {}

  async process(orderId: string, user: User): Promise {
    const order    = await this.orders.get(orderId);
    const discount = user.isPremium ? 0.1 : 0;
    const total    = order.amount * (1 - discount);
    await this.payments.charge(user.paymentMethod, total);
    await this.notifier.send(user.email, total);
    return total;
  }
}
// src/api/orderRoutes.ts   — HTTP layer (independently deployable)
// src/infra/PgOrderRepo.ts  — DB adapter (swap DB without touching domain)
// src/cli/processCmd.ts    — CLI for ops (shares domain, not HTTP)

Key takeaway

Architecture is not about making the system work — it's about making the system easy to work on, forever. Separate what changes for different reasons so each lifecycle phase (development, deployment, operation, maintenance) can proceed without interfering with the others.

Done with this lesson?

Mark it complete to earn XP and track your progress.