Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Чиста архітектура й тестованість»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

Позиція «фреймворк - деталь» (чиста архітектура): бізнес-логіка не повинна знати про Laravel. Ніяких моделей Eloquent, фасадів, хелперів у ядрі - лише чисті PHP-класи, інтерфейси й DTO, а фреймворк лише доставляє запити й зберігає дані.

Позиція Laravel-спільноти: фреймворк - не деталь, а інструмент продуктивності. Eloquent, фасади, черги, події дають швидкість розробки саме завдяки тісній інтеграції. Абстрагування від них - це подвоєння коду заради заміни, яка ніколи не станеться.

Що коштує повна ізоляція:

  • окремі доменні сутності й Eloquent-моделі + відображення між ними;
  • репозиторії-обгортки над Eloquent з інтерфейсами;
  • DTO на кожній межі;
  • втрата зручностей (зв'язки, ліниве завантаження, події моделей, toResource());
  • новим людям складніше: «Laravel-проєкт» не схожий на Laravel.

Що вона дає:

  • бізнес-правила тестуються миттєво, без бази й контейнера;
  • складна предметна область (фінанси, страхування, логістика) не тоне в інфраструктурному коді;
  • довговічність: ядро переживає великі оновлення фреймворку.

Прагматичний середній шлях, що працює в більшості проєктів:

  • дії (actions) чи сервіси для сценаріїв - бізнес-логіка не в контролерах, моделях чи Livewire-компонентах;
  • Eloquent лишається доменною моделлю - Active Record з методами домену ($order->cancel()), скоупами й кастами;
  • ізолювати зовнішні системи, а не власну базу: інтерфейси для платежів, SMS, сторонніх API - тут заміна й фейки реально потрібні;
  • value objects і енуми для доменних понять (гроші, статуси, діапазони дат);
  • arch-тести на напрям залежностей (домен не імпортує Illuminate\Http);
  • фасади - за потреби, з розумінням, що в тестах вони підміняються (Mail::fake()).

Критерій вибору: складність предметної області порівняно з технічною. CRUD-адмінка, каталог, блог - Laravel-шлях. Складні правила з багатьма інваріантами й довгим життям - більше ізоляції, хоча б для ядра цієї частини (модульний моноліт, де лише складний модуль має «чисту» архітектуру).

Найгірший варіант - половинчастий: інтерфейси репозиторіїв, що повертають моделі Eloquent, і «сервіси», що лише проксюють виклики. Складність є, а користі немає.

Докладніше в документації: Laravel: сервіс-контейнер

Еволюційна архітектура (Ніл Форд, Ребекка Парсонс, Патрік Куа) - підхід, за яким архітектуру не проєктують раз і назавжди, а постійно змінюють разом із системою. Ключове питання - як змінювати, не втрачаючи важливих властивостей (продуктивності, безпеки, модульності).

Функція придатності (fitness function) - автоматична перевірка конкретної архітектурної властивості. Термін запозичено з еволюційних алгоритмів, де функція придатності оцінює, наскільки рішення близьке до мети.

Приклади функцій придатності:

  • модульність: arch-тести Pest - «модуль Billing не залежить від внутрішніх класів Catalog», «домен не імпортує HTTP-шар»;
  • продуктивність: тест, що головна сторінка робить не більше 15 SQL-запитів (expectsDatabaseQueryCount); бюджет часу відповіді в навантажувальних тестах;
  • розмір збірки фронтенду: CI падає, якщо JavaScript-бандл перевищив 250 КБ;
  • безпека: composer audit без вразливостей високого рівня, заборонені функції через arch()->preset()->security();
  • якість: рівень PHPStan, покриття критичних модулів тестами;
  • операційні: моніторинг SLO в продакшені - «функція придатності», що працює постійно.

Види:

  • атомарні (одна властивість) і цілісні (поєднання - продуктивність разом із безпекою);
  • тригерні (запускаються в CI на кожну зміну) і безперервні (моніторинг у продакшені);
  • статичні (правила коду) і динамічні (вимірювання під навантаженням).

Чому це важливо: архітектура деградує поступово - кожен окремий pull request «трохи» порушує межі, і через рік модульний моноліт стає великою грудкою бруду. Функції придатності роблять архітектурні рішення виконуваними: порушення помітне в момент внесення, а не на ретроспективі.

Інші принципи еволюційної архітектури:

  • останній відповідальний момент для рішень - вирішувати, коли знань достатньо, але до того, як відкладання стане дорожчим;
  • зменшення зв'язності - чим менше залежностей, тим легше змінювати частини незалежно;
  • малі інкрементальні зміни з можливістю відкату - замість великих переписувань.

Як почати: обрати 2-3 властивості, порушення яких найболючіші для проєкту (часто - межі модулів і N+1), і автоматизувати їх перевірку в CI.

Докладніше в документації: Building Evolutionary Architectures

Роберт Мартін запропонував метрики, що перетворюють розмови про «зв'язність модулів» на числа.

Аферентна зв'язність (Ca) - скільки інших модулів залежать від цього. Висока Ca - модуль важливий для багатьох; його зміна зачіпає багатьох.

Еферентна зв'язність (Ce) - від скількох модулів залежить цей. Висока Ce - модуль залежить від багатьох; зміни в будь-якому з них можуть його зламати.

Нестабільність:

I = Ce / (Ca + Ce)       від 0 до 1
  • I ≈ 0 - стабільний модуль: від нього багато залежать, він сам ні від кого. Змінювати його дорого (багато споживачів). Приклад: спільні value objects, базові інтерфейси, ядро предметної області;
  • I ≈ 1 - нестабільний: ні від нього ніхто, а він від багатьох. Змінювати легко й безпечно. Приклад: контролери, консольні команди, UI.

Принцип стабільних залежностей: залежності мають іти в напрямку стабільності - нестабільні модулі залежать від стабільних, а не навпаки. Якщо стабільний модуль (від якого залежить половина системи) залежить від нестабільного, кожна зміна нестабільного «трусить» усю систему.

Абстрактність (A) - частка абстрактних класів та інтерфейсів у модулі. Принцип стабільних абстракцій: стабільні модулі мають бути абстрактними (щоб їх можна було розширювати без зміни), нестабільні - конкретними.

Головна послідовність: ідеально A + I ≈ 1. Відхилення показує проблемні зони:

  • «зона болю» (стабільний і конкретний: I ≈ 0, A ≈ 0) - від модуля всі залежать, але його неможливо розширити без зміни. Типово для «утилітних» класів і перевантажених базових моделей;
  • «зона марності» (нестабільний і абстрактний) - інтерфейси, які ніхто не використовує.

Як застосувати в PHP-проєкті:

  • інструменти: PhpMetrics, Deptrac (ще й перевіряє дозволені залежності між шарами), pdepend;
  • дивитися на тенденції між релізами, а не на абсолютні значення;
  • для модульного моноліту - головні метрики на рівні модулів (Billing, Catalog), а не окремих класів.

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

Докладніше в документації: Software package metrics

У великій команді один архітектор, через якого проходять усі рішення, стає вузьким місцем: рішення чекають тижнями, а ухвалюються далеко від людей, що знають деталі. У маленькій - навпаки, рішення ухвалюються «мимохідь», і через рік ніхто не пам'ятає чому.

Поділ рішень за вартістю зміни:

  • оборотні («двосторонні двері») - легко змінити: структура класів усередині модуля, вибір бібліотеки для внутрішньої задачі. Ухвалюються швидко, тими, хто робить роботу;
  • необоротні («односторонні двері») - дорого змінити: формат публічного API, основна база, розбиття на сервіси, модель даних, автентифікація. Тут варто зупинитися, зібрати думки й записати рішення.

Процес порад (advice process) - підхід, який описує Андрю Гармел-Ло в статті на сайті Мартіна Фаулера:

  • будь-хто може ухвалити архітектурне рішення, але спершу мусить порадитися з тими, кого воно зачепить, і з тими, хто має досвід у цій галузі;
  • порада не є вето - відповідальність за рішення лишається на тому, хто його ухвалює;
  • рішення фіксується в ADR - разом із контекстом, альтернативами й отриманими порадами;
  • архітектурний форум (регулярна зустріч) - місце, де обговорюють поточні рішення й діляться ними, а не орган затвердження.

Інструменти, що підтримують такий підхід:

  • ADR - пам'ять про рішення і їхні причини;
  • принципи архітектури - кілька загальноприйнятих правил («межі модулів не перетинаємо напряму», «нові інтеграції - через черги»), щоб більшість рішень можна було ухвалити самостійно, звіряючись із ними;
  • технологічний радар - перелік технологій зі статусами «використовуємо», «пробуємо», «не використовуємо»;
  • функції придатності - автоматична перевірка того, що вже вирішено.

Чого уникати:

  • рішення без обговорення з тими, хто житиме з наслідками (експлуатація, безпека, інші команди);
  • комітет, що затверджує все - повільно й знімає відповідальність з авторів;
  • рішення без запису - через рік їх «виправляють» люди, які не знають контексту.

На співбесіді важливо показати, що архітектура - не лише технічні знання, а й процес: як збирати інформацію, зважувати компроміси, документувати й поширювати рішення.

Докладніше в документації: Martin Fowler: Scaling the Practice of Architecture, Conversationally