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

Поговорим про разделение функционала между микросервисами.

Для начала я бы разделил два случая - распил монолита или создание нового приложения.

При распиле есть несколько путей, которые можно комбинировать:
1) отделять в первую очередь менее значительный функционал. Так можно будет набить шишки, настроить DevOps, инфраструктуру, а потом уже переходить к чему-то серьёзному
2) отделять функционал, который легче выделить. Например, он уже компилируется в отдельный war/ear, хотя и имеет общий релизный цикл с монолитом. Или он правильно спроектирован https://t.me/javaKotlinDevOps/59 и его отделение требует минимальных усилий.
3) делать новые фичи на микросервисах, закрывая монолит API Gateway для единообразия API.
Все это называется шаблоном Душитель https://microservices.io/patterns/refactoring/strangler-application.html Душим мы как вы понимаете монолит)

Если же мы делаем новый функционал - тоже есть разные варианты, размещу их в порядке приоритета
1) разделение по бизнес фичам. Где фича - это агрегат в терминах DDD. Или ресурс в REST. Как я уже говорил ранее - не операция, это слишком мелко https://t.me/javaKotlinDevOps/59
2) разделение по слоям. Например, можно выделить в отдельный сервис сохранение данных. Или шлюз к смежной системе
3) разделение по планируемой нагрузке. Я бы его применял как дополнительный фактор к первым двум. Более нагруженные сервисы выделяем отдельно.

Еще один важный фактор: возможность переиспользования сервиса. Имеет значение и при распиле монолита, и при разработке новых фичей. Пример: отправка уведомлений пользователю по-хорошему стандартизирована и такой сервис смогут переиспользовать все создаваемые микросервисы. Способствуем выполнению принципа DRY - https://ru.wikipedia.org/wiki/Don’t_repeat_yourself

И напоследок хотел бы напомнить простую истину - как нет серебряной пули или единственно верной архитектуры, так нет канонического способа разделения на микросервисы)

#microservices #сравнение
Всем привет!

Хочу разбавить тему микросервисов. Сегодня поговорим про Spring и Dependency Injection (DI).
Есть три способа внедрения бина:
1) через конструктор
2) через поле
3) через setter
Практически все слышали, что предпочтительнее внедрять через конструктор. Вопрос почему?

Первая и самая главная причина: внедрение зависимостей - это часть инициализации объекта. А конструктор - это простой и естественный способ инициализировать объект.
Что из этого следует:
1) список внедряемых бинов легко читается. Через setter-ы очень плохо читается, через поля получше, но конструктор вне конкуренции.
2) становится проще создать полностью инициализированный объект т.к. для этого есть готовый конструктор. Полезно в тестах, можно написать тест на чистом JUnit, без @SpringBootTest и других инструментов для инициализации контекста из Spring Test.
3) если классу нужно слишком много бинов для работы - SonarQube будет ругаться, что у конструктора слишком много полей. Считаю это плюсом, если ругается - это повод задуматься над рефакторингом
4) если у бинов циклические ссылки - инициализация контекста Spring упадет. Многие считают это минусом, но я не согласен: чем раньше найдешь циклическую зависимость - тем быстрее исправишь. См. https://t.me/javaKotlinDevOps/53
5) если с внедряемыми бинами надо что-то делать, то у нас есть место, где гарантированно все бины будут доступны - конструктор. При внедрении зависимостей через setter или поля вначале выполнится дефолтный конструктор, потом будут проиницилизированы поля.
6) для инициализации не используется рефлексия, как в случае autowired полей, т.е. инициализации работает немного быстрее
7) если конструктор единственный, то начиная со Spring 4.3 не нужно указывать аннотацию @Autowired. Для Kotlin в случае default конструктора есть особенности, для того, чтобы с ними не сталкиваться лучше использовать Spring 5+ ) Не сказать, что это большое преимущество, т.к. аннотацию для маркировки бина все равно придется использовать, поэтому класс без import Spring зависимостей так просто сделать не получится.

И второй важный момент - поддержка иммутабельности объектов, т.к. только в конструкторе можно инициализировать final поля в случае Java и val - в случае Kotlin.

#spring #java #kotlin #interview_question
Всем привет!

Продолжается перерыв в теме микросервисов.
Сегодня будет немного холивара)

Можно ли использовать static методы?

Исторически static-и прошли 3 стадии в своем восприятии.

1 этап: о круто, можно не создавать объект, давайте все методы делать статическими.
Если что - я конечно утрирую насчет всех, но static-и активно использовались, это факт, который легко увидеть в legacy коде. Еще факт: static-и есть во многих языках, что как бы намекает. Даже в Kotlin они есть, просто выглядят странно - я про companion object.

