Секрет устойчивых платформ для умного города: SOLID, GRASP и Clean Architecture в деле. Блог компании Фалькон Тех.. Блог компании Фалькон Тех. видеоаналитика.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы. искусственный интеллект.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы. искусственный интеллект. искуственный интеллект.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы. искусственный интеллект. искуственный интеллект. машинное зрение.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы. искусственный интеллект. искуственный интеллект. машинное зрение. умный город.. Блог компании Фалькон Тех. видеоаналитика. Инженерные системы. искусственный интеллект. искуственный интеллект. машинное зрение. умный город. Урбанизм.

Запускаем большой проект? Значит, берём SOLID. Мы не раз слышали это от CPO и менеджеров продукта. О нём, кажется, знают все — и все хотят применить. Но он не спасёт масштабные проекты. Например, устойчивый умный город с решениями на основе машинного зрения на одном SOLID не построить. По канонам SOLID можно писать аккуратный код, но это не создаст архитектуру всей системы. Что на самом деле работает — подобранный под бизнес-цели набор инструментов, направленный на защиту ядра приложения.

В статье — кейс Сергея Францишкова, начальника отдела разработки «Фалькон Тех», о рабочих решениях для крупной архитектуры и внедрении GRASP + Clean Architecture.

Мы — команда инженеров «Фалькон Тех», и уже 8 лет мы разрабатываем ПО с использованием ИИ и машинного зрения. За это время создали систему из более чем 3 000 программно-аппаратных комплексов в Москве. В прошлой статье рассказали, на каком стеке и железе работает видеоаналитика в мегаполисах.

Сегодня поделимся кейсом перехода с «голого» SOLID на рабочую систему SOLID + GRASP + Clean Architecture. Благодаря такому принципу проектирования бизнес-логика, работа видеоаналитики и в целом наша система комплексов не рухнут, даже если завтра город решится на кардинальные инфраструктурные изменения.

Из статьи вы узнаете:

  • зачем нужна архитектура и что работает помимо SOLID;

  • как GRASP и Clean Architecture помогают на крупных проектах;

  • как организовать кодовую базу так, чтобы она выдерживала смену технологий.

Зачем вообще архитектура

Цифровые системы должны помогать бизнесу повышать операционную эффективность, сокращать издержки и снижать риски. Для этого они должны состоять из связанных компонентов, которые вместе приносят результат. Некоторые реализуют God Object и всё связывают со всем. Этого нужно избегать — иначе одно исправление кода рискует навредить системе. А если она масштабна — например, охватывает целый город, — очень важно сделать её устойчивой к изменениям. При этом необходимо не допустить и другой крайности, чтобы она не развалилась на независимые кусочки. Следовательно, для системы нужна единая архитектура.

Есть две цели создания архитектуры:

  • повысить внутреннее качество кода; повысить прозрачность и ясность бизнес доменов. Все это упростит сопровождение и расширение системы.

  • изолировать бизнес-логику от инфраструктуры, чтобы безболезненно менять фреймворки, базы данных, плагины и т. п.

Архитектура помогает удерживать систему «в форме». Ниже рассказываем, что для этого делать. Но сначала дисклеймер: упомянутые в статье инструменты направлены на защиту ядра — главенствующей функциональности.

Секрет устойчивых платформ для умного города: SOLID, GRASP и Clean Architecture в деле - 1

Почему одного SOLID мало

SOLID — это пять принципов ООП:

  • S (Single Responsibility) — у сущности или её поведения запросы на изменения должны исходить от одного источника ответственности

  • O (Open-Closed) — система должна быть открыта для расширения и закрыта для изменения.

  • L (Liskov Substitution, принцип Барбары Лисков) — наследуемые сущности должны использоваться наравне с родительскими, без нарушения родительского контракта. Если есть объект A и объект B (наследник объекта A), в случае замены A на B в коде, система продолжит работать корректно с предсказуемым поведением, вне зависимости от знания, что используются сущности от наследников.

  • I (Interface Segregation) — большие интерфейсы хуже маленьких. Классу проще реализовать несколько, более специфичных ему, интерфейсов, чем тянуть за собой один громоздкий интерфейс с кучей ненужных методов.

  • D (Dependency Inversion) — зависимости должны быть направлены к ядру системы и не должны тянуть за собой детали реализации. Детали реализации должны исходить от клиентского взаимодействие, если ему требуется работать с инфраструктурными элементами (HTTP-подключение, клиент, CLI-взаимодействия), то реализация взаимодействия определяется на внешнем уровне. Это делает зависимости и интерфейсы гибче и устойчивее.

SOLID — не гарантия чистой системы

