Building Microservices with Spring Boot and Apache Thrift. Part 1 with servlet
https://dzone.com/articles/building-microservices-spring
In the modern world of microservices it's important to provide strict and polyglot clients for your service. It's better if your API is self-documented. One of the best tools for it is Apache Thrift. I want to explain how to use it with my favorite platform for microservices - Spring Boot.
All project source code is available on GitHub: https://github.com/bsideup/spring-boot-thrift
Project skeleton
I will use Gradle to build our application. First, we need our main build.gradle file:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE")
}
}
allprojects {
repositories {
jcenter()
}
apply plugin:'base'
apply plugin: 'idea'
}
subprojects {
apply plugin: 'java'
}
Nothing special for a Spring Boot project. Then we need a gradle file for thrift protocol modules (we will reuse it in next part):
import org.gradle.internal.os.OperatingSystem
repositories {
ivy {
artifactPattern "http://dl.bintray.com/bsideup/thirdparty/[artifact]-[revision](-[classifier]).[ext]"
}
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "ru.trylogic.gradle.plugins:gradle-thrift-plugin:0.1.1"
}
}
apply plugin: ru.trylogic.gradle.thrift.plugins.ThriftPlugin
task generateThrift(type : ru.trylogic.gradle.thrift.tasks.ThriftCompileTask) {
generator = 'java:beans,hashcode'
destinationDir = file("generated-src/main/java")
}
sourceSets {
main {
java {
srcDir generateThrift.destinationDir
}
}
}
clean {
delete generateThrift.destinationDir
}
idea {
module {
sourceDirs += [file('src/main/thrift'), generateThrift.destinationDir]
}
}
compileJava.dependsOn generateThrift
dependencies {
def thriftVersion = '0.9.1';
Map platformMapping = [
(OperatingSystem.WINDOWS) : 'win',
(OperatingSystem.MAC_OS) : 'osx'
].withDefault { 'nix' }
thrift "org.apache.thrift:thrift:$thriftVersion:${platformMapping.get(OperatingSystem.current())}@bin"
compile "org.apache.thrift:libthrift:$thriftVersion"
compile 'org.slf4j:slf4j-api:1.7.7'
}
We're using my Thrift plugin for Gradle. Thrift will generate source to the "generated-src/main/java" directory. By default, Thrift uses slf4j v1.5.8, while Spring Boot uses v1.7.7. It will cause an error in runtime when you will run your application, that's why we have to force a slf4j api dependency.
Calculator service
Let's start with a simple calculator service. It will have 2 modules: protocol and app.We will start with protocol. Your project should look as follows:
- calculator/
- protocol/
- src/
- main/
- thrift/
- calculator.thrift
- thrift/
- main/
- build.gradle
- src/
- protocol/
- build.gradle
- settings.gradle
- thrift.gradle
Where calculator/protocol/build.gradle contains only one line:
apply from: rootProject.file('thrift.gradle')
Don't forget to put these lines to settings.gradle, otherwise your modules will not be visible to Gradle:
include 'calculator:protocol'
include 'calculator:app'
Calculator protocol
Even if you're not familiar with Thrift, its protocol description file (calculator/protocol/src/main/thrift/calculator.thrift) should be very clear to you:
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);
}
Here we define TCalculatorService with only one method - calculate. It can throw an exception of type TDivisionByZeroException. Note how many languages we're supporting out of the box (in this example we will use only Java as a target, though)
Now run ./gradlew generateThrift, you will get generated Java protocol source in thecalculator/protocol/generated-src/main/java/ folder.
Calculator application
Next, we need to create the service application itself. Just create calculator/app/ folder with the following structure:
- src/
- main/
- java/
- com/
- example/
- calculator/
- handler/
- CalculatorServiceHandler.java
- service/
- CalculatorService.java
- CalculatorApplication.java
- handler/
- calculator/
- example/
- com/
- java/
- main/
- build.gradle
Our build.gradle file for app module should look like this:
apply plugin: 'spring-boot'
dependencies {
compile project(':calculator:protocol')
compile 'org.springframework.boot:spring-boot-starter-web'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
Here we have a dependency on protocol and typical starters for Spring Boot web app.
CalculatorApplication is our main class. In this example I will configure Spring in the same file, but in your apps you should use another config class instead.
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 Servlet calculator(TProtocolFactory protocolFactory, CalculatorServiceHandler handler) {
return new TServlet(new TCalculatorService.Processor<CalculatorServiceHandler>(handler), protocolFactory);
}
}
You may ask why Thrift servlet bean is called "calculator". In Spring Boot, it will register your servlet bean in context of the bean name and our servlet will be available at /calculator/.
After that we need a Thrift handler class:
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);
}
}
}
In this example I want to show you that Thrift handler can be a normal Spring bean and you can inject dependencies in it.
Now we need to implement CalculatorService itself:
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;
}
}
That's it. Well... almost. We still need to test our service somehow. And it should be an integration test.
Usually, even if your application is providing JSON REST API, you still have to implement a client for it. Thrift will do it for you. We don't have to care about it. Also, it will support different protocols. Let's use a generated client in our test:
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);
}
}
This test will run your Spring Boot application, bind it to a random port and test it. All client-server communications will be performed in the same way real world clients are.
Note how easy to use our service is from the client side. We're just calling methods and catching exceptions.
Building Microservices with Spring Boot and Apache Thrift. Part 1 with servlet的更多相关文章
- Building Microservices with Spring Boot and Apache Thrift. Part 2. Swifty services
http://bsideup.blogspot.com/2015/04/spring-boot-thrift-part2.html In previous article I showed y ...
- Quick Guide to Microservices with Spring Boot 2.0, Eureka and Spring Cloud
https://piotrminkowski.wordpress.com/2018/04/26/quick-guide-to-microservices-with-spring-boot-2-0-eu ...
- Spring Boot 2.X(十):自定义注册 Servlet、Filter、Listener
前言 在 Spring Boot 中已经移除了 web.xml 文件,如果需要注册添加 Servlet.Filter.Listener 为 Spring Bean,在 Spring Boot 中有两种 ...
- Building microservices with Spring Cloud and Netflix OSS, part 2
In Part 1 we used core components in Spring Cloud and Netflix OSS, i.e. Eureka, Ribbon and Zuul, to ...
- Spring Boot 整合 Apache Dubbo
Apache Dubbo是一款高性能.轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 注意,是 Apache Dubb ...
- Spring Boot 整合 Apache Ignite
关于Ignite的介绍,这边推荐三个链接进行学习了解. https://ignite.apache.org/,首选还是官网,不过是英文版,如果阅读比较吃力可以选择下方两个链接. https://www ...
- Microservices with Spring Boot
找到一套比较不错的Spring Boot/Cloud入门教程,推荐一下. https://dzone.com/users/1041459/ranga_pec.html
- Spring Boot 学习系列(08)—自定义servlet、filter及listener
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的filter及listener配置 在传统的Java web项目中,servlet.filter和li ...
- Spring Boot 整合Web 层技术(整合Servlet)
1 整合Servlet 方式一1.1通过注解扫描完成Servlet 组件的注册 1.1.1创建Servlet /*** 整合Servlet 方式一*/@WebServlet(name = & ...
随机推荐
- Day 4-9 subprocess模块
我们经常需要通过Python去执行一条系统命令或脚本,系统的shell命令是独立于你的python进程之外的,每执行一条命令,就是发起一个新进程,通过python调用系统命令或脚本的模块在python ...
- C# Note24: 指针的使用
C#为了类型安全,默认并不支持指针.但是也并不是说C#不支持指针,我们可以使用unsafe关键词,开启不安全代码(unsafe code)开发模式.在不安全模式下,我们可以直接操作内存,这样就可以使用 ...
- HTML5经典实例——1基础语法和语义
1指定DOCTYPE 在页面的最开始处指定HTML5 DOCTYPE DOCTYPE是不区分大小写的.可以任意的使用大小写. <!DOCTYPE html> <html lang=& ...
- hive子查询
如果集合中含有空值,不能使用not in的语法指令:但是可以使用in
- ssm框架整合配置,用maven配置依赖jar包
1.创建maven project 首先在pom.xml中指定工程所依赖的jar包 <project xmlns="http://maven.apache.org/POM/4.0.0& ...
- tomcat 和jboss区别
参考http://blog.csdn.net/sz_bdqn/article/details/6762175
- vuex2.0 基本使用(1) --- state
Vuex 的核心是 store, 它是一个通过 Vuex.Store 构造函数生成的对象.为什么它会是核心呢?因为我们调用这个构造函数创建store 对象的时候,给它传递参数中包装了state, mu ...
- Linux下git的使用——将已有项目放到github上
本地已经有一个项目了,需要将该项目放到github上,怎么操作? 步骤: 本地安装git,有github账号是前提. (1)先在github创建一个空的仓库,并复制链接地址.使用https,以.git ...
- 部署 Django
补充说明:关于项目部署,历来是开发和运维人员的痛点.造成部署困难的主要原因之一是大家的Linux环境不同,这包括发行版.解释器.插件.运行库.配置.版本级别等等太多太多的细节.因此,一个成功的部署案例 ...
- BZOJ1823[JSOI2010]满汉全席——2-SAT+tarjan缩点
题目描述 满汉全席是中国最丰盛的宴客菜肴,有许多种不同的材料透过满族或是汉族的料理方式,呈现在數量繁多的菜色之中.由于菜色众多而繁杂,只有极少數博学多闻技艺高超的厨师能够做出满汉全席,而能够烹饪出经过 ...