2 этап: static-и трудно тестировать, это антипаттерн, не надо их использовать. Ну разве что для Util классов, и то ...
Вот пример, демонстрирующий почему возникают такие мысли.
Возьмем другой антипаттерн - синглетон.

class Singleton {
private static final instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
....
}
Сразу скажу - это не одна из простейших реализаций, не потокобезопасная, взятая для примера, так делать не надо.

Предположим, мы хотим сделать вместо Singleton заглушку для теста. Ну например, он ходит в БД или читает данные из файла, а мы хотим, чтобы тесты работали быстро.
Можно сделать так:
class TestSingleton extends Singleton {
private static final testInstance = new TestSingleton();
public static Singleton getTestInstance() {
return testInstance;
}
....
}
Но это работает только если Sigleton можно как-то передать в тестируемый класс, он же System under Test (SUT).
А если это зависимость зависимости SUT? Или в более общем случае его нельзя передать через public API?
Основная проблема в том, что static методы не наследуются, а следовательно переопределить их нельзя. Можно создать метод с тем же именем, но вызываться будет метод того класса, который указан в конкретном куске кода. Или декларируемого, а не фактического класса переменной, поля или параметра, если static вызывается на объекте. В этом плане Java позволяя вызывать static метод из объекта только путает людей( К слову, в Kotlin так нельзя.

Есть конечно грязные хаки с мокированием static методов, даже Mockito это умеет. Но тот факт, что для включения этой фичи нужно добавить настройку в classpath https://www.baeldung.com/mockito-mock-static-methods говорит о том, что авторы Mockito не рекомендуют так делать.

3 этап, наше время: все не так однозначно. Точнее однозначно вот что: если static метод используется в паре со static полями - это точно зло.
И самостоятельное создание синглетонов тоже зло) Это я на всякий случай уточняю)
Но если присмотрется к вот такому static методу:
1) он не имеет доступа к полям объекта по определению
2) в классе нет static полей как я писал выше
3) пусть метод не имеет побочных эффектов. Т.е. не лезет в БД, в файловую систему, в другие сервисы
Т.е метод получает что-то на вход, вычисляет что-то и возвращает результат.
Это типичный метод Util класса, да.
Но еще это определение функции из математики. А функция - это функциональное программирование. А Java начиная с 8 версии умеет передавать ссылки на функции. И хотя в ней нет функциональных типов, но есть функциональные интерфейсы, которые делают тоже самое, просто немного с большим количеством кода. Java же, все как мы любим)

Подводя итог - считать static злом не надо. Надо лишь правильно его использовать.

#interview_question #holy_war #java #kotlin
Всем привет!

И еще один оффтопик)
Наверняка многие знают, что IDEA облегчает рефакторинг кода. Когда-то, лет 10 назад, для меня это было одним из основных ее преимуществом.
Со временем там много чего интересного появилось, см. https://t.me/javaKotlinDevOps/28

К чему это я.
Рефакторинги в IDEA являются контекстно-зависимыми.
Т.е. список доступных зависит:
1) от языка
2) от места, в котором вы кликнули мышью или вызвали shortcut.
Кстати, для рефакторингов есть один master shortcut, который точно стоит запомнить: Ctrl+Alt+Shift+T.
Он отображает все доступные в данном месте рефакторинги.

Так вот, я подозреваю, что многие знают к примеру Extract Method или Introduce Variable, но почти никто - Remove Middleman.
Поэтому рекомендую ознакомится со списком возможных рефакторингов.
На русском и на хабре:
https://habr.com/ru/post/530360/
В официальной документации:
https://www.jetbrains.com/help/idea/refactoring-source-code.html
И видос от создателей, как устроены сложные рефакторинги и как их выстраивать в цепочки, не дожидаясь, когда допилят IDEA)
https://www.youtube.com/watch?v=tmv_Grdbog4

#idea #IDE
Всем привет!

На какие грабли можно наступить при распиле монолита на микросервисы?
Небольшой дисклеймер: я подразумеваю, что внедряются микросервисы + Agile, т.к по определению у микросервиса должен быть владелец - команда.

Примеры взяты из жизни)

1) Как я уже говорил https://t.me/javaKotlinDevOps/60 - начинать распил лучше с каких-то небольших частей существующего монолита или новых фичей для этого монолита. Тут важен вот какой момент - скорее всего монолит выполняет некую критическую функцию для компании и, следовательно, для него существуют некие требования. Например, 99,99-ая доступность. И первые "тренировочные" микросервисы не стоит делать для другого сервиса=приложения=монолита, например, потому, что у него более лояльные пользователи или там проще внедрение. Если так сделать, то при переходе от простого монолита к сложному появится куча неожиданных требований по надежности, масштабируемости, ИБ, удобству сопровождения, и архитектуру придется переделывать.

