Репутація українського ІТ. Пройти опитування Асоціації IT Ukraine
×Закрыть

Слоистая архитектура на основе фреймворка 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. Смотрим рисунок для наглядности:


рис. 5

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 + система сборки = легко конфигурируемые и собираемые продукты.

Итог

Как показал опыт использования такой схемы работы в течении года в нашей компании: если возникнет необходимость переиспользовать какие-то расширения, то это не станет проблемой, процесс непрерывной интеграции выстроился тоже без особых проблем. Есть необходимый запас гибкости, позволяющий легко развивать и наращивать функционал приложений. Функциональные блоки можно делать очень быстро, не задумываясь о производительности и качестве кода, но если интерфейс продуман, то потом можно безбоязненно доделывать функционал, улучшать производительность, менять хранилище данных и проводить рефакторинг.

Продуманная система деплоя приложения позволяет очень быстро разворачивать систему для тестирования и минимизирует ошибки при развертывании на боевых серверах, связанных с невнимательностью человека.

Благодаря изолированности отдельных функциональных модулей всегда можно выделить отдельную группу разработки для работы с ними. Это позволяет решать проблемы роста отдела разработки и не превращаться в большой неуправляемый колхоз, где все работают над всем и поэтому сильно мешают друг другу.

LinkedIn

Похожие статьи

14 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Правильно я понимаю, что при таком неявном подключении

’geoip’ => array(’class’ => ’application.extensions.GeoIP.CGeoIP’)

code completion в конструкции

Yii::$app->geoip

без дополнительных телодвижений работать не будет?

Вы используете какое-либо staging-окружение, отдельное от development? Т.е. существует ли какое-то pre-production окружение, или вы доверяете системе деплоя безоговорочно, позволяя сразу раскидывать артефакты по серверам в автоматическом режиме?

Конечно, автоматическая сборка проходит не в боевом окружении :) В боевые выкатаем так же с помощью phing, но запуск обновления боевых продуктов только руками

А как вы в таком случае помечаете состояние проекта? Т.е. вот есть репозитарий разработчика — это сырой код, без тестов, только «из печи». Есть pre-production окружение, в которое попадают оттестированные в автоматическом режиме изменения. И есть боевой сервер, на который деплоится гарантированно провереные данные. Эти три состояния идентифицируются ведь не чисто по наличию их на определенном сервере? :)

Были выбраны следующие фреймворки и технологии

Можно вкратце, почему выбрали такие решения?

Интересно узнать плюсы и минусы.

о каких именно технологиях идет речь? yii — выбрали так как он на тот момент показывал самые хорошие результаты по тестам производительности из всех фреймворков. Вообще, «вкратце» тут не рассказать :)
Jenkins так как гибко(до этого использовали CruiseControl + phpUnderControl)
phpUnit — ибо стандарт для пхп.
Selenium — а есть аналоги достойные? :)
Postgres ибо у него есть расширение PostGIS

phing — так как на php и ApacheAnt бы потащил за собой ряд дополнительных зависимостей по библиотекам и пакетам.

А будет продолжение? Я не совсем понял при чем тут yii

да нет, продолжения именно этой темы не будет. Есть мысли по другим темам :) Описанная выше слоистая архитектура была реализована на базе yii, безусловно это не единственный возможный вариант реализации.

Вау, неужели на ДОУ появились технические статьи? Браво!

Без холивара. Смотрели в сторону symfony2? Для вашей архитектуры идеально бы подошел.

Когда мы это начинали делать был только Symfony 1.x. Тогда yii привлек своей скоростью засчет lazy load. Сейчас Symfony2 интересен реализацией IoC-контейнера. По производительности Symfony2 и yii мы пока не сравнивали, но не исключено, что попробуем Symfony2 для чего-нибудь :)

Подписаться на комментарии