Всем привет!
Продолжая тему Maven. Когда я изучал документацию по Maven, то наткнулся такую несколько странную возможность, как отключение компиляции через опцию -Dmaven.main.skip.
Почему, зачем? На первый взгляд - бессмысленно.
Но недавно на практике понял зачем она нужна.
Предположим в вашем pom есть некий плагин с достаточно сложными нестандартными настройками. Есть процесс, в котором нужно выполнить его goal. Если мы просто запустим goal командой mvn plugin:version:goal, то настройки из pom будут проигнорированы. Предположим, задать их из командной строки невозможно, т.к. плагин это не поддерживает. А даже если возможно - это будет дублирование, нарушение DRY, т.е. тоже плохой вариант)
Хуже всего то, что наш плагин находится в build lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference после фазы compile. Пусть проект уже собран, но как известно инкрементальная компиляция в Maven существует, но с ней есть нюансы: https://stackoverflow.com/questions/16963012/maven-compiler-recompile-all-files-instead-modified/19653164#19653164
К слову - именно по скорости компиляции Maven сильно проигрывает Gradle.
Так вот - именно тут опция -Dmaven.main.skip и выходит на сцену, сильно уменьшая время выполнения.
P.S. Да, по моему опыту - плагин для компиляции Kotlin, увы, ее не поддерживает.
#maven #java #kotlin
Продолжая тему Maven. Когда я изучал документацию по Maven, то наткнулся такую несколько странную возможность, как отключение компиляции через опцию -Dmaven.main.skip.
Почему, зачем? На первый взгляд - бессмысленно.
Но недавно на практике понял зачем она нужна.
Предположим в вашем pom есть некий плагин с достаточно сложными нестандартными настройками. Есть процесс, в котором нужно выполнить его goal. Если мы просто запустим goal командой mvn plugin:version:goal, то настройки из pom будут проигнорированы. Предположим, задать их из командной строки невозможно, т.к. плагин это не поддерживает. А даже если возможно - это будет дублирование, нарушение DRY, т.е. тоже плохой вариант)
Хуже всего то, что наш плагин находится в build lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference после фазы compile. Пусть проект уже собран, но как известно инкрементальная компиляция в Maven существует, но с ней есть нюансы: https://stackoverflow.com/questions/16963012/maven-compiler-recompile-all-files-instead-modified/19653164#19653164
К слову - именно по скорости компиляции Maven сильно проигрывает Gradle.
Так вот - именно тут опция -Dmaven.main.skip и выходит на сцену, сильно уменьшая время выполнения.
P.S. Да, по моему опыту - плагин для компиляции Kotlin, увы, ее не поддерживает.
#maven #java #kotlin
maven.apache.org
Introduction to the Build Lifecycle – Maven
Всем привет! Мне нравится идея сделать пошаговый гайд по разбору ошибок при деплое в k8s - https://habr.com/ru/companies/flant/articles/484954/ Жаль, что тут чистый кубер, без Istio. Но представляю, во что превратится схема, если добавить туда Istio ))) #k8s #istio
Хабр
Визуальное руководство по диагностике неисправностей в Kubernetes
Прим. перев. : Эта статья входит в состав опубликованных в свободном доступе материалов проекта learnk8s , обучающего работе с Kubernetes компании и индивидуальных администраторов. В ней Daniele...
Всем привет!
Я периодически провожу код-ревью. И лично у меня ранее был страх - а вдруг я не замечу что-то важное. Смотришь PR, явных ошибок не видно, но апрув "не ставится". А когда Pull Request (PR) очень большой - даже не хочется его открывать, т.к. примерно понимаешь, какой объем информации придется "загрузить в голову", чтобы точно можно было сказать - да, здесь ошибок нет. Крайний случай слишком тщательного подхода к код-ревью, встреченный мной на практике - когда разработчик просит освободить его от других задач, чтобы он мог полностью погрузиться в ревью: прочитать аналитику, изучить архитектуру кода...
Но как я уже писал в https://t.me/javaKotlinDevOps/146 - скорость прохождения код-ревью очень важна для быстрого выявления ошибок и предотвращения merge конфликтов. Что же тут можно сделать?
Во-первых я предлагаю рассматривать код-ревью как еще одну из сетей для поиска проблем в коде. Есть и другие сети - валидация в IDEA, модульные и интеграционные тесты, CI pipeline, SonarQube и другие инструменты статистического анализа, включая сканирования на уязвимости. Это если говорить о тех, что работают до код-ревью. После - регресс, тесты нового функционала, нагрузочное тестирование, демо (приемо-сдаточные испытания), канареечное развертывание, chaos engineering... Т.е. код-ревью - полезно, но это не последний рубеж обороны.
Второй момент: когда я провожу ревью - я вижу те проблемы, которые считаю важным. Т.е. мой взгляд субъективен, на него влияет мой опыт, и особенно проблемы, с которыми я сталкивался ранее. Да, важно то, что мой взгляд отличается от взгляда автора кода. Это позволяет найти те ошибки, которые автор из-за замыленности не увидел. Но с другой стороны - что-то я в принципе увидеть не могу. В этом плане будет полезно иметь несколько ревьюверов, внутри и кросс командное ревью.
Итого: ручное код-ревью полезно, к нему не стоит подходить формально, но не надо забывать про время. Исключения - когда время не критично. Например, это ревью кода новичков. Там важно передать текущие практики и требования к проекту.
#code_review
Я периодически провожу код-ревью. И лично у меня ранее был страх - а вдруг я не замечу что-то важное. Смотришь PR, явных ошибок не видно, но апрув "не ставится". А когда Pull Request (PR) очень большой - даже не хочется его открывать, т.к. примерно понимаешь, какой объем информации придется "загрузить в голову", чтобы точно можно было сказать - да, здесь ошибок нет. Крайний случай слишком тщательного подхода к код-ревью, встреченный мной на практике - когда разработчик просит освободить его от других задач, чтобы он мог полностью погрузиться в ревью: прочитать аналитику, изучить архитектуру кода...
Но как я уже писал в https://t.me/javaKotlinDevOps/146 - скорость прохождения код-ревью очень важна для быстрого выявления ошибок и предотвращения merge конфликтов. Что же тут можно сделать?
Во-первых я предлагаю рассматривать код-ревью как еще одну из сетей для поиска проблем в коде. Есть и другие сети - валидация в IDEA, модульные и интеграционные тесты, CI pipeline, SonarQube и другие инструменты статистического анализа, включая сканирования на уязвимости. Это если говорить о тех, что работают до код-ревью. После - регресс, тесты нового функционала, нагрузочное тестирование, демо (приемо-сдаточные испытания), канареечное развертывание, chaos engineering... Т.е. код-ревью - полезно, но это не последний рубеж обороны.
Второй момент: когда я провожу ревью - я вижу те проблемы, которые считаю важным. Т.е. мой взгляд субъективен, на него влияет мой опыт, и особенно проблемы, с которыми я сталкивался ранее. Да, важно то, что мой взгляд отличается от взгляда автора кода. Это позволяет найти те ошибки, которые автор из-за замыленности не увидел. Но с другой стороны - что-то я в принципе увидеть не могу. В этом плане будет полезно иметь несколько ревьюверов, внутри и кросс командное ревью.
Итого: ручное код-ревью полезно, к нему не стоит подходить формально, но не надо забывать про время. Исключения - когда время не критично. Например, это ревью кода новичков. Там важно передать текущие практики и требования к проекту.
#code_review
Telegram
(java || kotlin) && devOps
Всем привет!
Продолжаю тему code review, поговорим про рекомендации ревьюверам.
1) работа с PR - это как правило текстовая коммуникация: Bitbucket, Gitlab, ... При таком общении теряется часть информации - тон, эмоциональная окраска... Следовательно, фразу…
Продолжаю тему code review, поговорим про рекомендации ревьюверам.
1) работа с PR - это как правило текстовая коммуникация: Bitbucket, Gitlab, ... При таком общении теряется часть информации - тон, эмоциональная окраска... Следовательно, фразу…
Всем привет!
Большинство Java разработчиков знают про Mockito - самый популярный фреймворк для создания заглушек. Но не ошибусь, если скажу, что большая часть разработчиков также не любит читать документацию) В т.ч. Mockito. А используемые инструменты надо знать.
Поэтому могу порекомендовать отличную статью про Mockito https://habr.com/ru/articles/444982/
Для затравки - из статьи можно узнать:
1) как определить - mock это или нет
2) в чем разница между when и do, когда все же нужен do
3) как одной лямбдой сделать сложную проверку входных параметров в конструкции when
4) как проверить порядок вызова методов
5) про сессии Mockito
и много других интересных особенностей фреймворка.
#mockito #java #unittests
Большинство Java разработчиков знают про Mockito - самый популярный фреймворк для создания заглушек. Но не ошибусь, если скажу, что большая часть разработчиков также не любит читать документацию) В т.ч. Mockito. А используемые инструменты надо знать.
Поэтому могу порекомендовать отличную статью про Mockito https://habr.com/ru/articles/444982/
Для затравки - из статьи можно узнать:
1) как определить - mock это или нет
2) в чем разница между when и do, когда все же нужен do
3) как одной лямбдой сделать сложную проверку входных параметров в конструкции when
4) как проверить порядок вызова методов
5) про сессии Mockito
и много других интересных особенностей фреймворка.
#mockito #java #unittests
Хабр
Mockito и как его готовить
О статье Перед вами очередное руководство по Mockito. В нём я, с одной стороны, попытался описать функционал этой библиотеки так, чтобы незнакомый с нею читатель сразу получил возможность полноценно...
Всем привет!
Уже был пост о том, как не надо использовать контекстные функции в Kotlin https://t.me/javaKotlinDevOps/189
Сразу может возникнуть вопрос - а когда их стоит использовать?
По ссылке уже был пример - для установки полей объекта, которые нельзя установить через конструктор. То ли потому, что подходящего конструктора\builder нет, то ли потому, что в момент создания объекта их значения еще не известны. Вот он:
val man = Person("Vasya").apply {
age = 20
city = "Moscow"
}
К слову - эту задачу можно было решить лет 20 назад в Delphi с помощью оператора with, мне его очень не хватало в Java))) И интересно, что в Kotlin with немного отличается по смыслу: https://kotlinlang.org/docs/scope-functions.html#function-selection
Еще кейс - выполнение какого-то нефункционального требования или вспомогательного кода, типа отбрасывания логов, метрик:
return claim.also {
log.info("Возвращаем $claim")
}
Ключевой момент здесь, о котором я буду постоянно говорить - код логично выстроен и легко читается: возвращаем объект заявки, а заодно его логируем.
И еще кейс - функция однострочник, без явного тела. Ключевой момент - это реально должна быть функция из одной или нескольких строк. Если сделать портянку из цепочки вызовов контекстных функций - читаемость только ухудшится, лучше бы были скобки.
Пример как надо:
fun main() = doSomething().also { doAnotherThing(it) }
Если подкинете еще хорошие примеры в комментариях - буду благодарен.
#kotlin #readability
Уже был пост о том, как не надо использовать контекстные функции в Kotlin https://t.me/javaKotlinDevOps/189
Сразу может возникнуть вопрос - а когда их стоит использовать?
По ссылке уже был пример - для установки полей объекта, которые нельзя установить через конструктор. То ли потому, что подходящего конструктора\builder нет, то ли потому, что в момент создания объекта их значения еще не известны. Вот он:
val man = Person("Vasya").apply {
age = 20
city = "Moscow"
}
К слову - эту задачу можно было решить лет 20 назад в Delphi с помощью оператора with, мне его очень не хватало в Java))) И интересно, что в Kotlin with немного отличается по смыслу: https://kotlinlang.org/docs/scope-functions.html#function-selection
Еще кейс - выполнение какого-то нефункционального требования или вспомогательного кода, типа отбрасывания логов, метрик:
return claim.also {
log.info("Возвращаем $claim")
}
Ключевой момент здесь, о котором я буду постоянно говорить - код логично выстроен и легко читается: возвращаем объект заявки, а заодно его логируем.
И еще кейс - функция однострочник, без явного тела. Ключевой момент - это реально должна быть функция из одной или нескольких строк. Если сделать портянку из цепочки вызовов контекстных функций - читаемость только ухудшится, лучше бы были скобки.
Пример как надо:
fun main() = doSomething().also { doAnotherThing(it) }
Если подкинете еще хорошие примеры в комментариях - буду благодарен.
#kotlin #readability
Telegram
(java || kotlin) && devOps
Всем привет!
Есть такая отличная штука в Kotlin, как контекстные функции.
Вот документация https://kotlinlang.org/docs/scope-functions.html
Вот пример, хорошо иллюстрирующий зачем они нужны:
val man = Person("Vasya").apply {
age = 20 …
Есть такая отличная штука в Kotlin, как контекстные функции.
Вот документация https://kotlinlang.org/docs/scope-functions.html
Вот пример, хорошо иллюстрирующий зачем они нужны:
val man = Person("Vasya").apply {
age = 20 …
Всем привет!
Нашел полезную штуку - калькулятор сайзинга для Kafka - https://eventsizer.io/
P.S. Новый тэг - #utils
Нашел полезную штуку - калькулятор сайзинга для Kafka - https://eventsizer.io/
P.S. Новый тэг - #utils
Всем привет!
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо ругается SonarQube.
Решение такое - выделяем атомарные блоки кода или условия в отдельные private методы.
К слову, данный способ является одним из базовых рефакторингов под названием "extract method" из известной книжки Фаулера "Рефакторинг".
Может возникнуть вопрос - а если метод вызывается один раз?
Ответ - без разницы.
У нас есть умный компилятор, он должен такие методы заинлайнить. Сразу скажу, спецификации, подтверждающей этот факт я предоставить не могу)
Окей, но даже если вам не повезет, и ваш компилятор этого не сделает - нужно иметь RPS (request per second) сильно больше 1, чтобы лишние вызовы метода сыграли свою роль. Причем даже в таком случае длинная цепочка вызовов методов должна быть в основном сценарии. Кажется, что под эти три условия попадает не так много кода. И в любом случае у нас есть НТ.
Еще может возникнуть вопрос с unit tests - их число тоже нужно увеличивать? Ответ - не обязательно, нет жесткой связки 1 к 1 теста и метода. Тест можно написать на группу взаимосвязанных методов, главное закрыть все возможные пути выполнения кода. Да и не нужно тестировать private методы.
И еще важный момент - extract method позволяет решить еще один часто встречающийся кейс. Предположим у вас есть дублирующийся код, отличающийся только одним небольшим блоком. Решение навскидку (на Kotlin):
fun doSomething(booleanParam: Boolean): SomeResult {
if (booleanParam) {
doAnotherThing()
}
// some code
}
Но приходится вводить Boolean параметр, а это хоть и не общепризнанный антипаттерн, по этому вопросу идут hollywars, но приближается к этому статусу)
Поэтому можно сделать так:
fun doSomething(): SomeResult {
// some code
}
fun doSomethingExtended(): SomeResult {
doAnotherThing()
return doSomething()
}
Итог: не нужно боятся выделять новые методы.
#refactoring #readability #antipatterns
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо ругается SonarQube.
Решение такое - выделяем атомарные блоки кода или условия в отдельные private методы.
К слову, данный способ является одним из базовых рефакторингов под названием "extract method" из известной книжки Фаулера "Рефакторинг".
Может возникнуть вопрос - а если метод вызывается один раз?
Ответ - без разницы.
У нас есть умный компилятор, он должен такие методы заинлайнить. Сразу скажу, спецификации, подтверждающей этот факт я предоставить не могу)
Окей, но даже если вам не повезет, и ваш компилятор этого не сделает - нужно иметь RPS (request per second) сильно больше 1, чтобы лишние вызовы метода сыграли свою роль. Причем даже в таком случае длинная цепочка вызовов методов должна быть в основном сценарии. Кажется, что под эти три условия попадает не так много кода. И в любом случае у нас есть НТ.
Еще может возникнуть вопрос с unit tests - их число тоже нужно увеличивать? Ответ - не обязательно, нет жесткой связки 1 к 1 теста и метода. Тест можно написать на группу взаимосвязанных методов, главное закрыть все возможные пути выполнения кода. Да и не нужно тестировать private методы.
И еще важный момент - extract method позволяет решить еще один часто встречающийся кейс. Предположим у вас есть дублирующийся код, отличающийся только одним небольшим блоком. Решение навскидку (на Kotlin):
fun doSomething(booleanParam: Boolean): SomeResult {
if (booleanParam) {
doAnotherThing()
}
// some code
}
Но приходится вводить Boolean параметр, а это хоть и не общепризнанный антипаттерн, по этому вопросу идут hollywars, но приближается к этому статусу)
Поэтому можно сделать так:
fun doSomething(): SomeResult {
// some code
}
fun doSomethingExtended(): SomeResult {
doAnotherThing()
return doSomething()
}
Итог: не нужно боятся выделять новые методы.
#refactoring #readability #antipatterns
Всем привет!
У каждого разработчика есть набор любимых инструментов. И на самом деле это важно - инструменты сильно облегчают нам жизнь. И если рассматривать разработку как ремесло, то правильные инструменты - это конкурентное преимущество.
Исторически так сложилось, что я работаю на Windows. Mac просто не зашел.
Поэтому решил поделится своим набором инструментов под Windows.
На первом месте безусловно IntelliJ IDEA. Я бы сказал, что ее хватает для 80% задач разработчика. Более того, хорошо видно, что разработчики IDEA работают над тем, чтобы эта цифра приблизилась к 100))))
Но на текущий момент IDEA - не только лишь наше все) Как я уже писал - кроме собственно написания кода, предположим на Java, разработчик сталкивается с рядом сопутствующих задач - https://t.me/javaKotlinDevOps/26 и https://t.me/javaKotlinDevOps/26.
И для них нужны свои инструменты, которых нет в IDEA.
Notepad++ - для работы с текстовыми файлами и файлами настроек. Намного круче редактора IDEA, особенно с плагинами.
Total Commander - работа с файловой системой. Альтернатива - Far, но лично я не люблю UI из DOS )))
KeePass - хранение и обмен паролями.
RegexBuddy - отладка RegEx выражений.
Официальный Git клиент - для работы с репозиториями, не содержащими код. Также для клонирования новых репозиториев. Если удобнее работать из контекстного меню - есть TortoiseGit.
Linux утилиты в Windows. Без Linux никак, да) Есть два порта утилит Linux для Windows - Cygwin и MinGw, последний входит в состав дистрибутива Git. Что как по мне удобно - два в одном.
WinSCP, Putty, MTPutty - доступ по ssh для настройки серверов и просмотра логов. Замечу, что после перехода на Docker уже не так актуально.
Postman, Insomnia, SoapUI - тестирование http. Встроенный в IDEA http client подходит для простых случаев - когда не требуются проверки, сложная настройка кук, сред, переменных...
JMeter - нагрузочное тестирование по http.
Keystore Explorer - работа с SSL сертификатами.
Beyond Compare - сравнение файлов и каталогов. Для простых случаев хватает инструментов, встроенных в Total Commander.
Docker Desktop или VirtualBox плюс WSL (Windows subsystem for Linux) - для запуска Docker.
DBeaver, EMS SQL Manager, Navicat - если вам не хватает IDEA Database tools. Мне сейчас хватает))) Но раньше использовал все три, все три нравятся.
Менеджер буфера обмена, например ArsClip. Для работы вне IDEA,в IDEA уже есть работа с буфером обмена.
Работа с заметками. Лично я пользуюсь Evernote. Как альтернатива - OneNote. Еще альтернатива - Telegram канал, куда можно сбрасывать все заметки и расставлять тэги)))
WinRar или 7Zip - архивирование. Сейчас уже не так актуально, диски у нас условно безразмерные, но для отправки по почте - бывает нужно.
Отладчик Google Chrome или Yandex Browser (по вкусу).
kubectl или oc - клиент для работы с k8s или Openshift
Работающий VPN - для работы с тем же ChatGPT.
P.S На самом деле эта заметка преследует скрытую цель - узнать что-то новое. Если вы используете что-то полезное для разработки, а я о нем забыл, не знал - напиши, плиз, в комментах.
#tools #windows
У каждого разработчика есть набор любимых инструментов. И на самом деле это важно - инструменты сильно облегчают нам жизнь. И если рассматривать разработку как ремесло, то правильные инструменты - это конкурентное преимущество.
Исторически так сложилось, что я работаю на Windows. Mac просто не зашел.
Поэтому решил поделится своим набором инструментов под Windows.
На первом месте безусловно IntelliJ IDEA. Я бы сказал, что ее хватает для 80% задач разработчика. Более того, хорошо видно, что разработчики IDEA работают над тем, чтобы эта цифра приблизилась к 100))))
Но на текущий момент IDEA - не только лишь наше все) Как я уже писал - кроме собственно написания кода, предположим на Java, разработчик сталкивается с рядом сопутствующих задач - https://t.me/javaKotlinDevOps/26 и https://t.me/javaKotlinDevOps/26.
И для них нужны свои инструменты, которых нет в IDEA.
Notepad++ - для работы с текстовыми файлами и файлами настроек. Намного круче редактора IDEA, особенно с плагинами.
Total Commander - работа с файловой системой. Альтернатива - Far, но лично я не люблю UI из DOS )))
KeePass - хранение и обмен паролями.
RegexBuddy - отладка RegEx выражений.
Официальный Git клиент - для работы с репозиториями, не содержащими код. Также для клонирования новых репозиториев. Если удобнее работать из контекстного меню - есть TortoiseGit.
Linux утилиты в Windows. Без Linux никак, да) Есть два порта утилит Linux для Windows - Cygwin и MinGw, последний входит в состав дистрибутива Git. Что как по мне удобно - два в одном.
WinSCP, Putty, MTPutty - доступ по ssh для настройки серверов и просмотра логов. Замечу, что после перехода на Docker уже не так актуально.
Postman, Insomnia, SoapUI - тестирование http. Встроенный в IDEA http client подходит для простых случаев - когда не требуются проверки, сложная настройка кук, сред, переменных...
JMeter - нагрузочное тестирование по http.
Keystore Explorer - работа с SSL сертификатами.
Beyond Compare - сравнение файлов и каталогов. Для простых случаев хватает инструментов, встроенных в Total Commander.
Docker Desktop или VirtualBox плюс WSL (Windows subsystem for Linux) - для запуска Docker.
DBeaver, EMS SQL Manager, Navicat - если вам не хватает IDEA Database tools. Мне сейчас хватает))) Но раньше использовал все три, все три нравятся.
Менеджер буфера обмена, например ArsClip. Для работы вне IDEA,в IDEA уже есть работа с буфером обмена.
Работа с заметками. Лично я пользуюсь Evernote. Как альтернатива - OneNote. Еще альтернатива - Telegram канал, куда можно сбрасывать все заметки и расставлять тэги)))
WinRar или 7Zip - архивирование. Сейчас уже не так актуально, диски у нас условно безразмерные, но для отправки по почте - бывает нужно.
Отладчик Google Chrome или Yandex Browser (по вкусу).
kubectl или oc - клиент для работы с k8s или Openshift
Работающий VPN - для работы с тем же ChatGPT.
P.S На самом деле эта заметка преследует скрытую цель - узнать что-то новое. Если вы используете что-то полезное для разработки, а я о нем забыл, не знал - напиши, плиз, в комментах.
#tools #windows
Telegram
(java || kotlin) && devOps
Продолжим с hard-skills, не связанными с основным языком программирования.
7) базовые навыки DevOps. DevOps есть в любой более менее современной компании. Любой разработчик с ним соприкасается. Понимать основы надо.
Что я понимаю под основами: зачем вообще…
7) базовые навыки DevOps. DevOps есть в любой более менее современной компании. Любой разработчик с ним соприкасается. Понимать основы надо.
Что я понимаю под основами: зачем вообще…
Всем привет!
Я уже поднимал тему boolean параметров как антипаттерна https://t.me/javaKotlinDevOps/229. Давайте расширим ее до вопроса - когда стоит использовать if?
Является ли if антипаттерном?
По мнению некоторых товарищей - да, является: https://www.antiifprogramming.com/about-the-anti-if.php
Как по мне - не всегда, зависит от ситуации.
Чем плох if? // да, switch - это по сути тот же if.
1) может нарушать принцип Single Responsibility. Почему - думаю объяснять не нужно.
2) может ухудшать читаемость кода, я которую я всегда "топлю") Т.е. нарушает принцип KISS. Усугубляет ситуацию тот факт, что код как правило не остается неизменным. И обычный if else со временем может превратится в многоуровневого нечитаемого монстра.
3) может нарушать принцип Don't Repeat Yourself. Тут два очевидных варианта - либо во всех ветках if выражения есть дублирующийся код, либо чтобы обработать возврат некого метода всегда нужен if.
4) если в коде слишком много if (x != null) - это признак того, что вы неправильно работаете с nullability. Тут могу посоветовать Kotlin, т.к. он может сообщать о null значениях на этапе компиляции. Optional и его альтернативы в Java избавляют от NPE, но не избавляет от проверок на null. Я видел советы - просто не пишите код, который возвращает null - тогда проверки будут не нужны. Но это надежда на человеческий фактор, и компилятор (я про Kotlin) работает лучше)))
Да, я специально пишу везде слово "может". Бывают if-ы, которые не нарушают ни один из принципов.
Когда стоит волноваться?
1) подключаем SonarQube или Checkstyle и не игнорируем ошибки, связанные с цикломатической сложностью методов, см. https://t.me/javaKotlinDevOps/197
2) код просто сложно становится читать. Особенно хорошо эта проверка проходит на новых разработчиках)
Идеально конечно не писать код, приводящий к лишним if. Но я уже писал про человеческий фактор выше)
Что можно сделать? // будет некоторый повтор написанного тут https://t.me/javaKotlinDevOps/229
1) выделяем сложный код условия в отдельный метод.
2) вместо двух или более веток оператора if делаем несколько методов. Помогает в случае, если условно метод А всегда вызывает метод С с значением true, а метод Б - с значением false. Иначе будет как на знаменитой картинке - проблема не на моей стороне)))
3) используем not null объекты и переходим Kotlin
4) перепроектируем код, чтобы проверки выполнялись в одном месте, а не дублировались по коду. Для этого их придется перенести из вызывающего кода в вызываемый. И придумать правильное значение по умолчанию.
5) при необходимости вводим иерархию классов, чтобы каждый класс отвечал за одну ветку switch
6) используем паттерн Стратегия - по сути частный случай введения иерархии классов
7) используем паттерн Состояние (State), который кроме хранения состояния выполняет обработку, связанную с различными состояниями, тем самым убирая if из вызывающего кода
#antipatterns #if_antipattern #java #kotlin #solid #patterns #dev_compromises
Я уже поднимал тему boolean параметров как антипаттерна https://t.me/javaKotlinDevOps/229. Давайте расширим ее до вопроса - когда стоит использовать if?
Является ли if антипаттерном?
По мнению некоторых товарищей - да, является: https://www.antiifprogramming.com/about-the-anti-if.php
Как по мне - не всегда, зависит от ситуации.
Чем плох if? // да, switch - это по сути тот же if.
1) может нарушать принцип Single Responsibility. Почему - думаю объяснять не нужно.
2) может ухудшать читаемость кода, я которую я всегда "топлю") Т.е. нарушает принцип KISS. Усугубляет ситуацию тот факт, что код как правило не остается неизменным. И обычный if else со временем может превратится в многоуровневого нечитаемого монстра.
3) может нарушать принцип Don't Repeat Yourself. Тут два очевидных варианта - либо во всех ветках if выражения есть дублирующийся код, либо чтобы обработать возврат некого метода всегда нужен if.
4) если в коде слишком много if (x != null) - это признак того, что вы неправильно работаете с nullability. Тут могу посоветовать Kotlin, т.к. он может сообщать о null значениях на этапе компиляции. Optional и его альтернативы в Java избавляют от NPE, но не избавляет от проверок на null. Я видел советы - просто не пишите код, который возвращает null - тогда проверки будут не нужны. Но это надежда на человеческий фактор, и компилятор (я про Kotlin) работает лучше)))
Да, я специально пишу везде слово "может". Бывают if-ы, которые не нарушают ни один из принципов.
Когда стоит волноваться?
1) подключаем SonarQube или Checkstyle и не игнорируем ошибки, связанные с цикломатической сложностью методов, см. https://t.me/javaKotlinDevOps/197
2) код просто сложно становится читать. Особенно хорошо эта проверка проходит на новых разработчиках)
Идеально конечно не писать код, приводящий к лишним if. Но я уже писал про человеческий фактор выше)
Что можно сделать? // будет некоторый повтор написанного тут https://t.me/javaKotlinDevOps/229
1) выделяем сложный код условия в отдельный метод.
2) вместо двух или более веток оператора if делаем несколько методов. Помогает в случае, если условно метод А всегда вызывает метод С с значением true, а метод Б - с значением false. Иначе будет как на знаменитой картинке - проблема не на моей стороне)))
3) используем not null объекты и переходим Kotlin
4) перепроектируем код, чтобы проверки выполнялись в одном месте, а не дублировались по коду. Для этого их придется перенести из вызывающего кода в вызываемый. И придумать правильное значение по умолчанию.
5) при необходимости вводим иерархию классов, чтобы каждый класс отвечал за одну ветку switch
6) используем паттерн Стратегия - по сути частный случай введения иерархии классов
7) используем паттерн Состояние (State), который кроме хранения состояния выполняет обработку, связанную с различными состояниями, тем самым убирая if из вызывающего кода
#antipatterns #if_antipattern #java #kotlin #solid #patterns #dev_compromises
Telegram
(java || kotlin) && devOps
Всем привет!
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо…
Хочу рассказать про наверное самый способ улучшить читаемость. Например, у вас есть сложное условие из нескольких уровней, каждый из которых состоит из ряда проверок. Или длинный метод с кучей условий, который сложно понять и на который справедливо…
Всем привет!
Могу порекомендовать неплохую статью, объясняющую в чем принципиальная разница между классической архитектурой, основанной на слоях, и более современными и в целом похожими Onion Architecture (ну не привычно мне называть ее луковой :) ), и гексагональной, она же Порты и адаптеры.
Собственно статья https://habr.com/ru/articles/344164/
#arch
Могу порекомендовать неплохую статью, объясняющую в чем принципиальная разница между классической архитектурой, основанной на слоях, и более современными и в целом похожими Onion Architecture (ну не привычно мне называть ее луковой :) ), и гексагональной, она же Порты и адаптеры.
Собственно статья https://habr.com/ru/articles/344164/
#arch
Хабр
Слои, Луковицы, Гексогоны, Порты и Адаптеры — всё это об одном
Перевод статьи Mark Seemann о популярных архитектурах разработки ПО и о том, что между ними общего. Один из моих читателей спросил меня: Вернон, в своей книге «Implementing DDD» много говорит об...
Всем привет!
В этом посте хочу рассказать про несколько малоизвестных фактов про Docker.
На всякий случай напомню. Образ (image) - это шаблон, из которого создаются работающие контейнеры. Хост - система, на которой запускаются контейнеры Docker. Реестр - хранилище образов.
1) Docker - это компания, выпускающая набор инструментов с таким же именем. Компания Docker довела идею контейнеров до работающего состояния и популяризовала их использование. Но Docker - это не стандарт для контейнеров. Т.е. мы все зависим от пропиетарного ПО? Нет. Во-первых Docker Engine является open-source. Во-вторых, он разделен на части и те из них, которые касаются среды выполнения и формата образа, стандартизированы и есть альтернативные реализации - см. OCI. А когда появился k8s, в основе которого лежат контейнеры, то для работы с образами без завязки на Docker он создал стандарт CRI. Подробнее обо всем этом можно почитать вот тут https://habr.com/ru/companies/domclick/articles/566224/
К слову, для cli клиента Docker также есть альтернативы, хоть и не стандартизированные. Например, podman. И если смотреть бегло - он поддерживает те же самые команды, что и docker.
2) принцип хранения Docker образов схож с git-ом. Есть локальный реестр, хранится в папке /var/lib/docker/ и удаленные реестры. Локальный реестр нужен для ускорения, т.к. качать Мб или Гб с локального диска быстрее, чем по сети. Если нужно перенести куда-то локальный реестр, отдельные образы или сделать бэкап - это можно сделать командами docker save и docker load.
3) при построении нужного образа Docker есть одна потенциальная проблема - чем больше команд в Dockerfile, тем больше слоев в образе создается. Проблемой это становится, если в образ как-то попали лишние файлы - после их удаления в запущенном контейнере они пропадут, а вот в реестре содержащий их слой останется. Решить эту проблему помогут команды docker import и export. Их не нужно путать с save и load, т.к. они удаляют все метаданные из образа, сливая все слои в один. Важно: слои - это не обязательно зло, так как при правильном расположении слоев появляется возможность достаточно быстро и дешево обновлять приложение - обновляя только его последний слой.
Кстати, альтернативное решение по уменьшению числа слоев в Docker образе - использование Linux shell pipes, т.е. длинные команды типа cmd1 | cmd2 | .... | cmdN
4) Если нужно посмотреть, что происходит в файловой системе контейнера, т.е. что было изменено с момента старта - есть команда docker diff
5) Список запущенных локально контейнеров покажет docker ps. Linux shell tools рулят)))
6) Важно - файловая система внутри контейнера read only. Но часто контейнер должен что-то писать. Логи хотя бы. Тут два варианта - просто подключить volume, они будут лежать где-то в недрах /var/lib/docker/ на хосте или подключить конкретный каталог хоста. В первом случае volume автоматически удалится при удалении контейнера, во втором - останется на хосте включая все файлы, которые были в нем изначально или добавлены\изменены процессами в контейнере. Второй способ используют к примеру для работы СУБД. Также такой volume можно подключить к нескольким контейнерам и т.об. обмениваться данными через файловую систему. Лучше конечно REST или Kafka, но потребности бывают разные)
7) команды в Dockerfile по умолчанию запускаются под root. В т.ч. и команда, которая стартует главный процесс внутри контейнера. На это обычно ругаются безопасники. Но почему, ведь у нас изоляция??? Паранойя?) Частично)))
Первый кейс с уязвимостью - если контейнер запущен в privileged режиме, они имеет расширенный доступ к ядру Linux хоста. Кейс редкий, я знаю только про одно использование privileged - запуск Docker in Docker, он же dind, см. https://github.com/jpetazzo/dind
Второй кейс - какие-то уязвимости в Linux, которые могут привести к тому же результату.
Третий - окей, процесс внутри контейнера под root сможет делать все, что захочет. А если туда троян попал при сборке? Отправка всех проходящих сквозь контейнер данных злоумышленнику - да легко, спасет только firewall. Или не спасет(
В этом посте хочу рассказать про несколько малоизвестных фактов про Docker.
На всякий случай напомню. Образ (image) - это шаблон, из которого создаются работающие контейнеры. Хост - система, на которой запускаются контейнеры Docker. Реестр - хранилище образов.
1) Docker - это компания, выпускающая набор инструментов с таким же именем. Компания Docker довела идею контейнеров до работающего состояния и популяризовала их использование. Но Docker - это не стандарт для контейнеров. Т.е. мы все зависим от пропиетарного ПО? Нет. Во-первых Docker Engine является open-source. Во-вторых, он разделен на части и те из них, которые касаются среды выполнения и формата образа, стандартизированы и есть альтернативные реализации - см. OCI. А когда появился k8s, в основе которого лежат контейнеры, то для работы с образами без завязки на Docker он создал стандарт CRI. Подробнее обо всем этом можно почитать вот тут https://habr.com/ru/companies/domclick/articles/566224/
К слову, для cli клиента Docker также есть альтернативы, хоть и не стандартизированные. Например, podman. И если смотреть бегло - он поддерживает те же самые команды, что и docker.
2) принцип хранения Docker образов схож с git-ом. Есть локальный реестр, хранится в папке /var/lib/docker/ и удаленные реестры. Локальный реестр нужен для ускорения, т.к. качать Мб или Гб с локального диска быстрее, чем по сети. Если нужно перенести куда-то локальный реестр, отдельные образы или сделать бэкап - это можно сделать командами docker save и docker load.
3) при построении нужного образа Docker есть одна потенциальная проблема - чем больше команд в Dockerfile, тем больше слоев в образе создается. Проблемой это становится, если в образ как-то попали лишние файлы - после их удаления в запущенном контейнере они пропадут, а вот в реестре содержащий их слой останется. Решить эту проблему помогут команды docker import и export. Их не нужно путать с save и load, т.к. они удаляют все метаданные из образа, сливая все слои в один. Важно: слои - это не обязательно зло, так как при правильном расположении слоев появляется возможность достаточно быстро и дешево обновлять приложение - обновляя только его последний слой.
Кстати, альтернативное решение по уменьшению числа слоев в Docker образе - использование Linux shell pipes, т.е. длинные команды типа cmd1 | cmd2 | .... | cmdN
4) Если нужно посмотреть, что происходит в файловой системе контейнера, т.е. что было изменено с момента старта - есть команда docker diff
5) Список запущенных локально контейнеров покажет docker ps. Linux shell tools рулят)))
6) Важно - файловая система внутри контейнера read only. Но часто контейнер должен что-то писать. Логи хотя бы. Тут два варианта - просто подключить volume, они будут лежать где-то в недрах /var/lib/docker/ на хосте или подключить конкретный каталог хоста. В первом случае volume автоматически удалится при удалении контейнера, во втором - останется на хосте включая все файлы, которые были в нем изначально или добавлены\изменены процессами в контейнере. Второй способ используют к примеру для работы СУБД. Также такой volume можно подключить к нескольким контейнерам и т.об. обмениваться данными через файловую систему. Лучше конечно REST или Kafka, но потребности бывают разные)
7) команды в Dockerfile по умолчанию запускаются под root. В т.ч. и команда, которая стартует главный процесс внутри контейнера. На это обычно ругаются безопасники. Но почему, ведь у нас изоляция??? Паранойя?) Частично)))
Первый кейс с уязвимостью - если контейнер запущен в privileged режиме, они имеет расширенный доступ к ядру Linux хоста. Кейс редкий, я знаю только про одно использование privileged - запуск Docker in Docker, он же dind, см. https://github.com/jpetazzo/dind
Второй кейс - какие-то уязвимости в Linux, которые могут привести к тому же результату.
Третий - окей, процесс внутри контейнера под root сможет делать все, что захочет. А если туда троян попал при сборке? Отправка всех проходящих сквозь контейнер данных злоумышленнику - да легко, спасет только firewall. Или не спасет(
8) каждый образ можно пометить тэгом, например, с версией. Но есть нюанс - образ с конкретным тэгом можно перезаписать. Причем это не вопрос прав доступа, а фича Docker. И это несекьюрно. Поэтому нужно использовать хэш образа вместо тэга. Он не задается при сборке, а высчитывается реестром и меняется при изменении содержимого образа. Более продвинутая защита - подпись образа его создателем, для этого есть ряд утилит https://dev.to/snyk/signing-container-images-comparing-sigstore-notary-and-docker-content-trust-1bfm
#docker #security
#docker #security
Хабр
Различия между Docker, containerd, CRI-O и runc
Появление Docker привело к взрывному росту популярности контейнеров, но с тех пор появились и другие инструменты. К сожалению, разобраться в них может быть совсем непросто. Но мы попробуем! И если вы...
Всем привет!
В коде я часто вижу раздражающий меня паттерн - интерфейс с одной реализацией.
Почему так делается и когда так делать не стоит?
Для начала - когда полезны интерфейсы:
1) соблюдение принципа инверсии зависимостей, буква D из SOLID. См. https://t.me/javaKotlinDevOps/179 Высокоуровневые классы не должны зависеть от конкретных реализаций. На первый взгляд без интерфейсов тут никак. Да и на второй тоже) Но важно уточнить, что речь именно про базовые классы с логикой, т.наз. domain logic. Например, контроллера это не касается. Или адаптера к внешнему сервису.
2) соблюдение принципов гексагональной архитектуры. См. https://t.me/javaKotlinDevOps/232 Тут тоже есть нюанс - интерфейс и реализация должны находится в разных слоях приложения. Т.е. кейса, когда реализация лежит в подпакете impl рядом с интерфейсом это не касается. К слову, если мы говорим про слой бизнес-логики, то требования DI и гексагональной архитектуры - это одни и те же требования. И применимы они в основном для слоя бизнес-логики, она же предметная область.
3) Магии Spring проще работать с интерфейсами. Здесь важный момент - проще, но Spring вполне может работать и с классами без интерфейсов. Т.е. Spring или реализует интерфейс или наследуется от класса. Первое возможно всегда, второе - только если класс не финальный. И то, это важно для @Configuration, классов с методами, помеченными как @Transactional и @Async и возможно какими-то еще кейсами, о которых я забыл. Для Java просто не стоит делать их финальными. Для Kotlin - есть плагин kotlin-allopen, который сделает все за вас, а его актуальность увеличивает тот факт, что в Kotlin все классы по умолчанию финальные. И чтобы совсем уже не было вопросов - IDEA подсвечивает такие классы красным, предлагаю сделать их не финальными.
Почему если ваш случай не 1 и не 2 лучше не заводить интерфейс. Два аргумента:
1) меньше классов - лучше читаемость. А я за нее всегда топлю!) И за число строк кода не платят. Я надеюсь по крайней мере, что и у вас так)
2) мы не строим здания или мосты, мы пишем код. И при появлении второй реализации, например, тестовой, очень легко выделить интерфейс. Для этого есть готовый рефакторинг в IDEA. И еще есть рефакторинг переименование класса. Занимает 10 минут, шансов что-то сломать мало, особенно если есть достаточное покрытие кода тестами.
Вывод: интерфейсы полезны для соблюдения принципа Dependency Inversion и\или гексагональной архитектуры. Или когда есть хотя бы 2 реализации, включая тестовую. В остальных случаях нужно дважды подумать - нужны ли они вам.
#principles #dev_compromises
В коде я часто вижу раздражающий меня паттерн - интерфейс с одной реализацией.
Почему так делается и когда так делать не стоит?
Для начала - когда полезны интерфейсы:
1) соблюдение принципа инверсии зависимостей, буква D из SOLID. См. https://t.me/javaKotlinDevOps/179 Высокоуровневые классы не должны зависеть от конкретных реализаций. На первый взгляд без интерфейсов тут никак. Да и на второй тоже) Но важно уточнить, что речь именно про базовые классы с логикой, т.наз. domain logic. Например, контроллера это не касается. Или адаптера к внешнему сервису.
2) соблюдение принципов гексагональной архитектуры. См. https://t.me/javaKotlinDevOps/232 Тут тоже есть нюанс - интерфейс и реализация должны находится в разных слоях приложения. Т.е. кейса, когда реализация лежит в подпакете impl рядом с интерфейсом это не касается. К слову, если мы говорим про слой бизнес-логики, то требования DI и гексагональной архитектуры - это одни и те же требования. И применимы они в основном для слоя бизнес-логики, она же предметная область.
3) Магии Spring проще работать с интерфейсами. Здесь важный момент - проще, но Spring вполне может работать и с классами без интерфейсов. Т.е. Spring или реализует интерфейс или наследуется от класса. Первое возможно всегда, второе - только если класс не финальный. И то, это важно для @Configuration, классов с методами, помеченными как @Transactional и @Async и возможно какими-то еще кейсами, о которых я забыл. Для Java просто не стоит делать их финальными. Для Kotlin - есть плагин kotlin-allopen, который сделает все за вас, а его актуальность увеличивает тот факт, что в Kotlin все классы по умолчанию финальные. И чтобы совсем уже не было вопросов - IDEA подсвечивает такие классы красным, предлагаю сделать их не финальными.
Почему если ваш случай не 1 и не 2 лучше не заводить интерфейс. Два аргумента:
1) меньше классов - лучше читаемость. А я за нее всегда топлю!) И за число строк кода не платят. Я надеюсь по крайней мере, что и у вас так)
2) мы не строим здания или мосты, мы пишем код. И при появлении второй реализации, например, тестовой, очень легко выделить интерфейс. Для этого есть готовый рефакторинг в IDEA. И еще есть рефакторинг переименование класса. Занимает 10 минут, шансов что-то сломать мало, особенно если есть достаточное покрытие кода тестами.
Вывод: интерфейсы полезны для соблюдения принципа Dependency Inversion и\или гексагональной архитектуры. Или когда есть хотя бы 2 реализации, включая тестовую. В остальных случаях нужно дважды подумать - нужны ли они вам.
#principles #dev_compromises
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
В связи с развитием облачных решений стала актуальна технология Service Discovery. Это такая штука, которая позволяет регистрировать новые экземпляры сервиса, удалять несуществующие, проверять их доступность, а также отдавать эту информацию балансировщику, чтобы он мог перенаправить клиента на конкретный сервер. А в теории и сам Service Discovery может являться балансировщиком.
В облаке новые экземпляры сервисов появляются достаточно часто:
1) изменением одной настройки - числа реплик или настроек affinity
2) автоматически как результат восстановления требуемого числа подов после падения одного из них
3) автоматически при использовании HorizontalPodAutoscaler
4) автоматически при добавлении в кластер новых серверов и перераспределении подов для достижения равномерности нагрузки на сервера.
Поэтому понятно, почему в облаке эта технология очень востребована. В описании любого облачного решения вы встретите фразу, что оно реализует Service Discovery.
Самые популярные реализации сейчас - это Consul, распространённый везде, и Eureka, больше привязанная к миру Java. Оба поддерживают Spring-ом, а точнее Spring Cloud https://cloud.spring.io/spring-cloud-static/spring-cloud.html
Но как известно все новое, это хорошо забытое старое. Service Discovery был с нами с начала развития интернета. Ведь там стоит похожая задача - новые сайты появляются постоянно, и нужно перенаправить клиента на сервер, на котором этот сайт находится. Я о технологии DNS. Это по сути специфически реализованный Service Discovery. Более того, если взять k8s - он использует для разрешения внутренних DNS имен сервис CoreDNS, который позиционирует себя как DNS и Service Discovery https://coredns.io/ И еще факт: Consul, о котором я писал выше, поддерживает 2 протокола доступа: REST и ... DNS.
#cloud #service_discovery
В связи с развитием облачных решений стала актуальна технология Service Discovery. Это такая штука, которая позволяет регистрировать новые экземпляры сервиса, удалять несуществующие, проверять их доступность, а также отдавать эту информацию балансировщику, чтобы он мог перенаправить клиента на конкретный сервер. А в теории и сам Service Discovery может являться балансировщиком.
В облаке новые экземпляры сервисов появляются достаточно часто:
1) изменением одной настройки - числа реплик или настроек affinity
2) автоматически как результат восстановления требуемого числа подов после падения одного из них
3) автоматически при использовании HorizontalPodAutoscaler
4) автоматически при добавлении в кластер новых серверов и перераспределении подов для достижения равномерности нагрузки на сервера.
Поэтому понятно, почему в облаке эта технология очень востребована. В описании любого облачного решения вы встретите фразу, что оно реализует Service Discovery.
Самые популярные реализации сейчас - это Consul, распространённый везде, и Eureka, больше привязанная к миру Java. Оба поддерживают Spring-ом, а точнее Spring Cloud https://cloud.spring.io/spring-cloud-static/spring-cloud.html
Но как известно все новое, это хорошо забытое старое. Service Discovery был с нами с начала развития интернета. Ведь там стоит похожая задача - новые сайты появляются постоянно, и нужно перенаправить клиента на сервер, на котором этот сайт находится. Я о технологии DNS. Это по сути специфически реализованный Service Discovery. Более того, если взять k8s - он использует для разрешения внутренних DNS имен сервис CoreDNS, который позиционирует себя как DNS и Service Discovery https://coredns.io/ И еще факт: Consul, о котором я писал выше, поддерживает 2 протокола доступа: REST и ... DNS.
#cloud #service_discovery
Всем привет!
Продолжая тему в DNS - какие у этой технологии минусы при использовании как Service Discovery. Я рассматриваю общий случай, а не случай, когда DNS внутри облака.
1) скорость ответа. Во-первых для сайтов требования по времени отклика в общем случае слабее, чем для вызовов API. На одну страницу сайта может быть десятки вызовов API. Во-вторых регистраторов доменов и провайдеров в мире очень много, поэтому много DNS серверов, они находятся в разных сетях, доступность между сетями сильно отличается, DNS сервера выстраиваются в цепочки - т.об. в целом объяснимо, почему время отклика может быть достаточно большим.
2) решается проблема времени отклика в DNS кэшированием. В первую очередь на ближайших к пользователю DNS серверах - т.е провайдерских. Но это тоже может быть проблемой. Все, у кого есть свой домен и кто обновлял у него хостинг - могут подтвердить)
3) механизмы балансировки в DNS не сильно развиты. Да, DNS может балансировать нагрузку на несколько IP адресов, может даже в гео-балансировку, но все равно по возможностям сильно уступает тому же nginx с плагинами. Т.к. nginx может получить больше информации о клиенте, прочитав заголовки запроса, включая куки.
4) в DNS нет концепции порта. Исторически для http стандартный порт 80, https - 443, для других протоколов тоже есть стандартные порты. Если не хочешь стандартный - указывай его явно в URL, это не проблема DNS) В облаке типичны кейсы, когда на одном хосте несколько сервисов на разных портах, и хардкодить порт плохо.
И если первая проблема может быть решена путем контроля всей цепочки DNS серверов в облаке, то остальные - нет, т.к. это особенности протокола.
#dns
Продолжая тему в DNS - какие у этой технологии минусы при использовании как Service Discovery. Я рассматриваю общий случай, а не случай, когда DNS внутри облака.
1) скорость ответа. Во-первых для сайтов требования по времени отклика в общем случае слабее, чем для вызовов API. На одну страницу сайта может быть десятки вызовов API. Во-вторых регистраторов доменов и провайдеров в мире очень много, поэтому много DNS серверов, они находятся в разных сетях, доступность между сетями сильно отличается, DNS сервера выстраиваются в цепочки - т.об. в целом объяснимо, почему время отклика может быть достаточно большим.
2) решается проблема времени отклика в DNS кэшированием. В первую очередь на ближайших к пользователю DNS серверах - т.е провайдерских. Но это тоже может быть проблемой. Все, у кого есть свой домен и кто обновлял у него хостинг - могут подтвердить)
3) механизмы балансировки в DNS не сильно развиты. Да, DNS может балансировать нагрузку на несколько IP адресов, может даже в гео-балансировку, но все равно по возможностям сильно уступает тому же nginx с плагинами. Т.к. nginx может получить больше информации о клиенте, прочитав заголовки запроса, включая куки.
4) в DNS нет концепции порта. Исторически для http стандартный порт 80, https - 443, для других протоколов тоже есть стандартные порты. Если не хочешь стандартный - указывай его явно в URL, это не проблема DNS) В облаке типичны кейсы, когда на одном хосте несколько сервисов на разных портах, и хардкодить порт плохо.
И если первая проблема может быть решена путем контроля всей цепочки DNS серверов в облаке, то остальные - нет, т.к. это особенности протокола.
#dns
Всем привет!
Упоминал вскользь вопросы безопасности при использовании Docker https://t.me/javaKotlinDevOps/233
Есть еще один важный момент.
Часто для передачи каких-то стендозависимых данных в Docker сервис используют переменные среды.
Более того, использование переменных окружения рекомендуется в довольно известном манифесте "12 факторов", описывающем принципы разработки облачных приложений, в разделе про конфигурацию: https://12factor.net/ru/config
Кстати, рекомендую почитать его целиком, там все пункты - полезны, хотя некоторые из них кажутся очевидными)
Но как всегда есть нюансы. Не все можно хранить в переменных среды. Когда это лучше не делать? Чтобы ответить на этот вопрос, надо учесть следующие особенности переменных среды:
1) переменные среды наследуются дочерним процессом от родительского. Но в момент наследования связь между ними теряется. Т.е. изменение\добавление переменной в родительском процессе никак не влияет на дочерний. Те, кто меня JAVA_HOME или PATH в работающей системе меня поймут)
2) доступ к переменным среды никак не ограничен. Более того, их часто отображают в UI в качестве справочной информации, иногда они попадают в логи - т.е. легко допустить утечку информации. Да, можно перезаписать значение переменной (удалить нельзя), но см. п.1
Отсюда следует два ограничения:
1) нельзя хранить в переменных среды секреты
2) не стоит хранить данные, которые могут изменится в процессе работы приложения. Т.к. в этом случае pod\контейнер придется перезапускать.
#security
Упоминал вскользь вопросы безопасности при использовании Docker https://t.me/javaKotlinDevOps/233
Есть еще один важный момент.
Часто для передачи каких-то стендозависимых данных в Docker сервис используют переменные среды.
Более того, использование переменных окружения рекомендуется в довольно известном манифесте "12 факторов", описывающем принципы разработки облачных приложений, в разделе про конфигурацию: https://12factor.net/ru/config
Кстати, рекомендую почитать его целиком, там все пункты - полезны, хотя некоторые из них кажутся очевидными)
Но как всегда есть нюансы. Не все можно хранить в переменных среды. Когда это лучше не делать? Чтобы ответить на этот вопрос, надо учесть следующие особенности переменных среды:
1) переменные среды наследуются дочерним процессом от родительского. Но в момент наследования связь между ними теряется. Т.е. изменение\добавление переменной в родительском процессе никак не влияет на дочерний. Те, кто меня JAVA_HOME или PATH в работающей системе меня поймут)
2) доступ к переменным среды никак не ограничен. Более того, их часто отображают в UI в качестве справочной информации, иногда они попадают в логи - т.е. легко допустить утечку информации. Да, можно перезаписать значение переменной (удалить нельзя), но см. п.1
Отсюда следует два ограничения:
1) нельзя хранить в переменных среды секреты
2) не стоит хранить данные, которые могут изменится в процессе работы приложения. Т.к. в этом случае pod\контейнер придется перезапускать.
#security
Telegram
(java || kotlin) && devOps
Всем привет!
В этом посте хочу рассказать про несколько малоизвестных фактов про Docker.
На всякий случай напомню. Образ (image) - это шаблон, из которого создаются работающие контейнеры. Хост - система, на которой запускаются контейнеры Docker. Реестр …
В этом посте хочу рассказать про несколько малоизвестных фактов про Docker.
На всякий случай напомню. Образ (image) - это шаблон, из которого создаются работающие контейнеры. Хост - система, на которой запускаются контейнеры Docker. Реестр …
Всем привет!
Если просто написать какой-то правильный тезис - скорее всего он забудется и на зайдет.
Поэтому проиллюстрирую тезис, даже два, случаем из моей практики.
Была задача сделать систему управления контентом. Она же CMS. Но вот беда - в организации, где я работал, такой системы не было. А так как мы являемся бизнес-подразделением - внедрить готовое решение своими силами было очень сложно, практически невозможно.
Что ж, будем искать - а что же есть подходящее. Нашел систему управления справочниками. Свои справочники заводить можно. UI для их редактирования есть. Есть ролевая модель для управления доступом. Есть даже версионность - можно изменения заводить в виде версий, включаемых на ПРОМ атомарно.
Да, не UI бедноват, но на безрыбье и рак рыба...
В общем тогда мне это показалось хорошей идей.
Сейчас считаю это одним из самых больших своих факапов как архитектора.
В чем основные проблемы:
1) выяснилось, что заведение прикладных справочников хоть и технически возможно, но по факту запрещено. Точнее разрешено только через отдельную процедуру согласования. Т.е. из справочников хотят таки сделать как ни банально бы это звучало справочники - систему хранения нормативной, редко меняющейся информации. И с этим аргументом сложно поспорить)
2) более того - не только у нас одних возникла задача ведения контента, в недрах нашей организации ведется работа над CMS. На тот момент она была в пилотной эксплуатации, но при большом желании можно даже было в этом пилоте поучаствовать.
3) самое главное - даже если бы мы внедрили нашу реализацию, то с очень большой вероятностью через год-два столкнулись бы с тем, что UI справочников не позволяет удобно настраивать контент, также, как это делается в CMS. А дорабатывать UI никто не будет, т.к. это же справочники.
В итоге через год команда перешла на ту самую CMS, уже после начала ее промышленной эксплуатации.
Выводы:
1) не надо использовать сервисы, утилиты, фреймворки нецелевым образом. Рано или поздно это аукнется. В данном случае я считаю - хорошо, что аукнулось рано)
2) не изобретайте велосипеды, используйте уже существующие) А они в 95% случаев уже есть.
#fuckup #projects #arch
Если просто написать какой-то правильный тезис - скорее всего он забудется и на зайдет.
Поэтому проиллюстрирую тезис, даже два, случаем из моей практики.
Была задача сделать систему управления контентом. Она же CMS. Но вот беда - в организации, где я работал, такой системы не было. А так как мы являемся бизнес-подразделением - внедрить готовое решение своими силами было очень сложно, практически невозможно.
Что ж, будем искать - а что же есть подходящее. Нашел систему управления справочниками. Свои справочники заводить можно. UI для их редактирования есть. Есть ролевая модель для управления доступом. Есть даже версионность - можно изменения заводить в виде версий, включаемых на ПРОМ атомарно.
Да, не UI бедноват, но на безрыбье и рак рыба...
В общем тогда мне это показалось хорошей идей.
Сейчас считаю это одним из самых больших своих факапов как архитектора.
В чем основные проблемы:
1) выяснилось, что заведение прикладных справочников хоть и технически возможно, но по факту запрещено. Точнее разрешено только через отдельную процедуру согласования. Т.е. из справочников хотят таки сделать как ни банально бы это звучало справочники - систему хранения нормативной, редко меняющейся информации. И с этим аргументом сложно поспорить)
2) более того - не только у нас одних возникла задача ведения контента, в недрах нашей организации ведется работа над CMS. На тот момент она была в пилотной эксплуатации, но при большом желании можно даже было в этом пилоте поучаствовать.
3) самое главное - даже если бы мы внедрили нашу реализацию, то с очень большой вероятностью через год-два столкнулись бы с тем, что UI справочников не позволяет удобно настраивать контент, также, как это делается в CMS. А дорабатывать UI никто не будет, т.к. это же справочники.
В итоге через год команда перешла на ту самую CMS, уже после начала ее промышленной эксплуатации.
Выводы:
1) не надо использовать сервисы, утилиты, фреймворки нецелевым образом. Рано или поздно это аукнется. В данном случае я считаю - хорошо, что аукнулось рано)
2) не изобретайте велосипеды, используйте уже существующие) А они в 95% случаев уже есть.
#fuckup #projects #arch
Всем привет!
Сегодняшний пост начну издалека. Распределенные системы обмениваются сообщениями. Каждое сообщение можно рассматривать как событие, требующее какой-то обработки и передачи дальше - в другую подобную систему или для хранения в БД. Т.об. мы получаем распределенную цепочку микросервисов, через которые проходит событие. Существуют т.наз. семантики доставки сообщений:
- at-most-once
- at-least-once
- exactly-once
at-most-once - максимум один раз, т.е. 0 или 1. Т.е. есть вероятность потерять и не обработать сообщение. Подходит для случаев, когда поток сообщений большой, используется для сбора статистики и потеря одного сообщения не критична. Например - статистика по кликам на сайте.
at-least-once - минимум один раз, т.е. 1 или более. Т.е. есть вероятность повторно отправить какое-то сообщение и, соответственно, обработать его дважды. Подходит для систем, где важно знать последнее значение. Пример: стоимость акции на сайте брокера. Или для систем, идемпотентно обрабатывающих входящие сообщения - https://habr.com/ru/companies/domclick/articles/779872/
exactly-once - строго один раз. Идеальный случай.
Да, система может поддерживать сразу несколько семантик, т.е. по сути иметь несколько режимов работы.
Самый интересный и сложный - это конечно exactly-once. Как с ним обстоят дела?
Например, его поддерживает Kafka - самая распространённая система потоковой передачи сообщений - https://docs.confluent.io/kafka/design/delivery-semantics.html
А также самые распространенные системы потоковой обработки данных:
Kafka Streams - https://kafka.apache.org/33/documentation/streams/core-concepts
Flink - https://flink.apache.org/2018/02/28/an-overview-of-end-to-end-exactly-once-processing-in-apache-flink-with-apache-kafka-too/
Spark - https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
Кажется, что все хорошо. Но не совсем)
Если прочитать внимательнее, что пишут, например, создатели Kafka, то выяснится что exactly-once гарантируется на участке Producer - Kafka, но далее все зависит от того, как организована работа в Consumer-е. Вот неплохая переводная статья на эту тему от одного из создателей Kafka: https://habr.com/ru/companies/badoo/articles/333046/ // в статье detected american style самореклама, но все равно она неплохая)))
Создатели Flink тоже говорят, что
а) мы даем механизм для exactly-once - в случае сбоя мы откатим ваш обрабатывающий процесс на конкретное состояние (checkpoint), и вы четко будете знать, что это за состояние - его метку времени, что было до и после него, но
б) что делать уже обработанными записями, находящимися после восстановленного состояния - разбирайтесь сами
в) возможность вернуться к сообщениям, на которые указывает checkpoint - тоже на стороне разработчика. В случае Kafka это чаще всего элементарно - сообщения не удаляются сразу после чтения из топика, а если это MQ или сетевой сокет...
г) а еще можно рассмотреть кейс, если кто-то обновил хранилище, где хранится состояние
д) или если в функции потоковой обработки используются какие-то внешние вызовы, которые сломаются на повторах после отката...
Но по большому счету это частности. Основная проблема - системы типа Kafka или Flink могут обеспечить exactly-once на какой-то небольшой части вашей микросервисной системы. Как обеспечить ее на всей системе - в любом случае задача архитектора и разработчика.
Подсказка: наличие operationId (traceId), идемпотентность, транзакции там где это возможно, докаты и наконец админка для ручного разбора инцидентов если не помогли все предыдущие варианты.
#streaming #kafka #flink #arch #microservices #exactly_once
Сегодняшний пост начну издалека. Распределенные системы обмениваются сообщениями. Каждое сообщение можно рассматривать как событие, требующее какой-то обработки и передачи дальше - в другую подобную систему или для хранения в БД. Т.об. мы получаем распределенную цепочку микросервисов, через которые проходит событие. Существуют т.наз. семантики доставки сообщений:
- at-most-once
- at-least-once
- exactly-once
at-most-once - максимум один раз, т.е. 0 или 1. Т.е. есть вероятность потерять и не обработать сообщение. Подходит для случаев, когда поток сообщений большой, используется для сбора статистики и потеря одного сообщения не критична. Например - статистика по кликам на сайте.
at-least-once - минимум один раз, т.е. 1 или более. Т.е. есть вероятность повторно отправить какое-то сообщение и, соответственно, обработать его дважды. Подходит для систем, где важно знать последнее значение. Пример: стоимость акции на сайте брокера. Или для систем, идемпотентно обрабатывающих входящие сообщения - https://habr.com/ru/companies/domclick/articles/779872/
exactly-once - строго один раз. Идеальный случай.
Да, система может поддерживать сразу несколько семантик, т.е. по сути иметь несколько режимов работы.
Самый интересный и сложный - это конечно exactly-once. Как с ним обстоят дела?
Например, его поддерживает Kafka - самая распространённая система потоковой передачи сообщений - https://docs.confluent.io/kafka/design/delivery-semantics.html
А также самые распространенные системы потоковой обработки данных:
Kafka Streams - https://kafka.apache.org/33/documentation/streams/core-concepts
Flink - https://flink.apache.org/2018/02/28/an-overview-of-end-to-end-exactly-once-processing-in-apache-flink-with-apache-kafka-too/
Spark - https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
Кажется, что все хорошо. Но не совсем)
Если прочитать внимательнее, что пишут, например, создатели Kafka, то выяснится что exactly-once гарантируется на участке Producer - Kafka, но далее все зависит от того, как организована работа в Consumer-е. Вот неплохая переводная статья на эту тему от одного из создателей Kafka: https://habr.com/ru/companies/badoo/articles/333046/ // в статье detected american style самореклама, но все равно она неплохая)))
Создатели Flink тоже говорят, что
а) мы даем механизм для exactly-once - в случае сбоя мы откатим ваш обрабатывающий процесс на конкретное состояние (checkpoint), и вы четко будете знать, что это за состояние - его метку времени, что было до и после него, но
б) что делать уже обработанными записями, находящимися после восстановленного состояния - разбирайтесь сами
в) возможность вернуться к сообщениям, на которые указывает checkpoint - тоже на стороне разработчика. В случае Kafka это чаще всего элементарно - сообщения не удаляются сразу после чтения из топика, а если это MQ или сетевой сокет...
г) а еще можно рассмотреть кейс, если кто-то обновил хранилище, где хранится состояние
д) или если в функции потоковой обработки используются какие-то внешние вызовы, которые сломаются на повторах после отката...
Но по большому счету это частности. Основная проблема - системы типа Kafka или Flink могут обеспечить exactly-once на какой-то небольшой части вашей микросервисной системы. Как обеспечить ее на всей системе - в любом случае задача архитектора и разработчика.
Подсказка: наличие operationId (traceId), идемпотентность, транзакции там где это возможно, докаты и наконец админка для ручного разбора инцидентов если не помогли все предыдущие варианты.
#streaming #kafka #flink #arch #microservices #exactly_once
Хабр
Идемпотентность: больше, чем кажется
Друзья, всем привет! Идемпотентность в проектировании API — не просто формальность. Это свойство, часто рассматриваемое как способ получения одинакового ответа на повторяющийся запрос, на самом деле...
Всем привет в новом году!
Предположу - многие знают о том, что для Data Science, ML, DL, AI в основном используют Python. Я уже подымал эту тему тут https://t.me/javaKotlinDevOps/142
Хотел бы подчеркнуть важность экосистемы - в Python было создано так много ML\DL\NLP библиотек, что догнать его другим экосистемам стало очень сложно.
А тут недавно и ChatGPT появился, что привело к взрывному росту популярности ML и AI ... Ну так вот - уже летом этого года началась разработка 2 альтернатив для работы с GPT моделями в Java.
1) langchain4j https://github.com/langchain4j/langchain4j Характерно, что проект сделан на основе одноименной Python библиотеки. Поддерживается в Quarkus.
2) конечно же Spring в виде Spring AI - https://docs.spring.io/spring-ai/reference
Оба проекта находятся в начальной стадии своего развития, версии вида 0.х, обновления выходят часто.
Еще важный момент - оба проекта имеют коннекторы к популярным GPT провайдерам - OpenAI (ChatGPT), Azure OpenAI...
Вот тут описан элементарный пример работы со Spring AI - https://habr.com/ru/articles/784128
А вот тут можно почитать про основные понятия и примеры кода для полноценной работы с ML моделями:
https://www.javaadvent.com/2023/12/java-and-the-ai-frontier-leveraging-modern-tools-and-techniques-for-machine-learning.html
#ai #java #ml #libraries
Предположу - многие знают о том, что для Data Science, ML, DL, AI в основном используют Python. Я уже подымал эту тему тут https://t.me/javaKotlinDevOps/142
Хотел бы подчеркнуть важность экосистемы - в Python было создано так много ML\DL\NLP библиотек, что догнать его другим экосистемам стало очень сложно.
А тут недавно и ChatGPT появился, что привело к взрывному росту популярности ML и AI ... Ну так вот - уже летом этого года началась разработка 2 альтернатив для работы с GPT моделями в Java.
1) langchain4j https://github.com/langchain4j/langchain4j Характерно, что проект сделан на основе одноименной Python библиотеки. Поддерживается в Quarkus.
2) конечно же Spring в виде Spring AI - https://docs.spring.io/spring-ai/reference
Оба проекта находятся в начальной стадии своего развития, версии вида 0.х, обновления выходят часто.
Еще важный момент - оба проекта имеют коннекторы к популярным GPT провайдерам - OpenAI (ChatGPT), Azure OpenAI...
Вот тут описан элементарный пример работы со Spring AI - https://habr.com/ru/articles/784128
А вот тут можно почитать про основные понятия и примеры кода для полноценной работы с ML моделями:
https://www.javaadvent.com/2023/12/java-and-the-ai-frontier-leveraging-modern-tools-and-techniques-for-machine-learning.html
#ai #java #ml #libraries
Telegram
(java || kotlin) && devOps
Всем привет! Возвращаясь к теме Machine Learning. Если погуглить эту тему, то в первых сторонах будет Python и библиотеки на Python. Почему не Java - вот тут попытка дать ответ: https://habr.com/ru/companies/piter/articles/429596/ Согласен с тем, что для…
Всем привет!
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости оптимизирует его, включая компиляцию в бинарное представление для конкретной процессорной архитектуры. И основные оптимизации надо отметить происходят именно здесь, а не при первичной компиляции. Еще важный момент - классы\библиотеки подгружаются в память не обязательно при старте приложения, а по мере использования. Все это называется JIT - Just in time компиляция. Влиять на нее можно с помощью ряда флагов запуска Java приложения - -server, -client.
Плюс такого подхода - JVM позволяет в 90% случаев игнорировать, на каком железе запускается Java приложение. Минус - долгий старт Java приложения плюс время для "разогрева" и выхода на рабочий режим.
Но с другой стороны с развитием Docker мы и так можем игнорировать особенности железа и ОС на хост-сервере, главное, чтобы там можно было запустить Docker. И наконец кроме долгого старта и разогрева собственно JVM у нас как правило есть Spring с кучей модулей, число которых растет, и в итоге время старта типичного Spring Boot приложения доходит до совсем неприличных величин.
Альтернатива - AOT - Ahead-of-Time compilation. В этом случае мы компилируем исходники в бинарный код в момент первичной компиляции. Причем как собственно приложение, так и JVM и все JAR. Получается такой native image монолит. Проект называется GraalVM https://www.graalvm.org/, официально поддерживается Oracle. Есть open-source версия, основанная на OpenJDK.
Плюс этого подхода - скорость запуска. Это критически важно в облаках, т.к. k8s может "случайно" рестартовать под при изменении конфигурации железа или настроек Deployment. Еще будет выигрыш в скорости обработки запросов, т.к. не тратится CPU и память в runtime на JIT компиляцию.
Какие минусы?
1) невозможна динамическая\ленивая загрузка библиотек\плагинов, classpath фиксируется в момент компиляции. К слову - у этого ограничения есть и плюсы, сложнее эксплуатировать уязвимости типа log4j injection - см. https://t.me/javaKotlinDevOps/4
2) вопрос - откуда компилятор узнает, какой код ему нужно добавить в наш native монолит? Ответ: он идет от метода main. Соответственно, код который явно не вызывается, а, например, вызывается через рефлексию, он не увидит. Соответственно, никакой рефлексии в ПРОМ коде. Что, надо сказать, в целом правильно)
3) аналогично просто так не заработает магия Spring, основанная на рефлексии и динамических прокси. Из чего следует, что мало добавить в Spring приложение AOT компилятор - нужно дорабатывать сам Spring, что и было сделано в Spring Boot 3.2. Другие фреймворки также придется дорабатывать. Например, Mockito до сих пор не работает в native image. Справедливости ради тут причина такая же, как в анекдоте про неуловимого ковбоя Джо - не нужен Mockito в native image)
4) если продолжить про Spring - загрузка бинов по условию: @ConditionalOnProperty, @Profile - тоже не заработает. Нужно указывать при сборке необходимый профиль, чтобы уже при компиляции нужные бины были обнаружены и добавлены в дистрибутив.
5) еще вопрос - но ведь среднее Java приложение + библиотеки + JVM = миллионы строк кода, что будет с компиляцией? Ответ - компиляция будет долгой, до 10 минут на spring boot hello world. Поэтому в документации Spring прямо сказано, что хотя Spring поддерживает запуск тестов в native image - делать так нужно только для интеграционных тестов, лучше на CI, а модульные запускать по старинке, т.к. тут критична скорость получения результата.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #java_start_boost
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости оптимизирует его, включая компиляцию в бинарное представление для конкретной процессорной архитектуры. И основные оптимизации надо отметить происходят именно здесь, а не при первичной компиляции. Еще важный момент - классы\библиотеки подгружаются в память не обязательно при старте приложения, а по мере использования. Все это называется JIT - Just in time компиляция. Влиять на нее можно с помощью ряда флагов запуска Java приложения - -server, -client.
Плюс такого подхода - JVM позволяет в 90% случаев игнорировать, на каком железе запускается Java приложение. Минус - долгий старт Java приложения плюс время для "разогрева" и выхода на рабочий режим.
Но с другой стороны с развитием Docker мы и так можем игнорировать особенности железа и ОС на хост-сервере, главное, чтобы там можно было запустить Docker. И наконец кроме долгого старта и разогрева собственно JVM у нас как правило есть Spring с кучей модулей, число которых растет, и в итоге время старта типичного Spring Boot приложения доходит до совсем неприличных величин.
Альтернатива - AOT - Ahead-of-Time compilation. В этом случае мы компилируем исходники в бинарный код в момент первичной компиляции. Причем как собственно приложение, так и JVM и все JAR. Получается такой native image монолит. Проект называется GraalVM https://www.graalvm.org/, официально поддерживается Oracle. Есть open-source версия, основанная на OpenJDK.
Плюс этого подхода - скорость запуска. Это критически важно в облаках, т.к. k8s может "случайно" рестартовать под при изменении конфигурации железа или настроек Deployment. Еще будет выигрыш в скорости обработки запросов, т.к. не тратится CPU и память в runtime на JIT компиляцию.
Какие минусы?
1) невозможна динамическая\ленивая загрузка библиотек\плагинов, classpath фиксируется в момент компиляции. К слову - у этого ограничения есть и плюсы, сложнее эксплуатировать уязвимости типа log4j injection - см. https://t.me/javaKotlinDevOps/4
2) вопрос - откуда компилятор узнает, какой код ему нужно добавить в наш native монолит? Ответ: он идет от метода main. Соответственно, код который явно не вызывается, а, например, вызывается через рефлексию, он не увидит. Соответственно, никакой рефлексии в ПРОМ коде. Что, надо сказать, в целом правильно)
3) аналогично просто так не заработает магия Spring, основанная на рефлексии и динамических прокси. Из чего следует, что мало добавить в Spring приложение AOT компилятор - нужно дорабатывать сам Spring, что и было сделано в Spring Boot 3.2. Другие фреймворки также придется дорабатывать. Например, Mockito до сих пор не работает в native image. Справедливости ради тут причина такая же, как в анекдоте про неуловимого ковбоя Джо - не нужен Mockito в native image)
4) если продолжить про Spring - загрузка бинов по условию: @ConditionalOnProperty, @Profile - тоже не заработает. Нужно указывать при сборке необходимый профиль, чтобы уже при компиляции нужные бины были обнаружены и добавлены в дистрибутив.
5) еще вопрос - но ведь среднее Java приложение + библиотеки + JVM = миллионы строк кода, что будет с компиляцией? Ответ - компиляция будет долгой, до 10 минут на spring boot hello world. Поэтому в документации Spring прямо сказано, что хотя Spring поддерживает запуск тестов в native image - делать так нужно только для интеграционных тестов, лучше на CI, а модульные запускать по старинке, т.к. тут критична скорость получения результата.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #java_start_boost