2) должна быть зафиксированная и согласованная со всеми заинтересованными лицами архитектура.
Почему это критично - потому что архитектура - это основа. Можно конечно создавать архитектуру параллельно разработке, по принципу ввяжемся в бой, а там разберемся. Но это приведет к переписыванию микросервисов, к микросервисам-фантомам, которые на бумаге есть, а в жизни оказались не нужны, и к их антиподам, про которых просто забыли и конечно же к проблемам на всех стендах из-за меняющегося API. В конце концов при таком подходе слова архитектура и архитектор по отношению к новой микросервисной платформе станут ругательными)

3) нужно время на обучение разработчиков, тестировщиков и сопровождения. Обучение может быть и должно быть на практике - на реальных сервисах, но оно должно быть учтено в roadmap перехода. Сроки перехода должны быть согласованы командами разработки и сопровождения, а не спущены сверху. Звучит очевидно, но по моему опыту не для всех)

4) нужно изменение релизных и контролирующих политик в компании, иначе Lead Time и сложность вывода в ПРОМ для микросервисов останется на уровне монолита. Это сложный момент, т.к. люди консервативны, они привыкают к правилам, и не всегда осознают, что цена ошибки в монолите и в микросервисе разная. Особенно консервативно сопровождение и DBA, работа у них такая, цена ошибки высока) С другой стороны, конечно же микросервисная система должна быть спроектирована так, чтобы падение одного сервиса не приводило к каскадным падениям других. Это снова про критичное влияние архитектуры.
Еще важный момент - в монолите процесс согласования был централизован, им занимались специально обученные люди. С микросервисами релизный процесс попадает в Agile команды. А правило 7 +-2 действует не только для людей, а и для команд. Ну и большое число внешних взаимодействий противоречит Agile-у.

5) к началу разработки нужен работающий и простой DevOps. Разрабочики монолита как правило занимаются только разработкой и если заставить их учиться DevOps - потеряем время. Даже если набирать новых людей - все равно не каждый разработчик готов разрабатывать пайплайны. Т.е. должна быть минимальная конфигурация pipeline в декларативном стиле и работающие CI и CD pipeline. Опять же релизы микросервисов происходят намного чаще, для них это важно.
Также не должно быть невнятных инструкций и десятков настроечных файлов, необходимых для работы pipeline. Конфигурация - это не код, писать его не интересно, разбираться в ней тем более. И это не DevOps на самом деле) Должны быть - генераторы конфигураций, принцип convention over configuration.

6) микросервисам нужно больше инфраструктуры по сравнению с монолитом: сервера, репозитории - и больше доступов. Если в монолите как правило получением инфраструктуры занималась выделенная команда(ы), то при микросервисной архитекторе этим занимается каждая команда. Следовательно, процесс выдачи должен быть максимально автоматизирован и упрощен. Снова работает правило 7+-2. Разработчики должны заниматься тем, что у них лучше всего получается, а не заявками.

#microservices #fuckup #conv_over_conf
Всем привет!

Вспомнил еще пару косяков при переходе на микросервисы.

7) не стоит совмещать переход на микросервисы с внедрением новой, необкатанной технологии. Новой не в плане того, что разрабы еще не работали к примеру с k8s. А например такой: использовать какой-нибудь новый "более лучший" аналог Istio или Helm. Или решить отказаться от СУБД в пользу перспективного noSQL решения. Микросервисная система приносит с собой новые технологии, меняются интеграции, DevOps, инфраструктура, скорее всего будет больно при интеграции микросервисов и legacy, не стоит добавлять в процесс еще немного хаоса) В принципе очевидная вещь, но я видел эти грабли в действии)

8) в монолите разработчики ограничены по стеку технологий и по способам реализации фичей и это понятно. Энтропия растет, монолит имеет тенденцию становится "большим куском грязи", поэтому если разрешать каждому добавлять свою любимую библиотеку или делать однотипные вещи по разному - оно будет долго собираться, конфликтовать в runtime, а изучить такой код с нуля станет очень сложно. А одно из преимуществ микросервисов в том, что их можно писать на разных технологических стеках. С другой стороны есть набор эксплуатационных требований, требований ИБ и требований по надежности и отказоустойчивости. Часть из них можно и нужно реализовать снаружи - в k8s или Service Mesh, часть - только внутри. Есть соблазн ту часть, что внутри, вынести в платформенный слой. Это допустимо, хотя и не обязательно - можно просто выставить необходимое платформенное API. Главное помнить, что этот слой должен быть тонким, чтобы его изучение и подключение не стало занимать больше времени, чем разработка фичи)

