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
      • build.gradle
  • 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
  • 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的更多相关文章

  1. 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 ...

  2. 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 ...

  3. Spring Boot 2.X(十):自定义注册 Servlet、Filter、Listener

    前言 在 Spring Boot 中已经移除了 web.xml 文件,如果需要注册添加 Servlet.Filter.Listener 为 Spring Bean,在 Spring Boot 中有两种 ...

  4. 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 ...

  5. Spring Boot 整合 Apache Dubbo

    Apache Dubbo是一款高性能.轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 注意,是 Apache Dubb ...

  6. Spring Boot 整合 Apache Ignite

    关于Ignite的介绍,这边推荐三个链接进行学习了解. https://ignite.apache.org/,首选还是官网,不过是英文版,如果阅读比较吃力可以选择下方两个链接. https://www ...

  7. Microservices with Spring Boot

    找到一套比较不错的Spring Boot/Cloud入门教程,推荐一下. https://dzone.com/users/1041459/ranga_pec.html

  8. Spring Boot 学习系列(08)—自定义servlet、filter及listener

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的filter及listener配置 在传统的Java web项目中,servlet.filter和li ...

  9. Spring Boot 整合Web 层技术(整合Servlet)

    1 整合Servlet 方式一1.1通过注解扫描完成Servlet 组件的注册      1.1.1创建Servlet /*** 整合Servlet 方式一*/@WebServlet(name = & ...

随机推荐

  1. 【面试】MySQL的事务和索引

    MySQL事务 MySQL事务主要用于处理操作量大,复杂度高的数据. 比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这些数据库操作 ...

  2. vue前端框架面试问题汇总

    1.active-class是哪个组件的属性?嵌套路由怎么定义?答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么获取传过来的动态参数? 答: ...

  3. fastjson与各类型的转换

    参考:https://www.cnblogs.com/ceshi2016/p/7381478.html http://www.cnblogs.com/goody9807/p/4244862.html ...

  4. 如何消除原生Android网络状态上的惊叹号

    喜欢使用原生Android系统的朋友可能会发现自己的状态栏信号图标上经常有一个惊叹号标志. 这是怎么回事呢?原因是Android为了对网络状态进行检测,采用了一种叫做captive detection ...

  5. Tunnel Warfare(线段树取连续区间)

    emmmmmmmm我菜爆了 思路来自:https://blog.csdn.net/chudongfang2015/article/details/52133243 线段树最难的应该就是要维护什么东西 ...

  6. 洛谷 P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm

    题目描述 每年,在威斯康星州,奶牛们都会穿上衣服,收集农夫约翰在N(1<=N<=100,000)个牛棚隔间中留下的糖果,以此来庆祝美国秋天的万圣节. 由于牛棚不太大,FJ通过指定奶牛必须遵 ...

  7. .net core Include问题

    本文章为原创文章,转载请注明出处 当时不知道为什么这样写,可能是突然间脑子停止了转动,既然犯过这样的错误,就记录下来吧 错误示例 ).Include(a=>a.User).Select(a =& ...

  8. centos安装桌面,下面的几个包缺一不可

    yum groupinstall “X window system” yum groupinstall “Desktop” yum groupinstall “Chinese Support” 不然的 ...

  9. 第三方登陆——QQ登陆详解

    申请地址 QQ互联:https://connect.qq.com/index.html 腾讯开放平台:https://open.tencent.com/ 注册账号 登陆 进入QQ互联,点击登陆 资料填 ...

  10. BZOJ5210 最大连通子块和 【树链剖分】【堆】【动态DP】

    题目分析: 解决了上次提到的<切树游戏>后,这道题就是一道模板题. 注意我们需要用堆维护子重链的最大值.这样不会使得复杂度变坏,因为每个重链我们只考虑一个点. 时间复杂度$O(nlog^2 ...