Layers and dependency arrows: all point inward
Layers and dependency arrows: all point inward

Layers

Splitting a large program into layers makes every part do one job:

  • Domain: entities and business rules (Task.complete()); depends on nothing (no DB, no web).
  • Services (use cases): scenarios such as "create a task", "complete it"; they coordinate the domain and repository interfaces.
  • Infrastructure: DB, files, HTTP, email: all the "outside" details.
  • UI / API: CLI, web pages, REST: talking to the user.

Related: SRP, coupling and cohesion.

The domain layer

Domain classes are plain Python objects: the rules live inside them and they know nothing about the database. That is why they can be tested in seconds.

python
from dataclasses import dataclass

@dataclass
class Task:
    id: int
    title: str
    done: bool = False

    def complete(self):
        if self.done:
            raise ValueError("task already done")
        self.done = True

t = Task(1, "write tests")
t.complete()
print(t)
try:
    t.complete()
except ValueError as e:
    print("error:", e)
text
Task(id=1, title='write tests', done=True)
error: task already done

The dependency rule