- PVSM.RU - https://www.pvsm.ru -
В данной статье я бы хотел поделиться своим опытом организации перехода от классической 2-х звенки к парадигме SOA, также затронуть некоторые аспекты деплоя в рамках enterprise-решения и интеграции со смежными службами написанными на Java
Последние 3 года я работал в отделе внутренней автоматизации компании Новотелеком. Основное развитие систем для автоматизации внутренних процессов смежных с IT подразделений началось в 2008 году вместе с активным ростом самой кампании. В то время руководство не ставила целей делать качественные решения, основной целью было завоевание рынка, и это отложило отпечаток на принимаемые решения. Основной из систем над которыми работает отдел ВА — это внутренняя CRM система, которая включает в себя также элементы планирования человеческих ресурсов и справочные системы. Долгое время система писалась на самописном фреймворке, но после знакомства с Yii и реализации сайта компании на нем, было принято решение перевода системы на данный фреймворк. Обсуждения почему был выбран именно этот фреймворк выходят за рамки моей статьи.
При первоначальном переводе на Yii наша команда решила оставить архитектуру без кардинальных изменений т.к. темп разработки рос, а внутренние процессы организации работы были еще не на том уровне, чтобы ставить в приоритет архитектурные аспекты. Таким образом спустя год на Yii было переведено примерно треть функционала текущей системы. Определенно мы увеличили темп разработки т.к. фреймворк помог решить много типовых задач, на решение которых ранее уходило много времени. Но оставался один момент, который начал беспокоить все чаще. Нужно было все таки решить архитектурные вопросы, пока все не зашло в зону не возврата. Решающим фактором в принятии решения по переходу к службам вместо одного монолитного приложения стало выделение в кампании роли межсистемного архитектора, который начал приводить в порядок все внутренние процессы и архитектурные аспекты. На данную роль назначили архитектора одной из смежных групп, которые пишут ПО на Java. В их группе изначально была выбрана soa-парадигма и за последние 3-5 лет эта практика показала все плюсы и минусы данного подхода.
Изначально был выбран подход разработки с использованием модулей, которые объединяли в себе слои бизнес логики, например, заявка клиента, карточка клиента и т.п. При переходе к службам встал вопрос как организовать структуру приложения реализовать, как деплоить, как хранить в SVN.
Для решения подобных задач есть несколько подходов. Один из них в свое время предложили ребята из 2Gis в своей статье [2]. Также можно использовать похожее решение в организации структуры приложения — Yiinitializr [3]. Решение от 2amigos достаточно хорошее, но просто так взять и перенести все на него было уже достаточно проблематично. Поэтому пришлось сделать свой велосипед на основе уже имеющихся примеров и полученном опыте работы с Yii
На первом этапе были выделены следующие службы
На схеме выделены 2 внутренних слоя: core и portal
Core — слой общих компонентов, необходимых для корректной работы всех служб, плюс библиотеки, например yii, behat, ratcher и т.д., а также набор компонентов для интеграции со смежными службами.
Portal — пользовательские интерфейсы.
Кроме этого на схеме представлен слой внутренних и внешних служб и протоколы взаимодействия
Каждое приложение, будь то служба или пользовательский интерфейс, имеет одну из двух точек входа: web и console. Например, в случае службы web точкой входа является rest api.
Хранение конфигов сделано по аналогии с yiinitializr с учетом наших особенностей. Таким образом каждое приложение имеет следующую структуру конфигов:
За инициализацию приложения отвечает компонент ApplicationDispatcher в пакете common, который реализует следующие функции: подготовка конфига, прописывание внутренних алиасов, прописывание маршрутов и предоставляет методы для получения путей до директорий в зависимости от окружения, например, до папки с временными файлами или логами.
Компонент подключается в index.php и вызывается следующим образом
// Скрипт инициализации приложения
if(!file_exists('/usr/share/ntk-rm-common/protected/components/ApplicationDispatcher.php')) {
throw new Exception('Необходимо установить пакет ntk-rm-common');
}
require_once('/usr/share/ntk-rm-common/protected/components/ApplicationDispatcher.php');
// Создаем и запускаем экземпляр приложения
$dispatcher = ApplicationDispatcher::getInstance();
// Указываем тип окружения: бой или разработка
$dispatcher->setEnvironment(ApplicationDispatcher::ENV_PRODUCTION);
// Указываем тип приложения
$dispatcher->setApplicationType(ApplicationDispatcher::APP_TYPE_WEB);
// Запускаем скрипт создания приложения
$dispatcher->create('crm')->run();
/**
* Создание экземпляра приложения
* @param $service - название инициализируемой службы
* @return mixed
* @throws Exception
*/
public function create($service) {
$this->service = $service;
if(empty($this->app_type)) {
throw new Exception('Укажите тип приложения: web или console');
}
// Подключаем глобальный хелпер
require_once $this->getBasePath('common') . '/helpers/global.php';
$config = $this->prepareConfig();
// прописываем путь до папки с временными файлами
$config['runtimePath'] = $this->getRuntimePath($service);
// прописываем путь до папки с исходниками - protected
$config['basePath'] = $this->getBasePath($service);
$this->setAliases();
if ($this->app_type == self::APP_TYPE_WEB) {
$this->app = Yii::createWebApplication($config);
// Подгружаем правила маршрутизации
$this->setRoutes();
// Прописываем путь до папки assets в зависимости от окружения
$basePath = $this->getHtdocsPath($this->service) . '/assets/';
$this->app->getAssetManager()->setBasePath($basePath);
} else {
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
$this->app = Yii::createConsoleApplication($config);
}
return $this->app;
}
/**
* Склеивание конфигов в один.
* @return array|mixed
* @throws Exception - ошибка в случае если не найден конфиг приложения
*/
private function prepareConfig() {
if (!$this->isExistsServiceConfig()) {
throw new Exception('Конфигурационный файл службы «' . $this->getServiceConfigName() . '» не найден. Проверьте правильность пути.');
}
// Подключаем конфиги службы
$service_configs = array(
'/' . $this->service . '.' . $this->app_type . '.php',
'/env/local.php'
);
$config = $this->mergeConfigs(array(), $service_configs , $this->getConfigPath(($this->service)));
// Подключаем общие конфиги
$common_configs = array(
'/env/local.php',
'/common.base.php',
$this->app_type == self::APP_TYPE_WEB ? '/common.web.php' : '/common.console.php',
);
$config = $this->mergeConfigs($config, $common_configs, $this->getConfigPath('common'));
// Подключаем конфиги backend части
$backend_configs = array(
'/php-backend.base.php',
'/env/local.php',
);
$config = $this->mergeConfigs($config, $backend_configs, $this->getConfigPath('php-backend'));
return $config;
}
Как уже было сказано выше установка служб в production происходит через deb-пакеты. Для полной поддержки debian-way при установке deb-пакета приложение раскидывается по следующим директориям:
За сборку пакета отвечает утилита phing, которая:
<!-- ============================================ -->
<!-- Target: prepare -->
<!-- ============================================ -->
<target name="prepare" depends="clean">
<echo msg="Подготовка данных для создания пакета" />
<mkdir dir="${project.packageDir}" />
<copy todir="${project.packageDir}">
<fileset dir="${project.basedir}/debian">
<include name="**/*" />
<exclude name=".svn" />
<exclude name="cron.d/" />
<exclude name="cron.d/*" />
</fileset>
</copy>
<exec command="svn info | grep 'URL: '" outputProperty="project.tmp.svnInfo" />
<php expression="end(explode(': ', '${project.tmp.svnInfo}'));"
returnProperty="project.tmp.svnUrl" />
<echo msg="Получение исходных данных из SVN" />
<exec command="rm -Rf ${project.packageDir}/var/www/ntk-rm-crm/*" />
<exec command="svn export --force ${project.tmp.svnUrl} ${project.packageDir}/export/"/>
<echo msg="Создание и наполнение папки для веб-сервера /var/www/ntk-rm-crm/htdocs/" />
<mkdir dir="${project.packageDir}/var/www/ntk-rm-crm/" />
<copy todir="${project.packageDir}/var/www/ntk-rm-crm/htdocs/" >
<fileset defaultexcludes="false" expandsymboliclinks="true" dir="${project.packageDir}/export/htdocs/">
<include name="**/*" />
</fileset>
</copy>
<mkdir dir="${project.packageDir}/var/www/ntk-rm-crm/htdocs/assets/" />
<copy file="${project.packageDir}/var/www/ntk-rm-crm/htdocs/index-prod.php"
tofile="${project.packageDir}/var/www/ntk-rm-crm/htdocs/index.php" overwrite="true" />
<delete file="${project.packageDir}/var/www/ntk-rm-crm/htdocs/index-prod.php" />
<echo msg="Создание и наполнение папки исходников /usr/share/ntk-rm-crm/protected/" />
<mkdir dir="${project.packageDir}/usr/share/ntk-rm-crm/protected/" />
<copy todir="${project.packageDir}/usr/share/ntk-rm-crm/protected/" >
<fileset defaultexcludes="false" expandsymboliclinks="true" dir="${project.packageDir}/export/protected/">
<include name="**/*" />
<exclude name="configs/*" />
<exclude name="**/yiic*" />
</fileset>
</copy>
<delete dir="${project.packageDir}/usr/share/ntk-rm-crm/protected/configs/" includeemptydirs="true" />
<echo msg="Генерация файла yiic.php для боевого окружения" />
<copy file="${project.packageDir}/export/protected/yiic-prod.php"
tofile="${project.packageDir}/usr/share/ntk-rm-crm/protected/yiic.php" overwrite="true" />
<copy file="${project.packageDir}/export/protected/yiic-prod"
tofile="${project.packageDir}/usr/bin/ntk-rm-crm" overwrite="true" />
<echo msg="Создание и наполнение папки конфигов /etc/ntk-rm-crm/" />
<mkdir dir="${project.packageDir}/etc/ntk-rm-crm/" />
<copy todir="${project.packageDir}/etc/ntk-rm-crm/" >
<fileset defaultexcludes="false" expandsymboliclinks="true" dir="${project.packageDir}/export/protected/configs/">
<include name="**/*" />
<exclude name="**/crm.test.php" />
<exclude name="**/local.default.php" />
</fileset>
</copy>
<echo msg="Создание примера конфига окружения в /usr/share/doc/ntk-rm-crm/" />
<mkdir dir="${project.packageDir}/usr/share/doc/ntk-rm-crm/" />
<copy file="${project.packageDir}/export/protected/configs/env/local.default.php"
tofile="${project.packageDir}/usr/share/doc/ntk-rm-crm/local.default.php" overwrite="true" />
<echo msg="Создание cron-файла /etc/cron.d/ntk-rm-crm" />
<copy file="${project.basedir}/debian/cron.d/ntk-rm-crm"
tofile="${project.packageDir}/etc/cron.d/ntk-rm-crm" overwrite="true" />
<echo msg="Создание папки для логов /var/log/ntk-rm-crm/" />
<mkdir dir="${project.packageDir}/var/log/ntk-rm-crm/" />
<echo msg="Создание папки для временных файлов /var/tmp/ntk-rm-crm/" />
<mkdir dir="${project.packageDir}/var/tmp/ntk-rm-crm/" />
<echo msg="Удаление папки export" />
<delete dir="${project.packageDir}/export/" includeemptydirs="true" />
</target>
В заключении хотелось бы отметить, что у данного решения есть как плюсы, так и минусы. Из основных плюсов, полученных после внедрения данной схемы можно выделить поддержку debian-way для пакетов, что облегчает жизнь ребятам из поддержки. Также стало проще параллельно разрабатывать и внедрять новый функционал.
Что можно почитать:
Автор: Unclead
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/yii/60205
Ссылки в тексте:
[1] hessian: http://hessianphp.sourceforge.net/
[2] своей статье: http://habrahabr.ru/company/2gis/blog/130162/
[3] Yiinitializr: http://yiinitializr.2amigos.us/
[4] www.soa-manifesto.org/default_russian.html: http://www.soa-manifesto.org/default_russian.html
[5] www.gartner.com/it/content/754400/754413/twelve_common_soa_mistakes.pdf: http://www.gartner.com/it/content/754400/754413/twelve_common_soa_mistakes.pdf
[6] essay.utwente.nl/57339/1/scriptie_Steghuis.pdf: http://essay.utwente.nl/57339/1/scriptie_Steghuis.pdf
[7] Источник: http://habrahabr.ru/post/223385/
Нажмите здесь для печати.