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

Я уже поднимал тему интеграции микросервисов через Java API - https://t.me/javaKotlinDevOps/208
Напомню, основная проблема там, что Java API - это по сути DTO-шки, пусть даже и выделенные в отдельную библиотеку. Поэтому легко начать считать частью своего кода, после чего забыть про их своевременное обновление и поймать ошибку уже на поздних этапах интеграции.

Что же тут можно сделать?

1) monorepo. Подходит в случае, если микросервисы разрабатываются одной командой или в организации принята практика монорепозитория. Да, конечно же нужно хорошее покрытие интеграционными тестами, запускаемое на prcheck. Если оно есть, то изменения подтянутся на очередном pull-е. И в любом случае изменения, ломающие обратную совместимость, требуют отдельной процедуры вливания. Но правила работы с такими изменениями должны быть прописаны в monorepo. Впрочем это справедливо для всех вариантов синхронизации.

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

3) подключение внешних зависимостей с version range для минорных версий. В Maven это выглядит вот так
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>[1.0, 2.0)</version>
</dependency>
Тут разрешается брать все версии между 1.0 <= x < 2.0
Просто убирать версию не стоит, т.к. изменение major - это как правило поломка обратной совместимости.
Детали тут https://maven.apache.org/pom.html#dependency-version-requirement-specification

Gradle поддерживает похожий синтаксис
implementation('org.slf4j:slf4j-api') {
version {
strictly '[1.0, 2.0['
}
Детали https://docs.gradle.org/current/userguide/single_versions.html

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

4) git submodule - возможность в родительский репозиторий подключать подмодули, подтягиваемые из других репозиториев. Все библиотеки с Java API подключаются как подмодули. Детали тут https://git-scm.com/book/ru/v2/Инструменты-Git-Подмодули
Позволяет вносить изменение в общее API всем командам, работающим с ним.
Но у данного способа есть ряд подводных камней.
Все обычные команды:
git clone
git pull
git push
git checkout
некорректно работают с подмодулями. Точнее их игнорируют.
При этом для каждой такой команды есть или "волшебный" ключик командной строки, подключающий поддержку подмодулей, или отдельная команда для подмодулей.
Плюс у git есть возможность задавать алиасы - т.е. на эти команды можно повестить алиасы, и вызвать, например, не git push, а git push_with_submodule. Или лучше git pushs. А для действий, требующих указания ключей, можно через git config включить эти ключи в режиме по умолчанию на вашей рабочей станции. Если все это настроить и не забывать применять алиасы - все будет работать как часы. Главное не забывать)))
Примерный набор команд:
git config submodule.recurse true # добавляет ключ --recurse-submodules ко всем командам кроме clone. В частности checkout и pull
git config diff.submodule log # отображать также изменения в подмодулях при выполнении diff в корне
git config alias.clones 'clone --recurse-submodules' # подтягивать подмодули при клонировании
git config alias.diffs '!'"git diff && git submodule foreach 'git diff'" # показать изменения по всем подмодулям
git config alias.pushs 'push --recurse-submodules=on-demand' # запушить изменения вначале в подмодулях, потом в основной проект
git config alias.updates 'submodule update --remote --merge' # подтянуть изменения подмодулей из удаленных репозиториев и влить их в локальную копию. После этого можно самому вносить изменения в подмодуль

В любом случае предлагаю прочитать статью по ссылке выше.

#git #api #integraion
Всем привет!

Интересная статья с опросом разработчиков https://habr.com/ru/articles/797045/

Что хотелось бы сказать по итогу прочтения.
Тему зарплат я обсуждать не готов, это слишком сложно)
То, что именно джуны склонны приукрашивать свое резюме - ожидаемо.
Процент девушек в ИТ - ну да, ну да, все грустно.
Тот, факт, что в ИТ проходят без профильного высшего образования (или образование было профильным, но преподавали не то) - тоже понятен. Зато с техническим, что тоже понятно.
Интересно то, что таких людей становится больше. Я честно говоря, думал наоборот. Вроде ИТ кафедр больше, чем лет 10-15-20 назад ...
Видимо работает тренд "Войти в ИТ".
Что еще интересно - в более 50% случаев люди, пришедшие после курсов, без опыта, сумели влиться в команду.
Ну и самое интересное - оценка полезности курсов.
Яндекс Практикум - ок.
Хекслет и Karpov.Courses - не слышал, надо бы глянуть.
По крайней мере теперь понятно, куда направлять тех, кто хочет войти в ИТ)

#learning #it
Всем привет!

Периодически на работе приходится сталкиваться с задачей подключения той или иной платформенной компоненты. Платформа у нас большая, компонент много.
С подключением обычно два вида сложностей:
1) изучить кукбук, подключить правильные зависимости и сделать правильные настройки) Зависимости - это очевидно для разработки, а вот настройки - далеко не всегда.
2) административка: завести заявку на подключение - в JIRA, Confluence или даже по почте. Вот это совсем не интуитивно, а ошибки, связанные с отсутствием заявки сложно отлаживать. Оправдывают такой процесс обычно так:
а) что новый потребитель - это дополнительная нагрузка, ее нужно запланировать.
б) требованиями безопасности.
в) просто считается, что это нормально
А хуже, если заявки придется делать на каждый используемый стенд.

