MoSKito7 Min. Lesezeit

Ein Zähler, sie alle zu zählen

MoSKito 2.0.x führt Counter ein: Ereignisse mit den Annotationen @Count und @CountByParameter zählen, den mitgelieferten MaleFemale-Counter nutzen oder eigenen Counter samt Factory und Decorator bauen.

In letzter Zeit haben wir viel Arbeit investiert, um MoSKito noch besser zu machen und es auf Maven Central zu veröffentlichen – was übrigens abgeschlossen ist! Es wird aber Zeit, Ihnen etwas über die neuen Features in 2.0.x zu erzählen.

Heute geht es um Counter. Counter scheinen für eine Monitoring-Anwendung eine so naheliegende Idee zu sein, dass man sich fragen könnte, warum wir sie nicht schon früher hatten. Tatsächlich hatten wir sie. Es war nur nicht SO einfach, sie zu nutzen, wie jetzt. In den gar nicht so alten 1.x-Zeiten (also vor etwa 3 Monaten) mussten Sie die Counter selbst bauen. Das war nicht schwer, musste aber trotzdem gemacht werden. Meist bedeutete das, einen eigenen Custom Producer zu schreiben, mit viel Code und vielen Wenns und Danns darin. Damit ist jetzt Schluss!

Nehmen wir folgendes Beispiel: Sie betreiben ein Portal und haben das Glück, zahlende Kunden zu haben. Um Ihre verschiedenen Zahlungsarten im Blick zu behalten, möchten Sie die Zahlungen pro Zahlungsart zählen und eventuell mit Thresholds und Accumulators überwachen. Angenommen, Sie akzeptieren drei Zahlungsarten: cc (Kreditkarte), ec (EC-Karte) und PayPal. Alles, was Sie tun müssen, ist ein einfaches Objekt anzulegen:

package yourpackage;
import net.anotheria.moskito.annotation.Count;
@Count
public class PaymentCounter {
 /**
 * Electronic card payment (lastchrifteinzug in germany).
 */
 public void ec(){}
/**
 * Credit card payment.
 */
 public void cc(){}
/**
 * Payment via paypal.
 */
 public void paypal(){}
}

Glückwunsch, Sie haben jetzt einen Counter.

Um ihn zu verwenden, müssen Sie ihn nur irgendwo in Ihrem Code initialisieren und aufrufen:

PaymentCounter counter = new PaymentCounter();
//now make different payments
counter.ec();
//...
counter.cc();
//... counter.paypal();

Wenn Sie sich jetzt Ihre Zahlungsstatistiken ansehen möchten, öffnen Sie einfach Ihre WebUI:

Natürlich können Sie diesen Counter weiterverarbeiten und einen Threshold oder Accumulator hinzufügen.

Aber ist es nicht ein Anti-Pattern, eine ganze Klasse nur für Monitoring-Zwecke anzulegen? Eigentlich nicht – doch wenn Ihnen das unangenehm ist, können Sie dieselbe Annotation @Count auch an ausgewählten Methoden verwenden:

@Count private void ec(){}
@Count private void cc(){}
@Count private void paypal(){}

Die Methoden können sich irgendwo in Ihrem Code befinden. Solange sie in derselben Klasse liegen, werden sie automagisch im selben Producer zusammengefasst. Ist das nicht der Fall, Sie möchten es aber so haben, müssen Sie die Producer-ID explizit angeben:

@Count(producerId="MyProducerId")
private void paypal(){}

Wenn Sie nicht so viele Methoden hinzufügen möchten, können Sie MoSKito freundlich bitten, die Fälle nach Parametern statt nach Methodennamen zu trennen:

@CountByParameter public void pay(String method){};

Statt

counter.cc(); counter.ec();

aufzurufen, können Sie

counter.pay("cc"); counter.pay("ec");

aufrufen, wenn Ihnen das lieber ist. Sie können auch mehrere Methoden haben, jede davon wird ein eigener Producer:

@CountByParameter public void success(String method){};
@CountByParameter public void failure(String method){};

Ist das alles, was ein Counter kann?

Natürlich nicht. Wir bereiten weitere Counter vor, als Beispiele dafür, was Counter absolut einfach und OOTB leisten können. Diesmal liefern wir ein klassisches Beispiel (mit Producern und allem Drum und Dran):

Männer von Frauen trennen

Das kann für Portale interessant sein. Dafür können Sie den mitgelieferten MaleFemaleCounter verwenden. Das folgende Beispiel überwacht Aufrufe verschiedener Seiten in einer Webapp und könnte aus einem Filter heraus ausgeführt werden. Legen Sie zuerst einen Producer in init() oder im Konstruktor an.

OnDemandStatsProducer producer = new OnDemandStatsProducer("pages", "category", "subsystem", new MaleFemaleStatsFactory());

Immer wenn Sie in doFilter einen Hit zählen können und das Geschlecht kennen (zum Beispiel, weil Sie es aus der Session lesen), rufen Sie einfach den Producer auf.

//male client comes to the homepage
producer.getDefaultStats().incMale();
producer.getStats("homepage").incMale();

//female client comes to the homepage
producer.getDefaultStats().incFemale();
producer.getStats("homepage").incFemale();

//male client comes to the messaging page
producer.getDefaultStats().incMale();
producer.getStats("messaging").incMale();

Trennung nach Guest – MEMBER – PREMIUM.

Ein weiteres beliebtes Beispiel in Bezahlportalen ist die Trennung des Traffics nach Mitgliedsstatus. Wenn ich zum Beispiel die CPU-Zeit oder andere Ressourcen berechnen möchte, die für verschiedene Nutzertypen aufgewendet werden, muss ich wissen, welchen Status jeder Nutzer hat, und sie zählen. Doch statt Ihnen das fertige Objekt zu zeigen, zeigen wir Ihnen, wie Sie

IHREN EIGENEN COUNTER BAUEN.

Zuerst brauchen Sie den Counter selbst. Einen zu bauen ist ziemlich einfach, denn net.anotheria.moskito.core.counter.GenericCounterStats erledigt die ganze schwere Arbeit:

public class GuestBasicPremiumStats extends GenericCounterStats{

GenericCounterStats muss jetzt nur noch wissen, welche Werte gezählt werden sollen:

public GuestBasicPremiumStats(String name){
 super(name, "guest", "basic", "premium");
}
public GuestBasicPremiumStats(String name, Interval[] intervals){
 super(name, intervals, "guest", "basic", "premium");
}

Der Counter ist da, aber wir müssen GenericCounterStats noch sagen, dass tatsächlich etwas gezählt werden soll.

public void incGuest(){
 super.inc("guest");
}
public void incBasic(){
 super.inc("basic");
}
public void incPremium(){
 super.inc("premium");
}

und von Zeit zu Zeit möchten wir vielleicht die Werte abfragen:

public long getGuest(String intervalName){
 return get("guest", intervalName);
 }
public long getBasic(String intervalName){
 return get("basic", intervalName);
}
public long getPremium(String intervalName){
 return get("premium", intervalName);
}
public long getGuest(){
 return get("guest", null);
}
public long getBasic(){
 return get("basic", null);
}
public long getPremium(){
 return get("premium", null);
}

Eine letzte Methode wird von der Infrastruktur benötigt, um die Daten in der WebUI richtig darzustellen:

@Override
 public String describeForWebUI() {
 return "GuestBasicPremium";
 }

Der Code ist auch in unserem SVN verfügbar.

Um den neuen Counter mit OnDemandProducers zu verwenden (und genau das wollen Sie eigentlich), müssen Sie noch eine weitere Klasse hinzufügen, die Factory – aber das ist wirklich ein Dreizeiler:

public class GuestBasicPremiumStatsFactory implements IOnDemandStatsFactory {
 private Interval[] intervalSelection;
 GuestBasicPremiumStatsFactory(){
  this(Constants.getDefaultIntervals());
 }
 GuestBasicPremiumStatsFactory(Interval[] myIntervals){
  intervalSelection = myIntervals;
 }
 @Override
 public GuestBasicPremiumStats createStatsObject(String name) {
  return new GuestBasicPremiumStats(name, intervalSelection);
 }
}

Auch diesen Code können Sie sich im SVN ansehen.

Schließlich möchten Sie sich die gesammelten Daten wohl von Zeit zu Zeit ansehen, und hier kommt ein Decorator ins Spiel. Auch hier erledigt net.anotheria.moskito.webui.decorators.counter.GenericCounterDecorator die ganze schwere Arbeit, Sie müssen nur einige Namen angeben.

Jeder Decorator liefert drei Strings für einen Wert:

  1. Caption – der Spaltenname in der WebUI.
  2. ShortExplanation – Text, der beim Mouseover angezeigt wird.
  3. Explanation – (langer) Text, der auf der Hilfeseite angezeigt wird.

Wenn man das weiß, ist das Bauen eines Decorators ein Kinderspiel:

public class GuestBasicPremiumStatsDecorator extends GenericCounterDecorator{
 private static final String CAPTIONS[] = {
 "Guest", "Basic", "Premium"
 };
private static final String SHORT_EXPLANATIONS[] = CAPTIONS;
private static final String EXPLANATIONS[] = {
 "Number of calls, clicks, payments - whatever you wanted to count for guest users",
 "Number of calls, clicks, payments - whatever you wanted to count for basic users",
 "Number of calls, clicks, payments - whatever you wanted to count for premium users",
 };
private static final GuestBasicPremiumStats PATTERN = new GuestBasicPremiumStats("pattern");
public GuestBasicPremiumStatsDecorator(){
 super(PATTERN, CAPTIONS, SHORT_EXPLANATIONS, EXPLANATIONS);
 }
}

Voilà. Wir sind fertig. Rufen Sie jetzt irgendwo diese Zeile auf, und alles ist hübsch:

DecoratorRegistryFactory.getDecoratorRegistry().addDecorator(GuestBasicPremiumStats.class, new GuestBasicPremiumStatsDecorator());

Genug für heute? 🙂

Zumindest für mich. Was haben wir heute also gelernt:

  • Counter sind einfach zu verwendende, leichtgewichtige Stats-Objekte, die einfach nur zählen.
  • Counter unterstützen keine Zeitmessung und tauchen nicht in Journeys auf.
  • Counter können als OnDemandProducers verwendet werden (Beispiel MaleFemaleStats).
  • Die mitgelieferten eindimensionalen CounterStats lassen sich sehr bequem zusammen mit der OOTB-Annotation @Count verwenden.
  • Letztere funktioniert mit AOP und CDI (bzw. existiert für beide Welten).
  • … und Counter machen richtig Spaß, sobald Sie sie ausprobieren! 😉

Laden Sie MoSKito 2.0.2 von Central herunter und probieren Sie noch heute Ihren ersten Counter aus. Und lassen Sie uns danach wissen, wie es war 😉

Haben Sie ein System, das Sie ausbremst, oder eine Idee, die bisher niemand günstig genug bauen konnte?

Lassen Sie uns reden. 30 Minuten, ohne Pitch-Deck.

Worum geht es?