Принципы SOLID — это рекомендации по созданию поддерживаемых сущностей низкого уровня, например классов, объектов, модулей. В то время как Clean Architecture — это архитектурный шаблон более высокого уровня, который организует все приложение по слоям для обеспечения разделения задач, логики, абстракций. Но в реальных больших продуктах остаются структурные «дыры»:

  • Сильная связанность — SOLID не регламентирует, как модули и слои системы будут общаться. Иногда возникает скрытая тесная связь между подсистемами.

  • Дублирование логики — принципы S и I улучшают объект, но не решают проблему дублирования бизнес-правил. Становится сложно вносить изменения за счёт связанности отображения с поведением.

  • Инфраструктура ломает ядро — даже при соблюдении D инфраструктурные особенности «протекают» в домен через адаптеры и DTO. Ядро оказывается чувствительным к изменениям.

GRASP как следующий уровень

SOLID — набор концепций о том, как должен быть устроен класс, структура, модуль, а GRASP (General Responsibility Assignment Software Patterns) — набор паттернов, который описывает взаимодействие между классами и структурами.

Основные паттерны GRASP:

  • Information Expert — Если объект обладает всей или большей частью информации, необходимой для выполнения операции, то именно он ее и должен производить.

    Если нужно сделать расчёт финальной стоимости заказа, имеет смысл поместить этот расчёт в объект заказа, а не выносить это в отдельную структуру, которой придется запрашивать данные.

  • Controller — отвечает за обработку внешних событий и координацию поведения.

    Компонент, на границе домена/системы, который связывает основную логику с внешним миром. К примеру это может быть отдельный веб-сервис с http/grpc эндпоинтами, CLI-утилита, rabbit/nats consumer и пр.

  • Creator — объект создает другой объект, если он им владеет или его использует.

    Креатор — это не фабричный метод. Это правило, что мы должны давать возможность классу или структуре порождать другие объекты. А вот уже способом реализации будет фабричный метод или порождающий паттерн.

  • Низкая связанность (low coupling) — компоненты минимально зависят друг от друга.

    Когда компонент использует логику или данные другого компонента, это создает связность между ними. Ее нужно стараться уменьшить. Понятно, что компоненты находятся не в вакууме и избежать взаимодействия вряд ли получится. Но важно, чтобы один компонент не начинал пользоваться внутренними деталями другого как своими.

  • Высокая связанность (high cohesion) — внутри компонента всё максимально логически связано, а лишние методы/поля выносятся наружу или выделяются в другой компонент, действуя по такому же принципу логического объединения и сфокусированности.

    Высокая связанность относится непосредственно к компоненту. Чем активнее логика и используемые ею данные внутри одного компонента взаимодействуют друг с другом, тем выше его связанность — и это хорошо. Компонент должен быть цельным. А если внутри компонента есть логика, которая плохо фокусирует выполняемые задачи компонента, ее стоит выделить в отдельный компонент вместе с зависимыми данными.

Если резюмировать, GRASP — системное мышление в проектировании: он защищает от ситуации, когда бизнес-логика «размазана» по интерфейсам и вспомогательным компонентам. Мы ищем и обучаем специалистов, которые умеют с ним работать, чтобы делать качественные продукты. Читайте подробнее на Хабр Карьере.

Чистая архитектура (Clean Architecture) в реальном проекте

Максимально кратко, SOLID говорит, как проектировать компоненты, GRASP объясняет, как распределять обязанности между ними, а Clean Architecture — делает систему независимой от инфраструктуры.

«Термин Clean Architecture ввёл Роберт Мартин. Он считал, что бизнес-логика должна быть независимой от деталей реализации, а инфраструктуре необходимо подключаться как «плагины». Не ядро зависит от базы данных, а наоборот — технологии подключаются к приложению как сменные модули.»

– Цитата эксперта

Изображение: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

Четыре слоя Clean Architecture

Классическая схема чистой архитектуры включает четыре слоя. На практике не часто проводят грань между слоем фреймворка и инфраструктуры. Делают это для упрощения и ввиду того, что они часто разрабатываются вместе. Поэтому дальше они будут фигурировать как один слой.

Секрет устойчивых платформ для умного города: SOLID, GRASP и Clean Architecture в деле - 3

Получается так:

  • Домен — бизнес-сущности и правила;

  • Приложение — что система должна делать;

  • Инфраструктура и фреймворки — базы данных, REST/gRPC, API.

Все зависимости направлены внутрь, в сторону бизнес-логики. Так система становится модульной и устойчивой к изменениям технологий. Также часто выделяют отдельный слой под различные утилитарные задачи. Это модуль, который содержит вспомогательные функции, данные и прочее, их часто переносят из проекта в проект. Этот слой не участвует в бизнес-логике и может использоваться в любом другом слое без нарушения зависимости.

Секрет устойчивых платформ для умного города: SOLID, GRASP и Clean Architecture в деле - 4

Как наложить Clean Architecture на реальный код
(на примере сервиса Offense Collector в умном городе)

Как Clean Architecture выглядит в репозитории и почему это удобно? Возьмём реальный кейс из систем умного города. Сервис Offense Collector собирает доказательную базу для возможности оформления нарушений из разных источников.

