Перевод книги Кристиана Посты (Christian Posta) Microservices for Java Developers. A Hands-On Introduction to Frameworks & Containers. Продолжение. Предыдущая публикация.
Организационная гибкость
Компании, созданные по лекалам индустрии 20-го века, не предназначены быть гибкими. Они построены для максимальной эффективности, уменьшения вариативности процессов, устранения креативного
Клиенты не вписываются в аккуратные рамки или процессы. Они появляются там, где им хочется. Они хотят общаться с представителем службы поддержки, а не с автоматической телефонной системой. Они требуют вещей, которых нет в меню. Они хотят добавить что-то, чего нет в форме. Клиенты хотят удобства. Они хотят общения. И они злятся, если им приходится ждать.
Это означает, что клиентоориентированные сервисы должны принимать во внимание всё это разнообразие. Они должны быть готовыми реагировать на неожиданное. Это противоположность парадигме эффективности. Клиенты хотят общаться через сервис, который Вы им предоставляете, и если сервис не удовлетворяет их потребности и ожидания, Вам необходима для этого громкая, быстрая обратная связь. Обратная связь может использоваться персоналом, обслуживающим сервис, для быстрой настройки его и его интерфейсов на более отвечающее потребностям клиентов поведение. Вы не можете ждать, пока решения всплывут наверх через 18-ти месячный цикл планирования; Вам необходимо принимать решения по имеющимся данным быстро, прямо на границах вашего бизнеса. Вам необходимы автономные, целеориентированные, самоорганизующиеся команды, которые будут отвечать за предоставление услуг убедительного качества их клиентам (покупателям, бизнес-партнерам, коллегам и т.п.). Быстрые циклы обратной связи, автономные команды, общая цель и коммуникации — вот базовые требования, которые должны усвоить организации, чтобы выжить в этом пока неизвестном и неописанном постиндустриальном мире разрушения бизнеса.
Ни одна книга по микросервисам не будет полной без цитирования закона Конвея (Conway’s law): «организации, которые проектируют системы… ограничены в результатах проектирования копированием структуры взаимодействия в этих организациях».
Для создания гибких программных систем, Вы должны начать с построения гибкой организационной структуры. Эта структура станет основой для микросервисов, но какую технологию мы будем использовать? Построение распределенных систем является трудной задачей и в последующих частях книги мы рассмотрим проблемы, о которых Вы должны знать при проектировании таких систем.
Что такое микросервисная архитектура?
Микросервисная архитектура (MSA) — это подход к построению программных систем, представляющий собой декомпозицию моделей предметной области бизнеса в небольшие логичные, связанные контексты, реализованные в виде сервисов. Эти сервисы являются изолированными и автономными, но тем не менее взаимодействующими между собой для предоставления определенной части бизнес-функциональности. Микросервисы обычно реализуются и эксплуатируются небольшими командами с достаточной степенью автономности каждой из них. Сервис может менять детали своей внутренней реализации (включая полную замену!) с минимальным влиянием на остальную систему.
Команды взаимодействуют через механизм «обязательств», который представляет собой способ, где сервисы могут публиковать «намерения» в отношении других компонент или систем, использующих этот сервис. Они описывают такие обязательства через интерфейсы своих сервисов и через вики, документирующие их сервисы. Если документации недостаточно или описание API недостаточно понятно, значит поставщик сервиса не выполнил свою работу. Немного больше о понятии «обязательства» и теории «обязательств» — в следующей главе.
Каждая команда будет отвечать за проектирование сервиса, выбор подходящей для решения задачи технологии, развертывание, управление и подъем в два часа ночи в случае сбоев.
Например, в Amazon’е есть единственная команда, отвечающая за функциональность расчета налогов при оформлении покупки. Модели внутри такого сервиса (Товар, Адрес, Налог и т.д.) понимаются только «в контексте расчета налогов и сборов» при продаже и это не создает неоднозначностей для этих объектов (например — это возврат или продажа товара?). Команда, владеющая сервисом расчета налогов и сборов, проектирует его, разрабатывает код и обслуживает. Amazon может себе позволить зрелые инструменты самообслуживания для автоматизации множества шагов сборки/развертывания/эксплуатации, но мы к этому еще вернемся.
С микросервисами мы можем задать границы сервиса, которые помогут нам:
- Понять что сервис делает без впутывания других проблем вышестоящего приложения.
- Быстро построить и развернуть сервис локально.
- Выбрать подходящую для решения проблемы технологию (Множество операций записи? Множество запросов? Малая задержка? Ускорение?).
- Тестировать сервис.
- Собирать/разворачивать/выпускать версии в ритме необходимом для бизнеса, который может быть разным для разных сервисов.
- Определять горизонтально и вертикально масштабируемые части архитектуры по мере необходимости.
- Улучшить устойчивость системы в целом.
Микросервисы помогают решить проблему — «а как нам разделить сервисы и команды для масштабного и быстрого роста?». Они позволяют командам сфокусироваться на предоставлении сервиса и внесении изменений по мере необходимости и делать это без дорогостоящих мероприятий по синхронизации проекта. Вот некоторые из вещей, о которых Вы больше не услышите после внедрения микросервисов:
- Заявки в Jira.
- Излишние собрания.
- Общие библиотеки.
- Стандарты канонических моделей предприятия.
Подходит ли микросервисная архитектура для Вас? Микросервисы имеют множество преимуществ, но при этом, приносят свой собственный набор недостатков. Вы можете рассматривать микросервисы как способ оптимизации проблемы, когда необходимо получить возможность изменять вещи быстро, в большом масштабе, но за определенную цену. Это неэффективно. Это может потреблять больше ресурсов. Вы можете прийти к тому, что выглядит как дублирование. Обслуживание гораздо сложнее. Становится очень трудно понимать и оценивать систему глобально, в целом. Становится гораздо труднее отлаживать при поиске проблем. В некоторых областях Вам придется расширить толкование термина транзакция. Команды могут быть не приспособлены работать в таких условиях.
Не все части бизнеса должны при этом измениться, а вот множество приложений, взаимодействующих с клиентами, должны. Бэкенд системы могут не измениться. Но как только эти два мира начнут смешиваться, мы сможем увидеть причины, которые оправдывают распространение микросервисной архитектуры на другие части системы.
Проблемы и возможности
Проектирование «чистых» облачных приложений на основе микросервисной архитектуры требует отличных от традиционных подходов к сборке, развертыванию и эксплуатации. Мы не можем просто сделать приложение предполагая, что мы знаем все варианты его отказов, а затем просто предотвратить их. В сложных системах, таких как построенные на базе микросервисов, мы должны быть готовы к неопределенностям. В этом разделе мы определим пять главных вопросов, которые следует рассматривать при разработке микросервисов.
Проектирование для отказов
В сложных системах что-нибудь да ломается. Жёсткие диски отказывают, сетевые кабели выдергивают, рабочие базы данных необходимо останавливать на обслуживание, виртуальные машины исчезают. Отдельные отказы могут распространиться на другие части системы и привести к каскадному сбою, который приведет к краху всей системы.
Традиционно, при построении приложений, мы пытаемся предсказать какие части приложения (например, в многозвенной архитектуре) могут отказать и выстраиваем «стену», достаточно высокую, чтобы предотвратить отказы. Такой подход является сам по себе проблемой при применении в большом масштабе, так как мы не можем всегда предсказать что может пойти не так в сложной системе. Что-нибудь откажет, поэтому мы должны разрабатывать приложения так, чтобы не только попытаться предотвратить сбой, но и обработать его с минимальными издержками для продолжения работы. Мы должны обрабатывать отказы аккуратно, чтобы не дать им распространиться и привести к полному отказу системы.
Построение распределенных систем отличается от построения монолитных приложений с общей памятью и единственным процессом. Одно яркое отличие в том, что обмен по сети это не то же, что и локальный вызов в одном и том же адресном пространстве. Сети по определению ненадёжны. Вызовы по сети могут не пройти по множеству причин (из-за низкого уровня сигнала, плохого кабеля/маршрутизатора/коммутатора или файервола) и это может стать главным узким местом. Однако ненадежность сети влияет не только на производительность и время отклика клиентам вашего сервиса, она может также внести свой вклад в отказ вышестоящих систем.
Сетевые вызовы может быть очень трудно отлаживать из-за наличия задержки. В идеале, если сетевой вызов не может быть выполнен успешно, должно быть сгенерировано сообщение об ошибке и ваше приложение заметит отказ быстро (например через IOException). В этом случае мы можем быстро предпринять действия по исправлению, либо предоставив ограниченную функциональность, либо просто ответив клиенту сообщением о невозможности удовлетворения запроса и необходимости повторить попытку позже. Однако ошибки в сетевых запросах или распределенных приложениях не всегда происходят таким образом. А что если нижележащее приложение, к которому Вы должны обратиться, просто будет отвечать дольше чем обычно? Это убийственная ситуация потому, что теперь ваше приложение должно принимать во внимание эту медлительность, замедлив собственные запросы, отменить по тайм-ауту ранее сделанные запросы и потенциально застопорив любые обращения через ваш сервис. Эта ситуация распространится на вышестоящие сервисы, замедлит их и приведет к их остановке. И это причина каскадного сбоя.
Проектирование с учетом зависимостей
Чтобы быстро развиваться и быть гибкими с организационной точки зрения или с точки зрения распределенных систем, мы должны проектировать системы, принимая во внимания их зависимости. Мы должны ослабить связи между командами, в технологиях и в методах управления. Одна из целей микросервисов — это воспользоваться преимуществами автономных команд и автономных сервисов. Это значит быть в состоянии изменять вещи так быстро, как это требуется бизнесу: без ущерба для сервисов вокруг Вас или в системе в целом. Это также означает, что мы должны полагаться на сервисы, но если же они недоступны или «тормозят», мы должны быть в состоянии элегантно выйти из положения.
В своей книге «Ориентированное на зависимости мышление» (Dependency Oriented Thinking) (Серия InfoQ «Разработка корпоративного программного обеспечения»), Ганеш Прасад (Ganesh Prasad) попадает точно в яблочко, когда говорит — «Один из принципов творчества — это снять ограничение. Иными словами, Вы можете прийти к творческому решению проблем, если мысленно устраните одну или несколько зависимостей». Проблема в организациях заключается в том, что они были построены с прицелом на экономическую эффективность, а это приносит с собой множество запутанных зависимостей.
Например, когда Вам нужно проконсультироваться с тремя другими командами, чтобы внести изменения в свой сервис (команды баз данных, обеспечения качества и безопасности), то получается не очень гибко — каждая из этих точек синхронизации может привести к задержкам. Это хрупкий процесс. Как только Вы можете отбросить эти зависимости или встроить их в свою команду (мы наверняка не можем пожертвовать безопасностью, так что встройте этих компоненты в вашу команду), Вы свободны для творчества и можете более оперативно решать проблемы клиентов или бизнеса, без дорогостоящих человеческих узких мест.
Другой взгляд на проблему управления зависимостями заключается в том, что делать с устаревшими (legacy) системами. Публикация деталей реализации устаревших программ (структуры COBOL-а, форматы сериализации XML, используемые в конкретной системе, и т. д.) в связанные системы — это рецепт катастрофы. Одно небольшое изменение (например, изменение длины идентификатора клиента — с 16 на 20) распространится и разрушит предположения, сделанные связанными системами, потенциально нарушив их работу. Мы должны тщательно продумать, чтобы оградить остальную часть системы от таких зависимостей.
Проектирование с учетом предметной области
Для упрощения и понимания проблемы под определенным углом на протяжении веков использовались модели. Например, карты GPS на телефонах имеют отличные модели для навигации при прогулках или езде по городу. Однако они становятся полностью бесполезными для летящего на самолете. Здесь будут более подходящими модели, описывающие маршрутные точки, наземные ориентиры и реактивные потоки. Различные модели имеют больше или меньше смысла в зависимости от контекста, в котором они рассматриваются. Основополагающая книга Эрика Эванса (Eric Evans) «Проектирование на базе предметной области» (Domain-Driven Design, Addison-Wesley, 2004) помогает нам строить модели сложных бизнес-процессов, которые могут быть реализованы и в виде программного обеспечения. В конечном счете, реальная сложность в программном обеспечении — это не технология, а скорее неоднозначные, циклические, противоречивые модели, которые бизнесмены строят в своих головах «на лету». Люди могут воспринимать модели в определенном контексте, но компьютеры нуждаются в большей помощи. И сами модели и контекст должны быть отражены в программном обеспечении. Если мы сможем достичь такого уровня моделирования, который напрямую связан с реализацией (и наоборот), то при любом изменении в бизнесе мы сможем более ясно понять, как изменения следует внести в программное обеспечение. Построение этих моделей и описывающего их языка требует времени и быстрой обратной связи (с бизнесом — прим. переводчика).
Один из инструментов, представленный Эвансом — это выявление и явное разделение различных моделей, обеспечение их связности и однозначности в их собственных контекстах.
Связанный контекст — это набор объектов предметной области, реализующих модель, которая пытается упростить и связать бизнес, код и организацию. Например, мы стремимся к эффективности при проектировании какой-нибудь системы, в то самое время, как на самом деле нам нужна гибкость (звучит знакомо?). В простом приложении для управления производством и продажей автомобильных запчастей мы стараемся прийти к единой «канонической модели» всей предметной области и, в конце концов, останавливаемся на таких объектах, как «Запчасть», «Цена» и «Адрес». При этом, если приложение для управления складом использует объект «Запчасть» чтобы ссылаться на такие вещи как «тормоза» или «колесо», то в подсистеме управления качеством «Запчасть» может ссылаться на конкретную деталь с конкретным серийным номером и уникальным идентификатором для отслеживания определенных тестов качества, их результатов и т. д. Мы усердно старались эффективно повторно использовать одну и ту же каноническую модель, но вопросы инвентаризации и проверка качества — это разные бизнес-проблемы, которые используют объект «Запчасть» семантически по-разному. В связанном контексте, «Запчасть» будет явно смоделирована как «Тип запчасти» и будет пониматься и рассматриваться в этом контексте как описывающая «тип детали», а не как конкретный экземпляр этой детали. В двух отдельных связанных контекстах эти объекты «Запчасть» могут непрерывно развиваться в рамках своих моделей без создания странных зависимостей. Таким образом, мы достигаем нужного уровня гибкости.
Такое глубокое понимание предметной области занимает время. Чтобы полностью понять двусмысленности в бизнес-моделях, правильно их разделить и позволить им независимо изменяться, может потребоваться нескольких итераций проектирования. Это, как минимум, одна причина, по которой переход к построению микросервисов является сложным делом. «Пиление» монолита — задача не из легких, однако множество понятий уже реализовано в этом монолите и ваша задача — определить и отделить их. В новом же проекте Вам неоткуда и нечего вырезать до тех пор, пока Вы глубоко не вникните в предметную область. На самом деле, все истории успеха микросервисов, о которых мы слышим (такие как Amazon и Netflix) все начали свой путь с монолита, прежде чем совершили успешный переход к микросервисам.
Автор: Андрей А. Породько