How to properly configure the server StatsReceiver

The Finatra framework builds upon TwitterServer and for the most part functions as a direct extension of TwitterServer features and functionality. There is, however, one important divergence which is important to highlight when using the Finatra framework with Guice dependency injection.

c.t.server.Stats#statsReceiver

Warning

Do not set or access the com.twitter.finagle.stats.StatsReceiver via the c.t.server.Stats#statsReceiver trait when using the Finatra framework with Guice dependency injection.

The TwitterServer c.t.server.Stats trait provides access for implementations to override the defined statsReceiver which returns a com.twitter.finagle.stats.StatsReceiver. This trait is mixed into the main TwitterServer trait and thus this definition is available for use in the server.

However, Finatra users using the framework with Guice dependency injection should not configure or access the server com.twitter.finagle.stats.StatsReceiver in this manner. Doing so will prevent the framework from properly replacing the StatsReceiver in testing.

Use a StatsReceiverModule

Instead, users should prefer to always interact with the StatsReceiver via the object graph.

The framework provides a StatsReceiverModule which by default binds the StatsReceiver type to the LoadedStatsReceiver instance and is added as a framework Module in the c.t.inject.server.Twitter.

To configure a specialized StatsReceiver, override the c.t.inject.server.Twitter#statsReceiverModule with your customized implementation which will bind the StatsReceiver instance to the object graph.

import com.google.inject.Module
import com.twitter.finagle.stats.{StatsReceiver, LoadedStatsReceiver}
import com.twitter.inject.server.TwitterServer
import com.twitter.inject.TwitterModule

object MyCustomStatsReceiverModule extends TwitterModule {

  override def configure(): Unit = {
    bind[StatsReceiver].toInstance(LoadedStatsReceiver.scope("oursvr"))
  }
}

class MyServer extends TwitterServer {

  override val statsReceiverModule: Module = MyCustomStatsReceiverModule

  override protected def start(): Unit = {
    // It is important to remember to NOT BLOCK this method.
    val sr = injector.instance[StatsReceiver]
    ???
  }
}

Access the StatsReceiver from the Object Graph

Finatra users should also not refer directly to the c.t.server.Stats#statsReceiver as this again will prevent proper testing of the server. Users should instead obtain the server StatsReceiver instance from the object graph, e.g.:

Directly from the Injector:

import com.twitter.finagle.stats.StatsReceiver

val sr = injector.instance[StatsReceiver]

Or as an injected constructor argument to a class:

import com.twitter.finagle.stats.StatsReceiver
import javax.inject.Inject

class Foo @Inject() (statsReceiver: StatsReceiver) {
  ???
}

Or in an @Provides-annotated method in a TwitterModule

import com.google.inject.Provides
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.inject.TwitterModule
import javax.inject.Singleton

object MyModule extends TwitterModule {
  @Provides
  @Singleton
  def providesBar(
    statsReceiver: StatsReceiver): Bar = {
    ???
  }
}