
В предыдущей статье «Как организовать структуру приложения» обсуждались различные подходы к организации кода, включая монолитную архитектуру, многослойную архитектуру и принципы чистой архитектуры. В ней я акцентировал внимание на важности понятной структуры кода, которая облегчает понимание, внедрение новых функций и снижает вероятность ошибок. Особое внимание уделялось критериям понятности, таким как чёткое назначение файлов и папок, разделение логики по модулям или функциональным зонам, использование самодокументируемых названий и соблюдение стандартов кодирования. Также были рассмотрены проблемы, возникающие при неструктурированной организации кода, такие как сложность навигации, отсутствие модульности и нарушение принципов SOLID и GRASP.
В этой статье мы продолжим эту тему, сосредоточившись на сравнении двух подходов к организации кода: Package by Feature и Package by Layer. Мы подробно рассмотрим, как каждый из этих методов влияет на структуру проекта, поддерживаемость и масштабируемость кода.
🏗 Примеры из реальной жизни
Пример №1. Строительство города
Представь, что ты строишь город. В этом городе есть дома, школы, магазины, парки.Ты можешь организовать город по-разному:
Package by Layer – это когда все одинаковые здания стоят в одном месте: Все магазины в одном районе, Все дома в другом, Все школы в третьем. Хотели бы жить в таком городе?
Package by Feature – это когда у тебя есть районы, где всё нужное находится рядом: В одном районе есть и дома, и магазины, и школы – всё для жителей этого района.
Пример №2. Организация одежды в гардеробе
Представь, что ты складываешь одежду в шкафу. Ты можешь разложить ее по-разному:
Package by Layer – все футболки на одной полке, все брюки на другой, а все носки в отдельном ящике.
Package by Feature — А теперь представь, что ты складываешь вещи для разных занятий. Все вещи для плавания — в одном месте (купальник, полотенце, очки), все вещи для школы — в другом (учебники, тетради, ручки).
Пример №3. Организация кухни ресторана
Представь, что у нас есть ресторан и несколько поваров и нам пришел заказ приготовить пиццу, салат и десерт. У тебя есть много ингредиентов и инструментов: мука, сыр, помидоры, огурцы, яйца, миксер, ножи, доски для нарезки и так далее. Тебе нужно организовать всё так, чтобы готовить было удобно.
Package by Layer — Этот способ похож на то, как ты можешь разложить всё по типам, независимо от того, для какого блюда это нужно. Например:
-
Коробка «Все овощи»
-
Коробка «Все инструменты»
-
Коробка «Специи»
Если ты готовишь пиццу, тебе нужно взять коробку с овощами, коробку с инструментами, коробку с сыпучими продуктами и коробку с посудой. Это занимает много времени и сил. Ты можешь запутаться, потому что всё разбросано по разным коробкам.
Package by Feature — ты можешь разложить всё, что нужно для каждого блюда, в отдельные коробки или зоны на кухне. Например:
-
Коробка «Пицца»
-
Коробка Салаты
-
Коробка Дисерты
Ты не путаешься, потому что всё для каждого блюда лежит в одном месте. Повара не мешаю друг другу.
Пример №4. Собираем школьный рюкзак
Представь что нам нужно собрать школьный рюкзак.
Package by Layer — это как сложить все учебники вместе, все тетради вместе, все ручки вместе. Удобно, когда нужно найти все учебники, но неудобно собираться по предметам.
Package by Feature — это как сложить отдельно все для математики (учебник, тетрадь, линейка), отдельно все для рисования (альбом, краски, кисточки). Удобно собираться на конкретный урок, но сложнее найти все учебники сразу.
🏛 Как парадигмы программирования влияют на организацию кода?
Прежде чем углубляться в организацию кода, важно понимать, что разные парадигмы программирования могут влиять на выбор между Package by Layer и Package by Feature.
🔗 Обектно ориентированое програмирования (ООП) и организация кода
ООП — это парадигма, в которой программа строится вокруг объектов, которые представляют собой экземпляры классов. Основные принципы ООП включают:
-
Инкапсуляция: Сокрытие внутреннего состояния объекта и предоставление доступа к нему только через методы.
-
Наследование: Возможность создания новых классов на основе существующих, что позволяет повторно использовать код.
-
Полиморфизм: Возможность объектов разных классов обрабатываться как объекты одного класса.
-
Абстракция: Упрощение сложных систем путем моделирования классов, которые отражают только существенные характеристики.
Package by Feature
-
Package by Feature естественным образом поддерживает принципы ООП, такие как инкапсуляция и сокрытие реализации. Классы, связанные с одной функциональностью, группируются вместе, что упрощает понимание и поддержку кода.
-
Позволяет инкапсулировать всю логику, связанную с конкретной функциональностью, внутри одного пакета, что соответствует принципам инкапсуляции и абстракции.
-
Такой подход способствует созданию высокосвязных модулей с низкой связанностью между ними, что соответствует принципам SOLID.
-
Легче соблюдать принцип единственной ответственности (SRP), так как каждый пакет отвечает за конкретную функциональность.
-
Лучше сочетается с DDD (Domain-Driven Design), так как позволяет группировать объекты, связанные с одной бизнес-фичей, в одном месте.
Package by Layer
-
Package by Layer в ООП часто встречается в классических монолитах, где слои (контроллеры, сервисы, репозитории) разделены, и каждый слой отвечает за свою часть логики.
-
При package by layer объекты и классы, отвечающие за разные аспекты одной функции, также оказываются разделенными по разным слоям. Это может затруднить применение принципов ООП, таких как полиморфизм, и привести к созданию более сложных и запутанных связей между объектами.
-
🔗 Функциональное программирование (ФП) и организация кода
ФП — это парадигма, в которой программа рассматривается как набор функций, которые принимают входные данные и возвращают результаты. Основывается на идее чистых функций, неизменяемости данных и композиции. Основные принципы ФП включают:
-
Чистые функции: Функции, которые не имеют побочных эффектов и возвращают одинаковый результат для одних и тех же входных данных.
-
Неизменяемость: Данные не изменяются после создания, вместо этого создаются новые данные.
-
Функции высшего порядка: Функции, которые могут принимать другие функции в качестве аргументов или возвращать их.
-
Рекурсия: Часто используется вместо циклов для обработки данных.
Функциональное программирование основывается на идее чистых функций, неизменяемости данных и композиции. В ФП акцент делается на том, что делает программа, а не на том, как она это делает. Это ведёт к иному подходу к структуре кода:
Package by Feature
-
Хорошо сочетается с ФП, так как функциональность группируется вокруг конкретных задач или доменных областей. Это позволяет создавать модули, которые инкапсулируют логику, связанную с определенной функцией.
-
Функции, которые работают с одними и теми же данными или решают одну задачу, находятся в одном месте, что упрощает композицию функций и их повторное использование.
-
Такой подход способствует созданию чистых функций, так как каждая функциональность изолирована и не зависит от глобального состояния.
Package by Layer
-
Противоречит принципам ФП, так как он разделяет код по техническим критериям, а не по функциональности. Это может затруднить композицию функций и привести к разбросу логики по разным слоям. Функциональный код может стать менее читаемым, если функции, связанные с одной задачей, находятся в разных пакетах.
👴 Что говорят эксперты?
1️⃣ Эрик Эванс в «Domain-Driven Design»?
Эрик Эванс вводит четырехслойную архитектуру (Layered Architecture), которая помогает разделить бизнес-логику, инфраструктуру, пользовательский интерфейс и доступ к данным. Однако он не настаивает на конкретной структуре директорий, а лишь подчеркивает важность разделения зон ответственности.
«При создании сложных программ критически важно разделить код на слои с разными зонами ответственности.»
— Эванс, DDD, Глава 4 «Изоляция доменной логики»
📌 Четыре слоя по Эвансу:
-
Доменный слой (Domain) должен быть изолирован от инфраструктуры. (Entities, Value Objects, Aggregates)
-
Сценарии использования (Application) управляют бизнес-процессами, но не содержат бизнес-логики. (Use Cases)
-
Инфраструктурный слой (Infrastructure) реализует хранилища данных, API, интеграции.
-
Интерфейсный слой (UserInterface) отвечает за взаимодействие с пользователем
Эванс не говорит, что именно так должна быть организована структура файлов. Он дает концепцию разделения ответственности, но не конкретное правило именования папок. Также Эванс подчеркивает важность Bounded Context и разделения кода по контекстам, а не только по слоям.
«Когда большая система делится на несколько Ограниченных Контекстов (Bounded Contexts), каждый из них имеет свою собственную доменную модель и логику. Код должен быть организован так, чтобы четко отражать эти границы.»
Это намекает на Package by Feature, когда код разделен по фичам/контекстам (Bonus/, Customer/, Order/), а не просто слоям (Domain/, Application/, Infrastructure/).
2️⃣ Вон Вернон в «Implementing Domain-Driven Design»
Вон Вернон также поддерживает разделение слоев, но он более гибко подходит к организации кода. В книге «Implementing DDD» он даже упоминает Package by Feature как возможный вариант!
Bounded Context и организацию кода:
Ограниченный контекст (Bounded Context) — это центральный паттерн в Domain-Driven Design (DDD). Он определяет границы, в рамках которых конкретная модель применима и остается согласованной. Организация кода вокруг ограниченных контекстов, а не только вокруг технических слоев, помогает сохранять ясность и соответствие бизнес-домену.
«Ограниченный контекст (Bounded Context) — это логическая граница. Применение архитектурных слоев внутри него — это выбор, а не требование. Главное — сохранить чистоту и выразительность доменной модели в рамках этой границы.»
— Вернон, IDDD, Глава 2: Границы контекста
Организация кода и возможные подходы:
«Организация кода строго по слоям часто приводит к искусственному разделению ответственности. Вместо этого рассмотрите возможность группировки кода по доменным концепциям в первую очередь, а затем решите, нужны ли слои внутри каждого контекста.»— Вон Вернон, Реализация Domain
— Вернон, IDDD, Глава 4: Стратегическое проектирование с использованием ограниченных контекстов*
Что это говорит о структуре кода?
«DDD не диктует жесткую структуру пакетов. Важно, чтобы код отражал границы контекста и модель домена.»
— Вернон, IDDD, Глава 2 «Границы контекста»
Он признает, что Package by Feature можно использовать внутри DDD и советует группировать код по Bounded Context, а внутри можно использовать слои. Также предлагает гибридный вариант между Package by Layer и Package by Feature:

