Na tym blogu zdarza się, że są poruszane tematy tabu. Tym razem przyszedł czas na statyczne publiczne zmienne. Wiele już zostało powiedziane na temat zła, które wyrządziło światu ich stosowanie. W tym poście chciałbym przeanalizować szczególny przypadek ich użycia, a mianowicie metryki.
Metryki, wszędzie metryki
W poście Mierz logi na zamiary Bartek opisał jak istotne są metryki. Zbieranie miar jest zarazem bardzo proste. W spring framework, wystarczy wstrzyknąć MeterRegistry
…
No właśnie, wstrzyknąć MeterRegistry
. Czy naprawdę aby zbierać metryki, wszystko w moim kodzie musi być Bean’em? Czy jeśl chcę zbierać metryki w POJO, to muszę zrobić fabrykę, która będzie Bean’em i ustawi MetrerRegistry
w instancji POJO?
Oczywiście NIE! Przecież jeśli chcesz coś zalogować, to nie wstrzykujesz loggera (przynajmniej z tym się nie spotkałem). W zamian używasz statycznej instancji i logujesz co chcesz i gdzie chcesz. Wychodzę z założenia, że logi i miary nie są od siebie aż tak odległe. Spróbujmy więc zastosować podobne podejście do miar.
Zmienne globalne na ratunek
W najprostszym podejściu moglibyśmy po prostu utworzyć klasę z publicznym statycznym polem przechowującym instancję MeterRegistry
. Pole inicjowalibyśmy na starcie aplikacji a następnie dowolnie mogli go używać. Poniżej przedstawiam niewiele bardziej rozbudowaną implementację, w której mutator ma widoczność pakietową a akcesor publiczną.
public class MeterRegistryHolder {
private static MeterRegistry aMeterRegistry;
static void init(MeterRegistry meterRegistry) {
aMeterRegistry= meterRegistry;
}
public static MeterRegistry meterRegistry() {
return aMeterRegistry;
}
}
@Configuration
public class MeterRegistryHolderInitializer {
MeterRegistryHolderInitializer(MeterRegistry meterRegistry) {
MeterRegistryHolder.init(meterRegistry);
}
}
Taka implementacja jest bardzo prosta, a zarazem daje ogromną swobodę dodawania metryk w kodzie.
Jeśli potrzebujesz kilku instancji MeterRegistry
, bo np. wysyłasz metryki w różne miejsca, możesz rozbudować klasę MeterRegistryHolder
tak, aby przechowywała kilka instancji.
Dla wygody możesz dodać statyczny import na MeterRegistryHolder.meterRegistry
. Kod nie ulegnie większym zmianom. Zamiast meterRegistry.counter()
będzie meterRegistry().counter()
.
Możemy też skorzystać z klasy Metrics
z micrometer. Ma ona znacznie bardziej rozbudowane API za pomocą którego, co prawda, nie mamy dostępu do obiektu MetricRegistry
ale mamy szereg metod generujących instancje metryk powiązanych z globalnym MetricRegistry
.
Ograniczenia
Zmienne globalne nie bez powodu mają złą sławę. Poniżej przedstawiam dwa ograniczenia omawianego podejścia o których warto pamiętać.
Jeśli potrzebujesz testować metryki, możesz to osiągnąć ustawiając Spy lub Mock w MeterRegistryHolder
. Zauważ jednak, że wtedy testy metryk nie powinny być uruchamiane współbieżnie.
Zwróć również uwagę, że w tej implementacji nie kontrolujemy, w jakiej kolejności są inicjowane beany i kiedy MeterRegistryHolder
zostanie zainicjowany. Dlatego, jeśli próbujesz zbierać metryki w trakcie inicjalizacji kontekstu aplikacji, referencja meterRegistry
może być pusta. W takiej sytuacji pozostaje rozbudować MeterRegistryHolder
(przygotowałem przykładową implementację na github) lub użycie starego sprawdzonego wstrzykiwanie. Do wszystkiego co dzieje się po zainicjowaniu kontekstu można użyć prostej implementacji MeterRegistryHolder
.
Nie zakładam, że to podejście sprawdzi się we wszystkich przypadkach. W moich ostatnich projektach sprawdziło się świetnie dając dużą swobodę dodawania metryk. Jeśli widzisz jakieś sytuacje, gdzie to podejście całkowicie zawodzi, koniecznie opisz to w komentarzu. Będę bardzo wdzięczny.
Przekaz na dziś
Daj szansę zmiennym globalnym (zwłaszcza gdy zmienność ograniczysz do pakietu).