Tucker-Guice

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

This is painful

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;
}

This isn't much better

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
}

A custom Guice module

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()));
 }
}					    

Reducing boilerplate

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.

Like a provider method

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.

Provide reports, not components

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();
}

Support for async components

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.

Multiple inclusions

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.

Status page ordering is lost

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.