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
- A repository interface describing necessary repository methods.
- The use case uses repositories for reading and writing to external systems like databases.
- A repository interface describing necessary repository methods.
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)