(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

У любого Spring Boot приложения есть настройки. В базовом варианте они хранятся в application.yaml файле. Но вообще говоря алгоритм, по которым Spring ищет эти настройки достаточно сложный. Вот тут https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config и вот тут https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files он описан детальнее.
Когда это "тайное знание" может быть полезно - если нужно задать настройки по умолчанию или, наоборот, переопределить их. Или разобраться - откуда тянется та или иная настройка.

#spring_boot #spring
Всем привет!

Помнится лет 10 назад был такой легендарный вопрос на баше: как пропатчить KDE2 под FreeBSD)
У меня канал немного про другое, но при прочтении данной статьи https://telegra.ph/Kak-sdelat-ssylki-na-metody-druzhelyubnymi-dlya-otladki-07-23 вспомнилось)
А вообще интересен тот факт, что пропатчить JDK как выясняется не так уж и сложно.

#jdk
Всем привет!

Есть такой класс библиотек в Java как mappers. Служат для преобразования одного класса в другой без написания boiler plate кода, в котором:
а) легко допустить ошибку
б) сложно тестировать. Сложно не в плане сложности теста, а из-за того, что постоянно возникает вопрос - а надо ли это тестировать?)

Самым известным mapper-ом в Java является MapStruct. Альтернативы: ModelMapper, Dozer, JMapper, Orica. Если знаете еще - пишите в комментах.

Главная фишка любого mapper - он автоматически копирует значения совпадающих по имени полей, тем самым избавляя от boiler plate кода и потенциальных ошибок.
Также все вышеперечисленные библиотеки умеют работать с вложенными объектами, в частности мапить коллекцию на коллекцию.
Но что делать, если поля не совпадают по имени или по типу? Для этого есть возможность переопределения маппинга для отдельных полей, с указанием нужного имени и преобразования типа.
Но ближе к делу, вот пример маппинга с использованием MapStruct как самой распространенной библиотеки:

@Mapper(componentModel = "spring")
public interface MapStructConverter extends Converter {
@Mapping(source = "status", target = "orderStatus")
Order convert(SourceOrder sourceOrder);
}

Здесь все поля SourceOrder будут скопированы в одноименные поля Order, за исключением поля status, которое станет orderStatus.
Может возникнуть вопрос - это же интерфейс, где исполняемый код?
Код будет сгенерирован при сборке в виде реализации данного интерфейса. Другие вышеперечисленные библиотеки используют либо рефлексию, либо генерация байткода для маппинга.

Главный вопрос - когда это все нужно?
Общий ответ - маппинг модели в DTO или DTO в DTO.
Более конкретный:
1) Полностью совпадающие по полям объекты. Например, это могут быть сгенерированные по разным версиям xsd или Json Schema DTO, хранящие одни и те же данные.
Или если ваши доменные объекты совпадают с DTO. Идеальный кейс для применения mappers.
2) Подвид варианта 1 когда в объектах различаются пару полей.
3) Маппинг коллекций похожих объектов, это тоже boiler plate код. Тут надо смотреть насколько похожи объекты.

Что важно - не использовать библиотеки маппинга для достаточно сильно различающихся объектов, а делать его руками.
Т.к. в таком случае проявляются минусы данных библиотек:
1) этап кодогенерации сам по себе не является злом, но увеличивает сложность сборки. А если вместо генерации используется рефлексия - уменьшается производительность.
2) когда кастомного маппинга становится слишком много, то это усложняет чтение кода. Ты видишь код маппинга, а половины полей там нет...
3) часто кастомный маппинг делается в аннотациях, а это значит что IDEA не подсветит ошибки, они будут видны только во время сборки

P.S. Есть идея сделать статью по сравнению всех вышеперечисленных библиотек.

#java #mappers
Всем привет!

Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шаблоны_проектирования
И среди них есть много на первый взгляд похожих паттернов для подмены одного объекта\сервиса другим. Да и на второй тоже сложно понять где что использовать)
Как же их различать?
Здесь и далее я употребляю слово интерфейс, но если паттерн используется для внешних интеграций, то его можно заменить на API.