9) при переходе на микросервисы с большой вероятностью возникнут конфликты между командами разработки монолита и микросервисов. Как их разрешить - вопрос сложный. Как вариант - активно вовлекать монолитные команды в разработку микросервисов. Тут правда возникнет вопрос - а кто будет пилить бизнес-фичи на время перехода? Идеальный кейс - когда команда по очереди занимается и legacy, и микросервисами.

10) если речь про кровавый enterprise, то рядом с монолитами обычно живет ESB - Enterprise Service Bus, по которой монолиты общаются между собой. Может возникнуть идея сделать на основе k8s прямую замену ESB - как единую точку контроля и стандартизации интеграций. Если контроль делать через единый pipeline или проверку правил внутри k8s - ок. А если попытаться отдать разработку интеграций в ту же команду, что занималась ESB - то мы снова получим ESB как единую точку замедления разработки) Важно то, что задачи у k8s и ESB разные, и эту мысль нужно до всех доносить. У k8s - маршрутизация, автоматическое масштабирование, отказоустойчивость, наблюдаемость (observability) и прозрачное шифрование. У ESB - маршрутизация, стандартизация API и конвертация данных. Общая только маршрутизация. Т.е ESB это больше про унификацию бизнес логики, а облако и Service Mesh - про упрощение поддержки микросервисов.

#microservices #fails
Всем привет!

После прочтения новости о новых методах создания коллекций в JDK 19 https://inside.java/2022/10/24/sip069/ сразу возникает вопрос - зачем было ждать 17 версий, почему не выпустили раньше???)))
Думаю сэкономили бы сотни, возможно тысячи человекодней, потраченных на отладку. А фича на самом деле плевая.
Задачу в 17 году завели, кстати - https://bugs.openjdk.org/browse/JDK-8186958

Из подобных очевидно полезных, но появившихся очень поздно фичей вспоминается JDK 14 и человекочитаемое сообщение об NPE.

#java
И снова привет!

По поводу недавней уязвимости в Apache Commons Text https://habr.com/ru/company/pt/blog/694720/ у меня две мысли:
1) техника очень похожа на Log4Shell https://ru.wikipedia.org/wiki/Log4Shell, с которой и начался этот блог - и там, и там интерполяция выражений в текстовых строках с далеко идущими последствиями. Who is next?)))

2) лично я узнал о возможности интерполяции в обоих случаях после появления соответствующих уязвимостей))))

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

По следам отличной книги Владимира Хорикова Принципы юнит-тестирования, см https://t.me/javaKotlinDevOps/50, решил погрузится в тему и прочитать еще и классику жанра - Шаблоны тестирования xUnit. Рефакторинг кода тестов https://www.litres.ru/dzherard-mesarosh/shablony-testirovaniya-xunit-refaktoring-koda-t-48637685/
К слову, на английском книга выложена в виде сайта со списком паттернов тестирования http://xunitpatterns.com/
По сути книга и является большим справочников паттернов. Автор - человек дотошный: любую типовую практику, используемую для написания тестов он вынес в шаблон с подробным описанием истории возникнования, вариантов релизации и примеров, мотивирующих на использование шаблонов.
Вот примеры шаблонов из книги, на которые хотел бы обратить внимание:
1) 5 видов моков http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html - от Dummy object, который ничего не возвращает, не делает и не проверяет, а нужен лишь для передачи как параметр, до Fake Object - облегченной реализации боевой логики, без обращений во внешние системы и БД. Видел ссылки на эти 5 типов в других статьях и книгах.
2) Test Utility Method - одна из базовых практик при рефаторинге тестов, вынесение повторяющего кода в отдельные методы. Это может быть код настройки системы, код проверки результата с assert-ми и код очистики ресурсов. Очень полезная штука, с какого-то момента после рефакторинга вынесенных методов для их переиспользования мы получаем некий тестовый язык, по сути свой DSL, и это без всяких Cucumber-ов и прочих BDD. http://xunitpatterns.com/Test%20Utility%20Method.html

Остальное не влезло в пост телеги, см. на дзене https://dzen.ru/media/id/62fe543369f7d3314503a87d/recenziia-na-shablony-testirovaniia-xunit-6367bbba7d9dc52b40aa3ff3

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

Нашел пару статей на тему автоматической валидации настроек Spring Boot, как средствами IDEA, так и в runtime. Работает для yaml и properties форматов.