Что в итоге? Мыши колются, но едят кактус)
А хочется найти решение, как бы это должно работать в "идеальном мире"... Например, вот так.

Настройки:
а) максимум настроек должны иметь приемлемые значения по умолчанию
б) там, где это невозможно - нужен генератор приложения из каркаса, который сгенерирует шаблон и файл fixme.md с инструкцией как заполнять. Там, где настройки лежат вне приложения - или положить в приложение, или тоже генератор.

Заявки: в идеале должен быть портал, на котором можно заказать железо и там же указать используемые компоненты платформы и требования к ним: прогнозное число RPS и\или размер хранилища для планирования нагрузки, данные сертификатов для интеграции и прочую информацию, требуемую поставщиком. Возникает вопрос - как быть с тем, что железо нужно для разных стендов - например, сохранять заказ для первого стенда и потом клонировать его для других стендов. Данные заказа попадают в команду платформенной компоненты, а далее они уже сами решают как их обрабатывать - или подключают автоматизацию, или по старинке - руками. Итого получаем систему, которая хранит архитектуру модуля и информацию по полученной инфраструктуре. Причем актуальную информацию, т.к. от нее зависят доступы. Информацию лучше загружать в виде текстовых файлов, все же Infrastructure as Code рулит)

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

А основная цель предлагаемых изменений - автоматизировать ручной неочевидный труд, и т.об. снизить число ошибок и время на отладку.

#it #infrastructure #arch #platform
Всем привет!

Если посмотреть на формат pom файлов Maven https://maven.apache.org/pom.html#POM_Reference, то можно увидеть там несколько свойств с метаданными проекта - organization, developers, contributors, scm, issueManagement... Плюс есть возможность получить информацию о дереве зависимостей проекта через команду mvn dependency:tree. Т.е. некоторую информацию о проекте собрать можно. Но собирать ее воедино придется руками. И это информация о проекте, но не о конкретной сборке этого проекта.
А что, если у нас другой сборщик? Или не Java проект? Или АС, включающая несколько компонентов? А можно ли сохранить информацию о найденных в проекте уязвимостях?
Для всего этого есть https://cyclonedx.org/use-cases/
СycloneDX позволяет сохранить информацию о проекте в объектной модели, которую можно сохранить в JSON, XML или Protocol Buffers. И кажется, что его разработчики решили дать возможность собрать всю возможную информацию:
1) состав проекта - библиотеки, приложения, веб-сервисы, драйверы, файлы... Есть средства группировки компонентов проекта.
2) информацию о разработчиках проекта - людях и компании
3) хэши для контроля целостности
4) информация о лицензиях
5) release notes, в т.ч. информация о commit, из которого собрана сборка, и фичах, вошедших в релиз
6) информация об уязвимостях, найденных в компонентах проекта, а также ссылка на патч, исправляющий уязвимость. Последнее нужно для того случая, когда накатить исправление невозможно из-за dependency hell. Информация об уязвимостях может быть получена как в привязке к зависимостям проекта, так и просто в виде списка.
7) дерево зависимостей
8) ссылки на сайты, чаты, почту...
9) любые произвольные метаданные в формате ключ-значение
И это только то, с чем я успел разобраться)

Есть интеграция с множеством сборщиков https://cyclonedx.org/tool-center/

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

#metadata #release_notes #maven #standardization
Всем привет!

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

P.S. Этот же аргумент говорит о важности код-ревью - как внешнего взгляда на ваш код

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

Хочу сравнить разные способы оценки задач в разработке.

1) «честная» трудоемкость. Честная я взял в кавычки не потому, что она не реалистичная/оптимистичная. А потому, что на самом деле она в большинстве случаев не честная, т.к. не устраивает риски и фокус-фактор. Пример рисков: нечеткая постановка задачи, сломавшаяся локальная сборка, сломавшийся pipeline, неработоспособность стендов, проблемы у смежника, болезнь. В теории даже про то, что часть спринта попадает в отпуск можно забыть) Пример не учета фокус-фактора - часть времени участника команды тратится на Agile церемонии, на отдых в рабочее время, менторство, код-ревью, помощь коллегам, семинары, митапы...
Собственно, по этим двум причинам чистая трудоёмкость совсем не равна календарному времени. И поэтому это самый плохой способ оценки. Из плюсов - только простота.

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