Декоратор - добавляет поведение не меняя исходный интерфейс. Пример: добавить к сервису логирование, метрики или аудит.
Прокси - обеспечивает контроль доступа не меняя исходный интерфейс. Пример: проверка доступа, rate limiting.
Фасад - упрощает интерфейс для работы с объектом. Пример: SLF4
Адаптер - адаптирует один существующий интерфейс к другому. Пример: реализация Iterable для объекта, чтобы по нему можно было итерироваться
Gateway (шлюз) - паттерн уровня приложения https://martinfowler.com/articles/gateway-pattern.html в отличие от указанных выше паттернов проектирования, служит для добавления единого интерфейса к внешней системе. Пример: шлюз для доступа к любому внешнему сервису.

Если я что-то забыл или не согласны с чем-то - пишите.
Да, еще важный момент. Лично меня бесит, когда все классы с логикой в приложении называются Service. Не забывайте про 5 шаблонов, описанных выше!

#patterns
Всем привет!

Пару слов про gRPC - новый модный молодежный протокол для интеграции приложений.
Лично я всегда считал основных его преимуществом бинарный формат данных, а, следовательно, быстродействие.
Но это один из трех китов - трех преимуществ gRPC.
Какие же два других?

2) проблема общепринятого сейчас REST-а - в нем не было и нет встроенной схемы данных. Да, есть JSON Schema, OpenAPI и Consumer Driven Contracts. Но где-то они есть, а где-то - нет, причем это могут быть работающие вместе клиент и сервер) Можно же просто получить строку ответа и распарсить ее самостоятельно. И чем больше компания, чем больше у нее микросервисов внутри - тем сложнее будет поддержка и обновление зоопарка REST сервисов со временем. С этим столкнулись Google - разработчик gRPC, Netflix, Dropbox, Facebook - разработчик Thrift, аналога gRPC. В gRPC она есть, из нее генерируется код сервера и клиента. Не весь конечно, сервисная часть - без инфраструктурной и бизнес-логики. Schema first подход, без вариантов)

3) в схему gRPC изначально встроена возможность стриминга. Т.е. можно работать в режиме запрос-ответ, а можно использовать такие комбинации как:
а) запрос - несколько ответов
б) несколько запросов - один ответ
в) двунаправленный стриминг, где логика последовательности запросов и ответов определяется бизнес-процессом.
REST такое не умеет.
Причем схема со стримингом отличается от схемы запроса-ответа буквально одним словом. Код сервера\клиента конечно отличается сильнее)

Из минусов я бы отметил применимость в первую очередь для внутренних взаимодействий, наружу лучше выставлять REST или GraphQL, т.к. потребителям они понятнее. Также могут быть проблемы при изменениях, ломающих обратную совместимость, т.к. из-за бинарности и компактности формата данных жестко зафиксирован порядок полей в запросе\ответе. Возможно где-то будет проблемой то, что gRPC требует HTTP/2, в том же k8s\Openshift траффик HTTP и gRPC нужно разводить по разным портам. Ну и лично меня очень удивляет использование термина Stub в сгенерированном клиенте. Stub и в "боевом" коде... выглядит странно)))

#gRPC #integration #performance
Всем привет!

Внимание, короткий пост-вопрос.
Кто для чего использует аспекты (AspectJ) в Java? И использует ли?) Если нет, то почему?
Я вижу одно "красивое" применение - реализация шаблона Декоратор, о котором я упоминал в https://t.me/javaKotlinDevOps/124
Самые очевидные примеры, чем можно "декорировать" метод - журналирование, мониторинг, аудит.
Напишите в комментах, плиз)

#java #aspects
Всем привет!

Я уже писал про библиотеку с улучшенными assert-ами - Truth https://t.me/javaKotlinDevOps/51
Вот пример вывода ошибки в лог для такой банальной вещи, как проверка результата функции на true.
"Ванильный" JUnit 5:

