---
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)でした。