Skip to content

How (not) to design a Metrics API – Part 2: Delegation & Separation

This is part two in a series of articles on how best to design an application metrics monitoring library, in particular its API, providing versatility both in terms of application of the library across domains & environments and in its implementation by one or more vendors. Please read part 1 if you have not already done so in which we introduce the alternative libraries that will be compared with the JXInsight/OpenCore Metrics Open API.

There are many definitions of what constitutes an “Open API” (or “Openness in an API”) but in our opinion there are at least four key ingredients: delegation, separation, extension and contextualization. In this article the first two will be discussed. The remaining two will be discussed in a forthcoming posting.

Note: When it comes to openness of the implementation we need to look at configuration and integration.

Delegation

The single most important requirement for any Open API should be the ability to use an alternative implementation that adheres to the API contract whilst offering additional benefits over the default implementation in terms of performance, resource management, reliability, scalability, extensibility, serviceability, tooling, integration and so on. If you can’t switch to an entirely different implementation then it most certainly is not open, irrespective of the ability to access the source, and its debatable whether it really is an API (more so a library).

To use an alternative implementation of the JXInsight/OpenCore Metrics Open API all that is needed is the setting of a service provider interface (SPI) system property specifying the service provider factory class that should be used to create an instance of the actual service provider.

-Djxinsight.server.metrics.spi.factory=

The Metrics class, representing the entry point into the API and the only implementation class in the entire API, will on class initialization create an instance of a MetricsProvider via the class implementation of the MetricsProviderFactory specified in the above system property.

Below is a snippet code showing what an implementation of the above SPI interfaces would look like.

And here is actual code from the Metrics class showing call delegation through to the provider.

Note: The Metrics class does not expose the underlying service provider factory or service provider.

Yammer

When its comes to delegation this metrics library, hosted on Github, looks very much like a bunch of implementation classes hobbled together. Whilst the Metrics class does delegate most of its calls to the exposed MetricsRegistry class instance there is no means whatsoever to switch in an alternative implementation of the MetricsRegistry class as a way to bridge (route) calls to a more mature and professionally engineered library. Of course MetricsRegistry being a class and not an interface makes this all the more impossible but we will come back to this later.

Note: The Metrics class also hardwires in the creation of a JMX integration which ideally should be optional, delegated to the underlying implementation and configured externally by operations staff.

Netflix

This metrics library, also hosted on Github, does offer the means to delegate calls to an alternative implementation of the MonitorRegistry interface specified by way of a system property though the mechanism is somewhat cumbersome, convoluted and costly.

The DefaultMonitorRegistry class which is the entry point into this library creates an instance of itself, with a reference to an alternative implementation of MonitorRegistry. The static method, getInstance(), in the DefaultMonitorRegistry class then returns a reference to the INSTANCE named field of type MonitorRegistry (actually itself) which in turn delegates calls to an instance field named registry of type MonitorRegistry.

This interface coupling is unwarranted and hinders development of utility methods in the entry point class, DefaultMonitorRegistry, without polluting the MonitorRegistry interface. Not having a distinct SPI interface also complicates bootstrapping which usually needs additional methods and call backs to be introduced once a library goes beyond toy usage and needs to better manage the lifecycle of the underlying service provider.

Separation

To truly achieve delegation in an API there needs to be a separation between the API and one or more possible implementations (library) of the API. Delegation is rarely achievable in practice without the designer ensuring that no signatures or call paths expose an implementation class that prevents (restricts) alternative implementations offering the optimal solution in line with the intended goal(s) of such a library. Excluding supporting struct (value) and enum classes all should be interfaces except for the entry point class itself which serves to bootstrap and initialize the runtime.

We believe so strongly in this separation that there is only one class, Metrics, in the JXInsight/OpenCore Metric Open API and all others are inner interfaces within this class. Except for the SPI interfaces the whole Open API is enclosed in a single class (source) file. There is no implementation exposure or leaking of abstractions for that matter. The Metrics class ensures that the implementation is never exposed (at least “static”tically speaking).

Below is a class structure view of the JXInsight/OpenCore Metrics class within our favorite IDE. There are no other implementation (C) classes other than the Metrics class and no other packages needed by the client. All interface (I) classes are contained within this class.

Yammer

There is not a single interface used (referenced) in any of the many signatures in this library’s Metrics class. MetricName, Gauge, Counter, Histogram, Meter, Timer are all implementation class. It would be impossible to offer any sort of service delegation with this design unless all alternative implementations used the very same namespace and class names unnecessarily (over) exposed (which has implications with regard to intellectual property).

Note: Java interfaces should be used to indicate very clearly what is expected of implementations, communicate to users the possibility of different behavioral characteristics of such across implementations, and afford implementations the opportunity to optimize and tailor to the fullest.

NetFlix

This metrics library also fails the separation test in exposing the MonitorContext implementation class and its inner Builder implementation class by way of the Monitor interface used in the MonitorRegistry interface. In addition the MonitorRegistry interface directly references the AnnotatedObject implementation class which in turn exposes the AnnotatedAttribute implementation class.

Note: Both open source libraries distribute various utility packages containing implementation classes which only serve to further lock-in client callers and effectively eliminate any possibility of competition in terms of implementation. Dependencies across such packages and the core API package are a mixed bag. It is highly unlikely that any client of either library would be portable to another implementation.

Note: If a library is truly designed to be “open” it should ship with two distributions. One that only includes the actual API itself and the other including both the API and the default (vendor) implementation. JXInsight/OpenCore ships with two libraries that do this very thing – opencore-api.jar and opencore.jar.

Part 3: Groups, Collections & Samples