java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:87)
at org.junit.Assert.assertTrue(Assert.java:42)
at org.junit.Assert.assertTrue(Assert.java:53)

Понятно, что проверка не прошла, не понятно - где, пока не посмотришь код теста. Дело упрощается, если в методе один assertTrue, есть рекомендации так делать - один тест = одна проверка. Но это не всегда удобно. И в любом случае если код написан какое-то время назад - придется лезть в тест.

JUnit5 + Truth:

value of: needFilter(...)
expected to be true

Совсем другое дело!)))

#unittests
Всем привет!

Заметка из серии "хозяйке на заметку" про Docker.
При сборке docker образа мы можем передать в команду docker build контекст сборки.
Как правило во всех примерах это последний аргумент в виде точки. Т.е. текущего каталога.
Что это такое?
Это каталог в хостовой системе, из которого можно копировать данные в образ командами COPY в Dockerfile. Да, Dockerfile тоже должен лежать в контексте
Как его можно задать при сборке:
1) указав каталог
2) указав архив с нужными файлами
3) указав git репозиторий (!)
4) указать только Dockerfile как контекст, если копировать в образ ничего не надо
5) указать только Dockerfile как контекст, и с помощью команд RUN wget или RUN curl в нем выкачать данные для образа по http

Что важно - docker client при запуске сборки упаковывает все содержимое контекста и оправляет его docker daemon. Т.е. указывать корневую папку точно не стоит) Лучше создать новую папку и положить туда только необходимое. Альтернатива - использовать файл .dockerignore
Детали - https://docs.docker.com/engine/reference/commandline/build/

P.S Оффтопик - похоже создатели Docker вдохновлялись git-ом. docker push\pull\commit И упомянутый выше .dockerignore

#docker
Всем привет!

В протоколе HTTP, а также gRPC, основанном на HTTP/2, есть возможность передачи параметров запроса разными способами:
1) в заголовках
2) в параметрах
3) в path части url
4) в теле запроса

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

1) headers - не бизнесовые, технические данные. Примеры: id для tracing, для логирования, маршрутизации, токены\куки для авторизации. Ключевой момент - в бизнес-процессе эти данные не используются
2) params - как правило используются в GET запросах, по сути фильтр для выборки данных
3) path - как правило в REST запросе, построенном на концепции REST ресурсов, для идентификации ресурса: GET\PUT\DELETE .../order/{nnn}
4) body - атрибуты объекта для создания\обновления в REST. Любая закрытая информация - https шифрует заголовки и тело запроса, но само собой не шифрует параметры и путь. Большие объемы данных, в URL есть ограничения на длину, причем в разных браузерах и проксях они разные, лучше к ним не приближаться). По объему данных в заголовках также есть ограничения на веб-серверах, привет "413 Entity Too Large". Размер можно подкрутить в большую сторону, но опять же это не наш метод)

#rest #api
Всем привет!
При использовании JPA в проекте может возникнуть вопрос - нужно ли JPA Entity мапить на DTO, а точнее когда это нужно делать?
Вот хорошая статья с ответом на этот вопрос: https://thorben-janssen.com/object-mapper-dto/
#jpa #mappers
Всем привет!

При создании образа Docker нужно выбрать базовый образ - образ содержащий минимальный набор необходимых CLI утилит, сервисов, конфигураций и фреймворков необходимых для того, чтобы заработало ваше приложение. Какой минимальный размер этого образа?
Ответ - нулевой.
Есть специальное зафиксированное имя scratch - в DockerFile будет выглядеть как:
FROM scratch
https://docs.docker.com/build/building/base-images/#create-a-simple-parent-image-using-scratch
Но сразу скажу - там не будет даже shell - sh, bash. Вообще ничего.
Все необходимые утилиты нужно будет добавлять руками в процессе сборки образа.
Путь для сильных духом) Или случай каких-то очень специфичных образов.

