Clean Architecture
Python (web) apps

Przemek Lewandowski



  • Entities
  • Use cases
  • Adapters
  • Frameworks and Drivers
  • Rules between layers
  • Web vs Other UIs
  • Example in Python
  • Summary

Clean Architecture

  • Independent of Frameworks
  • Testable
  • Independent of UI
  • Independent of Database
  • Independent of any external agency

Uncle's Bob Clean Architecture

Dependency Rule

  • The further in you go, the higher level the software becomes.
  • Source code dependencies can only point inward.
  • Data formats used in an outer circle should not be used by an inner circle.
  • Anything in an outer circle to impact the inner circles.


Enterprise wide business rules

Use cases

Application specific business rules

Interface Adapters

Data is converted from the form most convenient for entities and use cases to the format most convenient for some external agency such as the Database or the Web.

Frameworks and Drivers

  • Tools such as the Database, the Web Framework, etc
  • Glue code that communicates to the next circle inwards.

Crossing boundaries

  • Dependency Rule
  • Dependency Inversion Principle
  • Data that crosses the boundaries is simple data structures.

Flow of control

Web vs Other UIs

  • Web is state-less
  • Clean Architecture principles
    are more generic than Web
  • Presenters & Controllers can work
    in different threads

Web vs Other UIs


Use case

Example code

in Python


from typing import List

class Invoice:

    def __init__(self, number: str, lines: List[InvoiceLine]):
        self.number = number
        self.lines = lines
        self._total_net_value = None

    def validate(self):

    def total_net_value(self):

    def total_net_value(self, value):

class InvoiceLine:

    def __init__(self, description: str, net_value: int):
        self.description = description
        self.net_value = net_value

    def validate(self):

Use cases

from typing import Iterable, Dict, Optional, ContextManager

from core.domain.invoice import Invoice

class InvoiceUseCase:

    def __init__(self, repository: InvoiceRepository):
        self.repository = repository

    def get_list(self, filters: Optional[Dict[str, str]]) -> Iterable[Invoice]:
        return self.repository.get_list(filters)

    def create(self, invoice: Invoice) -> Invoice:

        for line in invoice.lines:

        with self.repository.atomic():
            invoice =

        return invoice

    def _validate_invoice_number(self, invoice: Invoice):

Repository interface

from typing import Iterable, Dict, Optional, ContextManager

from core.domain.invoice import Invoice

class InvoiceRepository:

    def get_list(self, filters) -> Iterable[Invoice]:
        raise NotImplementedError()

    def save(self, invoice: Invoice) -> Invoice:
        raise NotImplementedError()

    def atomic(self) -> ContextManager:
        raise NotImplementedError()


from typing import Iterable, NamedTuple

class InvoiceLineData(NamedTuple):
    description: str
    net_value: int

class InvoiceData(NamedTuple):
    number: str
    lines: Iterable[InvoiceLineData]


from typing import Iterable, Dict, Optional, NamedTuple

from core.domain.invoice import Invoice
from core.usecases.invoice import InvoiceUseCase

class InvoiceAdapter:

    def __init__(self, repository: InvoiceRepository):
        self.usecase = InvoiceUseCase(repository)

    def get_list(self, filters: Optional[Dict[str, str]]) -> Iterable[InvoiceData]:
        invoices = self.usecase.get_list(filters)
        invoices_data = []
        for invoice in invoices:
        return invoices_data

    def create(self, invoice_data: InvoiceData) -> InvoiceData:
        invoice = self._data_to_invoice(invoice_data)
        invoice = self.usecase.create(invoice)
        return self._invoice_to_data(invoice)

    def _invoice_to_data(cls, invoice: Invoice) -> InvoiceData:

    def _data_to_invoice(cls, invoice_data: InvoiceData) -> Invoice:

Flask View

import json

from flask import request
from flask.ext.restful import Resource, Api

from core.adapters import InvoiceAdapter
from database import InvoiceRepository

class InvoiceResource(Resource):
    default_length = 100

    def __init__(self, *args, **kwargs):
        self.super().__init__(*args, **kwargs)
        self.adapter = InvoiceAdapter(InvoiceRepository())

    def get(self, number=None):
        if number:
            return self.get_one(number)
        return self.get_list()

    def get_one(self, number):
        invoice = self.adapter.get_list({'number': number})
        return json.dumps(invoice)

    def get_list(self):
        invoices = self.adapter.get_list()
        return json.dumps(invoices)

api = Api()
api.add_resource(InvoiceResource, '/invoices/<int:number>', '/invoices')


  • Everyone needs to respect rules
  • Significant boilerplate
  • Difficult to leverage frameworks
  • Beloved Active Record pattern does not help



Git repo

TBA soon

Join the team!




Clean architecture - Python (web) apps

By Przemek Lewandowski

Clean architecture - Python (web) apps

  • 7,206
Loading comments...

More from Przemek Lewandowski