--- title: Apache ThriftのサーバーサイドをSpring Bootで書く tags: ["Java", "Spring", "Spring Boot", "Thrift"] categories: ["Programming", "Java", "org", "springframework", "boot"] date: 2015-01-03T15:00:05Z updated: 2015-01-03T15:00:05Z --- [Apache Thrift](http://thrift.apache.org/)のサーバーサイドをSpring Bootで書いてみます。 Spring Bootとの連携は基本的にThrift ServletをBean定義するだけです。Spring MVCは不要。 今回の題材は[公式チュートリアル](http://thrift.apache.org/tutorial/java)でthriftファイルはこんな感じ。 ``` java namespace cpp demo.calculator namespace java demo.calculator namespace php demo.calculator enum TOperation { ADD = 1, SUBTRACT = 2, MULTIPLY = 3, DIVIDE = 4 } exception TDivisionByZeroException { } service TCalculatorService { i32 calculate(1:i32 num1, 2:i32 num2, 3:TOperation op) throws (1:TDivisionByZeroException divisionByZero); } ``` 依存関係は ``` xml org.springframework.boot spring-boot-starter-web org.springframework spring-webmvc org.apache.thrift libthrift ${thrift.version} org.springframework.boot spring-boot-starter-test test ``` でSpring MVCを抜きます(起動を軽くするため)。 エントリポイント兼Bean定義は以下のようにします。 ``` java package demo; import demo.calculator.TCalculatorService; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apache.thrift.server.TServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean TProtocolFactory protocolFactory() { return new TBinaryProtocol.Factory(); } @Bean /* Register Thrift Service */ TServlet calculator(TCalculatorService.Iface calcService) { return new TServlet(new TCalculatorService.Processor<>(calcService), protocolFactory()); } } ``` あとは`TCalculatorService.Iface`の実装を書いてコンポーネントスキャン対象にすればOK。 ``` java package demo; import demo.calculator.TCalculatorService; import demo.calculator.TDivisionByZeroException; import demo.calculator.TOperation; import org.apache.thrift.TException; import org.springframework.stereotype.Component; import java.util.EnumMap; import java.util.function.IntBinaryOperator; @Component public class CalculatorServiceImpl implements TCalculatorService.Iface { private static final EnumMap opMap = new EnumMap(TOperation.class) {{ put(TOperation.ADD, (x, y) -> x + y); put(TOperation.SUBTRACT, (x, y) -> x - y); put(TOperation.MULTIPLY, (x, y) -> x * y); put(TOperation.DIVIDE, (x, y) -> x / y); }}; @Override public int calculate(int num1, int num2, TOperation op) throws TException { if (opMap.containsKey(op)) { try { return opMap.get(op).applyAsInt(num1, num2); } catch (ArithmeticException e) { throw new TDivisionByZeroException(); } } else { throw new TException("Unknown operation " + op); } } } ``` 例外ハンドリングが雑。当然ここでSpringのDI機能をフルに活用できます。 これで`mvn spring-boot:run`で実行できますし、`mvn package`で**thrift serverの実行可能jar**ができます。 結合テストも簡単。 ``` java package demo; import demo.calculator.TCalculatorService; import demo.calculator.TDivisionByZeroException; import demo.calculator.TOperation; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransport; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) @WebAppConfiguration @IntegrationTest({"server.port:0"}) public class CalculatorTest { @Autowired TProtocolFactory protocolFactory; @Value("${local.server.port}") int port; TCalculatorService.Client client; @Before public void setUp() throws Exception { TTransport transport = new THttpClient("http://localhost:" + port + "/calculator"); TProtocol protocol = protocolFactory.getProtocol(transport); this.client = new TCalculatorService.Client(protocol); } @Test public void testAdd() throws Exception { assertThat(client.calculate(2, 3, TOperation.ADD), is(5)); } @Test public void testSubtract() throws Exception { assertThat(client.calculate(2, 3, TOperation.SUBTRACT), is(-1)); } @Test public void testMultiply() throws Exception { assertThat(client.calculate(2, 3, TOperation.MULTIPLY), is(6)); } @Test public void testDivide() throws Exception { assertThat(client.calculate(10, 5, TOperation.DIVIDE), is(2)); } @Test(expected = TDivisionByZeroException.class) public void testDivisionByZero() throws Exception { client.calculate(10, 0, TOperation.DIVIDE); } } ``` Thrift ServiceをSpring Boot実行可能jar方式にするのとても良いと思います。 ソースコードは[Github](https://github.com/making/thrift-server)に。 元ネタは[こちら](http://java.dzone.com/articles/building-microservices-spring)でした。