Сложность асинхронной, распределенной, динамично развивающейся системы в разы превышает сложность единого, монолитного приложения. Разделить систему на микросервисы зачастую очень сложно, если в компании «все делают все», и нет четко очерченных бизнес-областей. В таких случаях вместо микросервисов получается «распределенный монолит» (distributed monolith), неповоротливая, сложная система, вместо которой мог бы иметь место более эффективный монолит.
Не забывайте, что два остальных столпа концепции Cloud Native совершенно безразличны к битве монолитов, микросервисов и SOA! Вы можете совершенно спокойно развернуть классическое, монолитное приложение Enterprise Java в контейнере под управлением Kubernetes, получив многие преимущества без излишней сложности.
Один из давних соратников Мартина, Сэм Ньюмен (Sam Newman), написал отдельную, и кстати, не слишком «толстую», книгу по микросервисам, которую можно рекомендовать для чтения, и существует ее перевод на русский язык (полный список рекомендованных ресурсов вы найдете в конце этой главы).
Разбиение системы на микросервисы
Если осмыслить основные качества системы, созданной на основе микросервисов, начинает казаться, что их использование совершенно универсальное, великолепное решение, практически панацея, или как любят говорить в технологиях, «серебряная пуля». Запустив систему в облаке под управлением Kubernetes, где многие неприятности и специфичные проблемы микросервисов решаются за нас, можно ли спокойно получить все их преимущества?
Проблема же заключается больше в попытке понять, что будет микросервисом в вашей системе, а что будет лишь частью или библиотекой, работающей в составе большого сервиса. Неверное определение границ микросервисов (boundary) приведет к запутанному, сложному коду, чрезмерно раздутым программным интерфейсам API, и может сорвать все сроки разработки, а то и к поспешной попытке «склеить» все обратно в монолит.
Качественный процесс дизайна и архитектуры приложения подразумевает разделение компонентов, сервисов и объектов, представляющих собой данные, согласно области бизнеса (domain), для которого приложение разрабатывается. Это основа DDD, дизайна архитектуры приложения на основе области его применения (domain driven design). Однако именно здесь для микросервисов кроется неприятный подводный камень. Дело в том, что разбить компоненты и сервисы заранее очень тяжело, требования, как водяные знаки, появляются лишь по мере проявления приложения, пользователи зачастую полностью меняют свое мнение как только видят первые варианты приложения.
Это меньшая проблема для монолитов рефакторинг компонентов и их интерфейсов довольно просто сделать внутри одного процесса и одной базы технологий и языков. В случае микросервисов перенести часть функциональности в другой сервис, зачастую написанный на другом языке или платформе, полностью поломать и поменять крупные интерфейсы REST/gRPC между ними очень непросто.
Отсюда вытекает один из начальных этапов разработки, направленный на выявление границ микросервисов. Разработка идет в соответствии с базовыми принципами DDD, и приложение разбивается на модули согласно выявленным ограниченным контекстам (bounded context) области работы, автономной самой по себе. В примерах с вездесущими электронными магазинами это может быть обслуживание склада и инвентаря магазина, отдельно от них существует система доставки, обработка счетов и так далее. Но, модули работают как часть монолита, первого приближения эволюционного дизайна, упомянутого нами выше.
После получения первых, очень грубых приближений системы в целом, с минимумом возможностей, начинают проявляться более четкие границы ее ограниченных контекстов. Этот момент прекрасно подходит для разбиения модулей на микросервисы, если нужно, смену технологий в них, и децентрализацию данных. Принятые решения еще нужно будет пересмотреть, однако они уже основаны на реальном коде, дизайне, и не являются просто плодом сухой теоретической подготовки.
Обратная сторона медали
Подчеркнем еще раз за блеском и преимуществами микросервисов, лежащих на поверхности, легко не заметить всех сложностей и совершенно другой парадигмы именно работы, эксплуатации всей системы в целом. Даже если удастся удачно разбить систему по ограниченным контекстам и минимизировать их зависимости, создать хорошо настроенные программные интерфейсы API, изучить Kubernetes и успешно развернуть и масштабировать свое приложение, то понять в итоге, что же происходит в работающей системе, будет в разы сложнее.
Обратная сторона медали
Подчеркнем еще раз за блеском и преимуществами микросервисов, лежащих на поверхности, легко не заметить всех сложностей и совершенно другой парадигмы именно работы, эксплуатации всей системы в целом. Даже если удастся удачно разбить систему по ограниченным контекстам и минимизировать их зависимости, создать хорошо настроенные программные интерфейсы API, изучить Kubernetes и успешно развернуть и масштабировать свое приложение, то понять в итоге, что же происходит в работающей системе, будет в разы сложнее.
Можно забыть о прямой отладке в вашем редакторе IDE, если ваш код взаимодействует с несколькими микросервисами, и все они отвечают асинхронно. Интеграционные тесты будут очень сложны и поддержка их требует много ресурсов. Понять по журналам (logs) одного компонента, что происходит в системе в целом, невозможно. Производительность системы будет настолько распределена между отдельными микросервисами и сетевыми вызовами, что измерять нужно будет все сразу. Распределенные транзакции между различными хранилищами данных. Расчет на сбои всего и вся. Доверие образам контейнеров. Задача защиты трафика между микросервисами системы, обслуживание сертификатов SSL, авторизация, безопасность, роли Продолжать можно еще долго.
Есть хорошие новости экосистема созданных для облака приложений Cloud Native буквально наводнена инструментами и решениями для перечисленных нами вызовов. Зачастую они бесплатны и с открытым кодом, и каждый день появляются новые решения. Самая распространенная проблема управление сетевыми вызовами между микросервисами, отслеживание задержек, шифрование трафика неплохо решается так называемыми сетками микросервисов (service mesh) такими как Istio и Linkerd. Мы еще вспомним про них в дальнейших главах. Сбор распределенных журналов также отлично решается, например стеком ELK (Elastic, Logstash, Kibana), или Fluentd. Стандарт OpenTracing, метрики Prometheus, и отчеты Grafana уже встроены во многие библиотеки для создания микросервисов, и просто используя их, вы получите мощнейший центр наблюдения за своей системой.
Тем не менее, все это богатство надо изучить, выбрать нужное и подходящее вам, и настроить эти издержки надо обязательно добавить в общую стоимость разработки реальной системы из микросервисов.
Резюме
Микросервисы выглядят заманчиво, а вместе с контейнерами и оркестрацией Kubernetes и вовсе как очевидный выбор. Тем не менее, существует высокая цена эксплуатации системы, созданной на их основе, и экосистема для работы с ними требует инвестиций времени и ресурсов. В этой главе нет примеров каждая система уникальна, и зависит прежде всего от области своего применения (domain). Архитектурные решения высокого уровня трудно описывать без сложного конкретного примера, поэтому мы рассмотрели все с высоты птичьего полета, но все концепции данной главы можно попробовать перенести на свой собственный проект.
Чтобы понять архитектуру и философию микросервисов чуть лучше, можно посоветовать следующие книги и ресурсы:
Сэм Ньюмен (Sam Newman), «Создание микросервисов»
Сэм Ньюмен (Sam Newman), «От монолита к микросервисам»
www.cncf.io главный сайт фонда Cloud Native Foundation, посмотрите раздел проектов (projects), многие посвящены работе микросервисных систем, в том числе OpenTracing и Prometheus.
Некоторые видео с конференций KubeCon удачно описывают микросервисы для людей с разной степенью подготовки, найдите их канал на YouTube.
Эрик Эванс (Eric Evans), «Предметно-ориентированное проектирование (DDD).»
www.martinfowler.com статьи про микросервисы и связанные концепции. Кое-что доступно и на русском языке.
3. Контейнеры и Docker
Контейнеры (containers) относительно новое слово и концепция, мгновенно захватившая мир разработки программного обеспечения за последние несколько лет. Это несомненный прорыв в попытках разработчиков и системных администраторов максимально использовать доступные им вычислительные ресурсы, при этом снизив сложность разработки и выпуска приложений.
Если кратко вспомнить историю, то серверные приложения, сервисы и базы данных изначально располагались на выделенных для них физических серверах, в подавляющем большинстве случаев под управлением одного из вариантов операционной системы Unix (или ее клона Linux). С взрывным ростом вычислительных мощностей использовать один мощнейший сервер для одного приложения стало и расточительно, и неэффективно на одном сервере стали работать несколько приложений, внутренних сервисов или даже баз данных. При этом незамедлительно возникли серьезные трудности различные версии приложений использовали разные версии основных библиотек Unix, использовали разные несовместимые между собой пакеты расширений или дополнительные библиотеки, соревновались за одинаковые номера портов, особенно если они были широко используемы (HTTP 80, HTTPS 443 и т.п.)