Читать в телеге. Когда-то там были посты не только от меня.
Отличие команд и событий
Во всяких DDD и CQRS очень часто возникают команды и события. Объединяет их обычно то, что они обычно пересекают границы домена или контекстов, обрабатываются асинхронно и представляют собой (возможно устаревшие) ошметки информации о системе. На ранних стадиях проекта очень легко попасть в ловушку и реализовать их практически одинаково с технической точки зрения (а то и вовсе не разделять). Однако между ними на самом деле довольно много различий.
Семантическое. Команда и событие выражают разные сущности. Событие — это бизнес сущность, “что-то произошло”, обычно существительно + краткое причастие (например, “заказ создан”). Команда — это приказ, “надо что-то сделать”, и чаще это техническая сущность (например, “отправь e-mail”).
Количество потребителей и производителей. У команды “потребитель” ровно один — исполнитель команды, а у события — от 0 до бесконечности, причем они могу быть из разных доменов. Команду может дать кто угодно, а произвести событие о том, что “что-то случилось” — только одна сущность.
Срок жизни. Команду сделал и забыл (удалил), а событие обычно стоит сохранить для истории.
Порядок обработки. Обычно для команд не важен порядок обработки, потому что заранее команду создавать — это как-то тупо. А вот события лучше обрабатывать последовательно: вряд ли будет логично обработать сначала доставку заказа, а потом его создание (особенно если при этом клиенту шлется уведомление).
Как следствие, и технологии для потока команд и потока событий обычно стоит выбирать соответствующие: для команд лучше подойдут решения типа RabbitMQ, а для событий — Kafka.
Маштабирование старых приложений при HiDPI
Не все приложения, особенно старые, понимают, что включен режим высокого разрешения и надо поменять свой масштаб. Чтобы сделать это принудительно, можно сначала попробовать специальные переменные окружения, но они разные для каждого фреймворка и могут вообще не работать. В подобном случае мне помогла утилита run_scaled, которая создает временный логический экран с нужным масштабом специально для приложения.
Трейсинг
С удивлением обнаружил, что, оказывается, не все знают про существование трейсинга и зачем он нужен. В простейшем варианте он позволяет отследить все операции, которые совершаются в рамках одного логического запроса — например, как он пришел с фронта, какие микросервисы запрашивались, какие запросы к базе выполнялись и т.п. Для этого обычно достаточно создать уникальный идентификатор запроса (traceId), прокидывать его при всех взаимодействиях (например, через http-заголовки) и добавлять в логах.
При продвинутом использовании можно строить карту сервисов (кто кого вызывает), смотреть графическое представление выполнения запроса (сколько времени он “висел” в каждой конкретной операции или микросервисе). Причем это может быть полезно даже не в микросервисной архитектуре (если у вас есть хоть одна зависимость в виде базы или распределенной очереди).
Сравнительно недавно два конкурирующих стандарта, OpenTracing и OpenCensus, объединили в один — OpenTelemetry. Если у вас попсовый стек, то подключается обычно довольно легко с помощью инструментации на уровне какого-нибудь кубера или на уровне приложения (в JVM-мире это java agent). Визуализировать можно с помощью Jaeger. Если некуда девать деньги, то можно использовать монструозный Dynatrace или облачные решения, типа AWS X-Ray.
Нулевая терпимость к багам
В продолжение старого поста про то, что не надо игнорировать баги, даже если они мелкие. Часто слышал высказывание, мол, зачем тратить время на мелкие правки, на «перфекционизм», что ничего страшного не случится, особенно если баг особо не влияет не на что, что баг дешевле терпеть, чем править.
Однако при этом обычно редко рассматриваются такие факторы, как:
- снижение культуры разработки (например, зачем мне заводить баг, если все равно его не пофиксят, зачем мне стараться делать качественно, если на проде полно багов)
- снижение качества (например, мелкий баг может быть симптомом более крупной проблемы или маркером плохого процесса)
- снижение качества мониторинга (надо разделять известные и неизвестные баги в логах, для этого даже инструменты вроде Sentry делают; метрика количества 5xx может уже не показывать нормальную картину)
- некорректная работа прода (даже не из-за самого бага, а из-за сопутствующих проблем. Например, если плохо обрабатывать ошибки пользователя и кидать 500, то балансировщик может прихлопнуть работоспособный узел, при большой нагрузке такое чревато полной неработоспособностью)
- снижение культуры реагирования на алерты («да забей на этот алерт, он всегда орет из-за того бага»)
- увеличение когнитивной нагрузки (надо помнить известные баги, думать, править ли новые или нет и т.п)
Кому повысить зарплату?
Неплохой доклад про стратегии повышения зарплаты, в котором рассказаны базовые принципы и основные примеры, как не надо делать.
Очевидно, что надо повышать зарплату тем, кто больше приносит пользы бизнесу, а не кому больше надо, кто больше попросит или лучше себя продаст. При этом пользу надо как-то оценивать, но любую метрику, особенно количественную, мыслящий программист легко сломает и она перестанет отражать реальность.
Если оценивать что-то абстрактное, то надо трезво оценивать влияние конкретного сотрудника на это (например, продуктовые метрики могут расти сами по себе, а геройски решенная сложная задача могла быть решена и другим разрабом, просто ему назначили другую).
Одна из неплохих стратегий по мнению докладчика - определить текущее узкое место, и премировать по метрике, способствующей его устранению. Дополнительный совет: вместо измерения всех по какой-то общей шкале стоит опрашивать всех членов команды и сравнивать людей между собой (с правильно подобранными вопросами, разумеется). Кто набрал больше первых мест, тот обычно и молодец. Все-таки, относительные оценки людям даются лучше.
Контейнер Морозов
Продолжаю “ломать” изоляцию сетей:) Давным-давно писал про перенаправление портов, там среди прочих указан port-forward
kubectl port-forward some_service 5672:5672
это прекрасно работает, когда сам сервис развернут в кластере k8s. Но что делать, если он виден, но находится в другом кластере/сети? Форварднуть порты еще раз с помощью временного контейнера :)
kubectl run tmp-port-tunnel --rm --image=alpine/socat -it --expose=true --port=5672 tcp-listen:5672,fork,reuseaddr tcp-connect:some_host:5672
Работа с эффектами в Scala
Хороший видос про этапы развития Scala с точки зрения работы с эффектами (с небольшим предисловием про Haskell) — better Java, акторы, Future, Monix, Scalaz-stream, Cats, FS2, Cats Effect, ZIO.
Кроме этого, Нижников еще рассказывает про общие принципы работы IO монады (звучит страшно, но на самом деле все довольно просто) и сравнивает Cats Effect и ZIO.
Значение по умолчанию в SQL-запросе
Как получить данные по запросу, а если их нет, то использовать значение по умолчанию?
Элементарно, но только если не надо потом эти данные сразу же вставить, да все это еще и в одном запросе. Если бы не было нужно значение по умолчанию, то это тоже решалось бы легко подзапросом.
Можно подзапрос объединить через UNION
с временной табличкой, содержащей строку по умолчанию, но тогда надо очень аккуратно выбрать правильное значение дополнительным условием (возможно, через флаги с их последующим вырезанием).
В итоге у меня родился такой монстр:
INSERT INTO stats(day, key, value)
SELECT '2022-08-04', key, COALESCE(stats."value", 0)
FROM (VALUES(key)) as request(key)
LEFT JOIN stats on request.key=stats.key
WHERE (stats.value IS NULL) OR (stats."day" < '2022-08-04')
ORDER BY stats."day" DESC
LIMIT 1
ON CONFLICT DO NOTHING
Тут производится LEFT JOIN
с временной таблицей request
, чтобы всегда получить хотя бы 1 строку, в которой будет либо null
, либо ответ на запрос. Потом COALESCE(stats."value", 0)
выбирает первое не-null значение.
Запретный плод для Java
Один из популярных вопросов начального уровня на Java-собесе — “можно ли переопределить статический метод?”. И правильный ответ — нельзя.
Однако, если очень надо, то это возможно:
public class Foo {
public static void bar(){
System.out.println("Foo");
}
}
Foo.bar()
Mockito.mockStatic(Foo::class.java)
whenever(Foo.bar()).then { println("not foo") }
Foo.bar()
выведет:
Foo
not foo
Разумеется, если вам это вдруг нужно, то с большой вероятностью вы делаете что-то не то (мало того, что у вас рак в виде java, так еще и со статическими методами, которые требуют переопределения). Однако мне это пригодилось, когда понадобилось протестировать работу с переменными окружения, которые как раз засунуты в статический метод System.getenv()
(для этого я использовал system stubs).
Запретный плод
>>> (666).boo()
I'm just a 666, but boo!
>>> "I can be JS too! " + 2
'I can be JS too! 2'
Одна из фишек питона — возможность monkey-patching. Но многие считают, что у него есть предел, и нельзя патчить стандартную библиотеку. Но на самом деле можно:
>>> from forbiddenfruit import curse
>>> curse(int, "boo", lambda x: print(f"I'm just a {x}, but boo!"))
>>> (666).boo()
I'm just a 666, but boo!
>>> curse(str, "__add__", lambda x,y: x + str(y))
>>> "I can be JS too! " + 2
'I can be JS too! 2'
Это знание позволит вам поиздеваться над коллегами и еще меньше доверять написанному на питоне. Ходят слухи, что библиотека была сделана для тестов, но благими намерениями…