Переход от 2-х звенки к архитектуре служб в парадигме SOA

В данной статье я бы хотел поделиться своим опытом организации перехода от классической 2-х звенки к парадигме SOA, также затронуть некоторые аспекты деплоя в рамках enterprise-решения и интеграции со смежными службами, написанными на Java

Предистория

Последние 3 года я работал в отделе внутренней автоматизации компании Новотелеком. Основное развитие систем для автоматизации внутренних процессов смежных с IT подразделений началось в 2008 году вместе с активным ростом самой кампании. В то время руководство не ставило целей делать качественные решения, основной целью было завоевание рынка, и это отложило отпечаток на принимаемые решения. Основной из систем над которыми работает отдел ВА — это внутренняя CRM система, которая включает в себя также элементы планирования человеческих ресурсов и справочные системы. Долгое время система писалась на самописном фреймворке, но после знакомства с Yii и реализации сайта компании на нем, было принято решение перевода системы на данный фреймворк. Обсуждения почему был выбран именно этот фреймворк выходят за рамки моей статьи.

При первоначальном переводе на Yii наша команда решила оставить архитектуру без кардинальных изменений т.к. темп разработки рос, а внутренние процессы организации работы были еще не на том уровне, чтобы ставить в приоритет архитектурные аспекты. Таким образом спустя год на Yii было переведено примерно треть функционала текущей системы. Определенно мы увеличили темп разработки т.к. фреймворк помог решить много типовых задач, на решение которых ранее уходило много времени. Но оставался один момент, который начал беспокоить все чаще. Нужно было все таки решить архитектурные вопросы, пока все не зашло в зону не возврата. Решающим фактором в принятии решения по переходу к службам вместо одного монолитного приложения стало выделение в кампании роли межсистемного архитектора, который начал приводить в порядок все внутренние процессы и архитектурные аспекты. На данную роль назначили архитектора одной из смежных групп, которые пишут ПО на Java. В их группе изначально была выбрана soa-парадигма и за последние 3-5 лет эта практика показала все плюсы и минусы данного подхода.

Немного об окружении

  • OS Debian 6
  • веб-сервер nginx
  • php 5.3
  • framework yii
  • система сборки и деплоя phing
  • службы устанавливаются как deb-пакеты
  • протокол интеграции со смежными службами hessian


Ограничения

Изначально был выбран подход разработки с использованием модулей, которые объединяли в себе слои бизнес логики, например, заявка клиента, карточка клиента и т.п. При переходе к службам встал вопрос как организовать структуру приложения реализовать, как деплоить, как хранить в SVN.
Для решения подобных задач есть несколько подходов. Один из них в свое время предложили ребята из 2Gis в своей статье. Также можно использовать похожее решение в организации структуры приложения — Yiinitializr. Решение от 2amigos достаточно хорошее, но просто так взять и перенести все на него было уже достаточно проблематично. Поэтому пришлось сделать свой велосипед на основе уже имеющихся примеров и полученном опыте работы с Yii

Немного о SOA

На первом этапе были выделены следующие службы


На схеме выделены 2 внутренних слоя: core и portal
Core — слой общих компонентов, необходимых для корректной работы всех служб, плюс библиотеки, например yii, behat, ratcher и т.д., а также набор компонентов для интеграции со смежными службами.
Portal — пользовательские интерфейсы.
Кроме этого на схеме представлен слой внутренних и внешних служб и протоколы взаимодействия

Подробнее о реализации

Каждое приложение, будь то служба или пользовательский интерфейс, имеет одну из двух точек входа: web и console. Например, в случае службы web точкой входа является rest api.

Хранение конфигов сделано по аналогии с yiinitializr с учетом наших особенностей. Таким образом каждое приложение имеет следующую структуру конфигов:
  • service.web.php — конфиг для веб-части
  • service.console.php — конфиг для консольной части
  • service.test.php — конфиг для запуска автотестов
  • service.base.php — общий конфиг для всех предыдущих. Подключается внутри каждого из них
  • env/local.php — конфиг в который вынесены параметры, зависящие от окружения (dev, test или production)
  • sections/routes.php — правила маршрутизации

За инициализацию приложения отвечает компонент ApplicationDispatcher в пакете common, который реализует следующие функции: подготовка конфига, прописывание внутренних алиасов, прописывание маршрутов и предоставляет методы для получения путей до директорий в зависимости от окружения, например, до папки с временными файлами или логами.

Компонент подключается в index.php и вызывается следующим образом

Исходный код 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();


