Кожен клас знає своє місце
Контролер, який перевіряє форму, рахує вилку зарплати, пише в Eloquent і шле лист, працює, доки його не треба змінити. Щоб перевірити одне правило, тест піднімає HTTP, базу й пошту. Шари розкладають цей код за відповідальністю: приймає запит, веде сценарій, тримає правила, говорить з базою, поштою й чужими API.
Шари тримаються на : верхній шар викликає нижній, нижній про верхній не знає. У класичній схемі Еванса інфраструктура лежить у самому низу. У більшості DDD-проєктів, і в PHP Ukraine теж, домен сам оголошує потрібні йому інтерфейси, наприклад репозиторій, а інфраструктура їх реалізує. Тоді домен не залежить ні від чого, крім PHP.
Папки Domain і Infrastructure самі нічого не гарантують: один виклик фасаду DB у доменному класі, і межі вже немає. Тому правило перевіряє , і збірка падає, щойно хтось його порушить.
Виклики йдуть згори вниз
- ProfileController використовує WithdrawApplication (handle()).
- WithdrawApplication використовує Application (withdraw()).
- WithdrawApplication використовує ApplicationRepository (save()).
- EloquentApplicationRepository реалізує ApplicationRepository.
- 1Інтерфейс лише перекладає HTTP чи консоль на виклик сценарію. Бізнес-рішень у контролері немає.
- 2Сценарій дістає агрегат з репозиторію, викликає метод домену й зберігає результат.
- 3Домен не імпортує фреймворк. Усе, що йому потрібно ззовні, він описує інтерфейсом.
- 4Інфраструктура реалізує ці інтерфейси, а привʼязку до реалізацій робить сервіс-провайдер.
Шари перевіряє тест
У PHP Ukraine межі шарів записані в Pest-тесті. Він проходить по класах перелічених просторів імен і падає, якщо знаходить заборонену залежність: домен не бачить Laravel, сценарії не бачать інфраструктури й фреймворку, контролери й Livewire-компоненти не беруть інфраструктуру Hiring, ContentOps і Projects напряму.
Як шари зазвичай ламають
Шари заради шарів
Довідник міст проходить контролер, сервіс, репозиторій і мапер, хоча правил там немає. Кожна зміна поля зачіпає пʼять файлів.
Натомість: прості читання й CRUD робіть напряму. У PHP Ukraine каталог вакансій віддає query-сервіс JobCatalog: запит до бази й готові картки, без агрегатів.
Домен знає про фреймворк
Доменний клас викликає now(), config() чи фасад DB. Щоб перевірити правило, тест мусить підняти Laravel.
Натомість: час передайте через інтерфейс Clock, збереження через інтерфейс репозиторію. Фреймворк лишається в інфраструктурі.
Межа на словах
Правило записане в README, але не перевіряється. Або тест перелічує простори імен явно, і новий модуль у нього просто не потрапляє.
Натомість: архітектурний тест у CI. Додаючи модуль, додайте його простори імен у тест разом з першим класом.
Чи потрібні вам окремі шари
- Інтерфейс
- Вхід у застосунок: HTTP-контролери, Livewire-компоненти, Artisan-команди, обробники черги. Перекладає запит на виклик сценарію.
- Застосунок
- Сценарії на кшталт «відкликати відгук»: дістати агрегат, викликати метод домену, зберегти. Власних бізнес-правил не має.
- Домен
- Сутності, value objects, правила й інтерфейси, які потрібні домену. Не знає про фреймворк, HTTP і базу.
- Інфраструктура
- Реалізації інтерфейсів домену: Eloquent-репозиторії, пошта, HTTP-клієнти, читання файлів.
- Правило залежностей
- Залежність іде лише в один бік. Клас нижчого шару не імпортує класів вищого.
- Архітектурний тест
- Тест, який перевіряє не поведінку, а залежності між класами. У Pest це arch() з expect(...)->not->toUse(...).
Питання зі співбесід
Тоді правила перевіряються звичайним unit-тестом без Laravel, а оновлення фреймворку чи заміна бази не зачіпає бізнес-логіку.
Оголошено в домені, бо саме домену потрібно знаходити й зберігати агрегати. Реалізує інфраструктура, наприклад EloquentApplicationRepository, а сервіс-провайдер привʼязує інтерфейс до реалізації.
Папки Laravel групують класи за технічним типом. Шари групують за відповідальністю й задають напрям залежностей, тож домен не знає про HTTP і базу.