Шарувата архітектура на основі фреймворку yii

Саме про досвід в організації архітектури всієї лінійки продуктів такої компанії я й хочу розповісти. Наші зовнішні веб-продукти – це онлайн-версія, відкритий довідковий API та картографічний, сервіс відгуків. Внутрішні: CRM, білінг, алгоритмічні обчислення, системи статистики та експорту-імпорту даних.
Виведемо два поняття:
Веб-інфраструктура — сукупність веб-продуктів компанії, пов'язаних між собою та працюючих у близьких предметних галузях.
Технологічна база - єдина кодова база для веб-інфраструктури компанії. Містить набір програмних блоків із високим та низьким рівнем абстракції. Високоврівневі блоки — це вже напрацьовані командою розробників сутності у предметній галузі компанії. Низькорівневі блоки – це, наприклад, набір бібліотек чи фреймворків.
Для забезпечення критеріїв якості технологічної бази ми вирішили закласти шарувату архітектуру.Шарувата архітектура — це така архітектура системи, при якій система складається з деякої впорядкованої сукупності програмних підсистем, які називають шарами, такою, що:
- на кожному шарі нічого не відомо про властивості (і навіть існування) наступних (вищих) шарів;
- кожен шар може взаємодіяти з управління(звертатися до компонентів) з безпосередньо попереднім (нижчим) шаром через заздалегідь визначений інтерфейс, нічого не знаючи про внутрішню будову всіх попередніх шарів;
- кожен шар має певні ресурси, які він або приховує від інших шарів, або надає безпосередньо наступному шару (через зазначений інтерфейс) деякі їх абстракції.
Не розглядаючи мережу і сервери, які самі по собі є окремими шарами, розглянемо пристрій технологічної платформи. У ній можна виділити три шари:

На рис. 1 виділено три шари:
- Core layer - шар ядра. Тут розміщуються загальні низькорівневі бібліотеки та фреймфорки. Наприклад, yii.
- Shared layer — шар модулів, які реалізують якийсь функціонал системи, які можна використовувати у кількох проектах.
- Application layer - шар додатків. На цьому рівні розташовуються всі програми з їх специфічними модулями.
Розглянемо детальніше файлову організацію програми в Application layer з урахуванням специфіки yii.
рис 2. Файлова організація yii додатка
Як ми бачимо, компоненти та розширення є як на рівні програми, так і на загальному рівні (shared). Тут представлено структуру вже зібраного з репозитарію додатку. Звісно, у системі контролю версій організувати можна все інакше. У нас, наприклад, є два режими збирання, коли все заливається в папку програми і коли робляться симлінки на бібліотеку розширень. Перший необхідний для ізоляції різних програм, коли ми не можемо окремо оновити розширення без оновлення всіх програм, і для розгортання кількох інстансів на одній машині. Або розгортання різних гілок на одному сервері. Другий зручний розробникам під час роботи з кодом.
Отже, ми поставили гнучку основу для нашої веб-інфраструктури, але чи цього достатньо? Ні. Бракує двох важливих речей:
- архітектура додатка має бути зі слабким зачепленням і поділяється;
- система деплою та складання продукту.
Архітектура програми
В основу архітектури програми також покладемо шари. Виділимо такі рівні:

Шар «тонких» контролерів містить мінімум логіки та оперує API розширень. Шар бізнес-логіки складається з рівня розширень та рівня моделей даних. Шар Yii-розширень та моделі даних мають дуже сильний ступінь зв'язаності, на діаграмі це показано більшежирна стрілка.
Найбільший ступінь зв'язаності існує між додатком та «тонкими» контролерами. Імовірність перевикористання мінімальна. А ось зв'язаність між шаром «тонких» контролерів та шаром бізнес-логіки потрібно зробити якнайменше, оскільки бізнес-логіку ми можемо і повинні перевикористовувати в інших наших додатках. І зробити ми це можемо за допомогою гнучкості Yii та його конфігу.
Конфіг здійснює підключення необхідного нам набору компонентів і розширень. Приклад підключення розширення:
'geoip' => array ( 'class' => 'application.extensions.GeoIP.CGeoIP' ) ,
До розширення, що ініціалізується, в додатку можна буде звертатися за ключом geoip. Наприклад:
Під час першого звернення до компонента відбудеться ініціалізація класу CGeoIP. Гнучкість полягає в наявності ключа :-) Ми можемо в будь-який момент через конфіг підмінити реалізацію, а додаток буде працювати, як і раніше, за ключом geoip.
Грунтуючись на цій гнучкості, ми зможемо легко керувати пов'язаністю між додатком і шаром бізнес-логіки.
Групуючи логіку роботи з якоюсь сутністю докладання в розширення, ми робимо її відокремлюваною. Всі моделі таким чином інкапсульовані в розширенні і контролер програми працює з даними через розширення API.
Розширення – це додатковий рівень абстракції для нас, за яким ми можемо приховати джерела даних, необхідні для внутрішньої роботи тих чи інших методів розширення API.
Давайте трохи пофантазуємо і придумаємо додаток:

Наш додаток працює з кількома розширеннями. Розширення оперують набором даних та тісно пов'язані з шаром моделей. Важливий момент: рівень зв'язаності меду шарами зростає зверху вниз. Тіснішевсього пов'язані між собою моделі даних.

UserServiceRestClient поміщаємо в shared-рівень і змінюємо в конфізі програми клас, який потрібно використовувати. Оскільки API однакове, заміна реалізації пройшла без зміни коду. Дуже гнучко! Всі інші програми також працюють із сервісом користувачів, використовуючи UserServiceRestClient.
Отже, з архітектурою розібралися, але з таким підходом конфіг програми буде чималеньким. Руками прописувати всі залежності для програми - зовсім не по-джедайськи. Тим більше, коли проект живе та з'являються ще й міграції баз даних. Всі ці питання можна вирішити за допомогою своєї системи збирання та деплею продуктів.
Система деплою та складання
Що має робити система деплою:
- здійснювати доставку коду у вказану директорію на сервері;
- підтягувати всі залежності програми (розширення рівня shared та фреймворк);
- генерувати конфіг програми;
- виконувати SQL-міграції баз даних.
Загалом же завдання звичайні: файлові операції, робота з системами контролю версій, запуск сторонніх утиліт (PHPUnit, наприклад, або Doxygen) тощо Увага варто звернути на генерацію конфіга. Ми зробили її за шаблоном з мета-конфігураційним файлом:
рис. 6
При генерації конфігу Phing пропарсує шаблон і замінює всі placeholder'и на відповідні параметри, задані у мета-конфігураційному файлі. Вийшло дуже зручно. УсеЗалежно програми задаємо мета-конфіг:
## List of extensions, components and etc. to install ## EXTENSIONS = myExt, myExt2 COMPONENTS = component1, component2 HELPERS = myHeper, myTextHelper COMMANDS =
Також у мета-конфізі прописані бази даних, шляхи, хости — загалом, все. Додаткова зручність для автоматизації знову ж таки в тому, що всі ці параметри можна передати через командний рядок Phing і перевизначити. А в майбутньому, наприклад, налагодити складання пакетів під використовувану вами *nix ОС.
Таким чином, мета-конфігураційний файл – це шар абстракції від формату та кількості конфігів у нашому додатку.
У результаті гнучкість конфігурації Yii + система складання = легко конфігуровані та продукти, що збираються.
Тож, що нам показав досвід використання такої схеми роботи протягом року?
- Якщо виникне необхідність перевикористовувати якісь розширення, це не стане проблемою, процес безперервної інтеграції вибудувався теж без особливих проблем.
- Є необхідний запас гнучкості, що дозволяє легко розвивати та нарощувати функціонал додатків. Функціональні блоки можна робити вкрай швидко, не замислюючись про продуктивність та якість коду.
- Якщо інтерфейс продуманий, завжди можна безбоязно доробляти функціонал, покращувати продуктивність, змінювати сховище даних та проводити рефакторинг.
- Продумана система деплою програми дозволяє дуже швидко розгортати систему для тестування та мінімізує помилки при розгортанні на бойових серверах, пов'язані з неуважністю людини.
- Завдяки ізольованості окремих функціональних модулів можна виділити окрему групу розробки до роботи з ним. Це дозволяє вирішувати проблеми зростання відділу розробки та не перетворюватися навеликий некерований колгосп, де всі працюють над усім, і цим лише заважають один одному.