Слоистая архитектура на основе фреймворка yii
Представим себе компанию, которая реализует целый ряд продуктов: внешних и внутренних. Это не аутсорсинговая разработка, а работа и развитие собственных продуктов. Скорее всего, каждый продукт компании не будет существовать по отдельности, как сферический конь в вакууме, а будет в какой-то степени интегрирован с другими продуктами компании (интеграция с CRM как с внутренним продуктом или интеграция с каким-то внешним продуктом). То есть, в итоге все они вместе образуют некий слой взаимосвязанных между собой самостоятельно развивающихся (скорее всего, даже разными группами разработки) продуктов. Хороший пример — Яндекс (карты, почта, МойКруг) или Гугл (почта, Docs, карты). Понятно, что у перечисленных гигантов технологии в каждом продукте свои, но если взять компанию поменьше и работающую в более узкой предметной области, то можно предположить, что веб-продукты такой компании скорей всего будут выполнены на одних технологиях (ЯП, фреймворки и т.д.) Отсюда можем ввести два понятия:
Веб-инфраструктура — совокупность веб-продуктов компании, связанных между собой. Продукты образуют единый фронт присутствия компании в Интернете, работают в близких предметных областях.
Технологическая база — единая кодовая база для веб-инфраструктуры компании. Содержит набор программных блоков с высоким и низким уровнем абстракции. Высокоуровневые блоки — это уже наработанные командой разработчиков сущности в предметной области компании. Низкоуровневые блоки — это, например, набор библиотек или фреймворков.
Наверняка не раз каждый из вас работал над несколькими проектами и реализовывал модули, которые потом снова использовал в следующем проекте. Не всегда это выходит гладко: образуется совершенно отдельная версия модуля в отдельной ветке svn, а потом в третьем проекте необходимо применить нечто среднее между первыми двумя версиями модуля и т.д. и т.п. Другими словами, если не перечислять всех проблем, это критерии качества технологической базы. Давайте обозначим их:
- Переиспользование модулей и компонентов между проектами;
- расширямость функционала уже написанных модулей без переписывания всей системы;
- единая схема деплоя продуктов;
- масштабируемость по нагрузке.
Нашей целью было создание такой веб-инфраструктуры компании. Мы начали с подготовки технологической базы. Были выбраны следующие фреймворки и технологии:
- PHP;
- фреймворк для приложения — yii;
- фреймворк для деплоя и развертывания приложения — phing;
- фреймворк для написания unit-тестов — phpUnit;
- система непрерывной интеграции — Jenkins;
- функциональное и UI-тестирование — Selenium;
- СУБД Postgres;
- key-value хранилища memcached, redis;
- веб-сервер nginx.
Почему выбраны именно эти системы, обсуждать не будем, статья не об этом.
Для обеспечения критериев качества технологической базы мы решили заложить слоистую архитектуру.
Слоистая архитектура — это такая архитектура системы, при которой система состоит из некоторой упорядоченной совокупности программных подсистем, называемых слоями, такой, что:
- на каждом слое ничего не известно о свойствах (и даже существовании) последующих (более высоких) слоев;
- каждый слой может взаимодействовать по управлению (обращаться к компонентам) с непосредственно предшествующим (более низким) слоем через заранее определенный интерфейс, ничего не зная о внутреннем строении всех предшествующих слоев;
- каждый слой располагает определенными ресурсами, которые он либо скрывает от других слоев, либо предоставляет непосредственно последующему слою (через указанный интерфейс) некоторые их абстракции.
Таким образом, в слоистой программной системе каждый слой может реализовать некоторую абстракцию данных. Связи между слоями ограничены передачей значений параметров обращения каждого слоя к смежному снизу слою и выдачей результатов этого обращения от нижнего слоя верхнему. Недопустимо использование глобальных данных несколькими слоями. Более подное определение можно найти в работах Фаулера.
Не рассматривая сеть и сервера, которые сами по себе являются отдельными слоями, обратимся к устройству самого приложения. В приложении можно выделить три слоя:
Рис. 1. Слои архитектуры технологической платформы
На рис. 1 выделены три слоя:
- Core layer — слой ядра. Здесь располагаются общие низкоуровневые библиотеки и фреймфорки. Например, yii.
- Shared layer — слой модулей, реализующих какой-то функционал системы, которые могут быть использованы в нескольких проектах.
- Application layer — слой приложений. На этом уровне располагаются все приложения с их специфичными модулями.
При этом важным моментом является то, что модули должны иметь возможность легко перемещаться из слоя приложения в уровень общих модулей. Объясняется это очень просто: когда стартует новый проект, обычно реализуется самый необходимый функционал. По мере развития проекта, он начинает обрастать связями с другими продуктами (решили, например, подтягивать отзывы с одного проекта к кафешкам на другом нашем проекте), и, возможно, часть модулей может потребоваться для других проектов. Либо проект разделяется на части, и модули разделяются по нескольким приложениям.
Рассмотрим более детально файловую организацию приложения в Application layer с учетом специфики yii.
application framework - сюда деплоится фреймворк lib - компоненты из core и shared. Для этой папки в конфиге Yii заведен отдельный alias components extensions ... protected components extensions ... public themes configрис 2. Файловая организация yii приложения
Как мы видим, компоненты и расширения есть как на уровне приложения, так и на общем уровне (shared). Здесь представлена структура уже собранного из репозитария приложения. Конечно же, в системе контроля версий можно организовать все иначе. У нас, например, есть два режима сборки — когда все заливается в папку приложения и когда делаются симлинки на корку. Первый необходим для изоляции разных приложений, когда мы не можем отдельно обновить корку без обновления всех приложений, и для развертывания нескольких инстансов на одной машине. Или развертывания разных веток на одном сервере. Второй удобен разработчикам при работе с кодом.
Итак, мы задали гибкую основу для нашей веб-инфраструктуры, но достаточно ли этого? Нет. Не хватает двух важных вещей:
- архитектура приложения должна быть разделяемой и обладать слабым зацеплением;
- система деплоя и сборки продукта.
Теперь по каждому пункту подробней.
Архитектура приложения
В основу архитектуры приложения также положим слои. Выделим следующие уровни:
рис 3. Слои yii-приложения
Слой «тонких» контроллеров содержит минимум логики и оперирует API расширений. Слой бизнес логики состоит из уровня расширений и уровня моделей данных. Слой yii расширений и модели данных имеют очень сильную степень связанности, на диаграмме это показано более жирной стрелкой.
Наибольшая степень связанности существует между приложением и «тонкими» контроллерами. Вероятность переиспользования минимальна. А вот связанность между слоем «тонких» контроллеров и слоем бизнес-логики нужно сделать как можно меньше, так как бизнес-логику мы можем и должны переиспользовать в других наших приложениях. И сделать мы это можем при помощи гибкости yii и его конфига.
Подключение нужного нам набора компонент и расширений осуществляется через конфиг.
Пример подключения расширения:
'geoip' => array('class' => 'application.extensions.GeoIP.CGeoIP'),
К инициализируемому расширению в приложении можно будет обращаться по ключу geoip. Например:
Yii::$app->geoip.
При первом обращении к компоненту произойдет инициализация класса CGeoIP. Гибкость заключается в наличии ключа. :) Мы можем в любой момент через конфиг подменить реализацию, а приложение будет работать по-прежнему по ключу geoip.
Основываясь на этой гибкости, мы и сможем легко управлять связанностью между приложением и слоем бизнес-логики.
Группируя логику работы с некой сущностью приложения в расширении, мы делаем ее отделяемой. Все модели таким образом инкапсулированы в расширении, и контроллер приложения работает с данными через API расширения.
Расширения для нас — дополнительный уровень абстракции, за которым мы можем скрыть источники данных, необходимые для внутренней работы тех или иных методов API расширения.
Давайте немного пофантазируем и придумаем приложение:
рис 4. Пример архитектуры yii-приложения
Наше приложение работает с несколькими расширениями. Расширения оперируют набором данных и тесно связаны с слоем моделей. Важный момент: степень связанности меду слоями растет сверху вниз. Тесней всего связаны между собой модели данных.
Теперь давайте представим, что возникла необходимость в создании единого сервиса пользователей, чтобы все наши продукты работали с одними и теми же пользователями, была единая авторизация и.т.д Мы можем легко реализовать этот сервис в виде отдельного приложения. Для работы с новым приложением пишем расширение — rest клиента UserServiceRestClient. Клиент будет иметь такое же API, что и UserExtension API. Смотрим рисунок для наглядности:
UserServiceRestClient помещаем в shared уровень и меняем в конфиге приложения класс, который нужно использовать. Так как API одинаковое, подмена реализации прошла без изменения кода. Очень гибко. :) Все остальные приложения так же работают с сервисом пользователей, используя UserServiceRestClient.
Итак, с архитектурой разобрались, но с таким подходом конфиг приложения будет немаленький. Руками прописывать все зависимости для приложения — большая морока. Тем более когда проект живет и появляются еще и миграции баз данных. Все эти вопросы можно решить с помощью своей системы сборки и депллоя продуктов.
Система деплоя и сборки
Что должна делать система деплоя:
- осуществлять доставку кода в указанную директорию на сервере;
- подтягивать все зависимости приложения (расширения уровня shared и фреймворк);
- генерировать конфиг приложения;
- выполнять sql-миграции баз данных.
А также задачи запуска unit-тестов, сборка debug-версий проекта и т.д Все не перечисляю — дело вкуса, да и внимания всем этим операция уделено уже достаточно много в интернете. :) К примеру, у нас в компании деплой-система конфигурирует сопутствующее ПО типа Nginx, Sphinx и RabbitMQ и создает документацию. Удобно.
В целом, задачи обычные: файловые операции, работа с системами контроля версий, запуск сторонних утилит (phpUnit, например, или doxygen) и т.д Единственное, на что стоит обратить внимание, это генерация конфига. Мы сделали генерацию конфига по шаблону с мета-конфигурационным файлом. Т.е.:
application protected config main.php.template - шаблон конфига yii public themes build.prop - мета-конфигурационный файл build.xml - конфиг phing’aрис. 6
При генерации конфига phing пропарсивает шаблон и заменяет все placeholder’ы на соответствующие параметры, заданные в мета-конфигурационном файле. Получилось очень удобно. Все зависимости приложения задаем в мета-конфиг. Например:
## List of extensions, components and etc. to install ## EXTENSIONS = myExt, myExt2 COMPONENTS = component1, component2 HELPERS = myHeper, myTextHelper COMMANDS =
Также в мета-конфиге прописаны базы данных, пути, хосты — в общем, все. Дополнительное удобство для автоматизации состоит в том, что все эти параметры можно передать через командную строку phing’у и переопределить. А в будущем, например, наладить сборку пакетов под используемую вами *nix ОС.
Таким образом, мета-конфигурационный файл — это слой абстракции от формата и количества конфигов в нашем приложении. :)
В итоге гибкость конфигурации yii + система сборки = легко конфигурируемые и собираемые продукты.
Итог
Как показал опыт использования такой схемы работы в течении года в нашей компании: если возникнет необходимость переиспользовать какие-то расширения, то это не станет проблемой, процесс непрерывной интеграции выстроился тоже без особых проблем. Есть необходимый запас гибкости, позволяющий легко развивать и наращивать функционал приложений. Функциональные блоки можно делать очень быстро, не задумываясь о производительности и качестве кода, но если интерфейс продуман, то потом можно безбоязненно доделывать функционал, улучшать производительность, менять хранилище данных и проводить рефакторинг.Продуманная система деплоя приложения позволяет очень быстро разворачивать систему для тестирования и минимизирует ошибки при развертывании на боевых серверах, связанных с невнимательностью человека.
Благодаря изолированности отдельных функциональных модулей всегда можно выделить отдельную группу разработки для работы с ними. Это позволяет решать проблемы роста отдела разработки и не превращаться в большой неуправляемый колхоз, где все работают над всем и поэтому сильно мешают друг другу.
14 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.