使用Spring Boot和Apache Thrift构建微服务

首先

在现代的微服务世界中,为服务提供严格和多语言的客户端非常重要。最好的情况是API能够自我文档化。其中一个最佳工具是Apache Thrift。我想介绍在我最喜欢的微服务平台Spring Boot中使用Apache Thrift的方法。

所有项目的源代码都可以在 GitHub 上获得
https://github.com/bsideup/spring-boot-thrift

2. 制作一个电子计算器应用的模板文件

即使对于不熟悉Apache 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);
}

利用模板文件生成

在这里,我们只使用一个名为”calculate”的方法来定义TCalculatorService。
可以抛出TDivisionByZeroException类型的异常。
请注意,我们只使用了一种可以立即使用的语言。
(注意,这个例子只使用Java作为目标语言)

thrift --gen java calculate.thrift

使用Spring Boot设置计算器应用程序

让我们从一个简单的计算器应用开始吧。这个项目有两个模块,一个是协议,另一个是应用程序。我们从协议开始吧。

在这里,我们依赖于Spring Boot Web应用程序的协议和典型的启动器。

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

在这里,我们使用URL映射进行配置,以便通过Servlet调用/calculator/。

之後需要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处理器类的实现以及测试方法。通过这样做,客户端/服务器通信可以以与实际客户端相同的方式执行,从而提高了服务的易用性。

原文来源

 

广告
将在 10 秒后关闭
bannerAds