Skip to main content

Module 05: Architecture & Systems Design

Level: Advanced - Production Engineering | 7 Topics + 2 Projects

Consider two FastAPI applications. Both serve the same endpoints, handle the same traffic, pass the same tests. But one of them can be deployed to any cloud, swapped from PostgreSQL to MongoDB in an afternoon, tested without a running database, and extended with new features by a team of eight engineers without merge conflicts. The other falls apart the moment you try to change the payment provider.

The difference is not the framework. It is the architecture.

# This works. Until it doesn't.
@app.post("/orders")
async def create_order(order: OrderCreate, db: Session = Depends(get_db)):
product = db.query(Product).filter(Product.id == order.product_id).first()
if not product or product.stock < order.quantity:
raise HTTPException(status_code=400, detail="Insufficient stock")
product.stock -= order.quantity
db_order = Order(**order.dict(), total=product.price * order.quantity)
db.add(db_order)
db.commit()
send_email(order.customer_email, f"Order {db_order.id} confirmed")
return db_order

# Where is the business logic? In the route handler.
# Where is the database logic? In the route handler.
# Where is the notification logic? In the route handler.
# Can you test the ordering rules without a database? No.
# Can you switch from email to SMS without touching the route? No.
# Can you reuse this logic in a CLI admin tool? No.

If you have ever inherited a codebase where every route handler was 200 lines of tangled database queries, business rules, and side effects - you already understand the problem this module solves.

What This Module Is Really About

This module is not about drawing boxes on whiteboards. It is about a single engineering principle: managing dependencies so that the things that change most frequently do not force changes in the things that should be stable.

Your business rules (how to price an order, when to reject a payment, what makes a user eligible for a discount) should never depend on whether you use PostgreSQL or DynamoDB, whether you deploy on AWS or bare metal, whether you send notifications via email or Slack.

Architecture is the discipline of encoding that independence into the structure of your code.

What Mastery of This Module Looks Like

By the end of all 7 topics you will be able to:

  • Structure a Python application into layers where dependencies point inward toward business logic
  • Implement the Dependency Rule and explain why it matters more than any specific architecture pattern
  • Design ports (interfaces) and adapters (implementations) that make infrastructure swappable
  • Wire dependencies manually and with containers, understanding the tradeoffs of each approach
  • Use FastAPI's Depends system as a production-grade dependency injection mechanism
  • Build plugin systems with entry_points and importlib.metadata that allow extension without modification
  • Configure applications using environment variables, validated settings, and secrets management
  • Apply all 12 factors of the 12-Factor App methodology to Python projects
  • Make informed decisions about monolith vs microservice architecture based on team size, domain complexity, and operational maturity
  • Design inter-service communication using REST, gRPC, and message queues

The 7 Topics

01 - Clean Architecture

Every production codebase eventually faces the same question: where does the business logic live? Clean Architecture answers this with concentric layers and one inviolable rule - dependencies always point inward. The outer layers (frameworks, databases, HTTP) depend on the inner layers (entities, use cases), never the reverse. You will implement this in Python, understand where it maps naturally and where it fights the language, and learn to recognize when the overhead is justified.

02 - Hexagonal Architecture (Ports and Adapters)

Hexagonal Architecture reframes the layering question as a shape: your application is a hexagon. Each face has a port (an abstract interface) and an adapter (a concrete implementation). Your order service does not know about PostgreSQL - it knows about a OrderRepository port. A PostgresOrderRepository adapter plugs in. A FakeOrderRepository plugs in during tests. You will build a real FastAPI application structured this way and experience firsthand how it transforms testability.

03 - Dependency Injection

Dependency injection is the mechanism that makes clean and hexagonal architectures practical. Without it, your layers know about each other's concrete implementations, and the entire architecture collapses. Covers manual constructor injection, the dependency-injector library, FastAPI's Depends system, and the critical difference between DI as a pattern and DI as a framework.

04 - Plugin Systems

Some applications need to be extended without modifying their source code. IDEs, build tools, monitoring systems - they all use plugins. Python has a first-class mechanism for this: entry_points in packaging metadata. You will build a plugin-based CLI tool, implement discovery and loading with importlib.metadata, and understand how tools like pytest, flake8, and tox use the same mechanism.

05 - Configuration Management

Hardcoded values are the first thing that breaks when you move from development to staging to production. Configuration management is the practice of externalizing every environment-specific value - database URLs, API keys, feature flags - and validating them at startup rather than discovering failures at runtime. Covers python-dotenv, pydantic-settings, secrets management, and the configuration hierarchy that production teams actually use.

06 - The 12-Factor App

The 12-Factor App is a methodology for building applications that deploy cleanly, scale horizontally, and run identically across environments. Written by Heroku's engineers, it codifies hard-won lessons about what goes wrong when you ship software to production. You will walk through all 12 factors with Python-specific examples and understand which factors are non-negotiable and which require judgment.

07 - Microservices vs Monolith

