Message Composition and Decomposition

If there are multiple command handlers (i.e. in different modules) for the same Command, all handlers for that command will be executed (decomposition), and the results of command handlers will be merged into single response (composition).

from lato import Application, ApplicationModule, Command


class GetItemDetails(Command):
    item_id: str


pricing_module = ApplicationModule("pricing")


@pricing_module.handler(GetItemDetails)
def get_item_price(command: GetItemDetails):
    prices = {"pencil": 1, "pen": 2}
    return {"price": prices[command.item_id]}


warehouse_module = ApplicationModule("warehouse")


@warehouse_module.handler(GetItemDetails)
def get_item_stock(command: GetItemDetails):
    stocks = {"pencil": 100, "pen": 80}
    return {"stock": stocks[command.item_id]}


app = Application()
app.include_submodule(pricing_module)
app.include_submodule(warehouse_module)

assert app.execute(GetItemDetails(item_id="pen")) == {"price": 2, "stock": 80}

By default, handler responses of type dict will be recursively merged into a single dict, and responses of type list will be merged a into single list.

You can use Application.compose decorator to declare a custom composer. A custom composer will receive kwargs with names of the modules handling the response.

from lato import Application, ApplicationModule, Command, TransactionContext
from lato.compositon import compose

class GetAllItemDetails(Command):
    pass


pricing_module = ApplicationModule("pricing")

@pricing_module.handler(GetAllItemDetails)
def get_item_price(command: GetAllItemDetails):
    prices = {'pencil': 1, 'pen': 2}
    return prices


warehouse_module = ApplicationModule("warehouse")

@warehouse_module.handler(GetAllItemDetails)
def get_item_stock(command: GetAllItemDetails):
    stocks = {'pencil': 100, 'pen': 80}
    return stocks


app = Application()
app.include_submodule(pricing_module)
app.include_submodule(warehouse_module)

@app.compose(GetAllItemDetails)
def compose_item_details(pricing, warehouse):
    assert pricing == {'pencil': 1, 'pen': 2}
    assert warehouse == {'pencil': 100, 'pen': 80}

    details = [dict(item_id=x, price=pricing[x], stock=warehouse[x]) for x in pricing.keys()]
    return details


assert app.execute(GetAllItemDetails()) == [
    {'item_id': 'pencil', 'price': 1, 'stock': 100}, 
    {'item_id': 'pen', 'price': 2, 'stock': 80}
]