3️⃣ Карлос Буэносвинос, Кристиан Соронеллас и Кейван Акбари в книге «DDD in PHP»
В книге PHP in DDD также затрагивается вопрос организации кода, включая модели, слои и Bounded Context. Авторы книги ориентируется на принципы Эванса и Вернона, но с адаптацией под PHP.
Поддержка Bounded Context и Package by Feature
«Хорошая архитектура PHP организует код вокруг доменных концепций, а не технических слоев. Каждый контекст должен иметь свою собственную доменную логику, независимую от инфраструктуры.»
— PHP в DDD, Глава 6: Организация кода с использованием ограниченных контекстов.
Поддерживает Bounded Context и Package by Feature, а не только слои. Код внутри Domain Layer должен скрывать детали реализации и работать через Use Cases.
Слои используются гибко
«Слоеная архитектура — полезный инструмент, но она не должна диктовать, как структурировать ваше PHP-приложение. Главное — отделить бизнес-логику от инфраструктуры.»
— PHP в DDD, Глава 10: Роль уровней приложений и инфраструктуры
4️⃣ Дядюшка Боб в чистом коде
Взгляните на Принципы дизайна упаковки дядюшки Боба. Он объясняет причины и мотивы, лежащие в основе этих принципов, которые я подробно рассмотрел ниже. Следующие три принципа упаковки касаются связности упаковки, они говорят нам, что положить внутрь упаковки:

Как это связано с пакетированием по функциям и пакетированием по слоям?

Принципы дяди Боба показывают, что Package by Feature — более гибкий и масштабируемый подход, так как:
✅ Избегает циклических зависимостей.
✅ Каждая фича меняется независимо.
✅ Группирует код по смыслу, а не по техническим слоям.
Однако, если вы используете Package by Layer, вам нужно очень тщательно управлять зависимостями, чтобы не нарушить принципы! 🚀
Сравнение подходов к организации кода в DDD

Ключевые моменты:
🔹 Ограниченный контекст как основа (как подчеркивают все три автора).
🔹 Package by Context + Package by Layer внутри каждого контекста.
🔹 Гибкое использование слоев (отделяем домен от инфраструктуры, но избегаем излишней сложности).
🔹 Доменная модель играет ключевую роль (Сущность, Агрегат, Объекты-Значения).
🔹 DDD не диктует структуру папок, но требует четкого разделения ответственности.
🔹 Главный принцип: DDD → сначала контексты, затем слои, а не наоборот! 🚀
🎯 Use Cases в Чистой архитектуре и Package by Feature
Use Cases — это один из ключевых элементов Чистой архитектуры, который отвечает за обработку бизнес-логики.
Package by Feature — это способ организации кода, при котором все файлы, связанные с одной функциональностью (фичей), хранятся в одной папке.
Эти два подхода отлично дополняют друг друга: Use Cases позволяют выделить бизнес-логику, а Package by Feature делает кодовую базу более модульной и понятной.