The most expensive architectural decision most teams make - and the one most frequently made for the wrong reasons. This topic strips away the hype and examines when splitting a monolith into services actually helps, when it creates more problems than it solves, and what communication patterns (REST, gRPC, message queues) work for which kinds of inter-service boundaries. Covers the monolith-first approach, strangler fig migration, and shared library patterns.

The 2 Projects

#ProjectCore Skills
01Production FastAPI ApplicationClean architecture layers, hexagonal ports/adapters, DI with Depends, pydantic-settings config, 12-factor compliance
02Modular Plugin SystemPlugin discovery with entry_points, importlib.metadata, stevedore, extensible CLI architecture, plugin lifecycle management

Prerequisites for This Module

This module assumes you have completed the Python Intermediate course or have equivalent experience:

  • OOP and SOLID Principles - You understand interfaces, abstract base classes, the Dependency Inversion Principle, and common design patterns (strategy, factory, observer)
  • FastAPI - You have built REST APIs with FastAPI, used Depends, Pydantic models, and async route handlers
  • SQLAlchemy - You have used SQLAlchemy for database access, understand sessions and models
  • Python Packaging - You understand pyproject.toml, setup.cfg, and how Python packages are installed and distributed
  • Testing - You write unit and integration tests with pytest

:::note Prerequisites are strict for this module Architecture patterns build on top of OOP, SOLID, and framework experience. If you have not worked through design patterns (strategy, factory, repository) and dependency inversion, review those topics first. Without that foundation, the patterns in this module will feel like unnecessary ceremony rather than powerful tools. :::

Why Architecture Matters for AI/ML Engineering

If you think architecture is only for web applications, consider these real-world AI system problems:

  • Model serving - Your inference service needs to swap between a local PyTorch model and a remote API endpoint (SageMaker, Vertex AI) without changing the prediction logic. That is hexagonal architecture.
  • Feature stores - Your training pipeline reads features from a local CSV in development and from a Redis-backed feature store in production. That is a port/adapter boundary.
  • Experiment tracking - Your training code should not know whether it logs to MLflow, Weights & Biases, or a custom database. That is dependency injection.
  • Pipeline orchestration - Airflow, Prefect, and Dagster are all plugin-based systems. Understanding plugin architecture lets you extend them effectively.
  • Configuration - Hyperparameters, model paths, API keys, feature flags, environment-specific endpoints. ML systems have more configuration surface area than most web applications.

The architecture patterns in this module are not theoretical. They are how production ML systems at companies like Spotify, Netflix, and Stripe are actually structured.

The Central Insight

Every pattern in this module - clean architecture, hexagonal architecture, dependency injection, plugin systems, configuration management, 12-factor - is a different expression of the same underlying principle:

:::tip The Dependency Rule Source code dependencies must point inward - toward higher-level policies and away from implementation details. Your business logic must never import from your infrastructure. Your domain entities must never know about your web framework. Your use cases must never reference a specific database driver. When you internalize this single rule, every architecture pattern in this module becomes an obvious consequence rather than an arbitrary prescription. :::

How to Use This Module

Work through the topics in order. Each one builds on the previous:

  1. Clean Architecture establishes the mental model of layers and the dependency rule
  2. Hexagonal Architecture gives you a concrete structural pattern (ports and adapters) to implement those layers
  3. Dependency Injection provides the mechanism to wire the layers together without coupling them
  4. Plugin Systems extend the ports-and-adapters idea to external code that loads at runtime
  5. Configuration Management handles the environment-specific values that change between deployments
  6. The 12-Factor App provides a checklist for production-readiness
  7. Microservices vs Monolith addresses the system-level question of how to decompose large applications

Each topic has:

  • An opening puzzle that reveals why the pattern exists
  • A deep explanation of the underlying engineering principles
  • Runnable Python code demonstrating the pattern
  • A real-world example from production systems
  • Common mistakes and anti-patterns
  • An AI/ML engineering connection

:::danger Do not cargo-cult architecture Architecture patterns have costs. Clean architecture adds layers and indirection. Hexagonal architecture requires defining ports for every external dependency. DI containers add configuration complexity. A 200-line script does not need hexagonal architecture. A solo-developer prototype does not need a plugin system. Every topic in this module discusses when the pattern is worth its cost - and when it is not. Ignoring those tradeoffs is how teams end up with over-engineered systems that are harder to work with than the tangled code they replaced. :::

Key Takeaways for This Overview

  • Architecture is not about boxes on whiteboards - it is about managing dependencies so that stable business logic does not change when infrastructure changes
  • The Dependency Rule (dependencies point inward toward policy) is the single principle that unifies every pattern in this module
  • Clean architecture, hexagonal architecture, and dependency injection are complementary tools that work together, not competing alternatives
  • Plugin systems and configuration management extend the same principles to runtime extensibility and environment adaptation
  • The 12-Factor App codifies production deployment lessons that every Python engineer should internalize
  • Microservices are a deployment strategy, not an architecture - and premature decomposition is one of the most expensive mistakes in software engineering
  • Every pattern has a cost, and the engineering judgment of when to apply each one is more valuable than knowing how to implement it
© 2026 EngineersOfAI. All rights reserved.