Что же должно быть в образе для Java приложения:
1) JRE и все необходимое для того, чтобы она запустилась)
Далее все зависит от необходимости в отладке внутри образа. По моему опыту она нужна, а значит нужны:
2) Linux shell
3) CLI утилиты для работы с файловой системой
4) curl\wget - для проверки маршрутизации в k8s
5) CLI редактор - vi, nano - для правки конфигов "на ходу"
6) возможно ssh сервер
7) возможно утилиты из состава JDK

Минималистичные образы можно поискать по словам busybox и alpine.
Размер - до 5 Мб(!) Само собой они без Java. Но есть основанные на них образы с Java размером порядка 55 Мб с JRE (Linux Temurin Java 11). Для сравнения обычные образы c JRE - 85-150 Мб. С JDK размер можно умножать на 3.
Что важно - в Linux куча настроек, поэтому важно выбрать проверенный и оптимизированный для работы с Java базовый образ.

P.S. Точно не стоит использовать образы с полноценным Linux - Debian, Ubuntu... Размер будет порядка 500+ Мб, а пользы - ноль.

P.P.S. Еще нужно помнить, что активное использование Spring Framework легко добавляет в размер образа ~ 100 Мб)

#docker
Всем привет!

Чем хорош Spring Framework?
1) Inverse of Control и Dependency Injection
2) Очень много модулей-адаптеров к различным технологиям: Data, Data Kafka, Web MVC, WebFlux, Security, Statemachine, Cloud ... с упрощенным и насколько это возможно единообразным API.
Но это не все.
Но Spring приходит на помощь и там, где его не особо ждали) Хочу рассказать о менее известном, но полезном функционале Spring:
1) Расширенный маппинг Enum при передаче значений через REST API: https://www.baeldung.com/spring-boot-enum-mapping
2) Улучшение логирования при падении Spring Boot при старте - все эти bean not found exception: https://www.baeldung.com/spring-boot-failure-analyzer
3) Централизованная обработка ошибок: https://www.baeldung.com/exception-handling-for-rest-with-spring

#spring #spring_boot
Всем привет!

Возвращаюсь с постами из вынужденного перерыва, связанного как обычно бывает с работой)

Задавались ли вы вопросом: какие типы данных использовать в Java API - примитивные или типы-обвертки (boxed)?

Однонозначного ответа как водится нет, но есть ряд правил.

Плюсы примитивных типов:
1) занимают меньше памяти, т.к нет накладных расходов на Java объекты. Какой величины эти расходы - можно почитать тут: https://www.baeldung.com/jvm-measuring-object-sizes или посмотреть с шутками и прибаутками и хорошей дозой хардкора тут https://www.youtube.com/watch?v=3BmznLJAgaA

Плюсы обверток:
1) могут использоваться в generic-ах. А generic-и лежат в основе всяких разных фреймворков. Если вы не уверены, что не используете такие - то вот примеры: Mockito, Truth и AssertJ (см. https://t.me/javaKotlinDevOps/51), Hibernate
2) могут использоваться для передачи null значений. Да, не всегда это правильно, чаще неправильно, но такие кейсы бывают.

И если исходя из сказанного выше возникает мысль - может ну ее, эту производительность, пусть будет универсальный готовый к будущим изменениям код, то вот еще два фактора:

1) если в процессе написания кода потребуется boxing из примитивного типа в обвертку и наоборот, то в этих строчках кода могут возникать необъяснимые NPE. Особенно "хороши" такие NPE до Java 14)))
Примеры:
а)
Boolean variable;
if (variable) {
}
б)
Integer boxed;
int i = boxed;

Т.е. имеет смысл посмотреть на чужие API, которые мы используем и прикинуть, как часто придется преобразовывать типы.

