Это третий принцип. Весь список здесь.
Периодически слышу и вижу в комментариях, что структура папок не должна отображать архитектуру и наоборот. У меня другое мнение. Повторю: это мой опыт, вам он может не подойти. Проверьте, протестируйте.
Я исхожу из того, что код — это продукт, и первый пользователь — это коллега рядом. Он первым будет его смотреть, комментировать, рефакторить. Структура папок и кода — это дизайн продукта. Хороший дизайн соответствует принципу наименьшего удивления. Я делю код по смыслу согласно «решётке», поэтому организую структуру папок 2-я способами в зависимости от проекта:
Layer Folder Structure (LFS)
Scope Folder Structure (SFS)
Layer Folder Structure (LFS), когда папки верхнего уровня — это слои, а внутренние папки — это области или произвольная организация. Такая структура удобна в 2-х случаях:
для микросервисов на бэке. Микросервис покрывает только одну область. Дополнительное деление на более мелкие области будет лишним. Слои полезно обозначить.
для монолита или PWA. В этом случае:
границы UI компонент в слое контроллеров не совпадают границами модулей. Т. е. UI компонент не часть модуля.
UI компонент может использовать много разных модулей из слоя приложения и инфраструктуры одновременно.
компоненты внутри одного слоя подвержены изменчивости в процессе разработки, в остальных слоях стабильны.
В этом случае в слое контроллеров папки должны иметь какую-то структуру, но она может отличаться от модулей. В слоях приложения и инфраструктуры лучше соблюдать модульность. Я использую такой подход для PWA.
Scope Folder Structure (SFS), когда папки верхнего уровня — это области, внутренние папки — это слои. Удобно для сервисов на бэке. Здесь модуль определяет публичные интерфейсы взаимодействия, поэтому
контроллеры — часть модуля
границы модуля чёткие и понятные во всех слоях
модуль отвечает принципу единственной ответственности
код внутри модуля связан по смыслу.
Я использую эту структуру для бэковых сервисов. Сервис — покрывает поддомен, модуль — какой-то из интерфейсов API.
Конфигурация - отдельная папка на самом верхнем уровне, если другое не предусмотрено фреймворком или DI-контейнером. Здесь тоже лучше соблюдать модульность на уровне файлов: один файл — один модуль. Здесь ты создаёшь экземпляры контроллеров, сервисы разных слоёв, клиенты к хранилищам и очередям. При создании внедряешь одно в другое, передаёшь настройки из переменных окружения в параметры конструкторов и т. п.
Рано или поздно появится общий код между несколькими модулями внутри слоя. Он распространяется горизонтально в границах слоя (см. «решётку»). Универсальный способ организации — создать папку pkg или packages на самом верхнем уровне. Туда кладём общий код как модули, каждый в свою папку. Такой способ практикуется в go и js проектах. Для LFS можно поступить немного проще: сделать папку внутри слоя для общего модуля.
Это выглядит примерно так:
Layer Folder Structure (LFS)
src/
├── ui/ # слой контроллеров
│ ├── components/
| ├── layouts/
│ ├── route/
│ ├── pages/
│ └── filters/
├── app/ # слой приложения
│ ├── user
| ├── search
| ├── cart
| ├── category
│ └── ...
├── infra/ # слой инфраструктуры
│ ├── user
| ├── search
| ├── cart
| ├── category
| ├── http-client # общий http клиент, как модуль внутри слоя
│ └── ...
├── dc/ # конфигурация как контейнер зависимостей
│ ├── user.js
| ├── search.js
| ├── category.js
│ └── ...
└── pkg/ # общие пакеты
├── model/ # библиотека для создания всех моделей внутри слоя приложения
└── ...
Scope Folder Structure (SFS)
src/
├── modules/
| ├── search/
| │ ├── controllers/
| | ├── app/
| │ └── infra/
| ├── category/
| │ ├── controllers/
| | ├── app/
| │ └── infra/
| └── ...
├── dc/
│ ├── search.js
| ├── category.js
│ └── ...
└── pkg/
├── model/ # библиотека для создания всех моделей внутри слоя приложения
├── http-client # общий http клиент
└── ...
Многие не согласятся с такой структурой, я отвечу так: представь, ты новичок в проекте. Ты что-то слышал о слоённой архитектуре, примерно понимаешь, что такое модули. Ты ещё не открывал проект, не знаешь структуру папок.
К тебе пришёл пользователь/менеджер с проблемой: у меня не работает поиск, листинг товаров и т. п.
Твой вопрос — а это где? Быстрый ответ — страница поиска, где-то в Каталоге. Ты идёшь в гитлаб и ищет что-то похожее на Каталог. Допустим, это бэк сервис. Нашёл. Дальше ищешь что-то похожее на Поиск или Листинг. Если это go, скорее всего, будешь искать в internal. Заходишь внутрь. Видишь понятные модули: search, listing... Проблема с поиском — понятно куда идти, других вариантов нет. Идёшь. Видишь слои. Заходишь в контроллеры. Здесь всё понятно — это консольная команда, воркер или grpc. Как это в реальности запускается, ты разберёшься потом, когда поймёшь, что есть Di, конфиг и т. д. Но сейчас ты уже локализовал область в общей кодовой базе, ты знаешь, где искать проблему.
Бизнес/пользователь в 100% случаях формулирует задачу относительно конкретной функции в определённой области. В этой структуре новый разработчик, вероятнее всего, будет следовать именно такому сценарию, потому что эта структура соответствует принципу наименьшего удивления.
Пожалуйста, держи детали внутри модуля или слоя. Не нужно на верхнем уровне создавать папки port, adapter, persistent, repository, tools, utils... Здесь они слишком абстрактны, поэтому бессмысленны. Каждый понимает их по-своему. Может быть, даже так, что ты сам не понимаешь смысла, который вкладываешь.
Faceted-seach — нельзя понять по-своему, это модуль фасетного поиска. Всё, что внутри — часть реализации задачи фасетного поиска. Репозитории, сервисы, правила, события, модели, dto, vo — всё это есть. Есть ещё много всего, но всегда в контексте модуля или слоя.
Когда ты выносишь детали наверх, то сразу нагружаешь деталями коллегу-разработчика, увеличиваешь его когнитивную нагрузку. Ты разбиваешь внутреннее содержание модуля или слоя по папкам верхних уровней, тем самым уничтожаешь видимые границы модулей и слоёв. Ты разрушаешь «решётку», семантическую прочность модуля.
В книге «Предметно-ориентированное проектирование. Структуризация сложных систем» Эрик Эванс написал:
То, что при делении на модули должна соблюдаться низкая внешняя зависимость (low coupling) при высокой внутренней связности (high cohesion) — это общие слова. Определения зависимости и связности rрешат уклоном в чисто технические, количественные критерии, по которым их якобы можно измерить, подсчитав количество ассоциаций и взаимодействий. Но это не просто механические характеристики подразделения кода на модули, а идейные концепции. Человек не может одновременно удерживать в уме слишком много предметов (отсюда низкая внешняя зависимость). А плохо связанные между собой фраrменты информации так же трудно понять, как неструктурированную «кашу» из идей (отсюда высокая внутренняя связность).
Поэтому, пожалуйста, держи детали внутри.