Skip to content

Flagship Use Case: Order Platform

One application that uses every pico library doing its natural job — not a feature tour, but the shape a real service takes on this ecosystem. Every snippet below is executed against the real modules before each release; the behaviors described (retry counts, cache hits, hot reload) are asserted, not aspirational.

The domain: a coffee-shop order platform. An order comes in over HTTP, the price is looked up in a cached catalog, an external payment gateway is charged (it flakes), the order is persisted, an event fans out to projections, a record lands on the analytics stream, and an invoice is generated by a worker. At 03:00 a reconciliation job sweeps the day.

Concern Library Piece below
Container + auto-discovery pico-ioc + pico-boot create_app()
HTTP API pico-fastapi OrderController
Persistence + transactions pico-sqlalchemy Order, OrderRepository
Migrations on startup pico-sqlalchemy[migrations] database.migrations_path
Input validation pico-pydantic @validate on the stream consumer
External HTTP pico-httpx PaymentsApi
Retry + circuit breaker pico-resilience stacked on charge()
Caching (distributed) pico-caching + pico-data-redis Catalog.price_cents
Events / fan-out pico-rabbitmq OrderEvents, StockProjection
Streaming / consumer groups pico-kafka OrderAnalytics, AnalyticsProjection
Background tasks pico-celery InvoiceTasks
Cron / intervals pico-scheduling Reconciliation
Health + metrics + refresh pico-actuator /actuator/*
Tracing pico-otel zero code — install and configure
AuthN/AuthZ pico-server-auth + pico-client-auth JWKS + @requires_role
Testing pico-testing make_container

The application

One package, one module (split it as it grows — discovery is recursive):

"""orders/app.py — every pico lib doing its natural job."""

import asyncio

import httpx
from pydantic import BaseModel
from sqlalchemy import String

from pico_caching import cacheable
from pico_celery import task
from pico_fastapi import controller, get, post
from pico_httpx import http_client
from pico_httpx import post as http_post
from pico_ioc import component
from pico_kafka import kafka_consumer, kafka_producer, produce
from pico_pydantic import validate
from pico_rabbitmq import consumer, publish, publisher
from pico_resilience import circuit_breaker, retryable
from pico_scheduling import scheduled
from pico_sqlalchemy import (
    AppBase,
    Mapped,
    SessionManager,
    get_session,
    mapped_column,
    query,
    repository,
    transactional,
)

# ── Domain (pico-sqlalchemy) ─────────────────────────────────────


class Order(AppBase):
    __tablename__ = "orders"
    id: Mapped[int] = mapped_column(primary_key=True)
    sku: Mapped[str] = mapped_column(String(50))
    amount_cents: Mapped[int] = mapped_column()
    status: Mapped[str] = mapped_column(String(20))


class OrderRequest(BaseModel):
    sku: str
    quantity: int = 1


@repository(entity=Order)
class OrderRepository:
    def __init__(self, session_manager: SessionManager):
        self.sm = session_manager

    async def save(self, sku: str, amount_cents: int) -> Order:
        session = get_session(self.sm)
        order = Order(sku=sku, amount_cents=amount_cents, status="paid")
        session.add(order)
        await session.flush()
        return order

    @query(expr="status = :status")
    async def find_by_status(self, status: str) -> list[Order]: ...


@component
class SchemaSetup:
    """DatabaseConfigurer for dev; production sets database.migrations_path
    and lets Alembic run on startup instead."""

    priority = 0

    def configure_database(self, engine) -> None:
        async def _create():
            async with engine.begin() as conn:
                await conn.run_sync(AppBase.metadata.create_all)
            # asyncpg binds pooled connections to the loop that created
            # them; drop the DDL loop's pool so request loops start fresh.
            await engine.dispose()

        asyncio.run(_create())


# ── External payments (pico-httpx + pico-resilience) ─────────────


@http_client(name="payments")   # base_url from http.clients.payments.base_url
class PaymentsApi:
    @retryable(max_attempts=3, backoff_seconds=0.05, retry_on=(httpx.HTTPError,))
    @circuit_breaker(failure_threshold=5, reset_timeout_seconds=30)
    @http_post("/charges")
    async def charge(self, json: dict) -> dict: ...


# ── Catalog with distributed cache (pico-caching + pico-data-redis) ──


@component
class Catalog:
    @cacheable(ttl_seconds=300)   # Redis-backed the moment pico-data-redis is installed
    def price_cents(self, sku: str) -> int:
        return {"ESPRESSO": 250, "LATTE": 390}.get(sku, 999)


# ── Events (pico-rabbitmq) and analytics stream (pico-kafka) ─────


@publisher
class OrderEvents:
    @publish(exchange="events", routing_key="orders.created")
    def order_created(self, message): ...


@kafka_producer
class OrderAnalytics:
    @produce("orders")
    def order_placed(self, message): ...


@component
class AnalyticsProjection:
    @kafka_consumer("orders", group_id="analytics")
    @validate
    def on_order(self, message: dict): ...


@component
class StockProjection:
    @consumer("stock-projection", exchange="events", routing_key="orders.*")
    async def on_order_event(self, message: dict): ...


# ── Invoicing worker (pico-celery) ───────────────────────────────


@component(scope="prototype")
class InvoiceTasks:
    @task(name="invoices.generate")
    async def generate(self, order_id: int) -> dict:
        return {"order_id": order_id, "pdf": f"invoice-{order_id}.pdf"}


# ── Business service ─────────────────────────────────────────────


@component
class OrderService:
    def __init__(self, repo: OrderRepository, payments: PaymentsApi, catalog: Catalog,
                 events: OrderEvents, analytics: OrderAnalytics):
        self._repo = repo
        self._payments = payments
        self._catalog = catalog
        self._events = events
        self._analytics = analytics

    @transactional
    async def place(self, request: OrderRequest) -> dict:
        amount = self._catalog.price_cents(request.sku) * request.quantity
        await self._payments.charge(json={"amount_cents": amount})
        order = await self._repo.save(request.sku, amount)
        payload = {"id": order.id, "sku": order.sku, "amount_cents": amount}
        self._events.order_created(payload)
        self._analytics.order_placed(payload)
        return {**payload, "status": order.status}


# ── HTTP API (pico-fastapi) ──────────────────────────────────────


@controller(prefix="/api/v1/orders", tags=["Orders"])
class OrderController:
    def __init__(self, service: OrderService, repo: OrderRepository):
        self._service = service
        self._repo = repo

    @post("")
    async def place_order(self, request: OrderRequest):
        return await self._service.place(request)

    @get("/paid")
    async def paid_orders(self):
        return [{"id": o.id, "sku": o.sku} for o in await self._repo.find_by_status("paid")]


# ── Housekeeping (pico-scheduling) ───────────────────────────────


@component
class Reconciliation:
    def __init__(self, repo: OrderRepository):
        self._repo = repo

    @scheduled(cron="0 3 * * *")
    async def reconcile(self):
        await self._repo.find_by_status("paid")

Boot

main.py stays import-safe (factory pattern):

from fastapi import FastAPI
from pico_boot import init


def create_app() -> FastAPI:
    container = init(modules=["orders"])   # installed pico plugins auto-discovered
    return container.get(FastAPI)
uvicorn --factory orders.main:create_app

Configuration

One YAML, one prefix per module — this is the whole ops surface:

fastapi: {title: orders, version: 1.0.0}
database:
  url: postgresql+asyncpg://orders:secret@db/orders
  migrations_path: alembic          # upgrade head on startup
http:
  clients:
    payments: {base_url: https://payments.internal}
resilience: {enabled: true}         # hot-reloadable (see below)
caching: {enabled: true}
redis: {url: redis://cache.internal:6379/0}
celery: {broker_url: amqp://rabbit.internal/, backend_url: redis://cache.internal/1}
rabbitmq: {url: amqp://rabbit.internal/}
kafka: {bootstrap_servers: kafka.internal:9092, group_id: orders}
scheduling: {enabled: true}
otel: {endpoint: http://collector:4317}
auth_client: {issuer: https://auth.internal, audience: orders}
server_auth: {issuer: https://auth.internal, audience: orders}

What the verification asserts

The e2e harness boots this app with all 15 modules in one container (infra boundaries stubbed: payments on httpx.MockTransport, fakeredis, recording broker registrars) and asserts, in order:

  1. /actuator/health answers UP.
  2. /api/v1/auth/jwks serves the embedded issuer's keys.
  3. POST /api/v1/orders survives a gateway that fails twice: @retryable makes exactly 3 attempts and the order persists as paid.
  4. The rabbit event (events/orders.created) and the kafka record (orders) both leave the service.
  5. GET /api/v1/orders/paid returns the order through the derived query.
  6. A second order hits the Redis-backed catalog cache — zero extra lookups.
  7. invoices.generate is registered on the Celery app for the worker.
  8. The 03:00 reconciliation job is armed on the scheduler.
  9. Hot reload over HTTP: POST /actuator/refresh reports {"changed": ["resilience"]} and turns retries off live — the same request now fails on the first attempt — and flipping it back restores them.
  10. Alembic for real (level 2): database.migrations_path upgrades a real Postgres to head; the revision is asserted in alembic_version.
  11. A real Celery worker (level 2): invoices.generate is dispatched through the real broker and its result collected from the backend.
  12. Real JWT auth (level 2, uvicorn): the protected endpoint answers 401 without a token and 200 with an operator token minted by the embedded issuer — the pairing that uncovered pico-fastapi 0.3.1 and pico-client-auth 0.4.3.

Level 2: against real infrastructure

The same application also runs against real Postgres, Redis, RabbitMQ and Kafka (docker compose): the rabbit event round-trips through a real topic exchange into StockProjection, the kafka record through a real consumer group into AnalyticsProjection, the catalog entry is visible as a pico:cache:* key in Redis, and hot reload flips retries live against the real gateway path. Only the payments gateway stays mocked — it is a third party.

Testing it (pico-testing)

def test_place_order(make_container):
    container = make_container("orders", config={...})
    ...

pico-testing isolates every test from venv plugin auto-discovery and tears containers down for you.

Per-module deep dives

Each library's own docs cover its corner of this application in depth: pico-ioc · pico-fastapi · pico-sqlalchemy · pico-pydantic · pico-httpx · pico-resilience · pico-caching · pico-data-redis · pico-rabbitmq · pico-kafka · pico-celery · pico-scheduling · pico-actuator · pico-otel · pico-server-auth · pico-client-auth · pico-testing