2) код в настоящее стал достаточно гибким. В том плане, что если 50 лет назад для отладки программы нужно было набить код на перфокарте и дождаться своей очереди для ее исполнения на сервере, а 30 лет назад исправить большую Asm или C программу было тем еще приключением, то сейчас при правильном разбиении на микросервисы, использовании практик чистого кода и главное написании модульных тестов отрефакторить код не сложно. Если у вас не так - предлагаю над этим задуматься)

Итого:
1) Если нужны generic-и и\или придется часто преобразовывать в boxed типы - имеет смысл использовать их везде в своем API.
2) Если важна производительность - примитивы там, где большие объемы данных.
3) В остальных случаях предлагаю присмотреться к примитивам.
Если концепция поменялась - всегда есть рефакторинг. Чуть сложнее с Java API, выставленным наружу - выпущенным в виде библиотеки. Тут нас спасет версионирование API.

#java #types
Всем привет!

Выпил бокал пива и захотелось немного пофилософствовать)
У меня часть просят готовое решение какой-то проблемы. Это может быть способ интеграции, выбор места для хранения данных, языка программирования, способ разбиения проекта на микросервисы и модули, как сделать правильное API...
Что на это хочется сказать.
1) иметь каталог готовых решений - это круто. Именно он оправдывает существование архитекторов и техлидов в команде. Использование паттернов, #patterns - это как раз про это, про переиспользование знаний. И я над таким каталогом работаю)
2) вместе с тем приходится признать, что далеко не всегда есть готовые и главное наиболее подходящие к конкретной ситуации решения.
Хорошо если бы всегда было так: "Как нам сделать ххх?". Думаешь 10 минут. "Делайте так и так, а так не делайте". Всё понятно, все счастливы, приложение легко и безболезненно попадает на ПРОД. Мечты, мечты)

По факту самый лучший способ принять оптимальное решение по проекту - это понимать все возможные варианты и выбрать из них наиболее подходящий исходя из:
а) команды
б) компании
в) проекта
г) сроков
д) чего-то еще что я забыл)
И лучше всего, по принципам Agile, если понимая все возможные варианты, их плюсы и минусы, решение будет принимать команда. Почему лучше? Потому что решение, полученное извне, IMHO будет "чужеродным". Причины его принятия могут со временем потеряться.
Сторонний архитектор может банально не знать особенностей команды, как устроена кодовая база и т.д.

В этом плане само название должности - архитектор - неверное. "Каменную" архитектуру сложно отрефакторить. Программную при правильном подходе к разработке - легко. Если бы все дома вокруг строились по индивидуальным проектам - это банально было бы очень дорого. С ПО - это суровая правда жизни.

Вывод: надо изучать паттерны и существующие решения. После любого проекта нужна "ретроспектива" - индивидуальная или общекомандная, на которой стоит отметить что "выстрелило", а что лучше в будущем не использовать. Ну и конечно учиться, учиться и учиться...)))

#arch #arch_compromises
Всем привет!

Не знаю зарегистрировались ли вы на бесплатный день JPoint, но мне понравился там доклад "Пирамида потребностей Маслоу для Java/Kotlin-разработчика" https://jpoint.ru/talks/dfb53bfc4ec74165830e81c036a28ad8
Доклад был вчера, регистрация закрыта, увы, но можно скачать pdf.

Мне понравились две вещи:

1) идея разделения всех инструментов разработчика на уровни по их необходимости. Как это сделано в докладе - можно поспорить, но сама идея хороша.

2) нашел новые для себя инструменты - генератор тестов для бизнес-логики, расшаривание набора зависимостей между проектами в Gradle, автоматизация обновления библиотек в проекте. Еще интересна метрика Hits of code вместо Lines of code, полезно для для наблюдения за проектом. Жаль, что SonarQube ее не умеет считать.

Ну и интересно кто оказался на вершине пирамиды...

В общем рекомендую почитать, возможно найдете для себя что-то новое.

#conference #tools
Всем привет!