Для авподополнения в IDEA используется spring-boot-configuration-processor, который сканируется файлы с Spring аннотацией @Configuration и формирует необходимые метаднные. Также можно создавать руками, полезно для чужого кода
https://www.baeldung.com/intellij-resolve-spring-boot-configuration-properties

В runtime это делается через Jacarta Bean Validation API https://beanvalidation.org/, самая известная реализация Hibernate Validator https://hibernate.org/validator/ Кстати, несмотря на название к ORM библиотека прямого отношения не имеет.
Детали см. https://habr.com/ru/post/505628/
Если не хватает возможностей аннотаций из Validation API можно даже валидацию в коде описать, с помощью Spring Validator https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/Validator.html

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

#spring_boot #java #IDEA #validation
Всем привет!

Сегодня расскажу о нескольких неочевидных особенностях работы Maven.

1) за каждый этап жизненного цикла сборки Maven отвечает какой-то плагин. Один плагин может покрывать несколько этапов. Это не новость. Я надеюсь) Интересно то, что эти плагины не обязательно указывать в pom, вот минимальный рабочий pom, с которым ваш проект скомпилируется - https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Minimal_POM
Работает принцип convention over configuration.
А вот описание алгоритма определения нужного плагина: https://maven.apache.org/guides/introduction/introduction-to-plugin-prefix-mapping.html
Перескажу его вкратце.

Если вы указали при сборке фазу жизненного цикла, например, maven install, то определяются все необходимые шаги сборки и требуемые плагины как описано вот тут https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
А далее Maven ищет специальные файлы maven-metadata во всех подключенных к проекту репо, сливает их в один и ищет плагин по атрибуту prefix, который равен указаному вот тут как plugin https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#default-lifecycle-bindings-packaging-ejb-ejb3-jar-par-rar-war
Вот пример базового maven-metadata из Maven Central https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Как можно заметить, у большинства плагинов, поисковый префикс является частью artefactId. Это соглашение о наименовании, хотя и не обязательное. Т.е. всегда можно сделать свой maven-metadata-local.xml и привязать нужный плагин к нужной фазе там. Но я бы не рекомендовал так делать, т.к. это завязка на конкретный сборщик, которая хранится отдельно от кода вашего проекта. Также при создании плагина можно назвать его как угодно и указать к какой фазе сборки он привязан, может быть полезно, когда одним словом сложно выразить задачу плагина.

Если вы явно указываете цель для сборки, например, compiler:compile, то выполняется только эта цель, Maven пропускает разбор жизненного цикла сборки, сразу переходит к поиску нужного плагина в maven-metadata, в данном случае по префиксу compiler.

Вопрос - а как Maven определяет версию плагина? Есть еще один maven-metadata файлик с версиями у каждого плагина https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml

И как раз здесь лежит подводный камень: когда Maven-у понадобится конкретный плагин - он возьмет последнюю версию из тех, что найдет в подключенных к проекту репозиториях. А брать последнюю версию не всегда хорошо - там могут быть баги, или она просто может криво закачаться, если речь про внутренний прокси репозиторий.
Поэтому рекомендую начинать с минимальным POM, а перед выходом на ПРОМ фиксировать все версии плагинов.

Еще может возникнуть вопрос - а нельзя ли этот фокус провернуть с неофициальными плагинами? Ответ - можно, нужно лишь указать в settings.xml где еще искать maven-metadata.xml:
<pluginGroups>
<pluginGroup>org.codehaus.modello</pluginGroup>
</pluginGroups>

Еще момент - всегда можно добавить в pom конфигурацию плагина и там переопределить фазу сборки, на которой он запустится.
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>

В примере цель из плагина jacoco-maven-plugin привяза к фазе test.
Выше я описывал, как работает Maven в режиме convention over configuration.

Продолжение следует...

#maven #buildtool #conv_over_conf
Всем привет!

Продолжим про Maven.

2) Несмотря на то, что к каждой фазе жизненного цикла по умолчанию привязана одна цель (goal) из конкретного плагина, на каждую фазу можно повесить сколько угодно целей. Порядок выполнения - как указано в pom.xml. Это еще одна одна причина, почему полезно явно обьявлять плагины в pom.

3) Если у вас в settings.xml указано несколько репозиториев с артифактами, и поиск по всем не дал результата - будет ошибка, но с некорректным текстом. В ней будет сказано, что артифакта нет в первом по списку репозитории

4) Если какие-то из репозиториев в settings.xml требуют аутентификации, данные для аутентификации были указаны и протухли, то вы узнаете об этом только тогда, когда понадобится обновить библиотеку, которая есть только в этом репозитории. Ошибка с аутентификацией будет в логах каждой сборки, но сборку не ломает