3) story points. О минусах этой методики писал ранее https://t.me/javaKotlinDevOps/104
Плюсы: учет особенностей команды (velocity), оценка непосредственно исполнителями, согласование внутри команды.
Хорошо работает для оценки story, кроме первого релиза, т.к. со временем у опытной команды story должны стать похожими. Также хорошо работает и для задач внутри спринта при выполнении 2 условий: команда fullstack разработчиков и участники примерно одного уровня. Ну или когда в команде для большинства участников выполняются эти условия. Иначе мы получим, что из 8 человек, оценивших задачу по бэк разработке, только 2 примерно понимают ее трудоёмкость) Или в команде из 1 сеньора и 4 джунов оценки будут сильно смещены к уровню джунов, и сеньор часть времени будет «курить бамбук»)

4) capacity planning.
Решает большинство проблем story points. Вводится коэффициент фокус-фактора, причём оценка даётся со стороны исполнителя. Вводится коэффициент «сеньорности» для учёта разницы в опыте. Команда делится на группы: бэк, фронт , аналитики, тестировщика, оценка идёт только внутри группы. Сразу видно, когда не хватает трудоёмкости конкретной группы, например, тестировщиков, для выполнения задачи «под ключ» за спринт. Т.к. оценка в человеко-часах - проще понять, где затык внутри спринта и больше личной ответственности. При этом все плюсы story points сохраняются.
Соответственно, хорошо подходит для оценки задач внутри спринта.

5) диаграммы Ганта.
Главный плюс - учёт взаимозависимостей задач и наглядное представление точки во времени, куда сходятся все задачи.
Главный минус - сложность и необходимость ПО. Сфера применения: макропланирование на несколько команд для сложных интеграционных задач.

#planning #agile
Всем привет!

Сегодня будет пост про паттерн Saga.
Saga - это способ осуществить распределённую транзакцию. Обычная транзакция осуществляется в рамках одной сущности, как правило базы данных. Распределённая - между несколькими. Проблема здесь в том, что для одной системы - реляционной БД или кластера Kafka - можно воспользоваться встроенным механизмом транзакций, для распределённой - нет.
В общем случае распределённые транзакции могут понадобиться и для операций между несколькими монолитными приложениями, но наиболее актуальны они стали при переходе на микросервисы, т.к. при этом расширились границы существующих бизнес транзакций.

Возможно кто-то слышал в применении к распределённым транзакциям и Java такие аббревиатуры как XA или JTA. Это стандарт Java EE (Jakarta EE) для осуществления распределённых транзакций между JDBC и JMS источниками. Существует давно, есть работающие реализации - https://samolisov.blogspot.com/2011/02/xa-jta-javase-spring-atomikos-2.html
Что же с ним не так, раз понадобился новый патерн?
1) т.к. в JTA появляется новая сущность - координатор транзакций - и сам процесс двухфазный - подготовка и фиксация транзакции - то это приводит к накладным расходам на сетевые вызовы и увеличению задержек (latency)
2) JTA стандарт ограничивает нас JRE совместимыми языками. Более того, не все поддерживают JDBC - см. noSQL хранилища - и JMS - см. Kafka. Причём последняя не стала добавлять поддержку JMS/JTA принципиально https://docs.confluent.io/platform/current/clients/kafka-jms-client/index.html
3) диспетчер транзакций - это ещё одна точка отказа. Пусть их и так много - для транзакции из 3 фаз это минимум 9 = (сервис + хранилище + сеть) х 3, и это не учитывая датацентры, СХД... И кажется, что добавление ещё одной сильно ситуацию не ухудшит. Но эта диспетчер - это централизованная (единая) точка отказа, при сбое диспетчера придётся повторять всю транзакцию с начала.

Но вернёмся к саге. Во-первых у нее есть 2 варианта реализации - оркестрация и хореография. Оркестрация - транзакция управляется из одного микросервиса, хореография - нет единой точки управления, просто идёт обмен сообщениями между микросервисами. Оркестрацией проще управлять и тестировать, хореография более надёжна, т.к. нет единой точки отказа.
Вот тут неплохое описание отличий https://learn.microsoft.com/ru-ru/azure/architecture/reference-architectures/saga/saga
Во-вторых: сага - это не стандарт или библиотека, это архитектурный патерн - реализацию нужно будет писать самому.

Суть саги - одну большую транзакцию мы делим на ряд локальных транзакций, в рамках которых обеспечивается строгая тразакционность. Плюс все локальные транзакции мы упорядочиваем таким образом, что вначале идут компенсируемые транзакции, а потом - повторяемые. Первые в случае сбоя мы компенсируем - т.е. откатываем, вторые - докатываем. Соответственно, в середине есть поворотная (pivot) локальная транзакция, после успешного выполнения которой все последующие транзакции мы обязаны докатить.

To be continued...

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

Продолжение про сагу.

