Note: the most up-to-date examples are located in the finatra/examples project.

See examples/thrift-server for an example ThriftServer.

Thrift Server Definition

To start, add a dependency on the finatra-thrift library. We also recommend using Logback as your SLF4J implementation. E.g., with sbt:

"com.twitter" %% "finatra-thrift" % "18.12.0"
"ch.qos.logback" % "logback-classic" % versions.logback,

For more information on logging with Finatra see: Introduction to Logging With Finatra.

Create a new class that extends c.t.finatra.thrift.ThriftServer:

import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServer {

  override def configureThrift(router: ThriftRouter): Unit = {
    ???
  }
}

Or in Java, that extends the c.t.finatra.thrift.AbstractThriftServer:

import com.twitter.finatra.thrift.AbstractThriftServer;
import com.twitter.finatra.thrift.routing.JavaThriftRouter;

public class ExampleServer extends AbstractThriftServer {

  @Override
  public void configureThrift(JavaThriftRouter router) {
    ...
  }
}

A more complete example includes adding Modules, a Controller, and Filters.

import DoEverythingModule
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.filters.{LoggingMDCFilter, TraceIdMDCFilter}
import com.twitter.finatra.thrift.routing.ThriftRouter

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServer {

  override val modules = Seq(
    DoEverythingModule)

  override def configureThrift(router: ThriftRouter): Unit = {
    router
      .filter[LoggingMDCFilter]
      .filter[TraceIdMDCFilter]
      .add[ExampleThriftController]
  }
}

in Java:

import DoEverythingModule;
import com.google.common.collect.ImmutableList;
import com.google.inject.Module;
import com.twitter.finatra.thrift.AbstractThriftServer;
import com.twitter.finatra.thrift.filters.LoggingMDCFilter;
import com.twitter.finatra.thrift.filters.TraceIdMDCFilter;
import com.twitter.finatra.thrift.routing.JavaThriftRouter;
import java.util.Collection;

public class ExampleServer extends AbstractThriftServer {

  @Override
  public Collection<Module> javaModules() {
    return ImmutableList.<Module>of(new DoEverythingModule());
  }

  @Override
  public void configureThrift(JavaThriftRouter router) {
    router
      .filter(LoggingMDCFilter.class)
      .filter(TraceIdMDCFilter.class)
      .add(ExampleThriftController.class);
  }
}

Tip

Note: to add Modules to your Java server override the javaModules() method.

This should look familiar as the structure is similar to creating an HttpServer. The server can be thought of as a collection of controllers composed with filters. Additionally, a server can define modules for providing instances to the object graph.

Using Generated Java Code

Finatra assumes that users are using the Scrooge Thrift code generator for generating code from a Thrift IDL. Scrooge allows for code generation in multiple languages but Finatra supports either generated Java or generated Scala code only.

If you choose to use generated Java code for your service (instead of generated Scala), you MUST extend the AbstractThriftServer and configure the JavaThriftRouter.

Most typically this is done when the server is being written in Java but is not exclusively so. This means:

  • if you write your server in Scala you can use either generated Java or generated Scala code.
  • if you write your server in Java you MUST use generated Java code.

Naming Convention

The Finatra convention is to create a Scala object with a name ending in “Main” that extends your server class. The server class can be used in testing as this allows your server to be instantiated multiple times in tests without worrying about static state persisting across test runs in the same JVM.

object ExampleServerMain extends ExampleServer

The static object, e.g., ExampleServerMain, which then contains a static main method for the server would then be used as the application entry point for running the server in all other cases.

Java Naming Convention

In Java you would create a separate “main” class which defines a main method and accepts args for flag parsing:

public final class ExampleServerMain {
    private ExampleServerMain() {
    }

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

This would be the class used as the application entry point for running the server.

Override Default Behavior

Flags

Some deployment environments may make it difficult to set Flag values with command line arguments. If this is the case, Finatra’s ThriftServer‘s core flags can be set from code.

For example, instead of setting the -thrift.port flag, you can override the following method in your server.

import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

class ExampleServer extends ThriftServer {

  override val defaultThriftPort: String = ":9090"

  override def configureThrift(router: ThriftRouter): Unit = {
    ???
  }
}

For a list of what flags can be set programmatically, please see the ThriftServer class.

For more information on using and setting command-line flags see Flags.

Finagle Server Configuration

If you want to further configure the underlying Finagle server you can override configureThriftServer in your server to specify additional configuration on (or override the default configuration of) the underlying Finagle server.

For example:

import com.twitter.finagle.ThriftMux
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

class ExampleServer extends ThriftServer {

  override def configureThrift(router: ThriftRouter): Unit = {
    ...
  }

  override def configureThriftServer(server: ThriftMux.Server): ThriftMux.Server = {
    server
      .withMaxRequestSize(???)
      .withAdmissionControl.concurrencyLimit(
        maxConcurrentRequests = ???,
        maxWaiters = ???)
  }
}

For more information on Finagle server configuration see the documentation here; specifically the server documentation here.

Server-side Response Classification

To configure server-side Response Classification you could choose to set the classifier directly on the underlying Finagle server by overriding the configureThriftServer in your server, e.g.,

override def configureThriftServer(server: ThriftMux.Server): ThriftMux.Server = {
    server.withResponseClassifier(???)
}

However, since the server-side ResponseClassifier could affect code not just at the Finagle level, we actually recommend overriding the specific framework module, ThriftResponseClassifierModule instead. This binds an instance of an ThriftResponseClassifier to the object graph that is then available to be injected into things like the Thrift StatsFilter for a more accurate reporting of metrics that takes into account server-side response classification.

For example, in your ThriftServer you would do:

import com.google.inject.Module
import com.twitter.finatra.http.HttpServer
import com.twitter.finatra.http.routing.HttpRouter

class ExampleServer extends ThriftServer {

  override thriftResponseClassifierModule: Module = ???
}

The bound value is also then set on the underlying Finagle server before serving.

Testing

For information on testing a Thrift server see the Thrift Server Feature Tests section.