Создание микросервисов — страница 18 из 69

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

Когда жизненный цикл основных понятий заданной области четко смоделирован подобным образом, достигается вполне приемлемый эффект. У нас получается не только одно место для рассмотрения несоответствий состояния (например, когда кто-то пытается обновить данные об уже удаленном клиенте), но также место для придания поведения на основе таких изменений состояния.

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

Реактивные расширения

Реактивные расширения (reactive extensions, часто сокращается до Rx) представляют собой механизм компоновки результатов нескольких вызовов и запуска операций по их обработке. Сами вызовы могут быть как блокирующими, так неблокирующими. В основном Rx меняют порядок традиционных потоков. Вместо запрашивания каких-либо данных с последующим выполнением в отношении этих данных каких-либо операций изучается исход операции (или набора операций) и происходит реагирование на какие-либо изменения. Некоторые реализации Rx позволяют выполнять какие-либо функции над этими наблюдаемыми результатами, например в RxJava допускается использование таких традиционных функций, как map или filter.

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

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

DRY и риски повторного использования кода в мире микросервисов

Одним из часто употребляемых акронимов является DRY: don’t repeat yourself — «не повторяйтесь». Хотя это определение зачастую упрощенно рассматривается как попытка избежать использования продублированного кода, более точное значение DRY заключается в стремлении избежать продублированности поведения и осведомленности нашей системы. В целом это весьма разумный совет. Наличие большого количества строк кода, выполняющих одно и то же, делает объем исходного кода больше необходимого, затрудняя тем самым его осмысление. При желании изменить поведение, продублированное во многих частях системы, совсем не трудно забыть о каких-либо местах, в которые нужно вносить изменения, что может привести к появлению ошибок. Поэтому в целом придерживаться DRY-принципа все же стоит.

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

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

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

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

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

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

В качестве понравившейся мне модели клиентских библиотек можно назвать Amazon Web Services (AWS). Подразумеваемые SOAP- или REST-вызовы веб-сервиса могут быть сделаны напрямую, но каждый из них заканчивается использованием только одного из существующих наборов средств разработки программ (Software Development Kits (SDK)), предоставляющего абстракции поверх основного API. Но эти SDK написаны сообществом разработчиков AWS, а не теми, кто работал над самим API. Похоже, что такая степень разделения сработала и позволила избежать некоторых подводных камней клиентских библиотек. Одна из причин такого успеха заключается в том, что клиент знает, когда происходит обновление. Если вы сами пойдете по пути использования клиентских библиотек, обеспечьте точно такое же развитие событий.

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

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

Доступ по ссылке

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

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