Когда мы говорим про транзакции, сначала всплывает аббревиатура ACID. Транзакции должны обеспечивать принципы ACID. Посмотрим что тут у нас с сагой.
A - атомарность: или все выполняется, или все откатывается. Собственно атормарность есть в определении паттерна, см. выше. Единственное отличие - у нас нет волшебного rollback на всю распределённую транзакцию, бизнес логику отката придётся писать руками.
C - консистентность данных. Сага обеспечивает т.наз. eventually consistentcy - конечную согласованность. Т.е. данные будут согласованы только после окончания распределённой транзакции. В течение транзакции данные в разных микросервисах могут расходится. Транзакция в БД может обеспечить строгую согласованность изменяемых данных с нужным уровнем изоляции. Поэтому переходим к
I - изоляции изменений внутри транзакции от других операций. Сага не обеспечивает ее совсем, что с этим можно сделать описано в статье про это патерн от Microsoft по ссылке выше. Важный момент - в отличие от транзакции в БД, которая как правило длится миллисекунды, распределённая транзакция - это секунды, может даже десятки секунд. Несогласованность данных в течение этого времени из-за отсутствия изоляции нужно иметь в виду. В дополнение к описанным в статье по ссылке способом скажу ещё один - завершать транзакцию как можно быстрее и игнорировать несогласованность данных) Пример: клиент вряд ли будет жаловаться в службу поддержки, если после отмены заказа деньги и бонусы вернутся на счёт в течение минуты. И скорее всего будет - если это не будет сделано через час.
D - надёжность хранения данных, к саге отношения напрямую не имеет, обеспечивается используемыми хранилищами.

Т.к. в итоге мы получили ACD, причем неполноценный, то для распределенных транзакций придумали новую аббревиатуру - Basically Available, Soft-state, Eventually consistent - https://ru.m.wikipedia.org/wiki/Теорема_CAP#BASE-архитектура

Ещё один интересный момент про сагу: определение последовательности шагов - локальных транзакций. Единственно верной схемы нет, но есть рекомендации. Первая - fail fast. Т.е. если есть локальная транзакция, которая упадёт с большей вероятностью - ее нужно ставить вначале. Пример: резерв билета или товара. Вторая - если какая-то локальная транзакция проводит к критичной для клиента несогласованность данных - ее нужно выполнять как можно позже. Что делать, если эти рекомендации противоречат друг другу - зависит от сценария, но в целом я бы выбрал уменьшение времени неконсистентности.

Еще интересный момент касается саги в виде оркестрации. Т.к. ее главный плюс - сделать простой и понятной бизнес логику саги, то самая очевидная ее реализация вот такая:

class OrderSaga {
SagaResult execute() {
// шаг 1
// шаг 2
// ...
}
}

Назовём этот подход Transaction Script, есть такой Паттерн организации бизнес логики.
Просто - да. Но если процесс сложный, каждый шаг тоже, то мы ухудшим читаемость кода, получим замечание SonarQube про длину метода да и нарушим S из SOLID, принцип единой ответственности. Что делать? Использовать event driven подход:
class OrderSaga {
PrepareEvent start(...) {..}
ReserveEvent makeReservation(...) {...}
// ...
}
При необходимости обработку событий можно разнести в разные классы. Чтобы было понимание как работает процесс нужно написать пару модульных тестов - позитивный и негативные сценарии, ведь тесты в идеале - лучшая документация к коду. Ещё один плюс - в событийной стиле легко сделать весь процесс неблокирующим, например, через адаптер отправляя и принимая все события в Kafka. Да, есть ещё БД, запись в БД в эту парадигме - это такое же событие. В этом случае стоит посмотреть в сторону R2DBC https://www.baeldung.com/r2dbc Для REST endpoint и client есть Spring WebFlux.
К слову, Transaction script тоже может обеспечить неблокирующее выполнение, но только в языках программирования с async await: c#, python, rust https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await

To be continued...

#patterns #saga #microservices #acid #arch_compromises
Всем привет!

Этим постом завершается серия по паттерну Сага.

В предыдущем посте забыл упомянуть 3-й и 4-й способ реализации Саги.

Третий - если вы используете BPMN движок, например, Camunda, то он отлично подходит для оркестратора Саги. Более того, использовать BPMN как оркестратор - лучшая идея, чем использовать его как среду для low-code разработки. Ну не верю я в low-code, не сталкивался с работающими кейсами) Главные плюсы BPMN в данном - случае готовая state machine и визуализация Саги. К слову сама Camunda поставила этот use case на первое место в списке https://camunda.com/solutions/microservices-orchestration/ что как бы намекает. На всякий случай: Camunda - это самый распространенный BPMN движок, собственно движок - opensource, платить нужно только за UI консоль.

Аналогично - если вы уже используете Apache Camel - он тоже умеет в сагу, https://camel.apache.org/components/4.4.x/eips/saga-eip.html

Тут встает вопрос - стоит ли внедрять данные инструменты только ради Саги? Базовый ответ нет, идеальный кейс: если какой-то из этих компонент уже у вас используется - логично реализовать оркестрацию с его помощью. Я бы внедрял, если бы были какие-то еще плюсы от использования, кроме собственно реализации паттерна.