Создание архитектуры

Мы использовали «кричащую» архитектуру — когда имена модулей говорят, что за что отвечает. Это разгружает мозг разработчика.

Структура проекта по папкам

Структура проекта по папкам

Папка с infrastructure содержит модули setup, worker и реализации клиентов. В папке application лежат репозиторий и модуль use_cases с пользовательскими сценариями. И последний слой domain, который содержит бизнес-сущность правонарушения и её логику.

Структура проекта по папкам

Структура проекта по папкам
Структура проекта по папкам

Структура проекта по папкам
Структура проекта по папкам

Структура проекта по папкам

Папка с репозиториями — это набор интерфейсов по паттерну репозиторий, определяющий обмен данными, как пример с СУБД. На слое приложения их реализации нет — она лежит на уровне инфраструктуры.

Секрет устойчивых платформ для умного города: SOLID, GRASP и Clean Architecture в деле - 9

Дальше идёт реализация конкретного сценария системы (use cases).

Внедрение use cases

Один из ключевых элементов слоя приложения — это структура use cases. Рассмотрим конкретный случай — обработку, фиксацию и отправку сформированного сообщения с необходимыми данными для доказательной базы (далее – парсель). Если посмотреть на код, то внутри метода происходит следующее:

Пример проекта use case

Пример проекта use case
  1. Берём данные из репозиториев;

  2. Преобразуем их в доменные сущности;

  3. Передаём дальше.

Всё. Сценарий — инструкция, а реализации каждого шага — отдельные функции. Антикоррупционный слой здесь нужен только потому, что данные снаружи не совпадают с тем, что ожидает домен. Если убрать эту прослойку, метод сводится к линейной инструкции.

На уровне приложения зависимости есть только от своего слоя и доменного слоя.
Домен — автономен, никак не завязан на инфраструктуру, библиотеки и фреймворки.

Создание точка входа

main — место, где собираются плагины. Главный компонент производит инициализацию клиентов Rabbit/HTTP, баз, инстансов репозиториев — всего, что нужно юзкейсу, и сам юзкейс. Дальше происходит запуск.

Пример входной точки main

Пример входной точки main

«Такая чистая архитектура очень выгодна, если в будущем придётся поменять технологии. Например, сегодня use case получает события через RabbitMQ. Завтра нужно перейти на HTTP-сервер. Мы изменим только адаптер, который передаёт событие в сценарий. Весь остальной код трогать не будем.»

— Андрей Борисов, Старший back-end разработчик в «Фалькон Тех»

Что это дает бизнесу

Обобщим плюсы подхода:

  • Понятная структура проекта (кричащая архитектура);

  • Устойчивость бизнес-логики и ядро, не завязанное на фреймворках;

  • Возможность тестирования без инфраструктуры, простота для QA;

  • Защита от «грязного кома»: разделение слоёв, зоны ответственности, контролируемые зависимости.

И, конечно, чистый код, в котором каждый слой независим и изменения в одном слое не ломают другие.

Замена технологий (например, RabbitMQ → HTTP) проводится без исправления бизнес-логики. Мы получаем эволюцию проекта вместо переписывания его частей. Минусы тоже есть: высокий порог входа, множество файлов и невысокая скорость разработки — сочетание фреймворков не подходит для коротких проектов. Но если речь о крупных системах с использованием новых технологий, когда нужны ИИ и машинное зрение, этот подход работает.

Плюсы

Минусы

Чёткая структура приложения.
Даже новичку понятно, где что искать.

Порог входа выше. Нужно разбираться
в слоях, зависимостях, интерфейсах.

Слабая связанность компонентов.
Можно менять инфраструктуру,
не затрагивая бизнес-логику.

Сложно применять на маленьких проектах или MVP, где ценится скорость.

Упрощённое тестирование.
Нет необходимости в тяжёлых моках
или окружениях.

Много файлов, интерфейсов, структур — можно запутаться при плохом нейминге.

Возможность делегирования.
Каждый слой может разрабатываться независимо.

В одиночку SOLID не спасет

Резюмируем: SOLID нужен, но сам по себе большие проекты он не спасает. Однако, если добавить к нему GRASP + Clean Architecture, это комбо даст устойчивость и гибкость.

Практические рекомендации:

  • Не отказываться от SOLID, потому что он помогает делать аккуратные компоненты.

  • Понять, как реализовано взаимодействие между компонентами и интерфейсами.

  • Применить GRASP — определить, что за что отвечает.

  • Использовать Clean Architecture и делать систему независимой.

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

Желаем удачи на ваших проектах,
Команда инженеров «Фалькон Тех»

Нравятся строгие правила кодинга, системность и чистая архитектура?
Заходи на Хабр Карьеру — там делимся нашими условиями, требованиями к кандидатам, вакансиями с указанной зарплатой.

Автор: FalconTech

Источник