Apache ThriftとSpring Bootでマイクロサービスを構築する
概要
マイクロサービスを構築するうえでは、厳密なサービス契約と多言語クライアントを提供することが重要である。Apache ThriftはIDLでAPIを定義でき、API自体をドキュメントとして扱いやすいため、この目的に適したツールである。
ここでは、Spring BootアプリケーションにApache Thriftを適用する方法を説明する。
Spring Bootプロジェクトを作成する
curlコマンドでSpring Bootの初期プロジェクトを作成する。
curl https://start.spring.io/starter.tgz \
-d bootVersion=2.7.12 \
-d dependencies=web \
-d baseDir=spring-thrift \
-d groupId=com.devkuma \
-d artifactId=spring-thrift \
-d packageName=com.devkuma.calculator \
-d applicationName=CalculatorApplication \
-d packaging=jar \
-d javaVersion=11 \
-d type=gradle-project | tar -xzvf -
このコマンドにより、Java 11、Spring Boot 2.7.12ベースのWebプロジェクトが生成される。
Gradleビルドスクリプト
Thriftライブラリを追加する
build.gradleにThriftとhttpcoreのライブラリを追加する。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.thrift:libthrift:0.18.1'
implementation 'org.apache.httpcomponents:httpcore:4.4.16'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Thrift Plugin
Gradle Thriftプラグインは、Thriftコンパイラを使ってIDLファイルをコンパイルする。
plugins {
id "org.jruyi.thrift" version "0.4.2"
}
compileThrift {
recurse true
generator 'html'
generator 'java', 'private-members'
}
compileThriftを実行すると、生成されたJavaファイルは次のディレクトリに作成される。
build/generated-sources/thrift
Thriftテンプレート
src/main/thrift/calculate.thriftを作成する。
namespace java com.devkuma.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);
}
ここでは、calculateメソッドを1つ持つTCalculatorServiceを定義している。このメソッドはTDivisionByZeroExceptionを送出できる。
生成されるファイルには、TCalculatorService.java、TDivisionByZeroException.java、TOperation.javaが含まれる。
クラスを作成する
サービスクラス
まず、計算ロジックを通常のSpringサービスとして実装する。
@Service
public class CalculatorService {
public int add(int num1, int num2) { return num1 + num2; }
public int subtract(int num1, int num2) { return num1 - num2; }
public int multiply(int num1, int num2) { return num1 * num2; }
public int divide(int num1, int num2) {
if (num2 == 0) {
throw new IllegalArgumentException("num2 must not be zero");
}
return num1 / num2;
}
}
Thriftハンドラクラス
次に、生成されたTCalculatorService.Ifaceを実装するハンドラを作成する。
@Component
public class CalculatorServiceHandler implements TCalculatorService.Iface {
@Autowired
private CalculatorService calculatorService;
@Override
public int calculate(int num1, int num2, TOperation op) throws TException {
switch (op) {
case ADD: return calculatorService.add(num1, num2);
case SUBTRACT: return calculatorService.subtract(num1, num2);
case MULTIPLY: return calculatorService.multiply(num1, num2);
case DIVIDE:
try {
return calculatorService.divide(num1, num2);
} catch (IllegalArgumentException e) {
throw new TDivisionByZeroException();
}
default:
throw new TException("Unknown operation " + op);
}
}
}
Thriftハンドラは通常のSpring Beanなので、依存性注入を利用できる。
Thrift設定クラス
TServletを登録し、/calculator/*へマッピングする。
@Configuration
public class ThriftConfig {
@Bean
public TProtocolFactory tProtocolFactory() {
return new TBinaryProtocol.Factory();
}
@Bean
public ServletRegistrationBean<HttpServlet> stateServlet(
TProtocolFactory tProtocolFactory,
CalculatorServiceHandler handler) {
ServletRegistrationBean<HttpServlet> bean = new ServletRegistrationBean<>();
bean.setServlet(new TServlet(
new TCalculatorService.Processor<>(handler), tProtocolFactory));
bean.addUrlMappings("/calculator/*");
bean.setLoadOnStartup(1);
return bean;
}
}
テストコード
アプリケーションがJSON REST APIを提供する場合でも、通常はクライアント実装が必要になる。ThriftではクライアントAPIが生成されるため、この作業が簡単になる。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class CalculatorApplicationTests {
@Autowired
protected TProtocolFactory protocolFactory;
@Value("${local.server.port}")
protected int port;
protected TCalculatorService.Client client;
@BeforeEach
public void setUp() throws Exception {
TTransport transport =
new THttpClient("http://localhost:" + port + "/calculator/");
TProtocol protocol = protocolFactory.getProtocol(transport);
client = new TCalculatorService.Client(protocol);
}
}
テストでは、生成されたクライアントメソッドを呼び出して結果を確認し、ゼロ除算ではTDivisionByZeroExceptionを検証する。通信は実際のクライアント・サーバー呼び出しと同じ方法で行われる。
まとめ
ここでは、Spring BootとApache Thriftを使ってマイクロサービスを構築する方法を説明した。計算機アプリケーションを例に、IDL作成、コード生成、Spring Boot設定、ハンドラ実装、テストまでを扱った。Apache Thriftを使うと、厳密な契約、多言語クライアント、自己文書化されたAPIを提供できる。
参考
- Building Microservices with Spring Boot and Apache Thrift. Part 1
- jruyi/thrift-gradle-plugin | GitHub