JavaRush /Java Blog /Random-TL /Isang Gabay sa Java Microservices. Bahagi 3: pangkalahata...

Isang Gabay sa Java Microservices. Bahagi 3: pangkalahatang mga katanungan

Nai-publish sa grupo
Pagsasalin at pag-aangkop ng Java Microservices: A Practical Guide . Mga nakaraang bahagi ng gabay: Tingnan natin ang mga likas na problema ng mga microservice sa Java, simula sa mga abstract na bagay at nagtatapos sa mga konkretong aklatan. Руководство по микросервисам Java. Часть 3: общие вопросы - 1

Paano gawing nababanat ang microservice ng Java?

Alalahanin na kapag gumawa ka ng mga microservice, talagang ipinagpapalit mo ang mga tawag sa pamamaraan ng JVM para sa mga sabay-sabay na HTTP na tawag o asynchronous na pagmemensahe. Bagama't halos garantisadong makumpleto ang isang method call (maliban sa hindi inaasahang pag-shutdown ng JVM), hindi maaasahan ang isang tawag sa network bilang default. Maaari itong gumana, ngunit maaaring hindi ito gumana sa iba't ibang dahilan: ang network ay na-overload, isang bagong panuntunan sa firewall ay ipinatupad, at iba pa. Upang makita kung paano ito gumagawa ng pagkakaiba, tingnan natin ang halimbawa ng BillingService.

Mga Pattern ng Resilience ng HTTP/REST

Sabihin nating ang mga customer ay maaaring bumili ng mga e-book sa website ng iyong kumpanya. Upang gawin ito, nagpatupad ka lang ng microservice sa pagsingil na maaaring tumawag sa iyong online na tindahan upang bumuo ng mga aktwal na PDF invoice. Sa ngayon, gagawin namin ang tawag na ito nang sabay-sabay, sa pamamagitan ng HTTP (bagama't mas makatuwirang tawagan ang serbisyong ito nang asynchronous, dahil hindi kailangang madalian ang pagbuo ng PDF mula sa pananaw ng user. Gagamitin namin ang parehong halimbawa sa susunod seksyon at tingnan ang mga pagkakaiba).
@Service
class BillingService {

    @Autowired
    private HttpClient client;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
        // ...
    }
}
Upang buod, narito ang tatlong posibleng resulta ng HTTP na tawag na ito.
  • OK: natuloy ang tawag, matagumpay na nalikha ang account.
  • DELAY: Natapos ang tawag, ngunit napakatagal bago makumpleto.
  • ERROR. Nabigo ang tawag, maaaring nagpadala ka ng hindi tugmang kahilingan, o maaaring hindi gumagana ang system.
От любой программы ожидают обработку ошибочных ситуаций, а не только успешных. То же самое относится и к микросервисам. Даже если вам нужно приложить дополнительные усorя для обеспечения совместимости всех развернутых версий API, How только вы начнете с развертываний и выпусков отдельных микросервисов. Руководство по микросервисам Java. Часть 3: общие вопросы - 2Интересный случай, на который стоит обратить внимание, — случай задержки. Например, микросервисный жесткий диск респондента переполнен, и instead of 50 мс для ответа требуется 10 секунд. Ещё интереснее становится тогда, когда вы испытываете определенную нагрузку, так что неотзывчивость вашего BillingService начинает каскадно проходить через вашу систему. В качестве наглядного примера вообразите кухню, меееедленно запускающую “блок” всех официантов ресторана. Этот раздел, очевидно, не может дать исчерпывающий обзор темы устойчивости микросервисов, но служит напоминанием для разработчиков о том, что на самом деле это то, что нужно решать, а не игнорировать до вашего первого выпуска (что, по опыту, происходит чаще, чем следует). Популярной библиотекой, которая помогает вам думать о задержках и отказоустойчивости, является Hystrix от Netflix. Используйте её documentацию, чтобы больше погрузиться в тему.

Messaging Resilience Patterns (Отказоустойчивые паттерны обмена messagesми)

