Формируй структуру папок согласно делению на модули и слои

eac20fbe25e3c78c9668ee12e8782ae1

Это третий принцип. Весь список здесь.

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

Я исхожу из того, что код — это продукт, и первый пользователь — это коллега рядом. Он первым будет его смотреть, комментировать, рефакторить. Структура папок и кода — это дизайн продукта. Хороший дизайн соответствует принципу наименьшего удивления. Я делю код по смыслу согласно »решетке», поэтому организую структуру папок 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менты информации так же трудно понять, как неструктурированную «кашу» из идей (отсюда высокая внутренняя связность).

Поэтому, пожалуйста, держи детали внутри.

Мой канал

Habrahabr.ru прочитано 7029 раз