Один деплой, кілька модулів
Звичайний Laravel-проєкт ділить код за технічним типом: усі моделі в app/Models, усі контролери в app/Http. Через рік зміна у вакансіях зачіпає десяток папок, а будь-яка модель тягне будь-яку іншу. ділить код за предметними областями: кожен відповідає одному і всередині має власні шари.
Модуль показує назовні вузький : сценарії, query-сервіси, доменні інтерфейси. Внутрішні класи, особливо Eloquent-моделі, лишаються його справою. Інші модулі звертаються через цей вхід або дізнаються про зміни з подій. Те, що потрібно всім і не містить бізнес-правил, живе в .
Деплой, база й транзакції спільні, тож модулі не платять за мережу між сервісами. Якщо межі тримаються, модуль згодом можна винести в окремий сервіс. Якщо ні, після розділення вийде : ті самі звʼязки, тільки через HTTP.
Модулі й звʼязки між ними
- Content (статті, документація) містить: Roadmap, Tip.
- Search (пошук по всьому сайту) містить: SearchEngine.
- Projects (вітрина проєктів) містить: Project, Trust.
- Інші модулі містить: Analytics, Contact, Seo, Development.
- Hiring (вакансії, відгуки, компанії) містить: Job, Application.
- ContentOps (черга задач воркера) містить: ContentTask.
- Search використовує Content.
- Search використовує Projects.
- Search використовує Hiring.
- Content використовує Hiring.
- Projects використовує Hiring.
- ContentOps використовує Hiring.
- 1Модуль відповідає предметній області, а не технічному шару.
- 2Інші модулі беруть лише публічний вхід. Чужі Eloquent-моделі й таблиці під забороною.
- 3Спільне ядро маленьке й без бізнес-правил: час, текст, утиліти.
- 4Межі перевіряє тест, а не домовленість.
Де межа тримається, а де ні
Модулі PHP Ukraine це папки в src/: Hiring, Content, Projects, ContentOps, Search та інші, зазвичай з Domain, Application й Infrastructure усередині. Архітектурний тест перевіряє шари всередині модулів і забороняє Shared імпортувати Hiring і ContentOps, але залежності між самими модулями не обмежує. Найпомітніше межу перетинає пошук: EloquentSearchEngine читає Eloquent-моделі трьох інших модулів напряму.
Як модулі зростаються назад
Модулі за шарами
Папки Controllers, Services, Repositories на весь проєкт. Межі між вакансіями й контентом немає ніде, і будь-який сервіс тягне будь-який інший.
Натомість: папка на предметну область, а шари всередині неї.
Чужі таблиці
Модуль робить join по таблицях іншого або бере його Eloquent-модель. Перейменування колонки в одному модулі ламає інший.
Натомість: питайте через публічний вхід: query-сервіс чи інтерфейс. Якщо пошуку потрібна швидка вибірка, її надає модуль-власник.
Shared як смітник
Усе, що не знайшло місця, їде в Shared. Через рік там бізнес-правила, від яких залежать усі модулі.
Натомість: у спільному ядрі лише нейтральне: час, текст, гроші. У PHP Ukraine архітектурний тест забороняє Shared імпортувати Hiring і ContentOps.
Чи підходить вам модульний моноліт
- Модульний моноліт
- Один застосунок і один деплой, код якого поділено на модулі з явними межами.
- Модуль
- Код однієї предметної області: Hiring, Content, Projects. Має власні Domain, Application та Infrastructure.
- Bounded context
- Межа, всередині якої слова моделі мають одне значення. «Компанія» в найманні й у каталозі може означати різне.
- Публічний вхід модуля
- Класи, якими модуль дозволяє користуватися іншим: сценарії, query-сервіси, інтерфейси. Решта внутрішня.
- Спільне ядро
- Shared kernel: невеликий код, від якого залежать усі модулі. У PHP Ukraine це src/Shared: годинник, текстові утиліти й виклик Claude CLI.
- Розподілений моноліт
- Сервіси, які не можуть жити один без одного: спільна база, синхронні ланцюжки викликів, спільний реліз.
Питання зі співбесід
Модулі живуть в одному процесі, одному деплої й зазвичай одній базі, тому виклик між ними коштує як виклик методу, а транзакція може бути спільною. Мікросервіси розділені мережею й деплоєм, і кожна межа там коштує дорожче.
Архітектурним тестом: Pest arch() з expect(...)->not->toUse(...) для просторів імен інших модулів або Deptrac з описом дозволених залежностей. Тест запускається в CI разом з рештою.
Кожен модуль-власник віддає дані для пошуку через свій публічний вхід або подію, а Search будує власний індекс чи проєкцію. Тоді зміна схеми вакансій зачіпає лише код Hiring, який цей індекс наповнює.