Еще важный момент при реализации оркестратора - stateless или statefull? Да, любая бизнес операция имеет как минимум ID и состояние, которые нужно хранить. Но необязательно это делать в классе Саги. Особенно используя event driven подход, можно просто передавать все не необходимые данные в событиях\командах. Напомню, при этом сохранение состояния операции в БД - это тоже событие. Плюс такого подхода - не нужно думать о букве D из ACID, т.е. персистентности, для данных, хранимых в оркестраторе. А где персистентность, там и кэширование, т.к. обращение к БД - дорого. И восстановление данных из БД при сбоях. Поэтому если вы все же решили хранить состояние операции в коде - я бы рекомендовал не изобретать велосипед, а воспользоваться готовым фреймворком. Два из них я уже упомянул выше, но они достаточно "тяжелые". Вот еще несколько, заточенных собственно под паттерн Сага и под DDD, который в общем-то тесно связан с сагой. Ведь если мы делим систему на ограниченный контексты, Bounded Context, то их данные лежат в разных БД, а следовательно возникает распределенная транзакция...

1) Axios https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas
2) Eventuate Tram Saga https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
3) Seata https://www.seata.io/docs/user/mode/saga


Фреймворк помогает нам с:
а) персистентностью
б) кэшированием
в) созданием экземпляра саги для конкретной бизнес-операции
г) удобной работой с параметрами операции

При этом он не отменяет написания кода оркестрации и компенсирующих действий.

На этом пожалуй все.
Хотя нет. Остается вопрос - как же лучше реализовать Сагу? Ответ - лучше сделать свой ограниченный контекст = микросервис таким, чтобы Сага была не нужна)
А если серьезно.
1) постарайтесь использовать только локальные транзакции
2) если это не возможно, и у вас 2-4 шага - используйте хореографию
3) если шагов от 4+ и сервис создаётся с нуля - используйте оркестратор, для начала самописный, stateless event driven
4) у вас уже используется Camunda или Camel - делайте оркестратор на их основе
5) если вас нужен state - используйте фреймворки из последнего списка, например, Axios
6) если нужна сага и state machine - Camunda или Seata

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

Есть такая фраза - все новое - это хорошо забытое старое. Еще есть тезис, что все развивается по спирали.
К чему это я?)

Если вы имеете отношение к миру Java - вы наверняка слышали про virtual threads. https://openjdk.org/jeps/425
Для начала немного теории. В ОС есть процессы - это запущенная программа или фоновый процесс. У каждого процесса может быть несколько потоков, которыми также управляет ОС. Поток занимает ресурсы - как минимум память, а при выполнении - еще и процессорное время. Поток может блокироваться - ожидать какого-то события для продолжения операции. Больше того - не ошибусь, если скажу, что в большинстве сервисов время блокировки >=50% общего времени выполнения потока.
При блокировке расходуется только память и емкость пула потоков, из которого этот поток был взят (процессорное время - нет). Особенно опасна блокировка тогда, когда она массовая - тогда пул быстро заканчивается, и все новые запросы к приложению отбиваются. Еще более опасна, если поток не из пула, например, он обслуживает UI или это основной поток выполнения программы - метод main, т.к. тогда блокируется приложение целиком.

Виртуальные потоки призваны снизить остроту этой проблемы. Как именно?
Виртуальный поток - это как бы обычный поток, вот пример его создания:

Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));

того же класса Thread https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html
Но если обычный поток мапится один к одному на поток ОС, то виртуальные управляются JVM и работают в рамках одного системного потока, т.наз. carrier потока. Выглядит это так: https://belief-driven-design.com/images/2023/2023-10-05-java-21-virtual-threads-scheduler.webp
К чему приводит такой подход?
1) виртуальные потоки более "дешевые" в плане памяти, т.к. управление идет на уровне JVM
2) при блокировке виртуального потока поток ОС не блокируется, может выполнять полезную работу, например, обрабатывать запросы новых пользователей
3) можно использовать обычные блокирующие конструкции языка, без CompletableFuture и Reactive кода, которые проще создавать и что наверное более важно - проще отлаживать.

Но вернусь к новому и старому)

В Java 1.1 появились Green Threads https://en.wikipedia.org/wiki/Green_thread
Что это за зверь такой? А примерно тоже самое - создаем несколько потоков на уровне JVM, все они средствами JVM мапятся на один поток ОС. Зачем? Был 1997 год, памяти было мало, создавать потоки было очень дорого. Потом память подешевела, проблема казалось бы ушла. В Java 1.3 green threads убрали.
Потом памяти стало еще больше, но число клиентских запросов и сложность кода выросли еще сильнее, чем память. И в 2021 году к идее вернулись.

P.S. Потоки, управляемые виртуальной машиной - стандартная штука для языков, у которых эта виртуальная машина есть. У них есть общее название - fibers. Пример - goroutines в Go.

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

