Reference · open spec · 2025 · FastAPI + React

ServiceFlow Platform -
reference-бэкенд для helpdesk-систем.
ServiceFlow Platform -
reference backend for helpdesk systems.

Не клиентский проект, а учебник. Repository + UoW, доменный FSM, idempotency, outbox, ETag/If-Match, OpenTelemetry - все паттерны, к которым я возвращаюсь из проекта в проект, собранные в один читабельный код. Not a client project - a textbook. Repository + UoW, domain FSM, idempotency, outbox, ETag/If-Match, OpenTelemetry - every pattern I keep returning to across projects, collected into one readable codebase.

~/serviceflow/app/api/tickets.py open
@router.post("/tickets", status_code=201)
async def create_ticket(
    cmd: CreateTicket,
    idem_key: IdempotencyKey = Header("Idempotency-Key"),
    user: User = Depends(current_user),
    uow: UnitOfWork = Depends(),
) -> TicketView:
    async with uow:
        existing = await uow.idempotency.find(idem_key)
        if existing:
            return existing.response

        ticket = Ticket.create(cmd, opened_by=user.id)
        await uow.tickets.add(ticket)
        await uow.outbox.add(TicketOpened(ticket.id))
        await uow.idempotency.save(idem_key, ticket.view())
        await uow.commit()
    return ticket.view()

§ 01О чём проектWhat this is

За несколько коммерческих проектов я заметил, что объясняю одни и те же вещи: «почему Repository, а не Active Record», «зачем outbox, если есть транзакция», «как сделать идемпотентный POST». Объяснять на продакшен-коде нельзя - NDA. На примерах из книги - тоже плохо, они слишком стерильны. After several commercial projects, I noticed I keep explaining the same things: "why Repository and not Active Record," "why outbox when there's a transaction," "how to make an idempotent POST." Explaining with production code is off-limits - NDA. Book examples don't work either - too sterile.

ServiceFlow - это helpdesk-бэкенд, написанный достаточно подробно, чтобы на нём можно было показать всё, что я считаю важным. Достаточно простой, чтобы домен не закрывал паттерны. ServiceFlow is a helpdesk backend written in enough detail to demonstrate everything I consider important. Simple enough that the domain doesn't obscure the patterns.

§ 02Как устроеноHow it's built

  • Repository + UoW - чистый домен, тестируемый без БД.Repository + UoW - clean domain, testable without a database.
  • Доменный FSM - переходы статусов тикета как явная машина, не набор if'ов.Domain FSM - ticket status transitions as an explicit state machine, not a bunch of ifs.
  • Idempotency - заголовок Idempotency-Key, повтор запроса возвращает тот же ответ без побочных эффектов.Idempotency - Idempotency-Key header, retrying a request returns the same response with no side effects.
  • Outbox pattern - события в ту же транзакцию, отправка асинхронно.Outbox pattern - events in the same transaction, delivery is asynchronous.
  • ETag / If-Match - оптимистичный concurrency control на HTTP-уровне.ETag / If-Match - optimistic concurrency control at the HTTP level.
  • RFC 7807 ошибки - единый формат problem-details.RFC 7807 errors - unified problem-details format.
  • OpenTelemetry - трейсы, метрики, логи с первого коммита.OpenTelemetry - traces, metrics, logs from the first commit.
  • API keys на argon2id - проверка занимает 100 мс намеренно.API keys on argon2id - verification takes 100 ms intentionally.

§ 03СтруктураStructure

structure · project layout
serviceflow/
├── domain/         # entities · value objects · FSM · invariants
│   ├── ticket.py
│   ├── status.py   # FSM transitions, no IO
│   └── policies/
├── app/            # use-cases, ports
│   ├── tickets/
│   ├── idempotency.py
│   └── outbox.py
├── infra/          # adapters: Postgres, Redis, ARQ workers
│   ├── repo/
│   ├── outbox_worker.py
│   └── otel.py
└── api/            # FastAPI routers, pydantic IO models
    ├── v1/
    └── errors.py   # RFC 7807
Рис. 1 · Структура. Стрелки зависимостей идут вверх - домен ничего не знает.Fig. 1 · Structure. Dependency arrows point upward - the domain knows nothing.

Репозиторий открытый. README ведёт за руку: 10 минут - и всё крутится локально. ADR-папка объясняет каждое нетривиальное решение - почему UoW поверх SQLAlchemy 2.x, почему outbox, а не CDC, почему ARQ, а не Celery для этого размера. The repository is public. The README walks you through setup: 10 minutes and everything runs locally. The ADR folder explains every non-trivial decision - why UoW on top of SQLAlchemy 2.x, why outbox and not CDC, why ARQ and not Celery for this scale.

Не «делайте как я», а «вот примеры; разбирайтесь, что подходит вам». Not "do as I do," but "here are examples - figure out what works for you."

Нужно похожее решение для вашего бизнеса?Need a similar solution for your business?

Опишите задачу - отвечу в течение дня, оценку дам бесплатно.Describe your task - I'll reply within a day, the estimate is free.