5) Может возникнуть вопрос - а зачем Maven каждый раз ходит по удаленным репозиториям, если зависимости не менялись. Это поведение контролируется параметром репозитория updatePolicy в settings.xml:

<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
<releases>
<updatePolicy>always</updatePolicy>
</releases>
</repository>

Возможные значения: "always", "daily" (default), "interval:XXX" (in minutes) or "never".
Можно установить never, но есть два риска:
а) не будут подтягиваться новые snapshot,
б) не будут подтягиваться новые версии уже закачанных артифактов. А эту информацию индексирует IDEA и использует при AutoComplition

6) Если сохраненные локально зависимости были повреждены, то принудительно их перезагрузить можно через очистку локального репозитория, который находится в папке .m2. Командой mvn dependency:purge-local-repository, подробнее тут https://www.baeldung.com/maven-force-update или просто грохнув нужную папку в .m2

7) согласно уже упоминаемому ранее принципу convention over configuration по умолчанию в список репозиториев неявно включается Maven Central. Подробнее про settings.xml и его опции можно почитать тут https://habr.com/ru/post/339902/

8) список репозиториев можно задать как в pom.xml, так и в settings.xml. Предпочтительнее второй вариант, особенно при разработке библиотек, особенно внутренних. Причина: адрес репозитория может поменяться, а settings.xml проще обновить централизованно. Не говоря уже про библиотеки, которые придется перевыпускать. Конечно, если вы ходили по ссылке из предыдущего пункта - у нас есть возможность переопределить любой репозиторий из pom через зеркала\mirror в settings.xml. Причем при настройке зеркала можно не только указывать явно заменяемый репозиторий, но и использовать операторы * и ! Но это все же костыль)

#maven #buildtool #conv_over_conf
Всем привет!

Меня всегда поражала и даже не побоюсь этого слова восхищала гибкость развитых языков разработки, в частности Java.
Пример. Есть достаточно редкая проблема - как протестировать код, вызывающий System.exit().
У меня до сих пор такой необходимости не возникало, но понимаю кейсы, где такой код может быть.
Как думаете - можно протестировать? Конечно же да.
А сколько вариантов перехватить вызов?
...
Три!
https://www.baeldung.com/junit-system-exit
И это только технические решения, без учета рефакторинга кода с выделением в отдельный метод и созданием тестового двойника.
А рефакторить тоже можно по разному...

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

Прочитал еще одну книжку: "Распределенные системы. Паттерны проектирования" https://habr.com/ru/company/piter/blog/442514/
Главное, что хочу отметить: в книге никакого хардкора нет, читается легко.
Еще плюс - в конце многих глав есть простые примеры как развернуть реализацию паттерна в k8s. Можно и нужно экспериментировать)
Что характерно - автор участвовал в создании k8s, поэтому почти все примеры разворачиваются в k8s, даже Redis и Kafka.

В книге 3 части.

Паттерны уровня pod-а. Спойлер - речь про sidecar-ы.
sidecar-ы делятся по назначению:
1) добавление нового функционала к основному сервису - это собственно sidecar
2) прокси к шардированному сервису - Ambassador. Шардированному - это важно, в этом случае нельзя просто разбросать запросы по round robin, как может сделать Service из k8s
3) адаптация формата данных основного сервиса - Adapter.
Всегда думал, что сайдкар это просто сайдкар, а оказывается есть 3 вида)

Паттерны проектирования обслуживающих систем. Название странное, да)
Здесь сборная солянка из репликации, шардирования, алгоритма выбора лидера и Function As A Service (FaaS).
Репликация - это дублирование для отказоустойчивости и производительности, шардирование - создание отдельных экземпляров для обработки части данных, если весь набор не вмещается на одной машине. Можно совмещать оба подхода. Для это есть отдельный паттерн Scatter\Gather, распараллеливающий обработку "тяжелой" задачи на несколько серверов, после чего агрегирующий результат. Пример: поиск по большому объему текста. Есть хорошее замечание, что накладные расходы на распараллеливание растут с числом потоков и имеется предел, после которого ускорения не будет.
Хорошие примеры по каким критериям можно шардировать данные.
"На пальцах" описано выбор лидера на примере распределенных блокировок с помощью etcd. etcd к слову используется в k8s как хранилище состояния кластера.
Хорошо аргументировано когда полезен подход FaaS. Спойлер - когда запросов мало и для них дорого держать сервис постоянно работающим.