Давайте подробнее рассмотрим асинхронную коммуникацию. Наша программа BillingService теперь может выглядеть примерно так, при условии, что мы используем Spring и RabbitMQ для обмена messagesми. Whatбы создать счет, мы теперь отправляем сообщение нашему брокеру сообщений RabbitMQ, где есть несколько работников, ожидающих новых сообщений. Эти работники создают счета в формате PDF и отправляют их соответствующим пользователям.
@Service
class BillingService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        // преобразует счет, например, в json и использует его How тело messages
        rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
        // ...
    }
}
Теперь потенциальные ошибки выглядят немного иначе, так How вы больше не получаете немедленных ответов OK or ERROR, How это было с синхронным HTTP-соединением. Вместо этого у нас может быть три потенциальных варианта неправильного развития событий, которые могут вызвать следующие вопросы:
  1. Было ли мое сообщение доставлено и использовано работником? Или это потеряно? (Пользователь не получает счет-фактуру).
  2. Мое сообщение было доставлено только один раз? Или доставлено более одного раза и обрабатывается только один раз? (Пользователь получит несколько счетов).
  3. Конфигурация: От «Использовал ли я правильные ключи маршрутизации/имена для обмена» до «Правильно ли настроен и поддерживается мой брокер сообщений or переполнены его очереди?» (Пользователь не получает счет-фактуру).
Детальное описание каждого отдельного паттерна устойчивости асинхронного микросервиса выходит за рамки данного руководства. Тем не менее, тут есть указатели в правильном направлении. Тем более что они будут зависеть от технологии обмена messagesми. Примеры:
  • Если вы используете реализации JMS, например, ActiveMQ, вы можете обменять speed на гарантии двухфазных (XA) коммитов (two-phase (XA) commits).
  • Если вы используете RabbitMQ, для начала прочитайте это руководство, а затем хорошенько обдумайте подтверждения, отказоустойчивость и надежность сообщений в целом.
  • Возможно, кто-то хорошо разбирается в конфигурировании serverов Active or RabbitMQ, особенно в сочетании с кластеризацией и Docker (кто-нибудь?;))

Какой фрейморк будет лучшим решением для микросервисов Java?

С одной стороны, можно установить очень популярный вариант, такой How Spring Boot. Он позволяет очень легко создавать файлы .jar, поставляется со встроенным веб-serverом, таким How Tomcat or Jetty, и который можно запустить быстро и где угодно. Идеально подходит для создания приложений микросервиса. Не так давно появилась пара специализированных микросервисных фреймворков Kubernetes or GraalVM, частично вдохновлённых реактивным программированием. Вот ещё несколько интересных претендентов: Quarkus, Micronaut, Vert.x, Helidon. В конце концов, вам придется выбирать самостоятельно, но мы можем дать вам парочку рекомендаций, возможно, не вполне стандартных: За исключением Spring Boot, все платформы микросервисов обычно позиционируются How невероятно быстрые, с почти мгновенным запуском, малым объемом используемой памяти, возможностью масштабирования до бесконечности. В маркетинговых материалах обычно фигурируют впечатляющие графики, представляющие платформу в выгодном свете рядом с “бегемотом” Spring Boot or друг с другом. Это по идее щадит нервы разработчиков, поддерживающих легаси-проекты, которые порой загружаются по несколько minutes. Или разработчикам, работающим в облаке, которые хотят запустить\остановить столько микроконтейнеров, сколько им сейчас нужно в течение 50 мс. Руководство по микросервисам Java. Часть 3: общие вопросы - 3Проблема, однако, заключается в том, что такое (искусственное) время старта «голого железа» и время повторного развертывания едва ли влияют на общий успех проекта. По крайней мере влияют гораздо меньше, чем сильная инфраструктура фреймворка, сильная documentация, сообщество и сильные навыки разработчика. Так что лучше смотреть на это так: Если до сих пор:
  • Вы позволяете своим ORM работать в режиме безудержной генерации сотен requestов для простых рабочих процессов.
  • Вам нужны бесконечные гигаbyteы для запуска вашего монолита умеренной сложности.
  • У вас так много codeа и сложность столь высока (сейчас мы говорим не о потенциально медленных стартерах, таких How Hibernate), что вашему приложению нужно несколько minutes для загрузки.