Уже писал про Kotlin DSL как одно из преимуществ языка: https://t.me/javaKotlinDevOps/38
А как насчет примеров? Легко)
Где же используется Kotlin DSL?
1) В Spring для динамических конфигураций: Spring https://spring.io/blog/2023/03/16/kotlin-dsls-in-the-world-of-springdom
2) В Gradle как скрипт сборки вместо Groovy: https://docs.gradle.org/current/userguide/kotlin_dsl.html
3) TeamCity (конечно же!) для скриптов CI\CD - https://www.jetbrains.com/help/teamcity/kotlin-dsl.html#Editing+Kotlin+DSL

В статье про Spring также говорится о фичах Kotlin, которые позволяют использовать его как DSL.
А подробнее эта тема раскрыта тут: https://www.jmix.ru/cuba-blog/kotlin-dsl-from-theory-to-practice/

И на сладкое пример как пошагово сделать свой DSL на Kotlin: https://www.baeldung.com/kotlin/dsl

#kotlin #kotlin_dsl #dsl
Всем привет!

В развитие темы про готовые решения и переиспользование знаний хочу поговорить про переиспользование кода.
Ясно, что все разработчики переиспользуют готовые решения - Spring Framework, Apache Commons да и вообще любые подключаемые к проекту библиотеки.
Стоит ли добавлять еще одну?
Ответ как всегда неоднозначен.

Во-первых - чего не должно быть в общем (common) коде.
1) не должно быть доменных объектов. Почему? Потому что по большому счету домен у каждого свой. Приведу пример с банком. Наверное у половины команды разработки в банке в домене есть карта. Только это всегда разная карта. У кого-то это номер+ФИО+дата. А кого-то объект с н-цатью атрибутами: тип, платежная система, валюта, лимиты, персональный менеджер, офис выдачи, признак дополнительной, дизайн карты....
Первой команде не нужны атрибуты второй, второй не хватит слишком простого интерфейса первой.
Что касается доменной логики: если она общая у нескольких команд - вполне можно и нужно вынести ее в общий код в отдельный модуль, особое внимание обратив на версионирование.
2) не должно быть элементарного кода. Объяснять думаю не нужно.
3) не имеет смысла выносить в common код временные костыли, которые будут неактуальны через полгода.
4) не должно быть узкоспециализированного кода. Все же отдельная библиотека - это накладные расходы из-за отдельного CI стека: git, джоба сборки, проверка качества кода, хранилище артифактов.

Второе важное условие применимости common кода: наличие maintainers. Т.е. разработчиков, которые понимают как должен выглядеть общий код и что немаловажно - готовы тратить силы на его поддержку. Важны оба фактора. Первый - вспомним Spring: у него много модулей, но изучив Spring Core и какой-то из модулей - подключать другие становится достаточно просто. Фактор наличия времени важен т.к. очень мало алгоритмов, интеграций остаются неизменными. Часто попытка применить общий код в новом сервисе приводит к тому, что выявляется новый use case и требуется доработка.

Если же все условия выполняются и вы решили создать общие библиотеки - нужно помнить о следующих важных факторах.

1) должна быть документация с примерами использования. Модульные тесты вполне подойдут на эту роль, см https://t.me/javaKotlinDevOps/33. Еще вариант - эталонный проект, который должен собираться!) Иначе проще будет написать свой код чем разбираться в чужом.

2) процесс подключения общего кода в новый проект должен быть максимально простым, в идеале - через генератор каркаса а-ля https://start.spring.io/

3) нужно правильно выбрать модульность. Как пример можно привести Spring или семейство библиотек Apache Commons. Должна быть возможность подключать отдельные модули к проекту, для их группировки, если она нужна, можно использовать Spring starters.

4) должна быть описанная политика версионирования и API deprecation.

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

#common_code
Всем привет!

Один из не побоюсь этого слова трендов последнего времени - ChatGPT.
Статьи, доклады на конференциях. В т.ч. и по разработке.
Кто-нибудь пробовал уже? Для каких задач? Как оно?)
Сейчас баги уже не те)