Skip to main content

Use cases

Use cases implement and encapsulate all the application business rules.

If the use case wants to access a database (infrastructure layer), then the use case will use a data provider interface. The add_todo_use_case interacts with the infrastructure layer via TodoRepositoryInterface.

import uuid

from pydantic import BaseModel, Field

from features.todo.entities.todo_item import TodoItem
from features.todo.repository.todo_repository_interface import TodoRepositoryInterface


class AddTodoRequest(BaseModel):
title: str = Field(
title="The title of the item",
max_length=300,
min_length=1,
json_schema_extra={
"examples": ["Read about clean architecture"],
},
)


class AddTodoResponse(BaseModel):
id: str = Field(
json_schema_extra={
"examples": ["vytxeTZskVKR7C7WgdSP3d"],
}
)
title: str = Field(
json_schema_extra={
"examples": ["Read about clean architecture"],
}
)
is_completed: bool = False

@staticmethod
def from_entity(todo_item: TodoItem) -> "AddTodoResponse":
return AddTodoResponse(id=todo_item.id, title=todo_item.title, is_completed=todo_item.is_completed)


def add_todo_use_case(
data: AddTodoRequest,
user_id: str,
todo_repository: TodoRepositoryInterface,
) -> AddTodoResponse:
todo_item = TodoItem(id=str(uuid.uuid4()), title=data.title, user_id=user_id)
todo_repository.create(todo_item)
return AddTodoResponse.from_entity(todo_item)
  • Required
    • Each use case needs to have its own read and write model to handle custom requests inputs and outputs for each use case.
  • Optional
info

Changes to use cases should not impact the entities.

The use-case should only know of the repository interface (abstract class) before run-time. The concrete implementation of a repository is injected (dependency injection) into the use-case at run-time.

Testing use cases

Use the todo_repository fixture as input to use_cases.

import pytest
from pydantic import ValidationError

from features.todo.repository.todo_repository_interface import TodoRepositoryInterface
from features.todo.use_cases.add_todo import AddTodoRequest, add_todo_use_case


def test_add_with_valid_title_should_return_todo(todo_repository: TodoRepositoryInterface):
data = AddTodoRequest(title="new todo")
result = add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)
assert result.title == data.title


def test_add_with_empty_title_should_throw_validation_error(todo_repository: TodoRepositoryInterface):
with pytest.raises(ValidationError):
data = AddTodoRequest(title="")
add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)