5.2. Как организовать файловую структуру проекта

В предыдущем параграфе изучили основы чистой архитектуры и принципы её построения. Также познакомились с принципами S.O.L.I.D. Теперь, когда вы понимаете теоретическую базу, возникает практический вопрос: как организовать структуру проекта, чтобы она отражала принципы чистой архитектуры и S.O.L.I.D?

Существует два основных подхода к организации файловой структуры: Layer First (сначала слои) и Feature First (сначала фичи). Мы разберёмся, что кроется за этими аббревиатурами, а также рассмотрим преимущества, ограничения и критерии выбора между ними.

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

Что такое фича

Этот термин будет часто встречаться в параграфе (а ещё чаще — в процессе разработки), поэтому сперва давайте разберёмся, что же он значит.

Итак, фича (англ. feature) — это сленговое обозначение функциональности проекта. Например, фичами являются отображение списка заказов, просмотр информации о заказе, формирование нового заказа и так далее.

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

flutter

Воспользуемся примером про записную книжку контактов из параграфа про чистую архитектуру.

Фича записной книжки — это совокупность взаимосвязанных классов UI-interface, ContractService, Contract`Database. Каждый класс отвечает за определённую зону ответственности, обусловленную разделением на слои.

Вот как это можно представить на схеме:

flutter

Теперь мы готовы ввести определение. Фича — это набор взаимосвязанных классов, разделённых согласно зонам ответственности.

Итак, с терминологией разобрались. Теперь поговорим, как организовать структуру проекта. Напомним, что подходов два:

  • Подход №1: распределить классы, относящиеся к одной из фич, по папкам в зависимости от того, к каким слоям они относятся (он называется Layer First).

  • Подход №2: сгруппировать классы, относящиеся к одной фиче, в отдельную папку (это Feature First).

Разберём эти подходы подробнее.

Подход №1: Layer First

Тут мы работаем «от слоёв»: проект разбивается на логические слои, каждый из них отвечает за предметную область. Классы фич ложатся в слои по предметной принадлежности.

Чтобы стало понятнее, давайте рассмотрим пример. Предположим, мы решили следовать классической архитектуре MVC(Model — View — Controller, «Модель — Представление —Контроллер»). Для этого необходимо разделить проект на три слоя:

  • Модель отвечает за бизнес-логику и взаимодействие с данными.

  • Представление — за отображение информации и взаимодействие с ней.

  • Контроллер обрабатывает действия пользователя и управляет взаимодействием между моделью и представлением.

Представим, что нам нужно добавить новую фичу в проект со структурой Layer First. Сперва мы разобьём её на классы по слоям MVC. Получившиеся классы необходимо распределить по директориям в проекте, где каждая директория представляет отдельный слой.

В итоге получается структура, где классы фич распределены по слоям, как на рисунке.

flutter

Layer First глазами нового разработчика

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

Особенности подхода

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

  • Легкость в тестировании. Тут справедлив паттерн «Разделяй и властвуй». Изолированные слои приложения значительно проще тестировать по отдельности, что повышает качество и скорость тестирования.

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

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

  • Сложность реализации. Создание сложной структуры слоёв требует времени и усилий. Необходимо тщательно продумать структуру, чтобы избежать проблем в будущем.

Кому подойдёт (и не подойдёт)

В основу Layer First ложится контракт (или набор контрактов) о том, как будет формироваться новая фича — то есть в каких каталогах будут лежать классы, отвечающие за слои фичи.

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

Для крупных приложений с распределёнными командами Layer First не подходит, так как с ростом проекта становится сложнее контролировать соблюдение контрактов. В каждой отдельной команде начинают формулироваться свои собственные контракты. В итоге объединение результатов команд в единое целое становится непростой задачей. Чем сложнее иерархия в команде, тем сложнее контролировать её действия.

Теперь давайте поговорим про Feature First.

Подход №2: Feature First

Каталоги верхнего уровня проекта называются в соответствии с фичами. Внутри каталогов происходит разделение фичи на слои. Подход разделения на слои зависит от выбранного архитектурного паттерна (MVC, MVP, MVVM и так далее).

Давайте разберём структурный подход аналогично Layer First — тоже на примере реализации MVC. Вводные данные идентичны. На верхнем уровне иерархии проекта будут фичи Feature1, Feature2, Feature 3 и так далее. Каждая фича состоит из набора каталогов Models, Views, Controllers. Каждый каталог содержит фича-классы, соответствующие разделению на слои.

flutter

Глазами нового разработчика

Открыв проект, новый разработчик видит, какие фичи есть в приложении. Так он быстрее поймёт, что делает код проекта. Однако он не сможет быстро разобраться, как фичи связаны друг с другом и в чём суть приложения для пользователя.

Особенности подхода

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

  • Изолированность. Feature First позволяет быстро разрабатывать отдельные фичи. Это достигается за счёт разделения ответственности на мелкие команды. Команды могут изменять правила под свои нужды без опасения, что эти изменения как-то повлияют на соседей, так как контракты не распространяются на соседние фичи. Это и плюс, и минус одновременно: с одной стороны, разработчики в рамках одной фичи могут делать что угодно, с другой стороны, это нарушает единую концепцию построения фич проекта.

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

Кому подойдёт (и не подойдёт)

В основу Feature First ложится контракт о том, как формируются фичи. Разница между подходами заключается в том, что контракт будет действовать внутри фичи, следовательно, контролировать соблюдение контракта необходимо также внутри фичи.

Feature First обеспечивает оптимальную структуру для разработки проекта распределёнными командами. Такой подход специфичен для крупных проектов, где за каждой фичей закреплена отдельная команда. Контракты могут корректироваться под нужды команды, и это никак не повлияет на соседние фичи. Но на практике фичи могут иметь разную внутреннюю организацию. Это не проблема, пока не возникают конфликтующие зависимости или фичи не навязывают свою структуру другим.

Однако полная автономность фич — это риск. Более эффективно подчинять их компонентам/модулям, которые учитывают интересы не только разработчиков, но и бизнеса в целом. Компоненты устанавливают границы, предотвращают конфликты зависимостей и обеспечивают соответствие фич общим целям проекта, без чрезмерного замедления разработки.

Послесловие

Помимо Layer First и Feature First, существует более высокий уровень архитектурных решений — уровень компонентов. На этом уровне принимаются решения, выходящие за рамки обычной разработки, например переход на монорепозиторий, выбор платформ интеграции, организация CI/CD и т. д. Эти решения напрямую не влияют на структуру классов, контроллеров или стейт-менеджеров, но определяют общие правила игры.

На всех уровнях приходится искать компромисс: неоптимальное сегодня решение может стать идеальным завтра. Гибкость и скорость внедрения часто не менее важны, чем чёткая архитектура.

Реально комбинировать разные подходы: Layer First для стабильных компонентов, Feature First для более динамичных требований, — при этом подчиняя всё общим правилам на уровне компонентов.

***

Давайте коротко подведём итоги этого параграфа.

  • Мы продолжили погружение в особенности построения архитектуры программного обеспечения (англ. software architecture).

  • Изучили и детально разобрали подходы к построению файловой структуры приложения (Layer First и Feature First).

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

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

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

Отмечайте параграфы как прочитанные, чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф5.1. Clean architecture
Следующий параграф5.3. Inversion of control