Третья часть - паттерны потоковой обработки. Потоковая обработка - это асинхронные часто многошаговые задачи, когда ответ в режиме онлайн не требуется. Сейчас продвигается их замена на стримингом на основе Kafka, Kafka Streams и Spark, который способен выдать ответ в режиме онлайн, но есть кейсы, когда это не нужно. Типичный пример потоковой задач: периодическое построение отчетов, периодическая выгрузка данных.
Обозреваемые паттерны: Copier (параллельная обработка с разными параметрами, например, рендеринг видео с разных разрешениях), Merger (объединение нескольких очередей в одну), Filter (отбрасывание части задач), Splitter (разделение потока в зависимости от настроек задачи: уведомление клиента по sms или по email), Sharder (аналог Map в Map\Reduce, параллельная обработка), Reduce (объединение с агрегацией), Join (объединение с ожиданием всех).

Маленькое замечание: паттерны из последней части применимы не только в облаке, но и при многопоточке в Java. Книжка про распределённые системы, поэтому в ней любая функция выносится в облако. Нужно ли так делать всегда - нет. См. https://t.me/javaKotlinDevOps/59 Ключевые моменты: разные команды, возможность переиспользования, сильно различающийся функционал.
Еще интересный момент - приводится пример использования Job-ов из k8s. Пример хороший, но важной замечание - со Spring-ом так делать не рекомендую, сервис, который запускается 1 минуту, работает 1 минуту и останавливается - не рационально.
Также отдельный большой вопрос - нужно ли все "пихать" в облако. Краткий ответ - нет. Да, облако дает возможность хранить данные: см. StatefullSet https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/. Да, облако, особенно c Service mesh, дает продвинутые возможности маршрутизации. Но часто репликация и шардирование данных настолько сложны, что облачные механизмы тут не помогут, а тогда возникает вопрос - а нужно ли в таких случаях облако вообще?

В целом книжку могу рекомендовать если есть свободное время и желание изучить примеры реализации распределенных систем.

#books
Небольшой оффтоп. Т.к. книжка простая, думал, что напишу пост за 10 минут. По факту неделю не мог собраться с мыслями) Точнее не мог для себя сформулировать, что же я из нее вынес. Книжка же не вода, в целом полезная. Сложно писать о простом)))
Всем привет!

Я уже писал, как логирование может влиять на производительность: https://t.me/javaKotlinDevOps/15
Соберу в одном посте несколько важных опций, касающихся быстродействия log4j. Казалось бы логгер, что там может быть сложного, но...

1) Пару слов про то, почему лучше объявлять логгер статической переменной https://logging.apache.org/log4j/2.x/manual/usage.html

2) Две оптимизации касающиеся шаблонов сообщений: строка с параметрами в виде {} вычисляется лениво если включен нужный уровень логирования. Плюс поддержка передачи параметров в виде лямбд позволяет лениво вычислить значение параметра. https://logging.apache.org/log4j/2.x/manual/api.html
Итого конструкции вида if (logger.isTraceEnabled()) становятся не нужны.

3) логирование может быть синхронным и асинхронным. Последнее очевидно быстрее, но у него есть минусы: в случае ошибок сообщения могут терятся без уведомления прикладного кода, под асинхронное логирование нужен отдельный поток. Детали https://logging.apache.org/log4j/2.x/manual/async.html Там же есть сравнение по производительности, и оно впечатляет.

4) Начиная с версии 2.6 log4j переиспользует временные объекты и т.об. уменьшает нагрузку на сборщик мусора: https://logging.apache.org/log4j/2.x/manual/garbagefree.html
По ссылке говорится об ограничениях garbage free режима и есть графики производительности.

5) Логирование в memory-mapped файлы - по сути это область в памяти, за синхронизацию которой на диск отвечает ОС. https://logging.apache.org/log4j/2.x/manual/appenders.html#MemoryMappedFileAppender

Ну и сравнение производительности с logback и разных Appenders https://logging.apache.org/log4j/2.x/performance.html

P.S. Бросается в глаза, что авторы библиотеки начиная с версии 2 заморочились с производительностью. Куча графиков, статей

#logging #log4j
Всем привет!

В разработке сейчас много хайповых понятий, те же микросервисы, Service Mesh, GitOps, Serverless. Обо всем этом как-нибудь напишу, а сегодня пару мыслей про Cloud Native.

Если подумать, то для того, чтобы сделать native приложение для облака, нужно не так уж много.

1) k8s должен как-то понимать, что приложение поднялось и может принимать траффик. Для этого облачный сервис должен реализовать probes, они же healthchecks.
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
Вообще их три, но в случае простейшего приложения хватит одной - liveness. Остальные две нужны если приложение долго стартует - startup или может быть недоступно в процессе работы - readyness.

