Слоистая архитектура на основе фреймворка yii


    Представим себе компанию, которая реализует целый ряд собственных продуктов — как для внешних пользователей, так и для внутренних. Скорее всего, каждый продукт не сможет существовать по отдельности, как сферический конь в вакууме, а в какой-то степени будет интегрирован с другими. В итоге все они вместе образуют некий слой взаимосвязанных между собой и при этом самостоятельно развивающихся организмов. И скорее всего, их развитие осуществляется совершенно разными командами.

    Хорошие примеры такой среды — это Яндекс (поиск, Директ, карты, почта, вертикальные и внутренние сервисы) или Google. Понятно, что у перечисленных гигантов технологии в каждом продукте свои, но если взять компанию поменьше и работающую в более узкой предметной области, то можно предположить, что веб-продукты будут выполнены на одних технологиях (языках программирования, фреймворках и.т.д).

    Именно об опыте в организации архитектуры всей линейки продуктов у такой компании я и хочу рассказать.

    Наши внешние веб-продукты — это онлайн-версия, открытый справочный API и картографический, сервис отзывов. Внутренние: CRM, биллинг, алгоритмические вычисления, системы статистики и
    экспорта-импорта данных.

    Выведем два понятия:

    Веб-инфраструктура — совокупность веб-продуктов компании, связанных между собой и работающих в близких предметных областях.

    Технологическая база — единая кодовая база для веб-инфраструктуры компании. Содержит набор программных блоков с высоким и низким уровнем абстракции. Высокоуровневые блоки — это уже наработанные командой разработчиков сущности в предметной области компании. Низкоуровневые блоки — это, например, набор библиотек или фреймворков.

    Наверняка не раз каждый из вас работал над несколькими проектами и реализовывал модули, которые потом переиспользовал в следующем проекте. Не всегда это выходит гладко: образуется совершенно отдельная версия модуля в отдельной ветке 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 + система сборки = легко конфигурируемые и собираемые продукты.

    Итог


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


    P.S. И да, мы ищем yii-разработчиков в Новосибирске и Киеве. Приходите! :-)
    • +40
    • 25.1k
    • 8
    2ГИС
    163.86
    Карта города и справочник предприятий
    Share post

    Comments 8

      +8
      Рад, что yii идет в массы. Достойнейший фреймворк, а вакансии были в основном лишь на Zend и Symfony
        +3
        1) спасибо за интересную статью
        2) ждем продолжения для описания «почему были выбранны именно эти технологии» (меня интересует в разрезе выбора phing)

        чисто организационно:
        1) как много времени уходит на ревью и рефакторинг кода? при данной организации кода есть очень большие риски, что разработчик приладит функционал в не тот слой, в который надо.
        2) организация проекта в таком виде шла с нуля или приходилось отталкиваться от существующих рудиментов?
          +6
          Очень приятно, что статья понравилась. Спасибо. Будем писать еще.
          По вопросам:
          1. У нас существует программа адаптации для новых сотрудников, поэтому есть время разобраться в структуре проектов, посмотреть как реализован существующий функционал

          По опыту, вопросы чаще возникают по таким определениям в yii как компонент, расширение, виджет, хелпер, модуль. Как лучше/правильнее их применять и где.
          На ревью времени уходит не больше, чем на обычные yii-проекты, функциональные блоки которого уже хорошо известны команде.
          Для рефакторинга образуется больше возможностей, так как вся бизнес логика по работе с сущностью предметной области сосредоточена в расширении и его моделях. Работа с расширением идет через его API. Внутреннюю реализацию мы можем рефакторить, главно соблюдать заявленное API :)

          2. Организация проектов в таком виде шла с нуля. Немногочисленные оставшиеся рудименты постепенно переводятся на новую архитектуру.
          +5
          Что-то после скриншота блинов захотелось… :)
            0
            Такая архитектура обычно закладывается на годы вперёд, и это правильно. Но есть нюанс: веб стал невменяемо динамичным, и скорость только нарастает: например, языки появляются и уходят за считанные годы (раньше были хотя бы десятилетия); про фреймворки вообще молчу.

            Рассчитана ли ваша система на гетерогенность в плане языков/фреймворков? Например, если в какой-то момент через 2-3-4 года вы поймёте, что python-программистов на рынке стало больше и они лучше, чем php, а скорость обработки запросов в pypy в N раз выше чем в php. Или в результате коллективного умопомрачения мирового масштаба все начнут использовать Java/NodeJS/Go/ещё-что-нибудь. Не суть важно почему.
              0
              Ну собственно чтобы разрабатывать систему на уровне интерфейсов и абстракций, а не на уровне конкретных реализаций и была заложена такая архитектура. Сейчас центральное место занимает Yii, но ничто нам не мешает начать плавный процесс перехода на другие ЯП или фреймворки, переписывая приложение за приложением и компонент за компонентом, следя чтобы не ломались интерфейсы между ними.

              Собственно у нас это сейчас и происходит. Мы планомерно меняем высоконагруженные модули на PHP на серверные демоны на C++ c которыми работаем через Thrift протокол. А часть функциональных приложений прям напрашивается, чтобы их переписали на Erlang, т.к. от них требуется работа на распределенном кластере, высокая надежность и горячее обновление кода.
              0
              Возможно поздно спрашивать тут, спустя N-количество месяцев, но все же… В своем проекте вы отказывались от Yii ActiveRecords? И насколько у Вас большая нагрузка?
                0
                Спасибо за интересную статью! При чтении возник один вопрос, как устроено ваше АПИ? А именно как устроена передача объектов (данных) из нижних уровней, в верхние?
                На сегодняшний день приложения на Yii чаще всего оперируют объектами ActiveRecords, как объектами предметной области. В данной слоистой архитектуре мы должны абстрагироваться от моделей, и инкапсулировать их в расширения. Т.е. мы не можем передавать их вверх другим слоям.
                Значит необходимо создать некую коллекцию классов описывающих объекты предметной которая будет являться частью АПИ, и будет неизменна вне зависимости от реализации расширения.
                Для вашего примера с UserExstentionAPI необходимо как минимум создать класс User, который после успешной авторизации будет передаваться вверх модулям-пользователям расширения.

                Если я все правильно понимаю, то такой подход может значительно увеличить количество работы, и кода к примеру:
                описали модели внутри модуля, реализовали бизнес логику, написали АПИ которое состоит из методов и нескольких классов (объектов данных), которые приблизительно повторяют модели, но ими не являются.

                Так вот вопрос, как реализовано это у вас?

                Only users with full accounts can post comments. Log in, please.