Изобретаем велосипед на Java — пишем свой Framework (DI, ORM, MVC and etc)
Все началось с того, что я решил написать свою Java Common библиотеку. Очень часто для типичных задач на Stack Overflow находятся решения в 3-5-10 строк кода. Копипастить себе в проекты надоело. Решил вынести это в отдельную библиотеку, которую можно сделать Open Source и использовать в текущих и будущих своих проектах. Безусловно есть такие хорошие либы как Google Guava и Apache Commons, которые я тоже использую в работе, но они достаточно «правильные» и гибкие, что выливается не в одну строчку кода.
В итоге вечерами дома в свободное от работы время я покрыл за 1 неделю следующие направления: Sleep/Delay/Pause, Timer for Benchmark, Random range generator, File operations, Tasks/Threads, Reflection, JSON, URL, Logging, Strings. Смотрел свои проекты на предмет копи-паста стандартных решений и писал решения в библиотеку. В очередной раз применил TDD подход для разработки библиотеки. Сначала пишешь тест на не существующие классы и методы, а потом реализуешь код, что бы тесты стали зелеными. Решает две проблемы: во-первых, ты пытаешься удобно использовать свои классы и их методы до их реализации, во-вторых, у тебя остаются тесты, которые в будущем могут свалиться, и ты поймешь, что у тебя сломалось при очередном рефакторинге или багфиксе.
Дальше, больше. Я начал анализировать, как я и многие другие, типично использую Spring/JBoss и понял, что legacy и широта возможности все усложняет. Можно реализовать упрощенный типичный Dependency Injection. Сказано, сделано. Добавил в свою библиотеку DI Framework. Мои знакомые смотрели мою реализацию и говорили что разобраться, как устроен Spring просто нереально, там полная жесть наследований и обверток, а у тебя все видно прям на первом уровне реализации и все понятно. Им было интересно, как работать с аннотациями и т. д.
Реализовав DI Framework я задумался над тем, что еще чуть-чуть и будет полноценный Enterprise Server. Осталось добавить ORM и Web-сервер с MVC, REST и security. Все в лучших традициях, так сказать. И меня затянуло. Еще неделька вечерами после работы, ссоры с женой, и получился Simplified Enterprise Server. Я не придерживался стандартов JavaEE, так как писал, как бы мне казалось, было удобно и понятно использовать. Сам я на работе использую Spring Boot, Spring Data, JPA 2.0, Spring MVC, Spring Rest, Spring Security. До этого делал проект на JBoss, видел другую сторону JEE, так сказать. Но вся это универсальность и гибкость конечно в тему. Но когда тебе нужно быстро накидать прототип в стиле JEE или тебе нужно научится кодить серьездные проекты на Java, окунаться в мир Spring, Hibernate и т.д. долго и кропотливо. Единственная альтернатива это Spring Boot, но реально там много происходит скрыто от тебя и если ты не знаешь как работает Spring под капотом, это только тебя тормозит, так как любой шаг в лево или в право это полный нырок в детали…
В итоге код фреймворка на гитхабе github.com/evgenyigumnov/common
Пример веб-сервиса использующего этот фреймворк на гитхабе github.com/evgenyigumnov/example и в онлайне его тоже можно посмотреть java.igumnov.com:8181 Пользователь: demo Пароль: demo
В заключении, буду рад любой критике и предложению по улучшению кода библиотеки. Для себя я получил профит в разминании мозга при написание библиотеки и использовании замыканий/лямбд. Иногда скучно писать коммерческие продукты, хочется создать свой космический корабль. Не стесняйтесь форкать мою либу и самим ее модифицировать под свои нужды. Она достаточно проста и легка для внесения в нее модификаций. Буду признателен, если вы будете присылать пулл-реквесты, чтобы ваши доработки вносились в библиотеку. Я лично настроен достаточно быстро их проверять и принимать. Я просто фанатик-программер, меня это втыкает. Люблю кодить!
PS Да-да, я не люблю писать javadoc, шлите пулл-реквесты с ним, сейчас по коду либы очень понятно, что каждый метод ее делает…
Введение в Java EE
Java EE — что это?
Развитие Java EE
Архитектура Java EE приложений
Уровни приложений
Клиентский уровень представляет из себя некоторое приложение, которое запрашивает данные у Java EE сервера (среднего уровня). Сервер, в свою очередь, обрабатывает запрос клиента и возвращает ему ответ. Клиентским приложением может быть браузер, отдельное приложение (мобильное либо десктопное) или другие серверные приложения без графического интерфейса.
Средний уровень подразделяется, в свою очередь, на web-уровень и уровень бизнес-логики.
Web-уровень состоит из некоторых компонент, которые обеспечивают взаимодействие между клиентами и уровнем бизнес-логики.
На web-уровне используются такие технологии Java EE:
Уровень бизнес-логики состоит из компонент, в которых реализована вся бизнес-логика приложения. Бизнес-логика — это код, который обеспечивает функциональность, покрывающую нужды некоторой конкретной бизнес сферы (финансовая индустрия, банковское дело, электронная коммерция). Данный уровень можно считать ядром всей системы.
Технологии, которые задействованы на данном уровне:
Уровень доступа к данным. Данный уровень иногда называют уровнем корпоративных информационных систем (Enterprise Information Systems, сокращенно —EIS). EIS состоит из различных серверов баз данных, ERP (англ. Enterprise Resource Planning) систем планирования ресурсов предприятия и прочих источников данных. К этому уровню за данными обращается уровень бизнес-логики.
В данном уровне можно встретить такие технологии, как:
Сервера приложений, контейнеры, компоненты
Контейнеры апплетов выполняются большинством браузеров. При разработке апплетов можно сконцентрироваться на визуальной стороне приложения, в то время как контейнер обеспечивает безопасную среду.
Контейнер клиентского приложения (ACC) включает набор Java-классов, библиотек и других файлов, необходимых для реализации в приложениях Java SE таких возможностей, как внедрение, управление безопасностью и служба именования.
Веб-контейнер предоставляет базовые службы для управления и исполнения веб-компонентов (сервлетов, компонентов EJB Lite, страниц JSP, фильтров, слушателей, страниц JSF и веб-служб). Он отвечает за создание экземпляров, инициализацию и вызов сервлетов, а также поддержку протоколов HTTP и HTTPS. Этот контейнер используется для подачи веб-страниц к клиент-браузерам.
EJB (Enterprise Java Bean) контейнер отвечает за управление и исполнение компонентов модели EJB, содержащих уровень бизнес-логики приложения. Он создает новые сущности компонентов EJB, управляет их жизненным циклом и обеспечивает реализацию таких сервисов, как транзакция, безопасность, параллельный доступ, распределение, служба именования либо возможность асинхронного вызова.
Апплеты — это приложения из графического пользовательского интерфейса (GUI), выполняемые в браузере. Они задействуют насыщенный интерфейс Swing API для производства мощных пользовательских интерфейсов.
Приложениями называются программы, выполняемые на клиентской стороне. Как правило, они относятся к графическому пользовательскому интерфейсу (GUI) и применяются для пакетной обработки.
Веб-приложения (состоят из сервлетов и их фильтров, слушателей веб-событий, страниц JSP и JSF) — выполняются в веб-контейнере и отвечают на запросы HTTP от веб-клиентов. Сервлеты также поддерживают конечные точки веб-служб SOAP и RESTful.
Корпоративные приложения (созданные с помощью технологии Enterprise Java Beans, службы сообщений Java Message Service, интерфейса Java API для транзакций, асинхронных вызовов, службы времени) выполняются в контейнере EJB. Управляемые контейнером компоненты EJB служат для обработки транзакционной бизнес-логики. Доступ к ним может быть как локальным, так и удаленным по протоколу RMI (или HTTP для веб-служб SOAP и RESTful).
На диаграмме ниже представлена типичная архитектура Java EE приложения:
Справочник по Java Collections Framework
Данная публикация не является полным разбором или анализом (не покрывает пакет java.util.concurrent ). Это, скорее, справочник, который поможет начинающим разработчикам понять ключевые отличия одних коллекций от других, а более опытным разработчикам просто освежить материал в памяти.
Что такое Java Collections Framework?
Java Collection Framework — иерархия интерфейсов и их реализаций, которая является частью JDK и позволяет разработчику пользоваться большим количесвом структур данных из «коробки».
Базовые понятия
Интерфейс Map [doc]
Hashtable — реализация такой структуры данных, как хэш-таблица. Она не позволяет использовать null в качестве значения или ключа. Эта коллекция была реализована раньше, чем Java Collection Framework, но в последствии была включена в его состав. Как и другие коллекции из Java 1.0, Hashtable является синхронизированной (почти все методы помечены как synchronized ). Из-за этой особенности у неё имеются существенные проблемы с производительностью и, начиная с Java 1.2, в большинстве случаев рекомендуется использовать другие реализации интерфейса Map ввиду отсутствия у них синхронизации.
WeakHashMap — реализация хэш-таблицы, которая организована с использованием weak references. Другими словами, Garbage Collector автоматически удалит элемент из коллекции при следующей сборке мусора, если на ключ этого элеметна нет жёстких ссылок.
Интерфейс List [doc]
Реализации этого интерфейса представляют собой упорядоченные коллекции. Кроме того, разработчику предоставляется возможность доступа к элементам коллекции по индексу и по значению (так как реализации позволяют хранить дубликаты, результатом поиска по значению будет первое найденное вхождение).
ArrayList — как и Vector является реализацией динамического массива объектов. Позволяет хранить любые данные, включая null в качестве элемента. Как можно догадаться из названия, его реализация основана на обычном массиве. Данную реализацию следует применять, если в процессе работы с коллекцией предплагается частое обращение к элементам по индексу. Из-за особенностей реализации поиндексное обращение к элементам выполняется за константное время O(1). Но данную коллекцию рекомендуется избегать, если требуется частое удаление/добавление элементов в середину коллекции. Подробный анализ и описание можно почитать в этом хабратопике.
Интерфейс Set [doc]
Представляет собой неупорядоченную коллекцию, которая не может содержать дублирующиеся данные. Является программной моделью математического понятия «множество».
Интерфейс Queue [doc]
Этот интерфейс описывает коллекции с предопределённым способом вставки и извлечения элементов, а именно — очереди FIFO (first-in-first-out). Помимо методов, определённых в интерфейсе Collection, определяет дополнительные методы для извлечения и добавления элементов в очередь. Большинство реализаций данного интерфейса находится в пакете java.util.concurrent и подробно рассматриваются в данном обзоре.
Заключение
Java Collections Framework содержит большое количество различных структур данных, доступных в JDK «из коробки», которые в большинстве случаев покрывают все потребности при реализации логики приложения. Сравнение временных характеристик основных коллекций, которые зачастую используются в разработке приложений приведено в таблице:
При необходимости, разработчик может создать собственную реализацию, расширив или переопределив существующую логику, либо создав свою собственную реализацию подходящего интерфейса с нуля. Также существует некоторое количество готовых решений, которые являются альтернативой или дополнением к Java Collections Framework. Наиболее популярными являются Google Guava и Commons Collections.
Микросервисы на Java: практическое руководство
Вы можете использовать это руководство, чтобы понять что такое Java микросервисы, как вы будете их разрабатывать и создавать. А также получить обзор библиотек для разработки Java микросервисов.
7000 слов, вероятно, не стоит читать ее на мобильном устройстве. Добавьте ее в закладки и вернитесь позже.
Содержание
Основы Java микросервисов
Чтобы получить реальное представление о микросервисах Java, имеет смысл начать с самых основ: печально известного монолита на Java, что это такое и каковы его преимущества или недостатки.
Что такое монолит Java?
Представьте, что вы работаете на банк или финтех-стартап. Вы предоставляете пользователям мобильное приложение, которое они могут использовать для открытия нового банковского счета.
Для этого коде на Java требуется класс контроллера, который, упрощенно, выглядит примерно так, как показано ниже.
В чем проблема с Java монолитами?
По своей сути, в монолите Java нет ничего плохого. Просто опыт проекта показал, что если у вас:
В результате ваш маленький файл bank.jar превращается в гигантский кодовый гигабайт, который все боятся развертывать.
Как уменьшить размер монолита Java?
Это естественно приводит к вопросу о том, как уменьшить монолит. На данный момент ваш bank.jar работает в одной JVM, один процесс на одном сервере. Ни больше ни меньше.
Теперь вы можете придумать идею: «Служба проверки рисков используется другими отделами в моей компании, и она на самом деле не имеет ничего общего с моим доменом Mono (lithic) Bank, поэтому мы можем попробовать вырезать его из монолита и развернуть как его отдельный продукт, или, если говорить технически, запустите его как свой собственный процесс Java.
Что такое микросервис Java?
В практическом плане это означает, что вместо вызова метода riskCheck() внутри вашего BankController вы переместите этот метод / bean-компонент со всеми его вспомогательными классами в свой собственный проект Maven / Gradle, поместите его под контроль системы управления исходным кодом и разверните его независимо от вашего. банковского монолита.
Весь этот процесс извлечения не делает ваш новый модуль RiskCheck микросервисом как таковым, и это потому, что определение микросервисов открыто для интерпретации (что приводит к изрядному количеству обсуждений в командах и компаниях).
Вместо того, чтобы рассуждать об этом, мы будем придерживаться прагматичного подхода и делать две вещи:
Итак, подведем итог: сначала у вас был один процесс JVM, один банковский монолит. Теперь у вас есть процесс JVM банковского монолита и микросервис RiskCheck, который работает в своем собственном процессе JVM. И ваш монолит теперь должен вызвать этот микросервис для проверки рисков.
Как общаться между Java микросервисами?
По существу, у вас есть два варианта: синхронное взаимодействие или асинхронное взаимодействие.
Синхронное взаимодействие с помощью HTTP/REST сервисов
Синхронное взаимодействие микросервисов обычно осуществляется через HTTP и REST-подобные сервисы, которые возвращают XML или JSON — хотя это ни в коем случае не является обязательным (посмотрите, например, на Google Protocol Buffers).
Используйте REST-коммуникацию, когда вам нужен немедленный ответ, который мы используем в нашем случае, так как проверка риска обязательна перед открытием счета: нет проверки риска, нет счета.
Посмотрите ниже раздел Какие библиотеки лучше всего подходят для синхронных вызовов Java REST?
Асинхронное взаимодействие с помощью обмена сообщениями
Асинхронная микросервисная связь обычно осуществляется посредством обмена сообщениями с помощью реализации JMS и / или с помощью протокола, такого как AMQP. Обычно, на практике не следует недооценивать интеграцию по электронной почте / SMTP.
Используйте асинхронное взаимодействие, когда вам не нужен немедленный ответ, скажем, пользователи нажимают кнопку «купить сейчас», и вы хотите сгенерировать счет-фактуру, что, безусловно, не должно происходить в рамках цикла запроса-ответа пользователя на покупку.
Посмотрите ниже раздел Какие инструменты лучше всего подходят для асинхронного обмена сообщениями Java?
Пример: вызов REST API в Java
Предположим, что мы решили использовать синхронную микросервисную связь, наш Java код на низком уровне будет выглядеть примерно так. Низкоуровневый, потому что для микросервисных коммуникаций вы обычно создаете клиентские библиотеки, которые абстрагируют вас от реальных HTTP-вызовов.
Глядя на код, становится ясно, что теперь вы должны развернуть два Java (микро) сервиса: ваш банк и сервис RiskCheck. В итоге вы получите две JVM, два процесса. Графическое изображение этого будет выглядеть так:
Но остается вопрос: как именно вы вырезаете или настраиваете эти микросервисы? Что это за мелкие фрагменты? Какой правильный размер?
Давайте посмотрим что в реальности.
Микросервисная архитектура на Java
На практике компании пытаются разработать или спроектировать микросервисные проекты различными способами. Это зависит от того, пытаетесь ли вы превратить существующий монолит в проект микросервисов или начинаете с нового проекта.
От монолита к микросервисам
Одна довольно органичная идея — вычленить микросервисы из существующего монолита. Обратите внимание, что «микро» здесь на самом деле не означает, что сами извлеченные сервисы действительно будут микро — они сами могут быть довольно большими.
Давайте рассмотрим немного теории.
Идея: разбить монолит на микросервисы
Унаследованные проекты выигрывают от микросервисного подхода. Главным образом по трем причинам:
Это означает, что вы можете взглянуть на свой монолит Java-банка и попытаться разбить его по границам домена — это вполне разумный подход.
Реальность: пусть кто-то другой сделает это
Хотя этот подход определенно выглядит хорошо на бумаге и UML-подобных диаграммах, у него есть свои недостатки. В основном, вам нужны очень сильные технические навыки, чтобы справиться с этим. Почему?
Потому что существует огромная разница между пониманием того, что было бы неплохо извлечь, скажем, сильно связанный модуль управления учетными записями из вашего монолита, и сделать это (правильно).
Большинство корпоративных проектов достигают стадии, когда разработчики боятся, скажем, обновить 7-летнюю версию Hibernate до более новой, которая является лишь обновлением библиотеки, но требуются значительные усилия, чтобы не сломать ничего.
Эти же разработчики теперь должны копаться в старом, унаследованном коде с неясными границами транзакций базы данных и извлекать четко определенные микросервисы? Возможно, но часто это реальная проблема, и ее нельзя решить на доске или на совещаниях по архитектуре.
Здесь уместна цитата из @simonbrown в Twitter:
Я буду повторять это… если люди не могут правильно строить монолиты, микросервисы не помогут.
I’ll keep saying this… if people can’t build monoliths properly, microservices won’t help.
Simon Brown
Новый проект с микросервисой архитектурой
Ситуация выглядит немного иначе при разработке новых проектов на Java. Теперь эти три пункта, перечисленные выше выглядят немного иначе:
Это приводит к тому, что компании пытаются использовать микросервисы в новых Java проектах.
Техническая микросервисная архитектура
Первый подход является наиболее очевидным для разработчиков, вопреки настоятельным рекомендациям против него. Hadi Hariri (https://twitter.com/hhariri) рекомендует использовать рефакторинг «Extract Microservice» в IntelliJ.
Хотя следующий пример слишком упрощен, реализации, наблюдаемые в реальных проектах, к сожалению, не слишком далеки от него.
До микросервиса
С использованием Java микросервиса подстроки
Таким образом, вы, по сути, включаете вызов метода Java в вызов HTTP, без очевидных причин для этого. Одна из причин, однако, заключается в следующем: отсутствие опыта и попытка форсировать подход на основе микросервисов Java.
Рекомендация: не делайте этого.
Микросервисная архитектура, ориентированная на workflow (рабочий процесс)
Следующим распространенным подходом является разработка ваших микросервисов Java на основе рабочего процесса.
Пример из реальной жизни: в Германии, когда вы обращаетесь к (государственному) врачу, он должен записать ваше назначение в своем программном обеспечении CRM для здравоохранения.
Чтобы получить оплату от страховой компании, он отправит ваши данные о лечении и всех других пациентов, которых он лечил, посреднику через XML.
Посредник рассмотрит этот XML-файл и (упрощенно):
Если вы сейчас попытаетесь смоделировать этот рабочий процесс с помощью микросервисов, у вас получится это как минимум.
Примечание. В этом примере взаимодействие между микросервисами не имеет значения, но вполне может быть выполнено асинхронно с использованием брокера сообщений, таким как RabbitMQ, поскольку врач все равно не получает немедленной обратной связи.
Опять же, это то, что хорошо выглядит на бумаге, но сразу приводит к нескольким вопросам:
Интересно, что для некоторых архитекторов приведенная выше диаграмма выглядит проще, потому что у каждого сервиса теперь есть свое точное, четко определенное назначение. Раньше это выглядело как этот страшный монолит:
Несмотря на то, что можно спорить о простоте этих диаграмм, теперь вам определенно нужно решить дополнительные операционные задачи.
Рекомендация:
→ Не делай этого.
Прим. перев.:
Chaos Monkey — архитектурный принцип Netflix для поддержки автомасштабируемых stateless-микросервисов — любой инстанс может быть остановлен и автоматически заменен без какой-либо потери состояния. Chaos Monkey следит за тем, чтобы никто не нарушал этот принцип.
Однако с меньшей гиперболой.
Попытка моделировать микросервисы по доменным границам — очень разумный подход. Но граница домена (скажем, управление пользователями или выставление счетов) не означает, что нужно взять один рабочий процесс и разделить его на крошечные отдельные части (получить XML, проверить XML, переслать XML).
Следовательно, всякий раз, когда вы начинаете с новый проект на Java микросервисах, и границы домена все еще очень расплывчаты, старайтесь поддерживать размер ваших микросервисов на нижнем уровне. Вы всегда можете добавить больше модулей позже.
И убедитесь, что у вас есть исключительно сильные навыки DevOps в вашей команде / компании / подразделении для поддержки вашей новой инфраструктуры.
Полиглото или командно-ориентированная микросервисная архитектура
Существует третий, почти либертарианский подход к разработке микросервисов: предоставление вашим командам или даже отдельным лицам возможности реализовывать пользовательские истории с использованием любого количества языков или микросервисов (маркетинговый термин: полиглотное программирование).
Таким образом, приведенная выше служба проверки XML может быть написана на Java, в то время как микросервис правдоподобия написан на языке Haskell (чтобы сделать его математически обоснованным), а микросервис пересылки страховки должен быть написан на языке Erlang (потому что он действительно должен масштабироваться 🙂 ).
То, что может показаться забавным с точки зрения разработчика (разработка идеальной системы с вашим идеальным языком в изолированной среде), в сущности, никогда не является тем, чего хочет организация: унификация и стандартизация.
Это означает относительно стандартизированный набор языков, библиотек и инструментов, чтобы другие разработчики могли продолжать поддерживать ваш микросервис Haskell в будущем, когда вы перейдете на более экологичные пастбища.
Что интересно: историческая стандартизация зашла слишком далеко. Разработчикам в больших компаниях из списка Fortune 500 иногда даже не позволяли использовать Spring, потому что это «не входило в план компании по технологиям». Но переход на полноценный полиглотный подход — это почти то же самое, просто другая сторона той же монеты.
Рекомендация: если вы собираетесь использовать полиглотный подход, попробуйте меньшее разнообразие в одной и той же экосистеме языка программирования. Пример: Kotlin и Java (на основе JVM со 100% совместимостью друг с другом), а не Haskell и Java.
Развертывание и тестирование Java микросервисов
И есть одна замечательная вещь об экосистеме Java, или, скорее, о JVM: вы пишете свой код Java один раз, вы можете запустить его в основном на любой операционной системе, если хотите, если вы не скомпилировали свой код с более новой версией Java, чем ваша целевая версия JVM).
Это важно понимать, особенно когда речь идет о таких темах, как Docker, Kubernetes или The Cloud. Почему? Давайте рассмотрим различные сценарии развертывания:
Пример минимального развертывания микросервиса Java
Продолжая пример с банком, мы получили файл monobank.jar (монолит) и наш недавно извлеченный riskengine.jar (первый микросервис).
Следовательно, минимальное развертывание может состоять из двух каталогов, которые выглядят примерно так:
К сожалению, есть множество соблазнительных ответов на этот вопрос.
Как использовать инструменты сборки, SSH и Ansible для развертывания микросервисов Java
Скучный, но совершенно прекрасный ответ на развертывание Java-микросервисов — это то, как администраторы разворачивали любую серверную Java-программу в компаниях за последние 20 лет. С использованием набора инструментов:
Если вы не зациклены на создании «дышащего» облака на основе серверов с автоматической балансировкой нагрузки, chaos monkeys, управляющих вашими машинами, или горячего и неясного ощущения, что выборы ведущего в ZooKeeper работают, то эта установка уведет вас достаточно далеко.
Олдскулно, скучно, но работает.
Как использовать Docker для развертывания микросервисов Java
Вернемся к заманчивым выборам. Пару лет назад на сцену вышел Docker или тема контейнеризации.
Если у вас нет опыта работы с ним, это то, что нужно для конечных пользователей или разработчиков:
Это выглядит немного иначе для языков, таких как PHP или Python, где несовместимость версий или настройки развертывания исторически были более сложными.
Или, если ваше Java-приложение зависит от множества других установленных служб (с правильными номерами версий): например от базы данных, такой как Postgres, или хранилища значений ключей, таких как Redis.
Итак, основное преимущество Docker для микросервисов Java, а точнее для приложений Java, заключается в:
Если ваши развертываемые файлы похожи или вы хотите запустить небольшую базу данных Oracle на своем компьютере разработки, попробуйте Docker.
Как использовать Docker Swarm или Kubernetes для развертывания микросервисов Java
Теперь возникает вопрос: как вы управляете этим кластером, что означает запуск ваших контейнеров Docker, проверки работоспособности, развертывание обновлений, масштабирование (brrrr)?
Два возможных ответа на этот вопрос — Docker Swarm и Kubernetes.
Подробное описание обоих вариантов невозможно в рамках данного руководства, но на самом деле важно следующее: оба варианта в конце концов полагаются на то, что вы пишете файлы YAML (см. ниже Не вопрос: Рассказы об отступах в Yaml) для управления вашим кластером. Сделайте быстрый поиск в Твиттере, если хотите знать, какие чувства это вызывает на практике.
Таким образом, процесс развертывания для ваших микросервисов Java теперь выглядит примерно так:
Как протестировать микросервисы Java
Предположим, вы решили внедрить микросервисы в производство, но как вы тестируете интеграцию n-микросервисов во время разработки? Чтобы увидеть, работает ли полный рабочий процесс, а не только отдельные части?
На практике вы найдете три разных способа:
Кроме того, в дополнение к вашим микросервисам Java вам, вероятно, также понадобится работающий брокер сообщений (например, ActiveMQ или RabbitMQ) или, возможно, сервер электронной почты или любой другой компонент обмена сообщениями, с которым ваши микросервисы Java должны взаимодействовать друг с другом.
Это приводит к значительному занижению сложности на стороне DevOps. Взгляните на Microservice Testing Libraries, чтобы смягчить эту боль.
В любом случае, эта сложность приводит нас к общим проблемам микросервиса:
Общие вопросы о Java микросервиах
Давайте рассмотрим проблемы микросервисов, характерные для Java, от более абстрактных вещей, таких как устойчивость к конкретным библиотекам.
Как сделать микросервис Java устойчивым?
Напомним, что при создании микросервисов вы по сути заменяете вызовы методов JVM
на синхронные вызовы или асинхронный обмен сообщениями.
В то время как выполнение вызова метода в основном гарантировано (за исключением того, что JVM неожиданно завершает работу), сетевой вызов по умолчанию ненадежен.
Он может работать, он также может не работать по разным причинам: от неработающей или перегруженной сети, до внедрения нового правила брандмауэра и взрыва вашего брокера сообщений.
Чтобы увидеть, какое это имеет значение, давайте взглянем на примерный пример BillingService.
Шаблоны устойчивости HTTP / REST
Скажем, клиенты могут купить электронные книги на сайте вашей компании. Для этого вы только что внедрили микросервис биллинга, который может вызвать ваш интернет-магазин для создания фактических счетов в формате PDF.
Сейчас мы сделаем этот вызов синхронно, через HTTP. (Было бы более разумно вызывать эту службу асинхронно, потому что генерация PDF не обязательно должна быть мгновенной с точки зрения пользователя. Но мы хотим повторно использовать этот самый пример в следующем разделе и увидеть различия.)
Подумайте, какие возможные результаты может иметь этот HTTP-вызов. Обобщая, вы получите три возможных результата:
Обработка ошибок, а не только счастливых случаев, ожидается для любой программы. То же самое относится и к микросервисам, даже если вам нужно приложить дополнительные усилия для обеспечения совместимости всех развернутых версий API, как только вы начнете с развертываний и выпусков отдельных микросервисов.
И если вы хотите полностью использовать Chaos Monkey, вам также придется смириться с возможностью того, что ваши серверы будут просто очищены во время обработки запроса, и вы можете захотеть, чтобы запрос был перенаправлен на другой работающий экземпляр.
Интересный случай с предупреждением — это отложенный случай. Возможно, микросервисный жесткий диск респондента заполнен, и вместо 50 мс для ответа требуется 10 секунд. Это может стать еще более интересным, когда вы испытываете определенную нагрузку, так что неотзывчивость вашего BillingService начинает каскадно проходить через вашу систему. Подумайте о медленной кухне, медленно запускающей блок всех официантов ресторана.
Этот раздел, очевидно, не может дать исчерпывающий обзор темы устойчивости микросервисов, но служит напоминанием для разработчиков о том, что на самом деле это то, что нужно решать, а не игнорировать до вашего первого выпуска (что, по опыту, происходит чаще, чем следует)
Популярной библиотекой, которая помогает вам думать о задержках и отказоустойчивости, является Hystrix от Netflix. Используйте его документацию, чтобы больше погрузиться в тему.
Шаблоны устойчивости обмена сообщениями
Давайте подробнее рассмотрим асинхронное общение. Наш код BillingService теперь может выглядеть примерно так, при условии, что мы используем Spring и RabbitMQ для обмена сообщениями.
Чтобы создать счет, мы теперь отправляем сообщение нашему брокеру сообщений RabbitMQ, у которого есть несколько работников, ожидающих новых сообщений. Эти работники создают счета в формате PDF и отправляют их соответствующим пользователям.
Теперь потенциальные ошибки выглядят немного иначе, так как вы больше не получаете немедленных ответов OK или ERROR, как это было с синхронным HTTP-соединением. Вместо этого у вас будут примерно три случая ошибки:
Опять же, детальное описание каждого отдельного шаблона устойчивости асинхронного микросервиса выходит за рамки данного руководства. Более того, оно лишь дает указатели в правильном направлении, тем более что они также зависят от используемой вами технологии обмена сообщениями. Примеры:
Какой Java-микросервисный фреймворк лучший?
Недавно, частично вдохновленные параллельными разработками, такими как реактивное программирование, Kubernetes или GraalVM, возникла пара специализированных микросервисных сред.
В конце концов, вам придется сделать свой собственный выбор, но эта статья может дать некоторые, возможно, нетрадиционные рекомендации:
За исключением Spring Boot, все фреймворки для микросервисов обычно позиционируют себя как невероятно быстрые с очень быстрым временем запуска, малый объемом памяти, возможностью масштабирования до бесконечности, с впечатляющими графиками, сравнивающими себя с бегемотом Spring Boot или друг с другом.
Это поражает воображение разработчиков, которые поддерживают устаревшие проекты, которые иногда занимают несколько минут, чтобы загружаться, или разработчикам, работающим в облаке, которые хотят запустить-остановить столько микроконтейнеров, сколько им сейчас нужно, или они хотят им нужно в течение 50 мс.
Проблема, однако, заключается в том, что (искусственное) время запуска на «голом железе» и время повторного развертывания едва ли влияют на общий успех проекта, гораздо меньше, чем сильная экоструктура платформы, хорошая документация, сообщество и навыки разработчика.
Вы должны смотреть на это так.
Тогда добавление дополнительных требований для микросервисов (устойчивость, сеть, обмен сообщениями, DevOps, инфраструктура) будет иметь гораздо большее влияние на ваш проект, чем загрузка пустого hello world. А для горячих повторных развертываний во время разработки вы, наконец, вы можете использовать такие решения, как JRebel или DCEVM.
Вернемся к цитате Саймона Брауна (Simon Brown): если люди не могут создавать (быстрые и эффективные) монолиты, им будет трудно создавать (быстрые и эффективные) микросервисы — независимо от фреймворка.
Итак, выбирайте свой фреймвок с умом.
Какие библиотеки лучше всего подходят для синхронных REST вызовов Java?
Поговорим о более практических аспектах вызова HTTP REST API. На низком техническом уровне вы, вероятно, придете к одной из следующих клиентских библиотек HTTP:
Обратите внимание, что здесь я говорю «вероятно», потому что есть и другие способы, от старых добрых клиентов JAX-RS до современных клиентов WebSocket.
В любом случае, существует тенденция к генерации HTTP-клиента, вместо того чтобы возиться с HTTP-вызовами самостоятельно. Для этого вам нужно рассмотреть проект OpenFeign и его документацию в качестве отправной точки для дальнейшего чтения.
Какие брокеры являются лучшими для асинхронного обмена сообщениями Java?
Начиная разработку с асинхронного обмена сообщениями, вы, скорее всего, придете к ActiveMQ (Classic или Artemis), RabbitMQ или Kafka. Опять же, это просто популярный выбор.
Вот несколько основных моментов:
Чтобы лучше понять, когда использовать RabbitMQ (или традиционных брокеров сообщений в целом) или Kafka, в качестве отправной точки взгляните на соответствующий пост Pivotal в качестве отправной точки.
Тем не менее, в общем, старайтесь игнорировать любые искусственные причины производительности при выборе вашего брокера. Было время, когда команды и интернет-сообщества много спорили о том, насколько быстрым был RabbitMQ и насколько медленным был ActiveMQ.
Теперь у вас те же аргументы в отношении того, что RabbitMQ медленен, с только 20-30K сообщений в секунду. Для Kafka сообщается о 100K сообщений в секунду. С одной стороны, такие сравнения можно игнорировать учитывая, что вы на самом деле сравниваете яблоки и апельсины.
Но даже более того: оба значения пропускной способности могут быть на нижней или средней стороне для Alibaba Group, но вы и автор, никогда не видели проекты такого размера (миллионы сообщений в минуту) в реальном мире. Они определенно существуют, но эти цифры — не то о чем нужно беспокоиться для остальных 99% обычных бизнес-проектов Java.
Так что не обращайте внимания на шумиху и выбирайте мудро.
Какие библиотеки я могу использовать для микросервисного тестирования?
В зависимости от вашего стека вы можете использовать инструменты Spring (экосистема Spring) или что-то вроде Arquillian (экосистема JavaEE).
Вы захотите взглянуть на Docker и действительно хорошую библиотеку Testcontainers, которая помогает, например, легко и быстро настроить базу данных Oracle для разработки, локальных тестов или интеграции.
Для макетирования целых HTTP-серверов, посмотрите на Wiremock. Для тестирования асинхронного обмена сообщениями попробуйте встраивание (ActiveMQ) или докеринг (RabbitMQ), а затем написать тесты с помощью Awaitility DSL.
Кроме этого, применяются все ваши обычные «подозреваемые», такие как Junit, TestNG для AssertJ и Mockito.
Обратите внимание, что это далеко не полный список, и если вам не хватает вашего любимого инструмента, опубликуйте его в разделе комментариев, и я включу его в следующую редакцию этого руководства.
Как включить ведение журнала для всех моих микросервисов Java?
Ведение журнала в микросервисах — интересная и довольно сложная тема. Вместо того, чтобы иметь один файл журнала, с которым вы можете использовать less или grep, теперь у вас есть n файлов — журнала, которые вы хотели бы просматривать вместе.
Отличной отправной точкой для всей экосистемы ведения журнала является эта статья. Обязательно прочитайте ее, особенно раздел «Централизованное ведение журнала» с точки зрения микросервисов.
На практике вы найдете различные подходы:
Как мои микросервисы находят друг друга?
До сих пор мы предполагали, что все наши микросервисы знают друг друга, знают соответствующий IPS. Это статическая настройка. Итак, наш банковский монолит [ip = 192.168.200.1] знает, что ему нужно поговорить с сервером риска [ip = 192.168.200.2], который жестко задан в файле свойств.
Однако вы можете сделать вещи более динамичными:
В общих чертах, это то, что называется микросервисной оркестровкой и еще одна огромная тема.
Такие библиотеки, как Eureka или Zookeeper, пытаются «решить» эти проблемы, например, создание клиентов или маршрутизаторов, знающих, какие службы доступны и где. С другой стороны, они вносят много дополнительной сложности.
Просто спросите любого, кто когда-либо запускал установку ZooKeeper.
Как сделать авторизацию и аутентификацию с помощью микросервисов Java?
Еще одна огромная тема, достойная собственного эссе. Опять же, варианты варьируются от жестко закодированной базовой аутентификации HTTPS с собственными фреймворками безопасности до запуска установки Oauth2 с вашим собственным сервером авторизации.
Как мне убедиться, что все мои окружения выглядят одинаково?
То, что верно для развертываний без микросервиса, также верно и для развертываний микросервиса. Вы можете попробовать комбинацию Docker / Testcontainers, а также сценариев / Ansible.
Попробуйте и сохраняйте его проще.
Не вопрос: Рассказы об отступах в Yaml
Сделаем резкий разворот от конкретных библиотечных вопросов, давайте кратко рассмотрим Yaml. Это формат файла, который используется в качестве фактического формата файла для «записи конфигурации в виде кода». От более простых инструментов, таких как Ansible, до могучих Kubernetes.
Чтобы испытать «мучения» от отступов в YAML, попробуйте написать простые файлы Ansible и посмотрите, как часто вам нужно повторно редактировать файл, чтобы заставить отступ работать правильно, несмотря на различные уровни поддержки IDE. А затем вернитесь, чтобы закончить это руководство.
А как насчет распределенных транзакций? Тестирование производительности? Другие темы?
К сожалению, эти темы не вошли в эту редакцию данного руководства. Оставайтесь с нами, чтобы узнать больше.
Концептуальные проблемы микросервисов
Помимо специфических проблем микросервисов в Java, существуют также проблемы, которые возникают в любом микросервисном проекте. Их больше с организационной, командной или управленческой точки зрения.
Несоответствие фронтенд и бэкеенд
То, что происходит во многих микросервисных проектах, я бы назвал несоответствием внешнего интерфейса и интерфейса микросервиса. Что это обозначает?
Что в старых добрых монолитах, у разработчиков веб-интерфейса был один конкретный источник для получения данных. В микросервисных проектах у разработчиков веб-интерфейса неожиданно появляются n источников для получения данных.
Представьте, что вы создаете какой-то проект микросервисов Java-IoT. Скажем, вы выполняете мониторинг машин, таких как промышленные печи по всей Европе. И эти печи регулярно отправляют вам обновления с указанием их температуры и т.д.
Рано или поздно вы, возможно, захотите выполнять поиск печи в пользовательском интерфейсе администратора, возможно, с помощью микросервисов «поиска печи». В зависимости от того, насколько строго ваши бэкэнд-коллеги могут интерпретировать domain driven design или законы о микросервисах, может случиться так, что микросервис «поиск печи» возвращает только ваши идентификаторы, но не другие данные, такие как тип, модель или местоположение.
Для этого разработчикам фронтенда может потребоваться выполнить один или n дополнительных вызовов (в зависимости от вашей реализации пейджинга) в микросервисе «получить данные о печи» с идентификаторами, которые они получили от первого микросервиса.
И хотя это всего лишь простой (но взятый из реального проекта (!)) Пример, он демонстрирует следующую проблему:
Реальные супермаркеты получили огромное признание по определенной причине. Потому что вам не нужно ходить в 10 разных мест, чтобы покупать овощи, лимонад, замороженную пиццу и туалетную бумагу. Вместо этого вы идете в одно место.
Это проще и быстрее. То же самое касается разработчиков интерфейсов и микросервисов.
Ожидания руководства
Эта проблема вызывает нежелательные побочные эффекты со стороны отдельных разработчиков, программистских журналов или облачных компаний, предлагающих микросервисы:
У руководства сложилось впечатление, что вы теперь можете вкладывать в (всеобъемлющий) проект бесконечное количество разработчиков, поскольку разработчики теперь могут работать совершенно независимо друг от друга, каждый на своем микросервисе. В самом конце требуется лишь небольшая работа по интеграции (т.е. незадолго до запуска).
Давайте рассмотрим в следующих параграфах, почему этот взгляд является серьёзной проблемой.
Меньшие части не означают лучшие части
Одна довольно очевидная проблема состоит в том, что 20 меньших частей (как в микросервисах) на самом деле не означают 20 лучших частей. Чисто с точки зрения технического качества это может означать, что ваши отдельные службы по-прежнему выполняют 400 запросов Hibernate для выбора пользователя из базы данных по слоям и слоям не поддерживаемого кода.
Возвращаясь к цитате Саймона Брауна: если людям не удастся построить монолиты должным образом, им будет сложно создать надлежащие микросервисы.
Во многих микросервисных проектах мысли об устойчивости происходят после запуска, является такой, поэтому немного страшно смотреть, как микросервисы работают вживую.
Это имеет простую причину: поскольку Java-разработчики обычно не заинтересованы в том, чтобы не обучены должным образом в области устойчивости, сетей и других смежных тем.
Меньшие части приводят к большему количеству технических частей
Кроме того, существует печальная тенденция к тому, чтобы пользовательские истории становились все более и более техническими (и, следовательно, глупыми), более микро и отвлеченными от пользователя, от которого они получены.
Представьте, что вашей микросервисной команде предлагается написать технический микросервис для входа в систему с базой данных, примерно такой:
Теперь ваша команда может решить (и, возможно, даже убедить бизнесменов): это слишком просто и скучно, вместо службы входа в систему давайте напишем действительно «умный» микросервис UserStateChanged — без каких-либо реальных и ощутимых бизнес-требований.
А поскольку Java в настоящее время не в моде, давайте напишем микросервис UserStateChanged в Erlang. И давайте попробуем где-нибудь использовать красно-черные деревья, потому что Steve Yegge написал, что вы должны знать их наизнанку, чтобы подать резюме в Google.
С точки зрения интеграции, обслуживания и общего проекта это так же плохо, как написание слоев спагетти-кода внутри одного монолита.
Придуманный и заурядный пример? Да.
К сожалению, также не редкость в реальной жизни.
Меньшие кусочки ведут к меньшему пониманию
Затем возникает тема понимания всей системы, ее процессов и рабочих процессов, если вы, как разработчик, несете ответственность только за работу на изолированном микросервисе [95: login-101: updateUserProfile].
Это гармонирует с предыдущим параграфом, но в зависимости от вашей организации, уровня доверия и коммуникации это может привести к большому пожиманию плечами и обвинениям, если сломается какая-то часть всей микросервисной цепочки — никто не больше принимает полную ответственность.
Это не является недобросовестностью, скорее проблема в том, что на самом деле очень трудно понять количество отдельных частей и их место в общей картине.
Коммуникации и обслуживание
Это связано с последней проблемой: коммуникации и обслуживание. Это, очевидно, сильно зависит от размера компании, с общим правилом: чем больше, тем проблематичнее.
Кто работает на микросервисе № 47?
Они только что развернули новую несовместимую версию микросервиса? Где это было задокументировано?
С кем мне нужно поговорить для запроса новой функции?
Кто будет поддерживать микросервис на Erlang после того, как Макс покинул компанию?
Все наши микросервисные команды работают не только на разных языках программирования, но и в разных часовых поясах! Как мы правильно координируем?
Главной темой здесь является то, что, подобно навыкам DevOps, полноценный подход к микросервисам в более крупной, возможно, даже международной компании, сопряжен с кучей дополнительных коммуникационных проблем. Как компания, вы должны быть готовы к этому.
Заключение
Прочитав эту статью, вы можете заключить, что ваш автор строго рекомендует микросервисы. Это не совсем верно — я в основном пытаюсь выделить моменты, которые забыты в безумии микросервисов.
Баланс микросервисов
Полное использование Java-микросервисов — это одно положение маятника. Другое положение — что-то вроде сотен старых добрых модулей Maven в Монолите. Вы должны найти правильный баланс.
Особенно в новых проектах ничто не мешает вам придерживаться более консервативного, монолитного подхода и создавать меньше, лучше определенных модулей Maven, вместо того чтобы сразу начинать работу с двадцатью готовыми к работе облачными микросервисами.
Микросервисы создают тысячи дополнительных сложностей
Имейте в виду, что чем больше у вас микросервисов и чем меньше у вас действительно сильных талантов DevOps (нет, выполнение нескольких сценариев Ansible или развертывание на Heroku не учитывается), тем больше проблем у вас возникнет позже в работе.
Прочитать раздел Общие вопросы о Java микросервисах этого руководства уже утомительно. Затем подумайте о реализации решений для всех этих инфраструктурных задач. Вы очевидно поймете, что все это больше не связано с бизнес-программированием (за что вам платят), а скорее с фиксацией большего количества технологий на еще большем количестве технологий.
Siva Prasad Reddy (Шива Прасад Редди) отлично подвел итог в своем блоге:
Я не могу объяснить, как это ужасно, когда команда тратит 70% времени на борьбу с этой современной инфраструктурой и 30% времени на реальную бизнес-логику.
Стоит ли создавать микросервисы Java?
Чтобы ответить на этот вопрос, я хотел бы закончить эту статью очень дерзко, похоже на тизер Google интервью. Если вы знаете ответ на этот вопрос по своему опыту, даже если он, по-видимому, не имеет ничего общего с микросервисами, вы можете быть готовы к микросервисному подходу.
Сценарий
Представьте, что у вас есть монолит Java, работающий самостоятельно на самой маленькой выделенной машине Hetzner. То же самое относится и к вашему серверу баз данных, он также работает на аналогичной машине Hetzner.
И давайте также предположим, что ваш Java-монолит может обрабатывать рабочие процессы, такие как регистрация пользователей, и вы создаете не сотни запросов к базе данных на рабочий процесс, а только разумное количество (



