Creating an injectable util-app App

Lifecycle

If you haven’t already, take a look at the util-app App lifecycle documentation.

Getting Started

To create an injectable util-app App, first depend on the inject-app library. Then use the inject framework to create an injectable App. Finatra provides an injectable version of the util-app App trait: c.t.inject.app.App.

Extending the c.t.inject.app.App trait creates an injectable util-app App.

This allows for the use of dependency injection in a util-app App with support for modules which allows for powerful feature testing of the application.

Scala Example

Given a module definition:

import com.twitter.inject.TwitterModule

object MyModule1 extends TwitterModule {

  @Singleton
  @Provides
  def providesFooService: FooService = ???
}

You could define an c.t.inject.app.App:

import com.google.inject.Module
import com.twitter.inject.Logging
import com.twitter.inject.app.App
import com.twitter.inject.modules.LoggingModule

object MyAppMain extends MyApp

class MyApp extends App with Logging  {

  override val modules: Seq[Module] = Seq(
    LoggingModule,
    MyModule1)

  override protected def run(): Unit = {
    // Core app logic goes here.
    val fooService = injector.instance[FooService]
    ???
  }
}

Tip

We include the c.t.inject.modules.LoggingModule to attempt installation of the SLF4JBridgeHandler here as an example of how to bridge legacy APIs.

Then to test:

import com.twitter.inject.Test
import com.twitter.inject.app.EmbeddedApp

class MyAppTest extends Test {

  private val app = new EmbeddedApp(new MyApp)

  test("MyApp#runs") {
    app.main("some.flag1" -> "value1", "some.flag2" -> "value2")
  }
}

Important

Note: every call to EmbeddedApp#main will run the application with the given flags. If your application is stateful, you may want to ensure that a new instance of your application under test is created per test run.

Java Example

import java.util.Collection;
import java.util.Collections;

import com.google.inject.Module;

import com.twitter.inject.app.AbstractApp;
import com.twitter.inject.modules.LoggerModule$;

public class MyApp extends AbstractApp {

    @Override
    public Collection<Module> javaModules() {
        return Collections.<Module>singletonList(
                LoggerModule$.MODULE$,
                MyModule1$.MODULE$);
    }

    @Override
    public void run() {
        // Core app logic goes here.
        FooService fooService =
          injector().instance(FooService.class);
    }
}

Then create a “main” class:

final class MyAppMain {
    private MyAppMain() {
    }

    public static void main(String[] args) {
        new MyApp().main(args);
    }
}

See the Finatra examples for detailed examples with tests.

App#run

The single point of entry to the c.t.inject.app.App is the #run method.

The Finatra Startup Lifecycle ensures that the injector will be properly configured before access and provides the #run method as the function for implementing the app.

Using Injection

The c.t.inject.app.App exposes access to the configured c.t.inject.Injector which can be used to obtain instances from the object graph.

Note

You should take care to only access the injector in the App#run function as this is the correct place in the lifecycle where you are ensured to have a fully configured c.t.inject.Injector. Accessing the c.t.inject.Injector too early in the lifecycle (before it is configured) will result in an Exception being thrown.

An Example of Handling Signals

There may be cases where you want your application to handle an IPC signal instead of closing normally once the code execution is done, e.g., handling an INT (Ctrl-C) or TSTP (Ctrl-Z) signal.

You can use the com.twitter.util.HandleSignal utility to apply a callback to run on receiving the signal.

Note

Please consult the scaladocs for com.twitter.util.HandleSignal to make sure you are aware of the limitations of the code in handling signals.

For example, to exit the application upon receiving an INT signal:

import com.google.inject.Module
import com.twitter.inject.app.App
import com.twitter.inject.Logging

object MyAppMain extends MyApp

class MyApp extends App with Logging  {

  override val modules: Seq[Module] = Seq(
    MyModule1)

  HandleSignal("INT") { signal =>
    exitOnError(s"Process is being terminated with signal $signal.")
  }

  override protected def run(): Unit = {
    // Core app logic goes here.
    val fooService = injector.instance[FooService]
    ???
  }
}