2) облачное приложение не должно долго стартовать. Причина: k8s при добавлении новых нод или подов, или изменения их настроек Anti-affinity (возможности совместной работы на одной ноде) в любой момент может погасить под и поднять его на другом сервере. Да, можно указать startup probe чтобы траффик не шел на долго стартующее приложение. Можно указать maxUnavailable https://kubernetes.io/docs/tasks/run-application/configure-pdb/ для того, чтобы k8s оставлял запас подов при изменении их численности. Но все это обходные пути. Если вспомнить про Spring Boot, то я бы сказал что ему есть куда расти в плане оптимизации, не зря сейчас растет популярность его альтернатив, стартующих существенно быстрее: Quarkus, Micronaut, Helidon и использования native images.

3) cloud native приложение не должно зависеть от локального состояния - кэширования данных локально. Все критичные для работы данные должны хранится или в storage, или на клиенте. Причина все та же - k8s в любой момент может поднять под на другом сервере, локальный кэш при этом теряется.

4) для cloud native приложения крайне рекомендуется отбрасывать метрики и поддерживать distributed tracing - https://opentelemetry.io/docs/concepts/what-is-opentelemetry/. Причина: перенос в облако упрощает разработку, как правило идет рука об руку с микросервисами, следовательно, сервисов становится существенно больше, следовательно, должны быть инструменты для отслеживания их состояния и более точного понимания, где проблема.

Что на мой взгляд не относится к критичным требованиям для Cloud native приложений.

1) поддержка Docker. В Docker можно засунуть практически любое приложение, есть куча официальных образов. Даже IBM Websphere и WildFly, хотя использование их в облаке выглядит странным) Вижу проблему только с Windows native приложениями, но их остается не так уже много

2) поддержка внутри приложения cloud \ fault tolerance функций: circuit breakers, retries, timeouts, service discovery. Например, этот функционал реализуется в Spring Cloud библиотеке. Почему так? Потому что в связке k8s + service mesh все эти функции можно реализовать на уровне облака. Не хочу сказать, что Spring Cloud и его аналоги не нужны, но точно не обязательны для облака.

3) использование REST API. С одной стороны для HTTP траффика k8s + service mesh дает больше возможностей по маршрутизации, но с другой стороны tcp трафик тоже маршрутизируется.

#cloud_native #microservices #spring_boot #tracing
Всем привет!

Сегодня короткое ревью на книжку Непрерывное развитие API. https://habr.com/ru/company/piter/blog/472522/
Несмотря на многообещающее название - не понравилась.
Слишком менеджерская и "капитанская".
По технике практически ноль.
Приведу несколько идей, достаточно полезных, чтобы примерно было понятно, о чем речь:

1) рассматривайте API как продукт, есть компании, где API - единственный продукт: Twillo, Stripe
2) в первую версию API надо вкладывать больше усилий, потом можно ее непрерывно улучшать небольшими шагами, Lean, Agile, все как мы любим)
3) документация для API важна, особенно примеры использования, также для больших организаций полезен реестр API и инструменты для тестирования типа тех, что предоставляет Swagger https://editor.swagger.io/
4) тестируйте создаваемое API со стороны клиента, это поможет лучше его спроектировать
5) мониторьте боевые сервера API как на предмет ошибок, так и для сбора общей статистики по числу запросов, времени выполенния, типичным последовательностям запросов. Возможно постоянно повторяющиеся последовательности вызовов можно заменить одним.
6) с ростом организации централизовано реализовывать API невозможно, поэтому большУю часть решений нужно отдавать в команды. Централизовано имеет смысл отбирать список допустимых протоколов\технологий, и, возможно, согласование и тестирование API
7) ключевые решения по API должны примимать опытные разработчики
8) во внешнее API стоит вкладывать больше усилий, чем во внутреннее
9) автоматизация тестирования API требует больших затрат на разработку и поддержку как и любая автоматизация, но в целом полезна
10) API должно помогать достижению целей организации, можно использовать KPI и OKR для определения, достигает ли API поставленных целей. Вот на этом шаге и бросил читать)

Итого: верхнеуровнево есть правильные мысли, но с практическая ценность маленькая. Не рекомендую.

#books #api
2022-11-23_13-13-49.png
42.8 KB
Всем привет! Недавно IDEA подсказала мне интересную фичу - построение UML диаграмм для класса. Показывает родителей, интерфейсы и аннотации. IMHO полезная фича. На скрине пример из нашего CI pipeline. Комбинация клавиш Ctrl+Alt+Shift+U или ⌥ ⇧ ⌘ U Детали здесь https://www.jetbrains.com/help/idea/class-diagram.html?keymap=secondary_macos И снова советую не выключать или включить если выключена "подсказку дня": Help -> Tip of the day #IDEA