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

Нашел очередную статью про RAG https://habr.com/ru/companies/raft/articles/954158/
Казалось бы, нашел и нашел.
Но что интересно.
Вот есть AI. AI агент для примера.
Он общается с LLM.
LLM бывают разные.
Можно прикрутить AI proxy для совместимости по API.
А еще есть разные фреймворки для построения агентов.
Агент вызывает какие-то тулы.
Или MCP сервера по некому стандартизированному протоколу.
Или другого агента, для этого тоже есть стандартизированные протоколы.
Как источник домен-специфичных данных агент использует RAG.
RAG - это векторное хранилище, они бывают разные. Как специализированные, так и в виде расширения того же PotgreSQL.
Плюс есть технология преобразования данных (текстовых как правило, но не только) в векторный формат (embedding). Это тоже разные специализированные модели (но не те модели, что обрабатывают запрос пользователя).
Плюс есть технология разбиения данных на части (chunks), т.к. поиск по RAG - это сравнение векторов. Поэтому размер вектора для сравнения должен быть конечен, а документы в теории могут весить многие мегабайты.
Так вот. Даже для разбиения докумнетов на chunks уже появилось несколько конкурирующих библиотек, про это статья в начале поста.

О чем это говорит? Растет экосистема. Т.е. технология LLM потихоньку приходит к своей зрелости.

P.S. И поспорю сам с собой: за этот год по словам компетентных людей (я только учусь) сменилось три (!) принципа построения AI агентов.

#ai #rag
LLM как серебряная пуля?

Конечно же нет.
А если серьезно - что не умеет LLM?

1) выдавать актуальную информацию. Фиксится подключением веб-поиска

2) выдавать 100% точные ответы. LLM вероятностна по своей природе, поэтому даже самая мощная модель с огромным контекстным окном с выверенным промтом может галлюцинировать

3) отвечать на доменноспецифичные вопросы. Фиксится RAG, куда закачивается доменная специфика

4) выполнять сложные вычисления. Опять же LLM - статистический вычислитель, а не математический. Решается вынесением вычислений в тулы

5) работать с большими объёмами информации. Над этим - контекстное окно - работают все разработчики LLM, окно уже превышает 1 млн токенов, но в любом случае оно конечно. А ещё токены стоят денег. Альтернативныое решение - помещение больших документов в RAG, чтобы не гонять их туда сюда, или разбиение на части (chunks) перед взаимодействием с LLM.

6) формировать JSON. Ладно, если быть точным - плохо умеет. Как ни странно, уменьшение размера и упрощение шаблона ответа может ускорить его генерацию. structured output не спасает

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

8) понимать людей. Да, модель отвечает на любой вопрос. Ну почти любой, цензура как никак) Да, кому-то AI уже заменяет "кожаного" собеседника. Но в описанных случаях будет получен некий среднестатистический ответ, который должен порадовать спрашивающего. А если ваш вопрос уникальный и сложный, то промт должен быть непротиворечивым, компактным и структурированным. Так ли общаются люди? Да нет)))

9) изобретать, ставить задачи, мыслить. Опять же - LLM - это просто статистическая машина, обученная на большом объёме данных.

Так что кожаным мешками работа ещё найдётся.

P.S. и не только строителя дата-центров)

#ai
👍3
Бесконечный PostgreSQL

В SQL есть такая штука, как подзапросы.

Пример:
SELECT column1, column2
FROM table_name
WHERE column1 IN (
SELECT column1
FROM another_table
WHERE condition
);


Но конкретно в PostgreSQL у подзапросов есть целых 4 альтернативы:
1) CTE (Общее Табличное Выражение)
2) VIEW
3) MATERIALIZED VIEW
4) TEMPORARY TABLE

Все они отличаются от подзапросов тем, что позволяют переиспользование.
И практически все - кроме обычного VIEW - материализуются, т.е. сохраняют результат на диск.
Есть отличия во времени жизни. CTE живут в рамках запроса, временные таблицы - сессии, а VIEW - до удаления. Поэтому VIEW позволяют навешивать права доступа.

Детали как обычно в статье: https://habr.com/ru/articles/855694/

Также рекомендую почитать комменты к статье, там важные дополнения:
- CTE тоже могут материализоваться как и VIEW, но этим сложнее управлять. А наличие или отсутствие материализации играет роль если в выборке есть динамически вычисляемые столбцы - например, генерация uuid, текущая дата или просто random.
- про временные таблицы важно помнить, что они живут только в рамках текущей сессии (соединения). Поэтому при работе с пулом коннектов, т.е. практически всегда, ими пользоваться не стоит.

Ну и еще один важный момент - материализация = сохранение текущего состояния на диск. Данные обновляться не будут!

Меня удивило существование CTE. Хотя если поискать - они в том или ином виде существуют во всех основных СУБД. Даже в SQLite)

#rdbms #postgresql
Серия "хозяйке на заметку", а точнее разработчику библиотек на заметку.

Разработка библиотек отличается от разработки приложения тем, что публичные API в них живут намного дольше, и об этом надо помнить.
Любое публичное API, т.е. все public классы и методы, должно быть обратно совместимым как минимум в текущей мажорной версии.

Но жизнь как всегда сложнее. И что же у нас есть?

Java

1) объявить метод устаревшим с какой-то версии - @Deprecated.
Причем эту аннотацию можно не просто повесить на метод, у нее есть два поля: forRemoval и since, см. https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Deprecated.html

2) указать на что заменяем метод. @Deprecated не предоставляет такого механизма.
Но есть на свете добрые люди: https://errorprone.info/docs/inlineme
Как это может быть использовано:
а) статический анализ, собственно errorprone плагин: https://errorprone.info/docs/installation
б) миграция: https://docs.openrewrite.org/recipes/java/logging/log4j/inlinemethods

3) скрыть метод для потребителей не удаляя его из кодовой базы библиотеки. Т.е. оставляя его для тулов или для внутреннего использования. Java - снова нет( И даже добрые не помогли(

4) указать степень зрелости API. Стандартно - опять нет, но есть такая библиотечка @API Guardian https://github.com/apiguardian-team/apiguardian, позволяющая пометить API примерно так:

@API(status = STABLE, since = "1.0")
public class StableService {
@API(status = EXPERIMENTAL)
public void experimentalMethod() {
}

@API(status = DEPRECATED, since = "2.0")
public void deprecatedMethod() {
}

@API(status = INTERNAL)
public void internalMethod() {
}
}


Используется в JUnit, к слову.

Более простая альтернатива: @Beta из Guava, https://guava.dev/releases/23.4-jre/api/docs/com/google/common/annotations/Beta.html

5) потребовать у клиента явного подтверждения в коде, что он готов использовать бета-версию, а не просто warning компилятора - увы, нет.

Kotlin

1) @Deprecated
2) @Deprecated(replaceWith) https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-replace-with/
3) @Deprecated(level) https://www.baeldung.com/kotlin/deprecation
4-5) Механизм Opt-In: https://kotlinlang.org/docs/opt-in-requirements.html#opt-in-to-inherit-from-a-class-or-interface

// Library code
@RequiresOptIn(message = "This API is experimental. It could change in the future without notice.")
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class MyDateTime

@MyDateTime
// A class requiring opt-in
class DateProvider


// Client code
@OptIn(MyDateTime::class)

// Uses DateProvider
fun getDate(): Date {
val dateProvider: DateProvider
// ...
}

Все это, естественно, поддерживается в IDEA из коробки.
Плюс в Kotlin можно использовать @API Guardian

Получилась реклама Kotlin, что не удивительно, учитывая время его появления и назначение языка.

#api #java #kotlin
EXPLAIN не так прост, как кажется.

Многие - сужу по проводимым собесам - знают про EXPLAIN и план выполнения запроса.
Вопрос - насколько этот план, а точнее цифры в нем, отражают реальность? Например, время выполнения запроса.

Я бы ввел 4 уровня приближения к реальности:

1) EXPLAIN - ничего не выполняет на самом деле, вместо этого использует накопленную статистику. Следовательно, может ошибаться, если:
а) эвристики СУБД (пусть будет PostgreSQL) не подходят к запросу
б) статистика устарела.

Самый быстрый, практически мгновенный, с него лучше начинать.

Вывод:
[
{
"Plan": {
"Node Type": "Bitmap Heap Scan",
"Parallel Aware": false,
"Async Capable": false,
"Relation Name": "customer",
"Alias": "customer",
"Startup Cost": 12072.01,
"Total Cost": 59658.76,
"Plan Rows": 358140,
"Plan Width": 4,
"Recheck Cond": "(c_mktsegment = 'FURNITURE'::bpchar)",
"Plans": [
{
"Node Type": "Bitmap Index Scan",
"Parent Relationship": "Outer",
"Parallel Aware": false,
"Async Capable": false,
"Index Name": "idx_customer_mktsegment_acctbal",
"Startup Cost": 0.00,
"Total Cost": 11982.48,
"Plan Rows": 358140,
"Plan Width": 0,
"Index Cond": "(c_mktsegment = 'FURNITURE'::bpchar)"
}
]
}
}
]

Обратите внимание на строчки со Plan и Cost в названии: идет расчет по статистике.

2) EXPLAIN (ANALYZE) - выполняет запрос по настоящему, считает реальное время выполнения, число записей и использование памяти.

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

Вывод:
[
{
"Plan": {
"Node Type": "Bitmap Heap Scan",
"Parallel Aware": false,
"Async Capable": false,
"Relation Name": "customer",
"Alias": "customer",
"Actual Startup Time": 48.349,
"Actual Total Time": 184.486,
"Actual Rows": 359251,
"Actual Loops": 1,
"Recheck Cond": "(c_mktsegment = 'FURNITURE'::bpchar)",
"Rows Removed by Index Recheck": 0,
"Exact Heap Blocks": 43106,
"Lossy Heap Blocks": 0,
"Plans": [
{
"Node Type": "Bitmap Index Scan",
"Parent Relationship": "Outer",
"Parallel Aware": false,
"Async Capable": false,
"Index Name": "idx_customer_mktsegment_acctbal",
"Actual Startup Time": 39.360,
"Actual Total Time": 39.362,
"Actual Rows": 359251,
"Actual Loops": 1,
"Index Cond": "(c_mktsegment = 'FURNITURE'::bpchar)"
}
]
}
]


Появляются параметры со Actual в названии - это реальные показатели, поэтому то же время может меняться от вызова к вызову т.к. работает кэширование и меняется нагрузка на сервере.
Набор возвращаемых значений настраивается доп. параметрами.
В примере выше на самом деле использовался EXPLAIN (ANALYZE, TIMING), можно еще смотреть расход памяти, стоимость запроса в "попугаях", использование кэша, ...
3) EXPLAIN (ANALYZE, SERIALIZE, TIMING) - дополнительно появляется шаг преобразования данных на сервере, как это происходит при реальном запросе.

Дает еще более точный прогноз времени выполнения, но это все еще не реальное время, и дальше станет ясно почему.
Вывод:
[
{
"Plan": {
"Node Type": "Bitmap Heap Scan",
"Parallel Aware": false,
"Async Capable": false,
"Relation Name": "customer",
"Alias": "customer",
"Actual Rows": 359251,
"Actual Loops": 1,
"Recheck Cond": "(c_mktsegment = 'FURNITURE'::bpchar)",
"Rows Removed by Index Recheck": 0,
"Exact Heap Blocks": 43106,
"Lossy Heap Blocks": 0,
"Plans": [
{
"Node Type": "Bitmap Index Scan",
"Parent Relationship": "Outer",
"Parallel Aware": false,
"Async Capable": false,
"Index Name": "idx_customer_mktsegment_acctbal",
"Actual Rows": 359251,
"Actual Loops": 1,
"Index Cond": "(c_mktsegment = 'FURNITURE'::bpchar)"
}
]
},
"Serialization": {
"Time": 35.041,
"Output Volume": 4345,
"Format": "text"
}
}
]

Появился отдельный блок Serialization.

4) собственно выполнение оригинального запроса с клиента. Дополнительно включает в себя сетевую задержку между клиентом и сервером и преобразования на клиенте.
Перед выполнением лучше запустить count(*) для оценки масштаба бедствия)

#rdbmc #postgresql #troubleshooting
ООП и БД

Какая связь между двумя данными понятиями? Ответ вроде бы очевиден - ORM, JPA, Hibernate и все такое.
Либо если пойти другим путем - MyBatis или JOOQ.

Но на самом деле элементы ООП есть в БД. Я про наследование таблиц https://postgrespro.ru/docs/postgresql/current/ddl-inherit

Надо ли оно?
Прям моделировать объекты в БД в качестве базовой практики - нет, не понимаю какие у этого плюсы. А сложность добавляется.
Но можно посмотреть на наследование с другой стороны.

CREATE TABLE localities (
name text,
population float
);

CREATE TABLE cities (
) INHERITS (cities);

CREATE TABLE villages (
) INHERITS (cities);


Что получилось в такой конфигурации? Это аналог VIEW, собирающей данные из двух таблиц - cities и villages.
Если посмотреть с другой стороны - аналог таблицы localities с двумя партициями: cities и villages.

Из плюсов такой конфигурации: возможность менять структуру таблиц в наследниках. Добавлять поля или индексы.

Из минусов по сравнению с партициями:
а) работу с партициями PostgreSQL оптимизирует лучше
б) партиции лучше подходят для разделения по "бесконечным" данным, самый понятный пример - время.

Из минусов по сравнению с VIEW - VIEW легче за счет своей виртуальности (не MATERIALIZED).

Важная особенность использования наследования в таком режиме - в большинстве случаев вставку в родителя нужно запрещать, т.к. логически это будет выглядеть странно.
Населенный пункт является городом и деревней в нашем случае.
Как это сделать?
ALTER TABLE localities ADD CONSTRAINT localities_insert_lock CHECK (false) NO INHERIT;



В общем - использовать соблюдая меры предосторожности!) Т.е. четко понимая зачем.

#db #postgresql
1
PostgreSQL и кэш

Речь не про кэширование JPA, а про собственное кэширование БД.

1) на сервере есть 2 уровня кэширования: PostgreSQL buffer cache и Linux page cache. В отличие от Kafka, где он один.
А если использовать кэширование на уровне JPA, в итоге получается 4 уровня)

2) по умолчанию кэша выделяется мало, для PROD like его размер нужно увеличить.
Посмотреть сколько:
SHOW shared_buffers


Изменить:
ALTER SYSTEM SET shared_buffers TO <your_value>; -- при наличии прав


или через /etc/postgresql/../postgresql.conf
c рестартом сервера.

Универсальных рекомендаций по размеру быть не может, но можно начать с 1/4 ОЗУ.

3) размером Linux page cache напрямую управлять нельзя. Но его максимальный размер можно ограничить косвенно, через
sysctl vm.swappiness

Да, это процент использования swap файла, но как побочный эффект он ограничивает использование ОЗУ под page cache, и оставшееся пространство можно выделить PostgreSQL.

Зная эти два параметра можно рассчитать максимальное значение для кэша PostgreSQL:

shared_buffers ~= ОЗУ * vm.swappiness - резерв ОС - резерв PostgreSQL


4) со значением shared_buffers нужно быть осторожным, также как и с настройкой heap space в JVM. PostgreSQL ожидает, что вся эта память будет ему доступна.
Если ОЗУ закончится, а процесс будет все равно пытаться запрашивать память, придет злой OOM Killer и убьет его).

5) есть расширение pg_buffercache, предоставляющее одноименное view с информацией о страницах кэша PostgreSQL: к какой таблице относится, как используется.
Перед его использованием нужно само собой установить:
CREATE EXTENSION pg_buffercache;

перегрузить сервер и дать права:
GRANT pg_monitor TO xxx;


Использование:
SELECT count(*)
FROM pg_buffercache
WHERE relfilenode = pg_relation_filenode('table'::regclass);


6) при старте PostgreSQL все кэши пустые.
Интересно, что "SELECT *" не заполняет кэш на 100%, т.к. используется более хитрый механизм, когда для каждой выполняемой операции выделяется часть кэша.
А вот изменение данных, которое тоже идет через буфер, точно кэширует все изменяемые данные.

7) изменение данных - плохой способ заполнить кэш.
А часто прогреть кэш нужно, как минимум для справочных данных.
Да вообще говоря и для любых данных, т.к. если удастся поместить всю БД в кэш, то это ускорит работу с БД на пару порядков (ОЗУ vs диски).
Ну и если быть точным - ускорит серверную часть процесса, см. https://t.me/javaKotlinDevOps/487

И ... для прогрева кэша тоже есть расширение:

CREATE EXTENSION pg_prewarm;
ALTER SYSTEM SET shared_preload_libraries = 'pg_prewarm';

и рестарт.

Использование:
SELECT pg_prewarm('table');


8) PostgreSQL сам чистит кэш в фоновом режиме, а явно сбросить кэш можно только перезагрузкой сервера. Что-то мне это напоминает из мира Java)

#cache #postgresql #performance
Уровни изоляции БД.

Вопрос с собесов - расскажите про уровни изоляции БД. Их как известно четыре:
1) READ UNCOMMITED
2) READ COMMITTED
3) REPEATABLE READ
4) SERIALIZABLE
Вроде известные факты, ничего интересного.

Но интересно то, что это все теория, а на практике READ UNCOMMITED не реализован в большом числе СУБД.  Если брать большую четвёрку - Oracle и PostgreSQL не реализуют, MSSQL и MySQL - реализуют.

Вопрос - почему так?

А потому что сейчас он не особо нужен.
READ UNCOMMITED - это грязные чтения. Зачем видеть чужие незафиксированные данные? Да не зачем.

Окей, тогда может быть скорость - отсутствие блокировок?

Если говорить про чтение - снова мимо. Сейчас почти все СУБД реализуют механизм снимков - MVCC (Multiversion Concurrency Control), детальнее https://aristov.tech/blog/mvcc-v-postgresql/?ysclid=mi26mzeuj6767354782 - для обеспечения изоляции и транзакционности. А в этом случае при чтении выбирается нужная версия данных по определённому алгоритму без всяких блокировок.

Запись. Конкурентно записывать не дожидаясь фиксации более ранних изменений - мусор на выходе.
Единственный кейс - если клиент может гарантировать отсутствие конкуренции. А это можно сделать, если клиент один. В общем кейс редкий.

Подозреваю, READ UNCOMMITED - наследие времени, когда MVCC не было.

#DB #PostgreSQL
👍1
Разработчики AI переизобрели CSV

А теперь серьезно)
Я уже писал, что LLM общаются с помощью JSON и обработка JSON - не то, с чем LLM хорошо работает: https://t.me/javaKotlinDevOps/484
Поэтому появился TOON.
Почему не YAML или что-то еще?
Данный формат заточен под компактность и удобство обработки LLM. По сути это CSV с метаданными.
Чтобы не быть голословным - пример:

JSON
{ "users": [ { "id": 1, "name": "Alice", "role": "admin" }, { "id": 2, "name": "Bob", "role": "user" } ] }  


TOON
users{id,name,role}: 1,Alice,admin 2,Bob,user


Разница видна невооруженным глазом. Разные тесты показывают выигрыш по размеру на 20-60%, см. https://habr.com/ru/news/966734/
Но есть нюанс - по сути у нас таблица, и максимальная выгода получается на табличных данных. На вложенных структурах - сильно меньше.
Плюс улучшается точность работы модели, но уже не так сильно - процентов на 5.
С другой стороны модели в плане точности ответа уже дошли до такого уровня, когда любые проценты важны.

Другой важный момент - мир AI становится все ближе к обычному ИТ. Примеры:
1) TOON как оптимизированный протокол. Не gRPC, но движение в том же направлении.
2) все актуальнее в связи с нехваткой железа в датацентрах становится кэширование - как в рамках сессии, так и долгосрочное. А это тянет за собой TTL, инвалидацию кэша...
3) structured output - https://t.me/javaKotlinDevOps/473 - это тоже шаг к традиционным программам
4) RAG как некий аналог БД микросервиса

Что дальше?
Многопоточность? Полноценная БД? Транзакции? Очереди?

#ai #llm
ISR

Или Interface Segregation Principle из SOLID. По большому счёту частный случай Single Responsibility. Легко заметить нарушение - большое число методов с TODO или UnsupportedOperationException в одном классе. Или любое количество, но живущих там в течение долгого времени.

А как его не нарушать?

Самый простой способ - интерфейс из одного метода. Он же SAM - Single Abstraction Method. И лямбдах можно использовать.

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

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

Я вернулся)

Вернуться заставило вот это видео Егора
Бугаенко https://vkvideo.ru/playlist/3430647_-1001/video-226887147_456239441

Рекомендую. Егор делает то, что у него лучше всего получается) После просмотра возникает один вопрос - так, а что с AI?)

P.S. Ещё из интересного в докладе - linter для shell скриптов https://www.shellcheck.net/# С хорошим online редактором, что важно, т.к. не всегда есть пайплайн, куда можно его включить.
А из грустного - ArchUnit, о котором я хотел написать, все ещё сыроват. Но все равно напишу про него и Spring Modulith.

P.P.S. Надо было в конце доклада майку разыграть. С одной известной надписью-мемом)

#ai #linter #xunit
👍3
Заметка про стандартизацию AI.

Вот и кандидат на стандарт для хранения контекста диалога подъехал https://habr.com/ru/companies/bothub/news/972054 Не факт, что именно эта технология, см. Китай и Гонконг, но сам принцип вполне может стать стандартом.

P.S. Не понимаю, правда, почему его с RAG сравнивают. RAG для хранения доменноспецифичных данных

#ai
(java || kotlin) && devOps
Новая LTS Java. Я о Java 25. Вышла не вчера, поэтому также вышла и хорошая статья с обзором нововведений https://habr.com/ru/companies/T1Holding/articles/946778/. Там даже табличка включения новых фич от 21 до 25 версии есть. И примеры кода - было\стало.…
Небольшой комментарий про quantum resistant фичу в новой Java.

Да, кажется что ещё рано беспокоится.

Но кто мешает задампить что-то зашифрованное сейчас, и взломать при появлении работающего квантового компьютера?

Ещё момент - AI был известен в узких кругах давно, а для широкой публики возник внезапно только при года назад, когда появились более менее работающие LLM

#ai #security
Зачем нужны MCP сервера?

Как я уже говорил: основная проблема AI в разработке - результат работы AI по определению не детерминирован, а код должен решать какую-то определенную задачу с строгой бизнес-логикой.
А что, если не придумывать код на ходу - а это по сути восстановление информации после ее сжатия с потерями - а просто понять, какой именно код нужен и взять готовый пример.
Эту проблемы может решить MCP сервер, в который загрузили официальную документацию с примерами.
Например, https://context7.com/?q=spring
Идея кажется отличной, но есть пару вопросов:
1) по приведенной выше ссылке - какой источник для Spring мне выбрать? Где больше токенов? Или более новый? Или по всем искать? (но это же долго)
2) как понять, что информацию загрузили правильно - всю, без искажений? к какой именно версии относится? у каждого источника есть Benchmark, но как и кто мерит - не ясно
3) кто отвечает за загрузку? предполагается, что это делают авторы библиотек https://context7.com/docs/adding-libraries , но по факту это не так. будет ли загрузка по данному источнику и дальше работать? если это энтузиасты - большой вопрос... одно дело быть автором open-source библиотеки, другое - безымянным автором источника данных в context7

Вообще идея крутая - зачем восстанавливать информацию или парсить интернет, когда можно подсунуть LLM точный источник. Ее бы стандартизировать (что будет с context7 через год? хорошо, если он станет Docker в своей области, а если нет?), популяризовать и прикрутить верификацию авторов - вообще идеально бы было. Сделал jar - добавь source jar, javadoc jar и mcp сервер.

#mcp #ai
null safety в Java - счастье на горизонте?)

Я уже писал про проблему null safety в Java, особенно ярко видимую на фоне Kotlin.
https://t.me/javaKotlinDevOps/98
В посте по ссылке выше разработчики Kotlin собрали поддерживаемые ими виды аннотаций а-ля @NotNull https://kotlinlang.org/docs/java-interop.html#nullability-annotations
И их число говорит о многом, а точнее о состоянии разброда и шатания в Java мире.

Так вот - похоже в войне Nullable аннотаций наметился победитель, и это JSpecify https://jspecify.dev/docs/user-guide/.
С одной стороны это очередная внешняя библиотека:

dependencies {
implementation 'org.jspecify:jspecify:1.0.0'
....
}


Которую поддерживает IDEA при поиске проблем. Но она и другие библиотеки поддерживает.

Что же изменилось?

А то, что собрался ряд достаточно известных компаний: Google, Oracle, JetBrains, Uber, VMware/Broadcom (а значит и Spring), и они стандартизировали именно JSpecify.
Что это значит, ряд примеров:
1) Spring переводит свой фреймворк на JSpecify к Spring 7\Spring Boot 4
2) про JetBrains и IDEA c Kotlin я уже сказал
3) Google внедряет новые аннотации в Guava. И наверняка куда-то еще)
...

Две основные фишки нового подхода:
1) Uber доработала плагин компилятора NullAway на основе errorprone:

tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableAllChecks = true // Other error prone checks are disabled
option("NullAway:OnlyNullMarked", "true") // Enable nullness checks only in null-marked code
error("NullAway") // bump checks from warnings (default) to errors
option("NullAway:JSpecifyMode", "true") // https://github.com/uber/NullAway/wiki/JSpecify-Support
}
}


и warning в IDEA легким движением руки превращается в error компиляции.
Работает в JDK 17,21 и 22+ https://bugs.openjdk.org/browse/JDK-8225377
Аналогично можно сделать в Maven.

2) Другое важное изменение - возможность задавать значение null safety по умолчанию для пакета, модуля или класса.

@NullMarked
package org.example;

@NullMarked
class MyClass {


Эти объявления означают, что все поля и переменные в классе или пакете должны быть not null.
А если null значение все же нужно - нужно явно пометить его аннотацией @Null

Чтобы заменить старые аннотации на новые есть правила OpenRewrite https://docs.openrewrite.org/recipes/java/jspecify/jspecifybestpractices

Пару ложек дегтя:
1) Oracle в JDK пока аннотации не внедряет. Зато JDK пропатчила начиная с 17-й, чтобы валидация на этапе компиляции заработала.
2) Hibernate присматривается https://github.com/hibernate/hibernate-orm/discussions/6220.

Что я могу сказать в итоге - удачи, дело нужное!

А в прикладе внедрять уже можно. Ситуация НЕ похожа на известную шутку: было 10 разных стандартов, люди решили это изменить и их стало одиннадцать)
Проблема есть, и массовому внедрению ее решения мешал по большому счету тот факт, что единого решения не было.

#null_safety #java
🔥2
Версионирование для REST в Java - оно как бы есть, и его как бы нет)

Основная проблема с версионированием - которую многие, в т.ч. и я, не замечают - следующая.
Версионирование нужно и его почти везде используют, но при этом Spring, который также везде используют, не делает ничего для его поддержки.
Точнее не делал.
Начиная со Spring 7, который уже вышел, данный функционал наконец таки появился: https://habr.com/ru/companies/spring_aio/articles/967454/

Что добавили:
1) определение способа передачи версии:

# Path segment versioning (e.g., /api/v1/users)
spring.mvc.apiversion.use.path-segment=1

# Request header versioning (e.g., X-API-Version: 1.0)
spring.mvc.apiversion.use.header=X-API-Version

# Query parameter versioning (e.g., ?version=1.0)
spring.mvc.apiversion.use.query-parameter=version

# Media type parameter versioning (e.g., Accept: application/json;version=1.0)
spring.mvc.apiversion.use.media-type-parameter[application/json]=version


2) указание версию по умолчанию:

# Basic versioning configuration
spring.mvc.apiversion.default=1.0


3) указание списка поддерживаемых версий и, соответственно, их валидацию:

spring.mvc.apiversion.supported=1.0,2.0


или

# автоматическое детектирование по содержимому контроллеров
spring.mvc.apiversion.detect-supported = true


Естественно, все это можно сделать через код.

4) Стандартный парсер версий - по стандарту семантического версионирования

5) Само собой есть возможность определить нестандартный механизм передачи и формата версий через создание ApiVersionResolver и ApiVersionParser

6) Есть даже ApiVersionDeprecationHandler - стандартизация уведомления клиента об устаревших версиях и автоматическая 400-ка для неподдерживаемых

7) И конечно механизм маршрутизации по версиям, который автоматически разрешает вот такую конструкцию:

public class AccountController {
@GetMapping
public Account getAccount() {
}

@GetMapping(version = "1.1")
public Account getAccount1_1() {
}

@GetMapping(version = "1.2+")
public Account getAccount1_2() {
}

@GetMapping(version = "1.5")
public Account getAccount1_5() {
}
}


8) аналогично для endpoint в функциональном стиле:

RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", version("1.2"),
request -> ServerResponse.ok().body("Hello World")).build();


9) плюс все поддерживается для reactive stack

10) и на клиенте (для тестовых клиентов тоже):

RestClient client = RestClient.builder()
.baseUrl("http://localhost:8080")
.apiVersionInserter(ApiVersionInserter.useHeader("API-Version"))
.build();
...
Account account = client.get().uri("/accounts/1")
.apiVersion(1.1)
.retrieve()
.body(Account.class);


Еще одна важная фича, которая должна была появиться раньше.

#spring #versioning
Зоопарк консолей в Windows.

Пишу на примере Windows 11, но в 10-ке начиная с определенного билда все также.

1) Стандартная консоль Windows - теперь их стало две:
а) это связано с тем, что в Windows появился т.наз Windows Terminal. Это мультиоконная консоль, и при вызове cmd.exe вызывается именно именно. Но т.к. она мультиоконная - там же можно открыть любую другую консоль. Все в IDEA слизали)
б) а старая добрая однооконная консоль вызывается через conhost.exe. Зачем может пригодится старая консоль? Microsoft любят ломать совместимость, и новая консоль к примеру изменила API в плане скрытия окна консоли при старте программы.

2) Powershell - тоже использует Windows Terminal, и их стало даже три:
a) powershell.exe - это старая добрая синяя консоль (цвет значка остался синий, консоль по умолчанию черная), версия 5.х
б) pwsh.exe - новая черная консоль (я снова про цвет значка), версия 7.x. На данный момент ставится только руками. Ключевое замеченное мной отличие - есть API по считыванию данных в консоли, что удобно при работе AI агента, не надо выводить данные в лог.
в) conhost.exe powershell.exe - это способ запустить powershell в старом однооконном режиме

3) Git Bash - очень удобная штука, получаем консоль Linux с большим количеством утилит. Увы, не полный набор, а главное не хватает pacman - менеджера пакетов, который в свою очередь даст поставить все остальное. Еще важная особенность - там используются linux слэши и набор переменных среды хотя и наследуется, но отличается от Windows.

4) если утилит не хватает - можно поставить MSYS. Git Bash = урезанная версия MSYS без pacman + mingw64 (С-шный комполятор gcc в Windows)

5) еще можно поставить WSL - Windows Subsystem for Linux. Поверх нее работает Docker Desktop, а это значит не можно, а нужно ставить). В этом случае появляется еще консоль wsl.exe

В итоге у меня это выглядит как на картинке.
Но в целом получаем рабочую среду)

#win #dev
🤯21
Отличие сеньора от джуна

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

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

#memes #dev
👍3
Концепция venv (virtualenv) в Python

Концепция крутая, как по мне ноутбуки и виртуальные окружения - две самые крутые фичи в Python.

Если вкратце о ее сути: ты создаешь для каждого проекта отдельную виртуальную среду, со своим интерпретатором Python (разные версии) и своим набором библиотек.
Это удобно, и решает проблему конфликта зависимостей. Небольшое уточнение: если бы все сервисы и библиотеки явно указывали версии зависимостей - конфликтов бы не было, но мы же говорим о реальном мире.
К слову, концепцию позаимствовала и Java, я про jenv - https://t.me/javaKotlinDevOps/442 - правда, ограничить можно только версию Java.

Но блин.
Почему утилит, реализующих виртуальные окружения столько:
- venv
- virtualenv
- uv
- Poetry
- PDM
- Hatch
- Rye
- Conda
- Mamba
- Micromamba
- Pixi
- Pipenv
- pyenv-virtualenv
- virtualenvwrapper
- Tox
- Nox
- Devbox
- Flox
- Devenv.sh
- Spack
- Vex
????

Явная иллюстрация анекдота про 10 стандартов)

Ясно, что они не идентичны по функционалу.
1) кто-то добавляет возможность менеджера пакетов (и это правильно),
2) кто-то позволяет формировать список зависимостей проекта requirements.txt (и это тоже правильно),
3) кто-то добавляет возможность делать lock зависимостей (спорная фича IMHO).
Кто-то просто устарел. Кто-то заточен для тестов, где нужна куча разных сред. Кто-то просто добавляет небольшие фишки в другой менеджер, типа убирает необходимость явно включать использование виртуального окружения в консоли (activate).
Но все же...
Причем 4 из них имеют официальный статус)

P.S. Самой крутой и современной считается uv. На данный момент.

#python #java #virtual_env