Одна из моих любимых тем, вторая после читаемости кода, это скажем так "разработка - искусство компромиссов".
Редко встречаются ситуации, когда какой-то паттерн или принцип можно применять по формальным критериям всегда и везде. Более того, часто паттерны и принципы противоречат друг другу. Рассмотрим пример.

Есть такой паттерн, по моему опыту проведения интервью самый известный после синлетона - фабрика. А точнее абстрактная фабрика, именно такой паттерн описан в классике - Design Patterns: Elements of Reusable Object-Oriented Software. Суть его в том, что функция создания новых объектов выносится в отдельный класс. Создаваемые объекты являются наследниками какого-то базового класса или интерфейса. Классов фабрик может быть несколько, при этом каждая фабрика создает все (!) необходимые для проведения некой операции совместимые (!) между собой объекты. Вот более подробное описание с примером https://habr.com/ru/articles/465835/

Т.е. на первый взгляд паттерн работает четко в соответствии с принципом единственной ответственности (Single Responsibility) - создание отделено от доменного объекта, более того, для разных потребителей процесс создания объектов разнесен по разным фабрикам - ToyotaFactory и FordFactory из статьи выше.

А теперь изменим пример из статьи - будем создавать не разные типы кузовов автомобилей, а детали автомобиля. А деталей в авто подозреваю несколько тысяч... И список их более изменчивый, чем список типов кузовов. Т.е. по сути объединив в одном классе создание нескольких объектов мы уже заложили мину замедленного действия. Где находится грань между работой по Single Responsibility и его нарушением?

Базовый ответ был в первом абзаце - все зависит от бизнес-процесса. Но попробуем добавить конкретики.
Для начала можно вспомнить про лайфхак - можно оставить в фабрике один метод и передавать ему на вход Enum с типом создаваемого объекта.
class Factory {
SomeItem createSomeItem();
OtherItem createOtherItem();
}

vs

class Factory {
Item createItem(ItemType type);
}
Он немного упрощает добавление новых классов, т.к. не надо менять API фабрики, но в итоге приводит к тому же результату. Но дает нам подсказку: когда в Enum становится слишком много элементов - значит с фабрикой надо что-то делать.
Еще вариант - посмотреть, что скажет SonarQube. Он предлагает ограничиться 35 методами и 750 строками кода для одного класса. Как по мне - это много, я бы начинал делить фабрику на части раньше, при появлении 10-15 методов или по мере появления логических сущностей, позволяющих взять группу связанных методов из большой фабрики и вынести их в отдельную фабрику.

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

Немного не по теме блога, но выглядит впечатляюще - демо возможностей ChatGPT-4o https://www.youtube.com/live/DQacCB9tDaw?t=542s Который мультимодальный, понимает голос и анализирует видео с камеры смартфона в реальном времени, плюс может переводить текст на ходу.
В плане разработки - интересен пример с анализом куска кода на предмет того, что он делает, это прям отдельное направление. Не как картинка, код взят из буфера обмена, но это детали. Интересно, какого размера кусок кода он сможет проанализировать за раз?

P.S. Судя по отдельному спасибо NVidia в конце ролика - железо для этого дела нужно мощное.

P.P.S. ChatGPT-4o доступен бесплатно с ограничениями по числу сообщений в сутки с официального сайта при наличии Google аккаунта и VPN.

#ml #chatgpt
Всем привет!

Как LLM модели могут помочь разработчику? Накидаю варианты, которые видел и\или пробовал.

1) Самое очевидное - генерация больших кусков типового кода. Например, реализация алгоритма быстрой сортировки на языке Kotlin. Это пример вымышленный - не надо так делать на самом деле, наверняка уже есть подходящая библиотека. Еще пример - код инициализации RestTemplate без реактивщины с настройкой mTLS, таймаутов и обработкой ошибок. Существующие модели уже неплохо справляются с этим, но я вижу направление для развития - в больших компаниях со своими фреймворками\платформами доработка модели с использование DAG - локальной векторной БД с данными по используемому в компании ПО.

2) анализ существующего кода - что делает этот метод, этот класс, модуль, сервис. У большинства моделей пока здесь проблемы из-за ограничения по размеру подаваемого контекста, но ChatGPT-4o похоже эту проблему решает. Что не убирает требования к хорошей читаемости кода, конечно же))))

3) AutoCompetion кода в IDE. На этой поляне работают GitHub Copylot, IntelliJ и Sber GigaCode. Работают скажем так с переменным успехом. Здесь два рода проблем. Во-первых, контекст должен собираться автоматически плагином для IDE, а это нелегко - понять чего хочет разработчик. Да, есть имя класса, метода, переменных, уже написанный код, открытые в IDE файлы, печатаемый в данный момент код - но важно все эти ингредиенты правильно приготовить) Но даже если их приготовить - часть знаний все равно останется в голове разработчика. О второй проблеме уже писал - т.к. в данном кейсе код содержит больше генерируемого на лету, чем вытащенного из глубин модели, то велика вероятность мелких ошибок синтаксиса, см. https://t.me/javaKotlinDevOps/279 Задача сложная, но перспективная, т.к. набор встроенных AutoCompletion ограничен "фантазией" и размерами команды разработки IDE и, а главное - слабо учитывает контекст

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

