Привет! Меня зовут Женя, я Java-разработчик в Usetech, в последнее время много работаю с микросервисной архитектурой, и в этой статье хотела бы поделиться некоторыми моментами, на которые может быть полезно обратить внимание, когда вы пишете новый микросервис на Spring Boot. Опытные разработчики могут счесть приведенные рекомендации очевидными, однако все они взяты из практики работы над реальными проектами. В традиционной слоистой архитектуре класс контроллера принимает запросы и направляет их сервису, а сервис занимается бизнес-логикой. Однако иногда в методах контроллера можно встретить какие-либо проверки входных параметров, а также преобразование Entity в DTO. Например: С одной стороны, маппинг занимает всего одну строку, да и проверка на отсутствие результата смотрится вполне логично. Однако в подобном случае нарушается принцип единой ответственности контроллера. Пока валидация или маппинг простые, пара лишних строк кода в методе контроллера совсем не бросаются в глаза, но в дальнейшем логика как валидации, так и маппинга может усложниться, и тогда станет очевидно, что контроллер не только принимает и перенаправляет запросы, но еще и занимается бизнес-логикой. Чтобы не пришлось потом проводить объемный рефакторинг, лучше сразу, пока микросервис еще содержит минимальную функциональность, сделать все контроллеры "тонкими", лишь вызывающими методы сервиса, а валидацию и маппинг осуществлять в сервисном классе и возвращать из него сразу DTO. Метод контроллера после рефакторинга: Метод сервиса после рефакторинга: Представьте микросервис, который отдает некие данные в виде DTO по REST API, а также пишет сообщения в виде DTO в Kafka. В начале жизни микросервиса состав данных, которые нужно отдать по REST и передать в Kafka, может быть одинаков, что может спровоцировать нас использовать один и тот же DTO в обоих случаях. Проблема усугубится, если мы используем один DTO для еще большего количества разных клиентов. Если в дальнейшем изменятся требования по составу данных для различных случаев, и мы решим выделить отдельные DTO, то появится риск в процессе выполнения этой задачи пропустить место, где нужно произвести замену на новое DTO, или наоборот заменить, где не нужно. Лучшим вариантом будет сразу предусмотреть отдельные DTO для разных случаев, даже если сейчас они совпадают по полям. После того, как первая задача, реализующая минимальную функциональность нового микросервиса, готова, важно не забыть обратить внимание на предупреждения, которые выводит Spring Boot в логах, и по возможности сразу исправить их, чтобы они не накопились как снежный ком. Пример WARN, который выводится даже у "пустого" Spring Boot 2 приложения с Hibernate:
spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning Это предупреждение сообщает, что по-умолчанию в Spring Boot 2 включен режим Open Session In View, при котором сессия Hibernate держится открытой все время обработки HTTP-запроса.  Хотя режим Open Session In View позволяет избежать LazyInitializationException, которое возникает если мы пытаемся получить данные, когда сессия Hibernate уже закрыта, тем не менее, его использование является анти-паттерном. Этот режим провоцирует проблемы с производительностью приложения, поскольку приложение держит долгое соединение с базой данных, а также значительно увеличивается количество запросов, так как каждая связанная с сущностью коллекция будет загружена отдельным запросом (проблема n+1). Подробнее об этом можно прочитать в статье. Для того, чтобы отключить режим Open Session In View нужно сделать как раз то, что написано в предупреждении — добавить в application.yml настройку: Как известно, интеграционные тесты использованием @SpringBootTest поднимают контекст приложения и создают бины, благодаря чему мы можем протестировать приложение не с заглушками, а с настоящими бинами. Однако если у нас есть несколько тестовых классов, использующих аннотацию @SpringBootTest, то в некоторых случаях по логам можно заметить, что Spring поднимает контекст заново на каждый тестовый класс. На корректность тестов это не влияет, однако сильно влияет на общее время прохождения тестов и соответственно время сборки проекта. Если правильно настроить тестовые классы, то контекст будет подниматься всего один раз, кэшироваться и использоваться для всех тестовых классов. Что заставляет контекст перезапускаться заново: Чтобы контекст кэшировался нужно устранить все перечисленные причины, т.е. явные различия в настройках тестовых классов. Мне подошел подход с созданием базового абстрактного тестового класса, который: Иногда в этом классе также может быть удобно держать метод очистки, запускаемый после каждого теста (@AfterEach), однако это уже дело вкуса/особенностей тестов. Тестовые классы, в свою очередь, наследуются от абстрактного базового класса, не содержат конфигурации и даже полей, а только setUp метод и сами тестовые методы. В случае реальной необходимости перезапустить контекст у дочернего тестового класса можно использовать @DirtiesContext. Буду рада, если какой-то из пунктов окажется для вас полезным в работе. Java-разработчик