Если дело обстоит именно так, то добавление дополнительных микосервисных проблем (отказоустойчивостьЮ сеть, обмен messagesми, DevOps, инфраструктура) гораздо больше повлияет на ваш проект, чем загрузка пустого Hello, world. А для горячих повторных развертываний во время разработки вам, в конце концов, могут пригодиться такие решения, How JRebel or DCEVM. Не поленимся вновь процитировать Саймона Брауна: “если люди не могут создавать (быстрые и эффективные) монолиты, им будет трудно создавать (быстрые и эффективные) микросервисы независимо от структуры”. Так что выбирайте фрейморки с умом.

Какие библиотеки лучше всего подходят для синхронных вызовов Java REST?

На низкоуровневой технической стороне вы, вероятно, получите одну из следующих клиентских библиотек HTTP: Собственный HttpClient Java (начиная с Java 11), HttpClient Apache or OkHttp. Обратите внимание, что здесь я говорю «вероятно», потому что есть и другие варианты, начиная со старых добрых клиентов JAX-RS до современных клиентов WebSocket. В любом случае, существует тенденция к генерации HTTP-клиента, с отходом от самостоятельной возни с HTTP-вызовами. Для этого вам нужно взглянуть на проект OpenFeign и его documentацию в качестве отправной точки для дальнейшего чтения.

Какие брокеры являются лучшими для асинхронного обмена messagesми Java?

Скорее всего, вы столкнётесь с популярными ActiveMQ (Classic or Artemis), RabbitMQ or Kafka.
  • ActiveMQ и RabbitMQ являются традиционными, полноценными брокерами сообщений. Они предполагают взаимодействие “умного брокера” и “глупеньких пользователей”.
  • Исторически ActiveMQ имел преимущество простого встраивания (для тестирования), которое можно смягчить с помощью настроек RabbitMQ/Docker/TestContainer.
  • Kafka нельзя назвать традиционным “умным” брокером. Наоборот, это относительно «глупое» хранorще сообщений (файл журнала), для обработки которого нужны умные потребители.
Whatбы лучше понять, когда использовать RabbitMQ (or другие традиционные брокеры сообщений в целом) or Kafka, взгляните на этот пост в Pivotal(на английском языке) в качестве отправной точки. В целом, когда выбираете брокер обмена messagesми, старайтесь игнорировать искусственные причины производительности. Было время, когда команды и интернет-сообщества постоянно спорor о том, насколько быстрым был RabbitMQ и насколько медленным ActiveMQ. Теперь те же аргументы приводятся в отношении RabbitMQ, дескать он медленно работает с 20-30 тысячами сообщений в секунду. У Kafka фиксируется 100 тысяч сообщений в секунду. Откровенно говоря, такие сравнения подобны сравнению теплого с мягким. Кроме того, в обоих случаях значения пропускной способности могут быть на нижнем or среднем уровне, скажем, для Alibaba Group. Однако вы вряд ли сталкивались с проектами такого масштаба (миллионы сообщений в minutesу) в реальности. Они определенно существуют, и у них были бы проблемы. В отличие от остальных 99% “обычных” бизнес-проектов Java. Так что не обращайте внимания на моду и хайп. Выбирайте с умом.

Какие библиотеки я могу использовать для тестирования микросервисов?

Это зависит от вашего стека. Если у вас развёрнута экосистема Spring, будет разумно использовать специальные инструменты этого фреймворка. Если JavaEE — что-то вроде Arquillian. Возможно, стоит взглянуть на Docker и действительно хорошую библиотеку Testcontainers, которая помогает, в частности, легко и быстро настроить базу данных Oracle для локальных тестов разработки or интеграции. Для мок-тестов целых HTTP-serverов, обратите внимание на Wiremock. Для тестирования асинхронного обмена messagesми попробуйте внедрить ActiveMQ or RabbitMQ, а затем написать тесты с помощью Awaitility DSL. Кроме этого, применяются все ваши привычные инструменты — Junit, TestNG для AssertJ и Mockito. Обратите внимание, что это далеко не полный список. Если вдруг вы не нашли здесь вашего любимого инструмента, опубликуйте его в разделе комментариев.

Как включить логирование для всех микросервисов Java?