5) генерация комментариев для commit и Pull Request - уже писал про это https://t.me/javaKotlinDevOps/252

6) краткий пересказ статей и книг - много пробовал, пока слабовато, т.к. контекст - что мне интересно в тексте - сложно извлечь из головы. Но опять же есть надежда на ChatGPT-4o и его последователей. И рекомендую по возможности не просто пользоваться кнопкой "пересказать", а задавать контекст явно. Перспективно, т.к. объем информации, необходимый для изучения разработчиком чтобы "быть в тренде" - очень высок. Но важное замечание - важные вещи я бы читал сам, чтобы не упустить детали.

7) автоматический анализ ошибок. В частности стектрейсов, но не ограничиваясь ими. Почему важно - по моим наблюдениям гораздо больше времени тратится на отладку ошибок, чем на собственно разработку. Кажется, что по stackoverflow модели уже неплохо работают, но как и с генерацией кода важно дообучение на ошибках, специфичных для конкретной компании. Еще одна область для развития - автоматический анализ логов, автоматическое создание инцидентов, выстраивание их в иерархию, автоматическое создание багов в трекере. И ещё одна - встраивание инструмента в IDE, переход на стройку с ошибкой из stack trace (уже есть в IDEA) и предложения по исправлению

Что я забыл в плане разработки?

#llm #ml
Всем привет!

Разработка ПО - очень динамичная сфера. Мэйнфреймы, ассемблер, CSV, RDBMS, C, Delphi, Java, REST, MQ, git, DevOps, Docker, k8s, Kafka, noSQL, microservices, reactive programming, DataLake, GitOps, ChatGPT...
Но есть вещи, которые не меняются. 1967 год, сформулирован закон Конвея - Любая организация, которая разрабатывает систему (в широком смысле), вынуждена создавать проекты, структуры которых являются копией структуры связей организации.
Причем если верить wiki, а в данном случае IMHO это можно делать, закон даже был доказан, видимо на исследовании реальных компаний.
Так вот, читаю сейчас одну интересную книгу про внедрение DDD - Domain Driven Development, 2022 года выпуска. В главе про внедрение вижу такой совет - начать с того, что определить бизнесовые поддомены в компании, на основании которых будут строится ограниченные контексты - одна из ключевых сущностей DDD. Как их проще всего определить? Рекомендуется посмотреть на структуру организации. Закон Конвея в DDD)

P.S. Интересно и то, что в 1967 году разработка как отрасль уже достигла уровня, позволяющего формулировать определенные принципы.

#ddd #dev_law #book_review
Всем привет!

Попробую немного развить тему с законом Конвея из предыдущего поста.
Я достаточно много раз за свою карьеру в разработке сталкивался с упоминанием данного закона. Уже не вспомню где конкретно, но у меня осталось стойкое впечатление, что отношение к нему было как к неизбежности, с которой нужно бороться. Способы борьбы можно вспомнить такие:
1) корпоративные архитекторы, выравнивающие архитектурные шаблоны
2) внутренние платформы, обязательные к использованию внутри компании и реализующие единообразно нефункциональные требования
3) техрадар как способ ограничить технологический стек
4) единые практики найма и онбординга
5) корпоративная модель данных - как антипод принципа DDD, когда существует некая общая для организации единственно верная доменная модель
...

Так вот - что мне нравится в парадигме DDD, что она говорит - не надо бороться, надо принять как данность, расслабиться и получать удовольствие от своего ограниченного контекста) Ремарка – речь про применение закона в проектировании ПО.

#ddd #dev_law
Всем привет!

Agile сейчас стал мейнстримом. Большинство из моих знакомых работают в Agile командах, почти все соискатели на собеседованиях либо работают по Agile, либо как минимум знают, что это такое.
И Agile, в частности SCRUM, как самая распространенная его реализация - это очень крутая и полезная методология.
Хороша она как следует из названия гибкостью - т.е. развитие продукта можно корректировать каждые две недели, а не раз в квартал или полгода. С учетом скорости изменений в современном мире - это killer feature.

Но как говорится есть нюанс.
У SCRUM (Agile) есть фундаментальный косяк. Изначально методология разрабатывалась под процессы одной команды. 7-10 человек. Так сказать сферическая команда в вакууме.
Команда тесно взаимодействует с бизнес-заказчиком, целиком отвечает за продукт, вовлечена в процесс и итеративно выдает результат. Предлагаю рассмотреть ответственность команды за продукт.
Когда команда одна - да, она за него 100% отвечает. А если команда - часть большой компании? Где продукт выводится в рамках какого-то большого мобильного приложения или сайта, т.е. канала. Канал, а точнее подразделение, за него отвечающее, предъявляет ряд требований к продуктам. У компании есть своя платформа, обязательная к применению. Плюс ряд таких же обязательных требований по архитектуре, надежности и кибербезопасности, выполнение которых контролируется. Единые требования по дизайну и текстовкам. В конце концов в компании множество источников данных, т.к. активно используются микросервисы. И при этом запрещено дублирование бизнес-критичного функционала, т.е. за доработками нужно идти к смежникам. Замечу, что в последнем требовании нет ничего плохого, та же DDD (надеюсь я пока не надоел вам с ней) требует того же.

Итог такой - команда не отвечает за продукт на 100%, т.к. у нее куча смежников, выставляющих свои требования. А значит ломаются многие базовые Agile принципы, например, выпускать релизы, доставляющие реальную бизнес-ценность, каждый спринт становится проблематично.
А что SCRUM - в своей исходной версии он этот вопрос никак не регламентирует.
Как в анекдоте - к пуговицам претензии есть?)
Да, есть другие фреймворки для масштабирования Agile - Scrum of Scrum, SAFe, LESS... Вот небольшое их сравнение https://vc.ru/hr/100292-freimvorki-masshtabirovaniya-agile-na-kompaniyu Еще есть Sbergile https://habr.com/ru/companies/sberbank/articles/547036/. Последний подсказывает нам, что в теории другие большие компании также могут заводить свои фреймворки. Отсюда главный минус такого подхода - получаем зоопарк фреймворков, которые в целом похожи - https://habr.com/ru/articles/726302/ - но естественно имеют и отличия.

Итого: главный минус SCRUM - он изначально создавался под условно "стартапы" из одной команды. При этом ИТ рынок формируют компании с тысячами и даже десятками тысяч разработчиков: Google, Microsoft, Amazon, Yandex, VK, Сбер.

#agile #scrum
Всем привет!

В продолжение темы Agile.

Как решение данной проблемы можно было бы предложить даже в большом enterprise работать как множество независимых Agile команд.
В теории это возможно если:
1) в организации мало требований и стандартов, команды имеют высокую степень автономности
2) существующие проверки автоматизированы, степень автоматизации стремится к 100%
3) процедура приемо-сдаточных испытаний автоматизирована или отсутствует
4) команды разрабатывают функционал End To End - от фронта до БД
5) сопровождение ПРОМ находится в команде, как вариант - разработчики отвечают за сопровождение
6) допускается, хотя бы временно, дублирование реализации, если команда, разрабатывающая целевой функционал, не готова доработаться вовремя

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

Для больших компаний, переходящих от водопада к Agile приходится придумывать что-то свое или искать подходящий фреймворк. Фреймворков много, какой лучше подходит - не понятно....

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

При проектировании системы применяя микросервисный подход всегда появляется главный вопрос - как делить?
Сделаешь слишком крупно - получишь маленький монолит. Это как правило всем понятно, т.к. от монолита мы пытаемся уйти создавая микросервисы.
Но есть и другая крайность - слишком мелкое деление. Уже немного писал об этом https://t.me/javaKotlinDevOps/57
Сейчас же хочу проиллюстрировать эту крайность примером.
Предположим у нас есть некая система, представляющая клиентам CRUD REST API. Create, Read, Update, Delete методы. И еще List, который сильно отличается от Read поэтому должен быть выделен отдельно - pagination, сортировка, кэширование...
Можно применить назовем его "наивный" подход к микросервисам и сделать 5 микросервисов по числу методов API. Точнее даже "миллисервисов")
Что получим?
Вспоминаем, что у каждого микросервиса должна быть своя БД.
Это значит что от микросервисов Create и Delete зависят все остальные, т.к. им нужно будет обновить свою копию данных. Это может быть event driven подход с Kafka, CQRS или что-то другое, но в любом случае это зависимость.
От микросервиса Update зависят Read и List.
А если структура данных меняется?
И это зависимости "из коробки" на сферическом CRUD в вакууме. В реальном кейсе по мере развития системы число зависимостей будет больше. Что получилось? Получился распределённый "ком грязи". Такой же "ком грязи", как в старом неподдерживаемом монолите, от которого мы уходили, только хуже. Там хоть БД одна была и интеграций сильно меньше.
Можно попробовать вынести все взаимодействие с БД в отдельный микросервис Storage, но тогда мы нарушаем Single Responsibility - за ту же операцию Create отвечает и микросервис Create, и микросервис Storage. И Create скорее всего станет слишком простым для отдельного микросервиса.
Пример специально взят простой, в реальности выбор может быть сложнее. Зато на этом примере хорошо видны недостатки "миллисервисов".

P.S. За идею примера спасибо все из той же книжке по DDD, расскажу о ней в ближайшее время.

#microservices #rest #arch_compromises