<? phpukraine PHP-ПЛАТФОРМА
⌕Пошук по платформі
КОНЦЕПЦІЯ DDD · КРОК 2 З 10

ШариLayered architecture

Код ділять за відповідальністю: інтерфейс приймає запит, застосунок веде сценарій, домен тримає правила, інфраструктура говорить з базою й поштою. Залежності йдуть лише в один бік.

5 хв читання Junior → Middle
01 · ЩО ЦЕ

Кожен клас знає своє місце

Контролер, який перевіряє форму, рахує вилку зарплати, пише в Eloquent і шле лист, працює, доки його не треба змінити. Щоб перевірити одне правило, тест піднімає HTTP, базу й пошту. Шари розкладають цей код за відповідальністю: приймає запит, веде сценарій, тримає правила, говорить з базою, поштою й чужими API.

СЛОВНИКІнтерфейс. Вхід у застосунок: HTTP-контролери, Livewire-компоненти, Artisan-команди, обробники черги. Перекладає запит на виклик сценарію.
СЛОВНИКЗастосунок. Сценарії на кшталт «відкликати відгук»: дістати агрегат, викликати метод домену, зберегти. Власних бізнес-правил не має.
СЛОВНИКДомен. Сутності, value objects, правила й інтерфейси, які потрібні домену. Не знає про фреймворк, HTTP і базу.
СЛОВНИКІнфраструктура. Реалізації інтерфейсів домену: Eloquent-репозиторії, пошта, HTTP-клієнти, читання файлів.

Шари тримаються на : верхній шар викликає нижній, нижній про верхній не знає. У класичній схемі Еванса інфраструктура лежить у самому низу. У більшості DDD-проєктів, і в PHP Ukraine теж, домен сам оголошує потрібні йому інтерфейси, наприклад репозиторій, а інфраструктура їх реалізує. Тоді домен не залежить ні від чого, крім PHP.

СЛОВНИКПравило залежностей. Залежність іде лише в один бік. Клас нижчого шару не імпортує класів вищого.

Папки Domain і Infrastructure самі нічого не гарантують: один виклик фасаду DB у доменному класі, і межі вже немає. Тому правило перевіряє , і збірка падає, щойно хтось його порушить.

СЛОВНИКАрхітектурний тест. Тест, який перевіряє не поведінку, а залежності між класами. У Pest це arch() з expect(...)->not->toUse(...).
02 · МЕЖА

Виклики йдуть згори вниз

ІНТЕРФЕЙСapp/Http, app/Livewire, app/ConsoleЗАСТОСУНОКsrc/*/Application: сценаріїДОМЕНsrc/*/Domain: правила й інтерфейсиІНФРАСТРУКТУРАsrc/*/Infrastructure: Eloquent, поштаhandle()withdraw()save()ProfileControllerWithdrawApplicationApplication«interface»ApplicationRepositoryEloquentApplicationRepository
ІНТЕРФЕЙСapp/Http, app/Livewire, app/ConsoleЗАСТОСУНОКsrc/*/Application: сценаріїДОМЕНsrc/*/Domain: правила й інтерфейсиІНФРАСТРУКТУРАsrc/*/Infrastructure: Eloquent, поштаProfileControllerWithdrawApplicationApplication«interface»ApplicationRepositoryEloquentApplicationRepository
Відкликання відгуку в PHP Ukraine. Контролер викликає сценарій, сценарій викликає домен. EloquentApplicationRepository реалізує інтерфейс, який оголосив домен, тож домен про Eloquent не знає.
Абстракція Ключовий учасник Домен Інфраструктура Клієнт
  • ProfileController використовує WithdrawApplication (handle()).
  • WithdrawApplication використовує Application (withdraw()).
  • WithdrawApplication використовує ApplicationRepository (save()).
  • EloquentApplicationRepository реалізує ApplicationRepository.
  1. 1Інтерфейс лише перекладає HTTP чи консоль на виклик сценарію. Бізнес-рішень у контролері немає.
  2. 2Сценарій дістає агрегат з репозиторію, викликає метод домену й зберігає результат.
  3. 3Домен не імпортує фреймворк. Усе, що йому потрібно ззовні, він описує інтерфейсом.
  4. 4Інфраструктура реалізує ці інтерфейси, а привʼязку до реалізацій робить сервіс-провайдер.
03 · ЯК У PHP UKRAINE

Шари перевіряє тест

У PHP Ukraine межі шарів записані в Pest-тесті. Він проходить по класах перелічених просторів імен і падає, якщо знаходить заборонену залежність: домен не бачить Laravel, сценарії не бачать інфраструктури й фреймворку, контролери й Livewire-компоненти не беруть інфраструктуру Hiring, ContentOps і Projects напряму.