Логирование в случае с микросервисами — интересная и довольно сложная тема. Вместо того, чтобы иметь один файл лога, с которым вы можете работать посредством команд less or grep, теперь у вас есть n файлов логирования, и желательно, чтобы они не были слишком разрознены. Хорошо расписаны особенности экосистемы логирования в этой статье (на английском языке). Обязательно прочитайте его, и обратите внимание на раздел Централизованное ведение лога с точки зрения микросервисов. На практике вы столкнётесь с различными подходами: Системный администратор пишет определённые сценарии, которые собирают и объединяют файлы логов с различных serverов в один файл логов и помещают их на FTP-serverы для загрузки. Запуск комбинаций cat/grep/unig/sort в параллельных SSH-сессиях. Именно так поступает Amazon AWS, о чём вы можете сообщить своему менеджеру. Используйте такой инструмент, How Graylog or ELK Stack (Elasticsearch, Logstash, Kibana)

Как мои микросервисы находят друг друга?

До сих пор мы предполагали, что наши микросервисы знают друг о друге, знают соответствующий IPS. Поговорим о статической настройке. Итак, наш банковский монолит [ip = 192.168.200.1] знает, что ему нужно поговорить с риск-serverом [ip = 192.168.200.2], который захардкожен в файле properties. Однако вы можете сделать всё более динамичным:
  • Используйте облачный server конфигурации, с которого все микросервисы извлекают свои конфигурации instead of развертывания файлов application.properties на своих микросервисах.
  • Поскольку экземпляры ваших служб могут динамически менять свое местоположение, стоит присмотреться к службам, которые знают, где живут ваши службы, Howие у них IP и How их маршрутизировать.
  • Теперь, когда все динамично, появляются новые проблемы, такие How автоматическое избрание лидера: кто является мастером, который работает над определенными задачами, чтобы например, не обработать их дважды? Кто заменяет лидера, когда он терпит неудачу? По Howому принципу происходит замена?
В общих чертах, это то, что называется микросервисной оркестровкой и она представляет собой ещё одну бездонную тему. Такие библиотеки, How Eureka or Zookeeper, пытаются «решить» эти проблемы, показывая Howие службы доступны. С другой стороны, они привносят дополнительную сложность. Спросите любого, кто когда-либо устанавливал ZooKeeper.

Как организовать авторизацию и аутентификацию с помощью микросервисов Java?

Эта тема также достойна отдельного рассказа. Снова таки, варианты варьируются от захардкоженной базовой аутентификации HTTPS с самописными фреймфорками безопасности до запуска установки Oauth2 с собственным serverом авторизации.

Как убедиться, что все мои окружения выглядят одинаково?

То, что верно для развертываний без микросервиса, также верно и для развертываний с ним. Попробуете комбинацию Docker/Testcontainers, а также Scripting/Ansible.

Не вопрос: кратко о YAML

Давайте ненадолго отойдём от библиотек и связанных с ними вопросов и вкратце рассмотрим Yaml. Этот формат file, используется де-факто в качестве формата для «записи конфигурации в виде codeа». Используют его и простые инструменты, наподобие Ansible и гиганты вроде Kubernetes. Whatбы испытать боль от отступов в YAML, попробуйте написать простой Ansible-файл и посмотрите, сколько вам придётся редактировать файл прежде, чем он заработает How нужно. И это невзирая на поддержку формата всеми крупными IDE! После этого возвращайтесь, чтобы дочитать это руководство.
Yaml:
  - is:
    - so
    - great

А How насчет распределенных транзакций? Тестирование производительности? Другие темы?

Может, когда-нибудь, в следующих редакциях руководства. А пока — всё. Оставайтесь с нами!

Концептуальные проблемы микросервисов

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

Несоответствие Frontend и Backend

