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]
???
}
}