Skip to main content
Clean Architecture 60 XP · 6 min

Components: The Unit of Deployment

Components are the smallest deployable units — JARs, DLLs, Gems — the building blocks of a plugin architecture where modules connect at runtime.

Showing
Ad (728×90)

Why this matters

A component is the smallest deployable and developable unit of a system — the JAR file in Java, the DLL in Windows, the Gem in Ruby, the npm package in Node. Components are the granularity level at which software is deployed: they can be linked together into a single executable, or kept as separately deployable plugins that connect at runtime.

The evolution toward runtime plug-ability is significant. When components can be swapped at runtime without recompiling the rest of the system, the architecture becomes a plugin architecture — new features, new integrations, and new behaviors arrive as new components, leaving the existing core untouched. A monolithic module that mixes unrelated concerns forces the entire system to be tested and redeployed whenever any single part of it changes. The goal of component architecture is to break that coupling so that each independently deployable piece has its own versioning and release cadence.

The problem

A monolithic module bundles unrelated concerns — changing any one part forces a full redeploy of everything else.

Bad

# user_auth.py — HTTP logic, DB models, email, AND auth rules all mixed together

class User:
    id: int
    email: str

def hash_password(pw: str) -> str: ...
def send_welcome_email(user: User) -> None: ...  # depends on email server
def save_user(user: User) -> None: ...           # depends on database
def validate_token(token: str) -> bool: ...      # auth logic
def render_profile_page(user: User) -> str: ...  # depends on HTML templates

# Changing the email template forces a full redeploy of auth logic, DB code, etc.
// src/app.ts — auth, DB, email, HTTP all in one module

export function hashPassword(pw: string): string { /* ... */ }
export function sendWelcomeEmail(user: User): void { /* email dep */ }
export function saveUser(user: User): Promise<void> { /* DB dep */ }
export function validateToken(token: string): boolean { /* ... */ }
export function renderProfilePage(user: User): string { /* template dep */ }

// A bug in sendWelcomeEmail forces a full redeploy of all auth and DB code.

The solution

Separate packages with clear boundaries — each with its own version and release cadence. A change to notifications never touches auth or user packages.

Good

# auth/         ← pure auth logic, no external deps
#   tokens.py, passwords.py
#
# users/        ← user domain — depends on auth, not on email or HTTP
#   models.py
#
# notifications/ ← email/SMS — versioned separately
#   email.py
#
# api/           ← HTTP layer — depends on users + auth
#   routes.py

# Each package has its own version tag.
# A change to notifications/email.py triggers only notifications tests + deploy.
// packages/auth/         — pure auth logic
//   index.ts: export { hashPassword, validateToken }
//
// packages/users/        — user domain
//   index.ts: export { User, saveUser }
//
// packages/notifications/ — email/SMS
//   index.ts: export { sendWelcomeEmail }
//
// packages/api/          — HTTP layer
//   index.ts: import from auth, users, notifications

// Fixing a bug in notifications only redeploys that package.
// auth and users packages are never touched.

Key takeaway

If you can't deploy one part of your system without redeploying everything, you have a monolith masquerading as components. True components have independent versioning and deployment — changing one part leaves the rest provably unchanged.

Done with this lesson?

Mark it complete to earn XP and track your progress.