Use Cases и Package by Feature не противоречат друг другу. Напротив, они отлично работают вместе, поскольку обе методики направлены на улучшение структуры кода.
-
Use Cases помогают изолировать бизнес-логику и делают код тестируемым.
-
Package by Feature позволяет хранить весь код, связанный с одной функцией, в одном месте, делая его модульным.
Комбинируя эти подходы, вы получаете лучшее из обоих миров. В результате достигается:
-
Гибкий код, сгруппированный по фичам, который можно легко перемещать и тестировать независимо.
-
Поддерживаемый и масштабируемый код, который проще сопровождать и расширять.
-
Удобная для разработчиков кодовая база, в которой легче ориентироваться и работать.
Такое сочетание ведет к чистой, модульной и эффективной архитектуре. 🚀
💁 Когда что использовать?
В PHP-проектах оба подхода — Package by Feature и Package by Layer — применяются в зависимости от архитектурных решений и масштабов проекта. Давай разберем их с примерами.
Package by Layer подходит, когда:
-
У вас небольшое приложение
-
Много новых разработчиков в команде
-
Нужна простая, понятная структура
-
Функциональность тесно связана между собой
Package by Feature подходит, когда:
-
У вас большое приложение
-
Разные команды работают над разными функциями
-
Функциональность слабо связана между собой
-
Важна независимость модулей
-
Планируете переход на микросервисы
-
Проект большой с четкими доменными границами
-
Команда знакома с DDD
-
Используете собственную архитектуру или модульные фреймворки
🎯 Как организовать код?
Давайте рассмотрим на примерах.
Package by Layer (Пакетизация по структурным слоям)
Этот подход разделяет код по уровням ответственности:
🔹 Controller (контроллеры) – обработка HTTP-запросов
🔹 Service (сервисы) – бизнес-логика
🔹 Repository (репозитории) – работа с БД
🔹 Entity (сущности) – модели данных

Package by Layer (Пакетизация по архитектурным слоям)

Минус этих подходов в том, что логика одной фичи разбросана по разным слоям. При добавлении новой фичи приходится вносить изменения в несколько слоев.
Package by Feature (Пакетизация по фичам)
Здесь код группируется по фичам (сценариям работы), а не по слоям.

DDD и организация кода
На своем опыте я сталкивался с несколькими подходами. Я покажу два из них. Оба подхода технически работоспособны и потдерживают тактические патерны. На практике часто используется гибридный подход, который сочетает лучшие стороны Package by Feature и Package by Layer.
Подход №1. Доменно-ориентированные слои

С точки зрения DDD и Layer Architecture, более предпочтительным является такой подход и вот почему:
Ограниченные контексты (Bounded Contexts)
-
Первый подход лучше отражает концепцию ограниченных контекстов в DDD
-
Каждая функциональная область (order, bonus) представляет собой отдельный ограниченный контекст
-
Это обеспечивает лучшую изоляцию бизнес-логики и уменьшает связанность между разными доменами
Модульность и масштабируемость
-
При первом подходе легче добавлять новые функциональные модули
-
Каждый модуль содержит все необходимые слои и может развиваться независимо
-
Проще работать нескольким командам над разными модулями
Поддержка и навигация
-
Легче находить весь связанный код конкретной функциональности
-
Проще понять границы каждого модуля
-
Меньше риск случайного смешивания кода разных доменов
Соблюдение принципов DDD
-
Лучше отражает стратегический дизайн DDD
-
Четче видны границы агрегатов и доменных сервисов
-
Проще контролировать зависимости между модулями
Подход №2. Слои с доменными компонентами
Этот подход имеет следующие недостатки:

-
Смешивание разных доменных моделей в одном каталоге
-
Сложнее контролировать зависимости между доменами
-
По мере роста проекта становится труднее навигировать по коду
-
Выше риск нарушения границ между доменами
Советы по организации DDD-проекта:
-
Начните с выделения ключевых доменов.
-
Определите границы контекстов.
-
Создайте базовую структуру папок.
-
Поместите общий код в отдельный модуль shared/common.
-
Используйте чистую архитектуру внутри каждого домена.
-
Определите четкие правила взаимодействия между доменами.
Выводы
Оба подхода к организации кода имеют свои преимущества и недостатки. Выбор между package by feature и package by layer зависит от конкретных требований проекта и предпочтений команды разработчиков.
В целом, package by feature кажется более подходящим для современных приложений, так как он лучше сочетается с принципами ФП и ООП, способствует созданию более модульного и понятного кода, а также облегчает разработку и поддержку приложения. Особенно хорошо этот подход подходит для проектов, использующих DDD.
Однако, важно отметить, что package by layer также может быть эффективным в определенных ситуациях, особенно в проектах с четко выраженной слоистой архитектурой и строгими требованиями к разделению ответственности между слоями.
Вне зависимости от выбранного подхода, важно придерживаться единого стиля кодирования и обеспечивать хорошую документированность кода, чтобы облегчить его понимание и поддержку.
Источники
ссылка на оригинал статьи https://habr.com/ru/articles/882964/
Добавить комментарий