Несоответствие Frontend и Backend — весьма распространенная проблема многих микросервисных проектов. What она означает? Лишь то, что в старых добрых монолитах, у разработчиков веб-интерфейса был один конкретный источник для получения данных. В микросервисных проектах у разработчиков веб-интерфейса неожиданно появляются n источников для получения данных. Представьте, что вы создаете Howой-то проект микросервисов IoT (интернет вещей) на Java. Скажем, заведуете геодезическими машинами, промышленными печами по всей Европе. И эти печи регулярно отправляют вам обновления с указанием их температуры и тому подобными данными. Рано or поздно вы, возможно, захотите найти печи в пользовательском интерфейсе администратора, возможно, с помощью микросервисов «поиска печи». В зависимости от того, насколько строго ваши бэкэнд-коллеги применяют предметно-ориентированное проектирование or законы микросервисов, микросервис “найти печь” может возвращать только идентификаторы печей, а не другие данные, такие How тип, модель or местоположение. Для этого фронтенд-разработчикам нужно будет выполнить один or n дополнительных вызовов (в зависимости от реализации пейджинга) в микросервисе «получить данные о печи» с идентификаторами, которые они получor от первого микросервиса. Руководство по микросервисам Java. Часть 3: общие вопросы - 4И хотя это всего лишь простой пример, пускай и взятый из реального (!) проекта, даже он демонстрирует следующую проблему: супермаркеты стали чрезвычайно популярны. А всё потому, что с ними вам не нужно идти в 10 разных мест, чтобы купить овощи, лимонад, замороженную пиццу и туалетную бумагу. Вместо этого вы идете в одно место.Это проще и быстрее. То же самое касается разработчиков интерфейсов и микросервисов.

Ожидания руководства

У менеджмента складывается ошибочное впечатление, что теперь нужно нанимать бесконечное количество разработчиков во (всеобъемлющий) проект, поскольку разработчики теперь могут работать совершенно независимо друг от друга, каждый на своем микросервисе. В самом конце требуется лишь небольшая работа по интеграции (незадолго до запуска). Руководство по микросервисам Java. Часть 3: общие вопросы - 5На самом деле такой подход крайне проблематичен. В следующих параграфах мы постараемся пояснить, почему.

“Меньшие кусочки” не равно “лучшие кусочки”

Будет большой ошибкой считать, что разделённый на 20 частей code обязательно будет качественнее одного цельного куска. Даже если взять качество с сугубо технической точки зрения: наши отдельные службы по-прежнему могут выполнять 400 requestов Hibernate для выбора пользователя из базы данных, проходясь по слоям не поддерживаемого codeа. В очередной раз возвращаемся к цитате Саймона Брауна: если не удастся построить монолиты должным образом, будет сложно создать надлежащие микросервисы. Зачастую об отказоустойчивости в микросервисных проектах крайне несвоевременно. Настолько, что порой страшно смотреть, How микросервисы работают в настоящих проектах. Причина этого кроется в том, что Java-разработчики не всегда готовы изучать отказоустойчивость, сети и другие смежные темы на должном уровне. Сами “кусочки” — меньше, а вот “технических частей” — больше. Представьте, что вашей микросервисной команде предлагается написать технический микросервис для входа в систему базы данных, примерно такой:
@Controller
class LoginController {
    // ...
    @PostMapping("/login")
    public boolean login(String username, String password) {
        User user = userDao.findByUserName(username);
        if (user == null) {
            // обработка варианта с несуществующим пользователем
            return false;
        }
        if (!user.getPassword().equals(hashed(password))) {
            // обработка неверного пароля
            return false;
        }
        // 'Ю-ху, залогинorсь!';
        // установите cookies, делайте, что угодно
        return true;
    }
}
Теперь ваша команда может решить (и, возможно, даже убедить людей бизнеса), дескать, это всё слишком просто и скучно, лучше instead of службы входа в систему написать действительно полезный микросервис UserStateChanged (изменение состояния пользователя) без Howих-либо реальных и ощутимых бизнес-требований. А поскольку к Java в настоящее время некоторые люди относятся How к динозавру, напишем наш микросервис UserStateChanged на модном Erlang. И давайте попробуем где-нибудь использовать красно-черные деревья, потому что Стив Йегге написал, что вы должны знать их изнутри, чтобы подать заявку в Google. С точки зрения интеграции, обслуживания и общего проекта это так же плохо, How написание слоев спагетти-codeа внутри одного монолита. Искусственный и заурядный пример? Так и есть. Тем не менее, подобное может быть и в реальности.

Меньше кусочки — меньше понимания

