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

转载于:https://www.cnblogs.com/bigben0123/p/7483810.html

Pro Spring Boot 2: An Authoritative Guide to Building Microservices, Web and Enterprise Applications, and Best Practices Quickly and productively develop complex Spring applications and microservices out of the box, with minimal concern over things like configurations. This revised book will show you how to fully leverage the Spring Boot 2 technology and how to apply it to create enterprise ready applications that just work. It will also cover what's been added to the new Spring Boot 2 release, including Spring Framework 5 features like WebFlux, Security, Actuator and the new way to expose Metrics through Micrometer framework, and more. This book is your authoritative hands-on practical guide for increasing your enterprise Java and cloud application productivity while decreasing development time. It's a no nonsense guide with case studies of increasing complexity throughout the book. The author, a senior solutions architect and Principal Technical instructor with Pivotal, the company behind the Spring Framework, shares his experience, insights and first-hand knowledge about how Spring Boot technology works and best practices. Pro Spring Boot 2 is an essential book for your Spring learning and reference library. What You Will Learn Configure and use Spring Boot Use non-functional requirements with Spring Boot Actuator Carry out web development with Spring Boot Persistence with JDBC, JPA and NoSQL Databases Messaging with JMS, RabbitMQ and WebSockets Test and deploy with Spring Boot A quick look at the Spring Cloud projects Microservices and deployment to the Cloud Extend Spring Boot by creating your own Spring Boot Starter and @Enable feature Who This Book Is For Experienced Spring and Java developers seeking increased productivity gains and decreased complexity and development time in their applications and software services.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值