Перейти к содержанию

Решения, компромиссы и риски

Ключевые решения

Разделение frontend и backend

Система использует отдельный Angular frontend и Spring Boot backend API. Это снижает связность, позволяет независимо развивать UI и backend-логику, а также создает основу для будущих клиентских каналов.

Root-unit soft multi-tenancy

Каждый tenant представлен корневым organizational unit. Tenant-scoped entities содержат root_unit_id, а доступ ограничивается через repositories, specifications, services и API-level checks.

Context-aware authorization

Права доступа зависят не только от глобальной роли пользователя, но и от текущей организации и выбранного организационного контекста.

Это соответствует B2B-сценариям, где один и тот же пользователь может иметь разные права в разных организациях или подразделениях.

PostGIS как часть доменной модели

Местоположение растений, участки сада, оранжереи, грядки и полигоны моделируются как spatial data, а не как вторичный map overlay.

Гибридная модель текущего состояния и истории

Система разделяет текущее операционное состояние и данные, связанные с историей изменений и audit trail. Это позволяет эффективно работать с текущими записями и сохранять трассируемость важных изменений.

Контролируемое публичное раскрытие данных

Публичные страницы, карточки растений, списки, фотографии и данные карты публикуются через отдельные public endpoints и DTO.

Visibility rules предотвращают случайное раскрытие внутренних данных тенанта.

Архитектурные компромиссы

1. Мягкая мультитенантность вместо полноценной

Контекст

Система проектируется для множества организаций (как мультитентная). На ранней стадии продукту важны низкая операционная сложность, высокая скорость разработки и достижения функциональных требований (таксономический слой и возможность развивать сетевые сценарии между организациями).

Принятое решение

Для изоляции данных используется логическая multi-tenancy модель через атрибут root_unit_id. Корневое подразделение организации является границей организации, все сущности внутри дерева получают ссылку на этот root unit.

Отклонённая альтернатива

database-per-tenant или schema-per-tenant.

Обоснование

  • снижение инфраструктурной сложности на ранней стадии
  • допускает поглощение организаций или выделение отделов в организации с минимальной миграцией сущностей

Компромиссы

  • Изоляция данных логическая, а не физическая, поэтому любая ошибка фильтрации тенанта в любом API может привести к нарушению изолированности тенантов.
  • Сложнее выполнять резервное копирование данных отдельной организации, их восстановление, экспорт и физическое удаление (право на забвение).
  • Сложнее выделить крупного клиента на отдельную инфраструктуру.
  • Шардинг и региональное разделение данных потребуют дополнительного проектирования.

Компенсирующие меры

  • Все сущности, принадлежащие определенной организации, несут root_unit_id.
  • Доступ ограничивается на нескольких уровнях: запросы репозиториев, спецификации JPA, проверки уровня бизнес-логики, авторизация на уровне методов контроллеров.
  • Центральная авторизация вынесена в AccessControlService.
  • Публичные API возвращают только public DTO и не раскрывают внутренние поля.
  • Для критичных сценариев написаны интеграционные тесты запрета доступа к чужому тенанту.
  • В целях соответствия 152-ФЗ выделена отдельная зона доступности для пользователей РФ (всего пока два). Запланировано проектирование механизма репликации публичных данных между зонами доступности с учетом возможного отключения РФ-сегмента сети от глобального интернета.

Триггер пересмотра

  • появление enterprise-клиентов с требованием физической изоляции
  • рост объёмов данных до уровня noisy-neighbor проблем
  • необходимость регионального хранения данных или юридическом требовании отделять данные организаций физически.

2. Модульный монолит вместо микросервисов

Контекст

Продукт с широкой доменной моделью: растения, таксономия, культивары, списки, места, импорт, публичные страницы, пользователи, роли, медиа и обмен между организациями. Команда маленькая, а доменная модель ещё активно уточняется. Кроме того не ясно, насколько будет разниться нагрузка на отдельные сущности.

Принятое решение

Backend реализован как модульный монолит на Spring Boot: единый артефакт, но с разделением по доменным зонам через контроллеры, сервисы, репозитории, DTO.

Отклонённая альтернатива

Набор микросервисов: сервис таксономии, сервис коллекций (+ импорт), сервис медиа, сервис авторизации и идентификации, сервис ГИС, сервис публичных ресурсов.

Почему это разумно сейчас