Исходный код метода create
 /**
 * Создание экземпляра приложения
 * @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;
}


Логика подготовки файла конфигурации мало чем отличается от той, что используется в Yii-Boilerplate или Yiinitializr
	/**
	 * Склеивание конфигов в один.
	 * @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-пакета приложение раскидывается по следующим директориям:
  • /usr/share/<имя-службы>/protected/ — исходники службы
  • /usr/share/doc/<имя-службы>/ — пример конфига local.default.php
  • /usr/bin/<имя-службы> — исполняемый файл для запуска роботов
  • /var/www/<имя-службы>/htdocs/ — директория для веб-севера
  • /var/tmp/<имя-службы>/ — директория для хранения временных файлов
  • /var/log/<имя-службы>/ директория для хранения логов службы

Сборка пакета с помощью утилиты Phing

За сборку пакета отвечает утилита phing, которая:
  • выкачивает из svn актуальную версию из ветки trunk
  • разносит все по нужным директориями в соответствии с тем как описано выше
  • вызывает команду для сборки пакета
  • создает метку в svn
  • заливает пакет на сервер-репозиторий debian пакетов

Пример кода из задачи по формированию структуры пакета
  <!-- ============================================  -->
    <!-- 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 для пакетов, что облегчает жизнь ребятам из поддержки. Также стало проще параллельно разрабатывать и внедрять новый функционал.

Статья носит ознакомительный характер т.к. в одной статье не раскрыть все аспекты. Готов ответить на вопросы, если они появятся.

В дополнение приведу немного ссылок на материалы про парадигму SOA:
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 15

    0
    А где SOA-то? Как сделана маршрутизация, как сделаны service descriptions/service discovery? Где проходит граница автономности сервисов? Как вы решаете проблемы версионирования контрактов? Есть ли оркестровка, и если да, то на базе чего?
      0
      Где проходит граница автономности сервисов?


      На данный момент лишь сделан этап разделения бизнес логики на слои/службы с заранее оговоренным api, доступным либо внутри группы через механизм компонентов в Yii (эту задачу в частности выполняет backend), либо для всех смежных групп через rest (shared). Дальнейшим шагом развития и перехода к soa я вижу в организации реестра служб, над этой задачей в частности и работает наш межсистемный архитектор. Мы же со своей стороны постарались уйти от монолитного приложения к службам с заранее очерченными границами, выполняющими конкретную задачу бизнеса.
        0
        Как вы решаете проблемы версионирования контрактов?
          0
          В нашем отделе службы только на начальном этапе и проблемы обратной совместимости апи еще не возникало.
          Если же говорить о практике смежных отделов, то при разработке новых методов/доработке старых архитектор каждой группы анализирует возможные подводные камни, подключая при этом архитекторов или разработчиков из смежных групп. Если возникают моменты когда требуется кардинальная переработка существующего метода, например, требуется изменить сигнатуру, то добавляется новый метод. Какого либо версионирования помимо версии debian-пакета на данный момент нет. Все новые пакеты проходят интеграционное тестирование во всех остальных группах разработки.
          К сожалению я не погружен во все тонкости данной парадигмы и возможно не до конца понял ваш вопрос.
            0
            Как вы решаете проблемы консистентности бизнес-состояния между двумя службами? Предположим, за информацию о кредите покупателя отвечает один сервис, за информацию о наличии товара на складе — другой. Как вы решаете проблему того, что товар на складе резервируется только в случае достаточности кредитного лимита, но лимит уменьшается только в том случае, когда товар есть на складе?

            Как решаются проблемы отказоустойчивости и временной недоступности сервисов?
      +2
      Как вы решаете проблемы консистентности бизнес-операций между двумя службами?

      В компании активно развивается направление BPM, в частности на базе продукта Activiti реализуются некоторые процессы связанные с подключением услуг. Внедрение BPM показало рентабельность данного решения и на него переводится все больше БП. Но у Activiti есть один недостаток. Т.к. это по сути движок БП, то нормального интерфейса для работы с экземплярами процесса нет. Ранее в компании для автоматизации БП использовался workflow engine jira3

      Как решаются проблемы отказоустойчивости и временной недоступности сервисов?

      насколько мне известно данный вопрос решается лишь на уровне SLA + холодный резерв. Возможно для критически важных для бизнеса служб используется другая схема отказоустойчивости, например, в биллинге.

      Прошу прощения, что написал новым комментарием, а не ответом на предыдущий.
        –2
        В компании активно развивается направление BPM, в частности на базе продукта Activiti реализуются некоторые процессы связанные с подключением услуг. Внедрение BPM показало рентабельность данного решения и на него переводится все больше БП. Но у Activiti есть один недостаток. Т.к. это по сути движок БП, то нормального интерфейса для работы с экземплярами процесса нет. Ранее в компании для автоматизации БП использовался workflow engine jira3

        Это ответ вообще на другой вопрос.

        насколько мне известно данный вопрос решается лишь на уровне SLA + холодный резерв. Возможно для критически важных для бизнеса служб используется другая схема отказоустойчивости, например, в биллинге.

        То есть на уровне вашей архитектуры — никак.

        Собственно, я изначально по вашей статье так и подумал — собственно с SOA-то вы еще столкнуться не успели. Декомпоновка на автономные сервисы — это еще не SOA. Так что писать о «переходе» архитектур вам рановато. Деплой компонентов — да.
          +1
          Это ответ вообще на другой вопрос.

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

          Собственно, я изначально по вашей статье так и подумал — собственно с SOA-то вы еще столкнуться не успели. Декомпоновка на автономные сервисы — это еще не SOA. Так что писать о «переходе» архитектур вам рановато. Деплой компонентов — да.


          Тут вы правы, о полноценной реализации soa говорить рано. Основной задачей было начать процесс перехода, который естественно включает в себя много этапов. В частности благодаря вашим вопросам я узнал новые аспекты, на которые следует обратить внимание. Целью статьи было показать как можно организовать структуру приложений в случае реализации служб, плюс возможно затронуть некоторые аспекты деплоя через deb-пакеты.
            0
            Например, абоненту нужно активировать точку доступа лишь когда он пополнит баланс на необходимую сумму, а баланс клиент может пополнить лишь при созданном лицевом счете и выделенной точке доступа. В данном случае идет взаимодействие между несколькими службами

            Как решается вопрос консистентности данных?

            как можно организовать структуру приложений в случае реализации служб

            Вы понимаете, что после решения озвученных выше проблем эта организация скорее всего изменится?
              0
              Как решается вопрос консистентности данных?


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

              Если же требуется выполнить 2 связанных элементарных операции вне процесса, то данная логика реализуется в esb службе, в которой уже заложена логика в зависимости от результата выполнения каждой операции. Конкретная реализация зависит от конкретной задачи. Общего алгоритма на все случаи жизни нет.

              Вы понимаете, что после решения озвученных выше проблем эта организация скорее всего изменится?

              Безусловно что-то придется допиливать, без необходимого опыта сделать все идеально с первого раза нельзя. Но имея базис и некий опыт работы с данной структурой гораздо проще спланировать дальнейшее развитие системы. В голову приходят строки «Москва не сразу строилась»
                0
                данную задачу решает BPM, собственно это его основная задача. Например, сначала создается лицевой счет, если удалось то процесс переходит на следующий шаг, если нет, то выполняет заданное в процессе действие.

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

                Но имея базис и некий опыт работы с данной структурой гораздо проще спланировать дальнейшее развитие системы.

                К сожалению, некоторые вещи в систему нужно закладывать изначально, иначе развитие будет сопоставимо по сложности (а то и превышать, учитывая требования обратной совместимости) с разработкой с нуля.
                  0
                  К сожалению, некоторые вещи в систему нужно закладывать изначально, иначе развитие будет сопоставимо по сложности (а то и превышать, учитывая требования обратной совместимости) с разработкой с нуля.


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

                  В монолитной системе это решается транзакцией, при которой либо будет изменено состояние обеих систем, либо не будет изменено ничего. А в вашей распределенной?


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

                    Да я, в общем, основное, что помню, уже перечислил. Стабильность контракта, версионирование, маршрутизация, отказоустойчивость, обеспечение консистентности. У Эрла наверняка еще перечислено, но зачем же книжку пересказывать?

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

                    Искренне завидую.

                    Возможно на стороне смежных групп это решается проще ибо написаны они на java и взаимодействие между ними требует меньше накладных ресурсов.

                    А вот это как раз показывает, что вы не до конца грокнули SOA. Одна из основных прелестей SOA — это интероперабельность, для честной SO-архитектуры не важно, на чем написан каждый конкретный сервис (добавьте интероперабельность в список выше).
                      0
                      Искренне завидую.


                      Думаю не стоит быть столь категоричным. Было немало не менее интересных и увлекательных задач, но это уже за рамками данной темы.

                      А вот это как раз показывает, что вы не до конца грокнули SOA


                      Да вы правы и я это выше отметил. Понимание данной парадигмы один из шагов в моем развитии как профессионала, я думаю многие разработчики проходят через это рано или поздно.
                      Собственно данная статья была написана еще и с целью получения фидбека и комментариев от людей, ранее столкнувшихся с soa и наступивших на свои грабли.
            +1
            Собственно роль дирижера в нашем случае выполняет либо Activiti, если это бизнес процесс с оговоренными границами, либо ESB служба, реализующая часть бизнес логики.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое