We have several applications assembled with Guice, each using Tucker to build a satus page
Status pages by their nature fan out across a lot of the app
Java brings quite enough boilerplate, let's try and reduce it
private final Health health;
private final Stoppable stoppable;
private final OverdueRankingsComponent overdueRankingsComponent;
private final AlarminglyOverdueRankingsComponent alarminglyOverdueRankingsComponent;
/* etc.. */
@Inject
public AppStatusServletProvider(Health health,
Stoppable stoppable,
OverdueRankingsComponent overdueRankingsComponent,
AlarminglyOverdueRankingsComponent alarminglyOverdueRankingsComponent,
TimFlowTickSendingComponent timFlowTickSendingComponent,
CasHealthComponent casHealthComponent,
EodPriceMessagesComponent eodPriceMessagesComponent,
/* 20 more... */) {
this.health = health;
this.stoppable = stoppable;
this.overdueRankingsComponent = overdueRankingsComponent;
/* etc... */
}
public ApplicationInformationServlet get() {
ApplicationInformationServlet infoServlet = new ApplicationInformationServlet("app", stoppable, health);
StatusPageGenerator statusPage = infoServlet.getStatusPageGenerator()
statusPage.addComponent(new JvmVersionComponent());
statusPage.addComponent(new HeapUsageComponent());
statusPage.addComponent(overdueRankingsComponent);
/* etc... */
return infoServlet;
}
class AppStatusServletProvider @Inject() (val health: Health,
val stoppable: Stoppable,
val overdueRankingsComponent: OverdueRankingsComponent,
val alarminglyOverdueRankingsComponent: AlarminglyOverdueRankingsComponent,
val timFlowTickSendingComponent: TimFlowTickSendingComponent,
val casHealthComponent: CasHealthComponent,
val eodPriceMessagesComponent: EodPriceMessagesComponent,
/* etc.. */) extends Provider[ApplicationInformationServlet] {
def get = {
val infoServlet = new ApplicationInformationServlet("app", stoppable, health)
val statusPage = infoServlet.getStatusPageGenerator
statusPage.addComponent(new JvmVersionComponent)
statusPage.addComponent(new HeapUsageComponent)
statusPage.addComponent(overdueRankingsComponent)
/* etc... */
infoServlet
}
Just list the classes of the components once.
public class AppInformationModule extends InfoModule {
public void configureComponents() {
addComponent(JvmVersionComponent.class);
addComponent(HeapUsageComponent.class);
addComponent(OverdueRankingsComponent.class);
// etc..
}
@Provides
public StatusPageGenerator statusPage() {
return new StatusPageGenerator("app", new JarVersionComponent(getClass()));
}
}
Every component still requires a new class, what can we do?
addComponent(new Component("thing", "State of the thing") {
@Inject
private Thing thing;
public Report getReport() {
return new Report(Status.INFO, thing.status());
}
});
So at least creating a component is just a few lines. Still looks a bit nasty, encourages using field injection.
Modelled after provider methods, we can have component methods.
@OnStatusPage
public Component thing(final Thing thing) {
return new Component("thing", "State of the thing") {
public Report getReport() {
return new Report(Status.INFO, thing.status());
}
};
}
It would be nice to get rid of the anonymous Component subclass, which is also repetitive.
A component method can return Report (in which case a Component is synthesised by the module)
@OnStatusPage
@Label("State of the thing")
public Report thing(Thing thing) {
return new Report(Status.INFO, thing.status());
}
Or possibly even:
@OnStatusPage
@Label("State of the thing")
public String thing(Thing thing) {
return thing.status();
}
Components can be wrapped in AsyncComponent whether bound as a linked binding or using component methods
public void configureComponents() {
addAsyncComponent(ServiceHealthCheck.class)
.withRepeatSchedule(13, SECONDS)
.withStalenessLimit(2, MINUTES)
.withUpdateHook(ServiceHealthUpdated.class);
}
@OnStatusPage
@Label("State of the thing")
@Async(interval = 13)
public Report thing(Thing thing) {
return new Report(Status.INFO, thing.status());
}
When using InfoModule
, a Iterable<? extends AsyncComponent>
can be injected into a refresh service. An adaptor as a Guava Service
is shared between the apps.
You can have multiple InfoModules, and the status page components will be unioned.
For example, conditionally included chunks of the app can define their own status components.
Generally, those added with addComponent
will stay in order, but there is no defined ordering.
If you have multiple InfoModules, they may be added in any order.