Монолит снижает оверхед от распределенных систем: нет сетевых контрактов между сервисами, нет распределенных транзакций, необходимости строить инфраструктуру поиска сервисов, нет сложной обозреваемости и оркестрации межсервисных отказов. Это ускоряет доставку и позволяет держать доменную модель целостной, пока продукт ещё не вышел за границы первых пилотов.

Компромиссы

  • Нельзя независимо масштабировать отдельные доменные модули.
  • Ошибка в одном модуле может повлиять на весь backend.
  • Со временем появляется риск неявных зависимостей между доменными областями.
  • Импорт, медиа и GIS могут иметь разные профили нагрузки, но пока живут в одном приложении.

Компенсирующие меры

  • Жёсткое пакетное разделение по доменным областям. Сервисный слой как граница для бизнес-логики, DTO как граница API. Тем самым снижается стоимость выделения ограниченного контекста в сосбтвенный сервис
  • Асинхронное выполнение тяжёлых импортов, чтобы не держать один поток и не хватать таймауты.

Триггер пересмотра

Выделение сервисов имеет смысл, когда конкретный модуль получает независимый масштаб нагрузки, отдельную команду владения, отдельный график релизов или отдельные требования к отказоустойчивости. Первые кандидаты на выделение: import pipeline, media processing/storage gateway, public map/search read model.


3. Монорепозиторий для backend и frontend вместо отдельных репозиторий

Контекст

Проект разрабатывается небольшой командой, где один разработчик отвечает за архитектуру, backend, frontend, deployment и интеграцию. Для таких условий важнее скорость согласованных изменений, чем организационная независимость команд.

Принятое решение

Backend и frontend хранятся в одном репозитории.

Отклонённая альтернатива

Отдельные репозитории для backend, frontend, инфраструктура и документация.

Почему это разумно сейчас

Монорепо позволяет делать атомарные изменения API и UI, проще держать архитектурный контекст целиком, быстрее проводить full-stack рефакторинг и LLM-генерацию кода. Результат более стабильный потому что модель видит связанную картину продукта.

Компромиссы

  • Границы ответственности могут размываться при росте команды.
  • Сложнее ограничивать доступ к отдельным частям кодовой базы.
  • Выше риск широких изменений без понимания радиуса влияния.

Компенсирующие меры

  • Раздельные frontend/backend папки и независимые команды сборки и запуска.
  • Интеграционные тесты взаимодействия frontend и backend.

Триггер пересмотра

Раздельные репо станут оправданными, если появятся независимые команды с разным релизным циклом, разные политики доступа к коду, необходимость публиковать части системы независимо.


4. Docker Compose вместо cloud native.

Контекст

На ранней стадии нужно быстро разворачивать систему на VPS, демонстрировать продукт, проводить пилоты и держать инфраструктурные расходы низкими.

Принятое решение

Backend, frontend, PostgreSQL/PostGIS и MinIO запускаются в одном Docker Compose контуре.

Отклонённая альтернатива

Полноценная cloud-native инфраструктура с отдельными мощностями под БД, объектное хранилище, с автоскейлингом контейнеров, мониторинг и развертку с множественными зонами доступности.

Почему это разумно сейчас

Docker Compose даёт быстрый быстрый холодный старт, воспроизводимое окружение, низкую стоимость и простую операционную модель. Для MVP, демо-стенда и раннего пилота это лучше, чем преждевременная сложность cloud native.

Компромиссы

  • Один VPS является единой точкой отказа.
  • БД, объектное храниище и сервисы конкурируют за ресурсы.
  • Масштабирование в основном вертикальное.
  • Нет полноценной high availability модели, нет учёта пиков нагрузки. Нельзя считать продуктовой архитектурой.
  • Резервное копирование, восстановление и мониторинг становятся критичными операционными задачами.

Компенсирующие меры

  • Сервисы остаются stateless.
  • Конфигурация должна быть env-based.
  • Данные БД и объектного хранилища живут в персистивных томах с регулярными резервным копированием.
  • Reverse proxy / TLS / rate limits выносятся в инфраструктурный слой.
  • Целевой путь миграции должен быть заранее описан: отдельные инстансы PostgreSQL/PostGIS, S3-совместимые хранилища, горизонтальное масштабирование контейнеров сервисов.

Триггер пересмотра

Переход нужен при появлении платящих клиентов, SLA-ожиданий, роста объёма фото/импортов, требований к высокой доступности, регулярных простоев VPS или необходимости регионального размещения данных.

... для портфолио опубликована только часть компромиссов.

См. также Architecture Decision Records.