Commands and Queries

Let’s start the tutorial by creating a test to show how to use lato. This test will demonstrate how to interact with any app built with lato. We’re following a CQRS pattern, where you only need to use app.execute for the invocation of commands and queries.

Note

CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates the read and write operations of an application. Commands are used to change the state to the application, while queries are used read the state.

With help of pytest, we can create our our first happy path acceptance test:

def test_create_and_complete_todo_scenario(app: Application):
    """This acceptance tests verifies the basic flow of creating and completing a todo"""
    app.execute(CreateTodo(todo_id=UUID(int=1), title="Publish the tutorial"))

    todos = app.execute(GetAllTodos())
    assert len(todos) == 1

    app.execute(CompleteTodo(todo_id=UUID(int=1)))
    assert len(app.execute(GetSomeTodos(completed=True))) == 1
    assert len(app.execute(GetSomeTodos(completed=False))) == 0

Running this code requires the app fixture, and a bunch of commands (CreateTodo, CompleteTodo) and queries (GetAllTodos, GetSomeTodos). Let’s start with latter ones.

A commands in lato is pydantic data structures with no behavior, which contains all the necessary:

from datetime import datetime
from typing import Optional
from uuid import UUID

from lato import Command


class CreateTodo(Command):
    """This commands represents an intent to create a new todo"""

    todo_id: UUID
    title: str
    description: str = ""
    due_at: Optional[datetime] = None


class CompleteTodo(Command):
    """This commands represents an intent to complete an existing todo"""

    todo_id: UUID

Take note that the identifier for a new todo, todo_id, is explicitly provided. This simplifies testing for the app and aligns with the CQRS pattern. By supplying the id upfront, there’s no need to return it, and we can eliminate the reliance on the database for id generation.

Queries are similar to commands, note that some of the queries does not require any arguments:

from typing import Optional
from uuid import UUID

from lato import Query


class GetAllTodos(Query):
    """A query to get a list of all todos"""


class GetSomeTodos(Query):
    """A query to get specific subset of todos"""

    completed: Optional[bool]


class GetTodoDetails(Query):
    """A query to get details of a single todo"""

    todo_id: UUID

In the next step we will implement a module responsible for handling the queries and commands.