php LayersTest.php фрагмент, скорочено
1arch('domain layers stay framework-free')
2 ->expect(['PhpUkraine\Hiring\Domain', 'PhpUkraine\ContentOps\Domain', 'PhpUkraine\Seo\Domain', 'PhpUkraine\Search\Domain', 'PhpUkraine\Analytics\Domain', 'PhpUkraine\Content\Domain', 'PhpUkraine\Contact\Domain', 'PhpUkraine\Development\Domain', 'PhpUkraine\Projects\Domain'])
3 ->not->toUse(['Illuminate', 'Livewire', 'App']);← домен без фреймворку
4
5arch('application layers do not reach into infrastructure or the web layer')
6 ->expect(['PhpUkraine\Hiring\Application', 'PhpUkraine\ContentOps\Application', 'PhpUkraine\Search\Application', 'PhpUkraine\Analytics\Application', 'PhpUkraine\Contact\Application', 'PhpUkraine\Projects\Application'])
7 ->not->toUse(['PhpUkraine\Hiring\Infrastructure', 'PhpUkraine\ContentOps\Infrastructure', 'PhpUkraine\Projects\Infrastructure', 'App\Http', 'App\Livewire', 'Livewire', 'Illuminate']);← застосунок без інфраструктури
8
9arch('the web layer never touches infrastructure directly')
10 ->expect(['App\Http', 'App\Livewire'])
11 ->not->toUse(['PhpUkraine\Hiring\Infrastructure', 'PhpUkraine\ContentOps\Infrastructure', 'PhpUkraine\Projects\Infrastructure']);← веб без інфраструктури
04 · ТИПОВІ ПОМИЛКИ

Як шари зазвичай ламають

✕ 1

Шари заради шарів

Довідник міст проходить контролер, сервіс, репозиторій і мапер, хоча правил там немає. Кожна зміна поля зачіпає пʼять файлів.

Натомість: прості читання й CRUD робіть напряму. У PHP Ukraine каталог вакансій віддає query-сервіс JobCatalog: запит до бази й готові картки, без агрегатів.

✕ 2

Домен знає про фреймворк

Доменний клас викликає now(), config() чи фасад DB. Щоб перевірити правило, тест мусить підняти Laravel.

Натомість: час передайте через інтерфейс Clock, збереження через інтерфейс репозиторію. Фреймворк лишається в інфраструктурі.

✕ 3

Межа на словах

Правило записане в README, але не перевіряється. Або тест перелічує простори імен явно, і новий модуль у нього просто не потрапляє.

Натомість: архітектурний тест у CI. Додаючи модуль, додайте його простори імен у тест разом з першим класом.

05 · ДЕРЕВО РІШЕНЬ

Чи потрібні вам окремі шари

1
Чи є в коді правила, які не зводяться до збереження форми?
Наприклад: відкликати відгук можна лише доти, доки в нього немає підсумку.
НІ →Звичайний Laravel з Form Request і моделлю впорається. Шари лише додадуть файлів.
2
Чи хочете ви перевіряти ці правила без бази й HTTP?
Unit-тест на мілісекунди замість feature-тесту з міграціями.
НІ →Тримайте логіку в Action-класах поруч із фреймворком і тестуйте feature-тестами.
3
Чи житиме проєкт роками й чи змінюватимуть його кілька людей?
Код, який через рік читатиме не його автор.
НІ →Для прототипу вистачить поділу на контролери й сервіси.
ТАК НА ВСІ →Вам потрібні шари. Почніть з домену без фреймворку й одного архітектурного тесту.
06 · СЛОВНИК
Інтерфейс
Вхід у застосунок: HTTP-контролери, Livewire-компоненти, Artisan-команди, обробники черги. Перекладає запит на виклик сценарію.
Застосунок
Сценарії на кшталт «відкликати відгук»: дістати агрегат, викликати метод домену, зберегти. Власних бізнес-правил не має.
Домен
Сутності, value objects, правила й інтерфейси, які потрібні домену. Не знає про фреймворк, HTTP і базу.
Інфраструктура
Реалізації інтерфейсів домену: Eloquent-репозиторії, пошта, HTTP-клієнти, читання файлів.
Правило залежностей
Залежність іде лише в один бік. Клас нижчого шару не імпортує класів вищого.
Архітектурний тест
Тест, який перевіряє не поведінку, а залежності між класами. У Pest це arch() з expect(...)->not->toUse(...).
07 · ПЕРЕВІР СЕБЕ

Питання зі співбесід

Усі питання з архітектури →

Тоді правила перевіряються звичайним unit-тестом без Laravel, а оновлення фреймворку чи заміна бази не зачіпає бізнес-логіку.

Оголошено в домені, бо саме домену потрібно знаходити й зберігати агрегати. Реалізує інфраструктура, наприклад EloquentApplicationRepository, а сервіс-провайдер привʼязує інтерфейс до реалізації.

Папки Laravel групують класи за технічним типом. Шари групують за відповідальністю й задають напрям залежностей, тож домен не знає про HTTP і базу.