В последние несколько лет мы неоднократно использовали данную схему в компании ThoughtWorks, и сам я делал это не один раз. Одним примечательным примером был клиент, искавший возможность выпустить новый сайт для своих продуктов. Сначала он хотел сделать все на CMS, но ему еще предстояло выбрать, на какой именно системе это делать. Вместо этого мы предложили ему рассматриваемый здесь подход и приступили к разработке лицевого сайта. В ожидании выбора CMS мы имитировали такую систему с помощью веб-сервиса, который просто выставлял наружу статический контент. В итоге у нас при использовании сервиса-имитатора контекста, производящего выставляемое для живого сайта содержимое, получился вполне работоспособный сайт еще до выбора CMS. Чуть позже мы смогли просто вставить выбранный наконец-то инструментарий, не внося никаких изменений в лицевое приложение.
Используя данный подход, мы свели к минимуму работу CMS и переместили адаптацию в собственный технологический стек.
CRM, или Customer Relationship Management, то есть система управления взаимосвязями с клиентами, — это часто встречающийся инструмент, считающийся неким чудовищем, способным вселить страх в душу даже самого отважного архитектора. Этот сектор, судя по характеристикам таких поставщиков, как Salesforce или SAP, изобилует примерами инструментов, пытающихся все делать за вас. Это может привести к тому, что сам инструмент может стать и единственной точкой сбоя, и запутанным узлом зависимостей. Многие реализации CRM-инструментов, попадавшиеся мне на глаза, являли собой массу наилучших примеров связанных (в отличие от сцепленных) сервисов.
Обычно поначалу масштабы применения такого инструмента невелики, но со временем он становится все более важной частью стиля работы вашей организации. Проблема в том, что направления и варианты в рамках этой теперь уже жизненно важной для вас системы зачастую выбираете не вы, а сам поставщик инструмента.
Недавно я участвовал в попытке возвращения управления. Организация, с которой я работал, пришла к выводу, что, хотя CRM-инструментарий использовался в ней для решения многих задач, от увеличения стоимости платформы особой выгоды они не получали. В то же время несколькими внутренними системами для интеграции использовались далеко не идеальные API CRM. Мы хотели переместить архитектуру системы в то место, в котором имелись сервисы, моделирующие нашу область бизнеса, а также заложить основу для потенциальной миграции.
Сначала мы определили для нашей области основные концепции, которыми уже владела CRM-система. Одной из них была концепция проектов, то есть то, что может быть назначено штатному сотруднику. Проектная информация нужна была нескольким другим системам. То, чем мы занимались, заменяло сервис проектов. Этот сервис выставлял проекты в виде RESTful-ресурсов, и внешние системы могли перемещать свои точки интеграции на новый сервис, с которым было легче работать. Внутри сервис проектов представлял собой всего лишь фасад, за которым скрывались детали основной интеграции. Все это можно увидеть на рис. 4.12.
Рис. 4.12. Использование фасадных сервисов в качестве маскировки основного CRM
Работа, которая в момент написания этих строк еще продолжалась, заключалась в определении в нужной области других понятий, с которыми справлялась система CRM, и создании скорее фасадов для них. Когда настанет время уйти от базовой CRM, можно будет по очереди пересмотреть каждый фасад, чтобы решить, есть ли соответствующие требованиям внутренние программные решения или что-либо из готовых программных продуктов.
Когда дело доходит до устаревших или даже COTS-платформ, которые находятся полностью под вашим контролем, приходится справляться с ситуациями, при которых нужно их удалить или по крайней мере от них отойти. В таком случае пригодится шаблон под названием Strangler Application Pattern. Во многом подобно примеру выстраивания лицевой части CMS с помощью собственного кода, с использованием шаблона Strangler добывают и перехватывают вызовы к старой системе. Это дает возможность принять решение, нужно ли направлять эти вызовы существующему устаревшему коду или же направлять их к новому коду, который вы могли написать. Это позволяет со временем заменить функциональные свойства, не требуя для этого больших переделок кода.
Когда же дело касается микросервисов, то для выполнения перехвата вместо использования одного монолитного приложения, перехватывающего все вызовы к существующей устаревшей системе, можно воспользоваться серией микросервисов. Захват и перенаправление исходных вызовов может оказаться в этой ситуации намного сложнее, что может потребовать использования прокси-сервиса, делающего все это за вас.
Мы рассмотрели ряд вариантов интеграции, и я поделился своими размышлениями о том, какие решения могут скорее всего обеспечить то, что наши микросервисы останутся как можно более разобщенными со всем, с чем они совместно работают.
• Любой ценой избегайте интеграции с помощью баз данных.
• Разберитесь с компромиссами между REST и RPC, но как следует присмотритесь к REST как к хорошей стартовой точке для интеграции по схеме «запрос — ответ».
• Отдавайте предпочтение хореографическому, а не оркестровому принципу.
• Избегайте критических изменений и необходимости прибегать к управлению версиями, разобравшись с законом Постела и использованием толерантных считывателей.
• Подумайте о пользовательских интерфейсах как о композиционных уровнях.
Здесь было рассмотрено множество тем, углубиться в которые мы просто не могли. Тем не менее это может послужить неплохой основой для задания вашему пути верного направления, если вы захотите продолжить изучение.
Мы также потратили время на рассмотрение порядка работы с системами, которые не полностью нами контролируются и относятся к COTS-продуктам. Оказывается, данное описание может быть с легкостью применено и к программам, которые мы пишем сами!
Некоторые из показанных здесь подходов могут с одинаковой эффективностью применяться и к устаревшим программным продуктам, но что делать, если нужно решить такие проблемы, как подчинение устаревших систем своим интересам и их разбиение на более полезные для нас части? Подробно данный вопрос рассматривается в следующей главе.
5. Разбиение монолита на части
Мы уже выясняли, на что похож хороший сервис и почему более мелкие сервисы могут подойти лучше. Также рассмотрели важность получения возможности развития конструкций наших систем. Но как справиться с тем, что уже может существовать большой объем исходных кодов, по своей сути не отвечающих принятым нами схемам? Как справиться с декомпозицией этих монолитных приложений, не ввязываясь в широкомасштабное переписывание кода?
Со временем монолит разрастается. Он с устрашающей скоростью обзаводится новыми функциональными возможностями и новыми строками кода. Вскоре он становится большим, ужасным гигантом, живущим в нашей организации, страшно к нему прикасаться или вносить в него изменения. Но еще не все потеряно! Имея в своем распоряжении нужные инструменты, мы можем убить этого зверя.
В главе 3 мы согласились с тем, что наши сервисы должны обладать слабой связанностью и сильным зацеплением. Проблема монолита в том, что зачастую он обладает прямо противоположными качествами. Вместо стремления к сильному зацеплению и группировке, вместо всего того, что обычно изменяется вместе, мы получаем и сцепляем всевозможный неродственный код. Слабая связанность также практически отсутствует: как только понадобится внести изменения в строку кода, это можно будет сделать довольно легко, но я не могу выполнить развертывание этого изменения без потенциального распространения влияния на основную часть монолита, и мне, несомненно, придется заново развертывать всю систему.
Майкл Физерс (Michael Feathers) в своей книге Working Effectively with Legacy Code (Prentice-Hall) дал определение понятия стыка как порции кода, которая не может рассматриваться изолированно и работать, не влияя на весь остальной исходный код. Нам также нужно дать определение стыкам. Но вместо поиска определения для более четкого понимания исходного кода нужно определить стыки, которые могут превратиться в границы сервисов.
Итак, по каким же критериям можно определить хороший стык? Как уже говорилось, превосходными стыками могут послужить ограниченные контексты, поскольку по определению они представляют собой сильно зацепленные, но все же слабо связанные границы внутри организации. Следовательно, первым шагом должно стать определение этих границ в нашем коде.
В большинстве языков программирования имеется понятие пространства имен, позволяющее группировать вместе соответствующий код. Понятие из пакета Java представляет собой, конечно, довольно слабый пример, но, по большому счету, соответствует нашим потребностям. Все остальные широко распространенные языки программирования имеют сходные встроенные понятия, и только JavaScript, вероятно, является исключением.
Представим себе, что имеется большой внутренний монолитный сервис, определяющий основное поведение онлайн-систем MusicCorp. Для начала в соответствии с приемами, рассмотренными в главе 3, нужно определить границы ограниченных контекстов высокого уровня, которые, как мы понимаем, имеются в организации. Затем нужно будет попытаться понять, на какие ограниченные контексты отображается монолит. Представим, что изначально были определены четыре контекста, которые охватывает монолитный внутренний сервис.
• Каталог. Все, что касается метаданных товарных позиций, предлагаемых на продажу.
• Финансы. Отчеты по счетам, платежам, возмещению убытков и т. д.