Блог программиста Томаша Фиалковского

Глобальное состояние — зло, пока не доказано обратное. На этом блоге я иногда затрагиваю запретные темы, как в материале «Optional как поле» и «что вы собираетесь мне за это сделать?». На этот раз речь пойдёт о статических публичных переменных. О вреде их использования уже много сказано. В этом посте я хочу проанализировать конкретный случай их применения — метрики.

В посте Бартека Галека «Mierz logi na zamiary» автор подробно описал, насколько важны метрики. Собирать метрики при этом очень просто. В фреймворке Spring достаточно инжектировать MeterRegistry… Ну да, просто инжектировать MeterRegistry. Но обязательно ли всё в моём коде должно быть Bean, лишь чтобы собирать метрики? Если я хочу собирать метрики в простом POJO, нужно ли создавать фабрику, которая будет Bean, и устанавливать MeterRegistry в экземпляр POJO? Ведь когда нужно логировать, вы не инжектируете логгер (по крайней мере мне не встречалось такого). Вместо этого используется статический экземпляр, и логируют что угодно и откуда угодно. Предполагаю, что логи и метрики друг от друга недалеко. Так почему бы не попробовать применить к метрикам похожий подход?

Глобальные переменные приходят на помощь. В самом простом варианте можно просто создать класс с публичным статическим полем, хранящим экземпляр MeterRegistry. Это поле инициализируется при старте приложения, а затем используется повсеместно. Ниже приведена чуть более продуманная реализация, где мутатор имеет пакетную видимость, а аксессор — публичный. Эта реализация очень проста, но даёт огромную гибкость при добавлении метрик в код. Если требуется несколько инстансов MeterRegistry, например, потому что метрики отправляются в разные места, можно расширить класс MeterRegistryHolder, чтобы он хранил несколько экземпляров. Для удобства можно добавить статический импорт . Код при этом останется в основном неизменным: вместо () будет meterRegistry().counter(). Можно также воспользоваться классом Metrics из Micrometer. У него гораздо более обширный API, и через него, хотя у нас нет прямого доступа к объекту MetricRegistry, доступен набор методов для получения экземпляров метрик, ассоциированных с глобальным MetricRegistry.

Однако глобальные переменные не случайно имеют дурную славу. Ниже — два ограничения обсуждаемого подхода, о которых стоит помнить. Если нужно тестировать метрики, это можно сделать, установив Spy/Mock в MeterRegistryHolder или инициализировав его через SimpleMeterRegistry. Но учтите, что в таком случае тесты метрик не должны запускаться параллельно. Также обратите внимание: в этой реализации мы не контролируем порядок инициализации бинов и момент, когда MeterRegistryHolder будет инициализирован. Поэтому, если пытаться собирать метрики во время инициализации контекста приложения, ссылка на meterRegistry может быть пустой. В такой ситуации можно либо расширить MeterRegistryHolder (у меня есть пример реализации на GitHub), либо прибегнуть к проверенному инжекшену. Простая реализация MeterRegistryHolder подойдёт для всего, что происходит после инициализации контекста.

Я не утверждаю, что этот подход сработает во всех случаях. В моих недавних проектах он замечательно себя зарекомендовал, давая большую свободу при добавлении метрик. Дайте шанс глобальным переменным (особенно когда вы ограничиваете их изменяемость пакетной видимостью).

Programming Chi (или Qi) — блог о скромных идеях, багах и неудачах, с которыми я сталкиваюсь ежедневно. К счастью, помимо неудач, есть и множество успешных и элегантных решений, о которых я тоже надеюсь писать.