Затем естественным образом всплывает вопрос о понимании системы в целом, её процессов и рабочих потоков, но при этом вы, How разработчик, несете ответственность только за работу на своём изолированном микросервисе [95: login-101: updateUserProfile]. Он гармонирует с предыдущим параграфом, но в зависимости от вашей организации, уровня доверия и коммуникации это может привести к большому количеству недоумения, пожиманий плечами, обвинениям в случае случайной поломки в микросервисной цепочке. И нет того, кто бы принял на себя полную ответственность за случившееся. И дело вовсе не в недобросовестности. На самом деле очень трудно соединить разные детальки и понять их место в общей картине проекта.

Коммуникации и обслуживание

Уровень коммуникации и обслуживания сильно зависит от размера компании. Тем не менее, общая зависимость очевидна: чем больше, тем проблематичнее.
  • Кто работает на микросервисе № 47?
  • Они только что развернули новую несовместимую версию микросервиса? Где это было заdocumentировано?
  • С кем мне нужно поговорить чтобы requestить новую функции?
  • Кто будет поддерживать тот микросервис на Erlang, после того, How единственный кто знал этот язык покинул компанию?
  • Все наши микросервисные команды работают не только на разных языках программирования, но и в разных часовых поясах! Как мы всё это правильно скоординируем?
Руководство по микросервисам Java. Часть 3: общие вопросы - 6Главная мысль заключается в том, что How и в случае с DevOps, полноценный подход к микросервисам в крупной, возможно, даже международной компании, сопряжен с кучей дополнительных коммуникационных проблем. И компания должна серьезно к этому подготовиться.

Выводы

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

Микросервисы or монолит?

Использование Java-микросервисов всегда и везде — это одна крайность. Другой оказывается что-то вроде сотен старых добрых модулей Maven в монолите. Ваша же задача найти правильный баланс. Особенно это касается новых проектов. Здесь вам ничто не помешает придерживаться более консервативного, “монолитного” подхода и создавать меньшее количество хороших модулей Maven, instead of того, чтобы начинать с двадцати микросервисов, готовых к работе в облаках.

Микросервисы генерируют дополнительную сложность

Имейте в виду, что чем больше у вас микросервисов и чем меньше у вас действительно мощных DevOps’ов (нет, запуск пары-тройки сценариев Ansible or развертывание на Heroku не считается!), тем больше проблем у вас возникнет позже в работе. Даже просто прочитать до конца раздел этого руководства, посвящённый общим вопросам о микросервисах Java — довольно утомительное занятие. Хорошенько подумайте о реализации решений для всех этих инфраструктурных задач, и вы внезапно поймете, что все это больше не связано с бизнес-программированием (за что вам платят), а скорее с фиксацией большего количества технологий на еще большем количестве технологий. Шива Прасад Редди отлично резюмировал в своем блоге: “Вы не представляете себе, How это ужасно, когда когда команда 70% времени борется с этой современной инфраструктурой и только 30% времени остаётся на реальную бизнес-логику” Шива Прасад Редди

Стоит ли создавать микросервисы Java?

Whatбы ответить на этот вопрос, я хотел бы закончить эту статью очень дерзким, похожим на собеседование в Google тизером. Если вы знаете ответ на этот вопрос по своему опыту, даже если он, по-видимому, не имеет ничего общего с микросервисами, вы можете быть готовы к микросервисному подходу.

Сценарий

Представьте, что у вас есть Java-монолит, работающий в одиночку на самом маленьком выделенном serverе Hetzner. То же самое относится и к вашему serverу баз данных, он также работает на аналогичной машине Hetzner. И давайте также предположим, что ваш Java-монолит может обрабатывать рабочие процессы, скажем, регистрацию пользователей, и вы создаете не сотни requestов к базе данных на рабочий процесс, а более разумное количество (<10).

Вопрос

Сколько соединений с базой данных должен открыть ваш монолит Java (пул соединений) на вашем serverе баз данных? Почему так? Как вы думаете, сколько активных пользователей одновременно может (приблизительно) масштабировать ваш монолит?

Ответ

Оставьте свой ответ на эти вопросы в разделе комментариев. Я с нетерпением жду всех ответов. Руководство по микросервисам Java. Часть 3: общие вопросы - 8Теперь решайтесь. Если вы дочитали до самого конца, мы очень вам благодарны!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION