Skip to content

Рефакторинг бэкенда на clean architecture, разбиение фронтенда на слои - #21

Open
nylinary wants to merge 3 commits into
sputnik-llc:mainfrom
nylinary:refactor/clean-architecture
Open

nylinary wants to merge 3 commits into
sputnik-llc:mainfrom
nylinary:refactor/clean-architecture

Conversation

@nylinary

@nylinary nylinary commented Sep 7, 2026

Copy link
Copy Markdown

Бэкенд

  • Слои: domain / application / infrastructure / presentation, зависимости
    направлены только внутрь. src/container.py - composition root, единственный
    модуль, который знает про все слои сразу. Правило зависимостей проверяется: tests/unit/test_architecture.py обходит AST всех
    модулей и падает, если внутренний слой импортирует внешний.
  • Сущности - обычные dataclass'ы без импорта фреймворка, положенные в
    таблицы через imperative mapping SQLAlchemy. Домен остаётся чистым
  • Переходы состояний перенесены в сами сущности. Cтроковые статусы заменены на
    StrEnum.
  • HTTPException больше не выходит за пределы HTTP-слоя: use case'ы бросают
    доменные ошибки, один обработчик превращает их в коды ответа. Раньше их бросал
    слой доступа к данным, который импортировал и Celery-воркер, - 404 внутри
    фоновой задачи не значит ничего.
  • Исправлено 14 багов, в том числе: удаление файла, по которому уже был алерт,
    падало на внешнем ключе; blob удалялся с диска до коммита транзакции; при
    неудачной вставке блоб оставался на диске навсегда; блокирующий I/O в event
    loop; необъявленный greenlet, без которого async-слой SQLAlchemy не работает;
    DSN вида postgresql+asyncpg://None:None@None:None/None при отсутствующей
    переменной окружения; неограниченный размер загрузки; неочищенные имена файлов;
    данные Postgres писались мимо volume; alembic.ini и миграции отсутствовали в
    образе. Полная таблица - в backend/README.md.
  • Оптимизации: загрузка и извлечение метаданных идут потоком с постоянным
    расходом памяти (счётчики строк и символов побайтово воспроизводят
    str.splitlines() при любой нарезке на чанки); цепочка из трёх Celery-задач
    свёрнута в один вызов на одной сессии; добавлены индексы под оба списочных
    запроса и под alerts.file_id; один event loop на процесс воркера;
    eager_defaults убирает лишний SELECT после каждой записи; отдача файлов
    через sendfile.
  • Инструменты: uv, ruff, ty и 99 тестов на pytest

Фронтенд

  • page.tsx разбит по Feature-Sliced Design: app / views / widgets /
    features / entities / shared, с одним HTTP-клиентом и общими примитивами
    таблиц.
  • Строгий TypeScript, зафиксированные версии зависимостей, origin API берётся из
    NEXT_PUBLIC_API_URL.
  • Починена сборка Docker: она копировала .env.production, которого нет в
    репозитории, и падала. Также исправлена иконка, ссылавшаяся на несуществующий
    путь.
  • Дашборд опрашивает бэкенд, пока файлы ещё обрабатываются.

Nikita Sysoev and others added 3 commits September 6, 2026 22:50
Backend
- Layered as domain / application / infrastructure / presentation with the
  dependency rule pointing inwards; src/container.py is the composition root.
  The rule is enforced by tests/unit/test_architecture.py, which walks the AST
  and fails if an inner layer imports an outer one.
- Entities are framework-free dataclasses persisted through SQLAlchemy's
  imperative mapping, so the domain stays pure without a duplicate ORM model.
- State transitions moved onto the entities; statuses became StrEnums.
- HTTPException no longer escapes the HTTP layer: use cases raise domain errors
  and one handler maps them to status codes.
- 14 bugs fixed, including: deleting an alerted file failed on the FK; delete
  unlinked the blob before committing; a failed insert orphaned the blob;
  blocking I/O on the event loop; missing greenlet; a DSN made of "None" when an
  env var was absent; unbounded uploads; unsanitised filenames; Postgres data
  written outside its volume; alembic assets missing from the image.
- Optimisations: uploads and metadata extraction are streamed in constant memory
  (the counters reproduce str.splitlines() exactly under arbitrary chunking);
  the three chained Celery tasks collapse into one invocation on one session;
  indexes added for both list queries and for alerts.file_id; one event loop per
  worker process; eager_defaults removes the post-write refresh; downloads use
  sendfile.
- Tooling: uv, ruff, ty, and 99 pytest tests that need neither Postgres nor Redis.

Frontend
- page.tsx split by Feature-Sliced Design: app / views / widgets / features /
  entities / shared, with a single HTTP client and shared table primitives.
- strict TypeScript, pinned dependencies, API origin from NEXT_PUBLIC_API_URL.
- Fixed a Docker build that copied a .env.production that is not in the repo,
  and a favicon pointing at an unserved path.
- The dashboard polls quietly while files are still processing.

API behaviour is unchanged: same routes, response fields, statuses and alert
messages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Затронуты src/, tests/, миграции, Dockerfile'ы, docker-compose и pyproject.
Исполняемый код не менялся: AST всех 57 модулей (без докстрингов) совпадает
с предыдущим коммитом.

Отключены правила ruff RUF001-RUF003: проверка на омоглифы рассчитана на
кодовые базы, где кириллица неожиданна, и на русских комментариях даёт только
ложные срабатывания.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant