LoginSignup
0
1

Spring Boot と Apache Thrift を使用したマイクロサービスの構築

Last updated at Posted at 2023-04-08

1. はじめに

現代のマイクロサービスの世界では、サービスに厳密で多言語のクライアントを提供することが重要です。 API が自己文書化されている方がよいでしょう。
そのための最適なツールの 1 つは Apache Thrift です。
私のお気に入りのマイクロサービス プラットフォームである Spring Boot での使用方法を説明したいと思います。

すべてのプロジェクトのソース コードは GitHub で入手できます
https://github.com/bsideup/spring-boot-thrift

2. 電卓アプリのテンプレートファイルを作成する

Apache Thrift に慣れていなくても、そのテンプレートファイルは非常にわかりやすいです。

calculate.thrift
namespace cpp com.example.calculator
namespace d com.example.calculator
namespace java com.example.calculator
namespace php com.example.calculator
namespace perl com.example.calculator
namespace as3 com.example.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);
}

3. テンプレートファイルからジェネレートする

ここでは、calculate という 1 つのメソッドだけを使用して TCalculatorService を定義します。
タイプ TDivisionByZeroException の例外をスローできます。
すぐに使用できる言語の数に注意してください。
(ただし、この例ではターゲットとして Java のみを使用します)

thrift --gen java calculate.thrift

4. Spring Boot での電卓アプリの設定する

簡単な電卓アプリから始めましょう。プロトコルとアプリの2 つのモジュールがあります。プロトコルから始めます。プロジェクトは次のようになります。

ここでは、Spring Boot Web アプリのプロトコルと典型的なスターターに依存しています。

pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.thrift</groupId>
        <artifactId>libthrift</artifactId>
        <version>${thrift.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

CalculatorApplication はメイン クラスです。 この例では、同じファイルで Spring を構成しますが、アプリでは代わりに別の構成クラスを使用する必要があります。

package com.example.calculator;

import com.example.calculator.handler.CalculatorServiceHandler;
import org.apache.thrift.protocol.*;
import org.apache.thrift.server.TServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.*;
import javax.servlet.Servlet;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class CalculatorApplication {
    public static void main(String[] args) {
        SpringApplication.run(CalculatorApplication.class, args);
    }

    @Bean
    public TProtocolFactory tProtocolFactory() {
        //We will use binary protocol, but it's possible to use JSON and few others as well
        return new TBinaryProtocol.Factory();
    }
    
    @Bean
    public ServletRegistrationBean <HttpServlet> stateServlet(TProtocolFactory protocolFactory, CalculatorServiceHandler handler) {
        ServletRegistrationBean <HttpServlet> servRegBean = new ServletRegistrationBean <>();
        servRegBean.setServlet(new TServlet(new TCalculatorService.Processor <>(handler), protocolFactory));
        servRegBean.addUrlMappings("/calculator/*");
        servRegBean.setLoadOnStartup(1);
        return servRegBean;
    }
}

ここでは、/calculator/ が Servlet で呼び出されるようにURLマッピングを使って設定をしています。

その後、Thrift ハンドラー クラスが必要です。

package com.example.calculator.handler;

import com.example.calculator.*;
import com.example.calculator.service.CalculatorService;
import org.apache.thrift.TException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CalculatorServiceHandler implements TCalculatorService.Iface {
    
    @Autowired
    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 であり、それに依存関係を注入できることを示したいと思います。

次に、CalculatorService 自体を実装する必要があります。

package com.example.calculator.service;

import org.springframework.stereotype.Component;

@Component
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;
    }
}

それで最後になります。
サービスを何らかの方法でテストする必要があります。

通常、アプリケーションが JSON REST API を提供している場合でも、そのためのクライアントを実装する必要があります。 倹約はあなたのためにそれを行います。 気にする必要はありません。 また、さまざまなプロトコルをサポートします。 生成されたクライアントをテストで使用してみましょう。

package com.example.calculator;

import org.apache.thrift.protocol.*;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.*;
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.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CalculatorApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
public class CalculatorApplicationTest {
    
    @Autowired
    protected TProtocolFactory protocolFactory;

    @Value("${local.server.port}")
    protected int port;
    
    protected TCalculatorService.Client client;

    @Before
    public void setUp() throws Exception {
        TTransport transport = new THttpClient("http://localhost:" + port + "/calculator/");
        
        TProtocol protocol = protocolFactory.getProtocol(transport);
        
        client = new TCalculatorService.Client(protocol);
    }

    @Test
    public void testAdd() throws Exception {
        assertEquals(5, client.calculate(2, 3, TOperation.ADD));
    }

    @Test
    public void testSubtract() throws Exception {
        assertEquals(3, client.calculate(5, 2, TOperation.SUBTRACT));
    }
    
    @Test
    public void testMultiply() throws Exception {
        assertEquals(10, client.calculate(5, 2, TOperation.MULTIPLY));
    }
    
    @Test
    public void testDivide() throws Exception {
        assertEquals(2, client.calculate(10, 5, TOperation.DIVIDE));
    }
    
    @Test(expected = TDivisionByZeroException.class)
    public void testDivisionByZero() throws Exception {
        client.calculate(10, 0, TOperation.DIVIDE);
    }
}

このテストでは、Spring Boot アプリケーションを実行し、ランダムなポートにバインドしてテストします。 すべてのクライアント/サーバー通信は、実際のクライアントと同じ方法で実行されます。

クライアント側からのサービスの使いやすさに注目してください。 メソッドを呼び出して例外をキャッチしているだけです。

5. 最後に

この記事では、Spring BootとApache Thriftを使用してマイクロサービスを構築する方法を説明しました。Apache Thriftは厳密で多言語のクライアントを提供し、APIが自己文書化される利点があります。簡単な電卓アプリケーションを例に、テンプレートファイルの作成、コード生成、Spring Bootでの設定、Thriftハンドラークラスの実装、およびテスト方法を示しました。これにより、クライアント/サーバー通信が実際のクライアントと同じ方法で実行され、サービスの使いやすさが向上します。

翻訳元サイト

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1