Getting started with Apache Dubbo and Spring Boot


This week I decided to play with Apache Dubbo. I follow Github trending repositories daily and for many weeks and months Apache Dubbo is one of their popular Github Java repository. It has more than 20,000 stars. So, I decided to give it a shot. One of the reasons for Dubbo popularity is that it is created by software engineers at Alibaba. Alibaba is a Chinese multinational conglomerate specializing in e-commerce, retail, Internet, AI and technology.

From its website, Apache Dubbo is

A high-performance, light weight, java based RPC framework. Dubbo offers three key functionalities:

  1. Interface based remote call

  2. Fault tolerance and load balancing

  3. Automatic service registration & discovery

From the description on Apache Dubbo website, it looks like a framework that can be used to handle communication in Microservices architecture.

If you are aware of Netflix OSS stack then in my view Apache Dubbo does the role of following:

  1. Eureka: Service registry
  2. Ribbon: Client side load balancer
  3. Zuul: Server side load balancing and routing
  4. Feign: Interface based client

In this post, I will show you how to get started with Apache Dubbo and Spring Boot. We will build a simple Spring Boot Microservices app. Microservices will talk to each other using Dubbo.

I found Apache Dubbo Spring Boot auto configure approach broken. Also, document is not easy to follow. So, I hope this post will help you get started with building apps using Dubbo and Spring Boot.

Step 1: Create a multi-modular Spring Boot Maven project

We will create directory structure as shown below.

└── calculator-app
    ├── api
    │   └── src
    │       ├── main
    │       │   └── java
    │       └── test
    │           └── java
    ├── consumer
    │   └── src
    │       ├── main
    │       │   └── java
    │       └── test
    │           └── java
    └── provider
        └── src
            ├── main
            │   └── java
            └── test
                └── java

19 directories, 0 files

To create this structure, we will use following command:

$ mkdir -p calculator-app/{api,consumer,provider}/src/{main,test}/java

Next, create a new file pom.xml inside the root project and populate it with following content.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>calculator-app</artifactId>
        <groupId>com.calculatorapp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.calculatorapp</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.3.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Next, create pom.xml under api module with the following content

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>calculator-app</artifactId>
        <groupId>com.calculatorapp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api</artifactId>


</project>

Then, create pom.xml under provider module with the following content

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>calculator-app</artifactId>
        <groupId>com.calculatorapp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.calculatorapp</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    </dependencies>

    <!--<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.3.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>-->

</project>

Finally, create pom.xml under consumer module

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>calculator-app</artifactId>
        <groupId>com.calculatorapp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.calculatorapp</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    </dependencies>

    <!--<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.3.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>-->

</project>

You can build the project to ensure everything is working:

$ mvn clean install
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] calculator-app ..................................... SUCCESS [  0.285 s]
[INFO] calculator-api ..................................... SUCCESS [  0.807 s]
[INFO] calculator-consumer ................................ SUCCESS [  0.330 s]
[INFO] calculator-provider ................................ SUCCESS [  0.048 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.569 s
[INFO] Finished at: 2018-12-14T20:13:00-07:00
[INFO] Final Memory: 17M/309M
[INFO] ------------------------------------------------------------------------

Step 2: Create service interface in api module

Dubbo is an RPC framework that allow multiple services to talk to each other. It supports interface based remote calls. So, the API interface is shared between consumer and provider. To share interface between provider and consumer, we will create our interface in the api module.

Create an interface Calculator service inside a package com.calculatorapp.api

package com.calculatorapp.api;

public interface CalculatorService {

  int add(int a, int b);

}

Step 2: Create the service implementation in provider module

The provider module will be a Spring Boot application. It will provide implementation of the CalculatorService interface. It will not be an HTTP service but will use Dubbo own protocol called dubbo.

Create the CalculatorProviderApplication and populate it with following content

import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
@DubboComponentScan(basePackages = "com.calculatorapp.provider")
public class CalculatorProviderApplication {

  public static void main(String[] args) {

    new SpringApplicationBuilder(CalculatorProviderApplication.class)
      .web(WebApplicationType.NONE)
      .run(args);

  }

}

As you can see in the above code snippet:

  1. It is a Spring Boot application as it is annotated with @SpringBootApplication

  2. We have enabled Dubbo’s component scan using the @DubboComponentScan annotation.

  3. In our main method, we are telling Spring Boot that we do not want run this application as web application by passing WebApplicationType.NONE flag.

    Next, we will create DubboConfig class. This class creates the beans that are required by Dubbo. Spring Boot Dubbo project currently does not auto configure these beans so we have to manually do it for now.

    import com.alibaba.dubbo.config.ApplicationConfig;
    import com.alibaba.dubbo.config.ProtocolConfig;
    import com.alibaba.dubbo.config.RegistryConfig;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class DubboConfig {
    
     @Bean
     public ApplicationConfig applicationConfig() {
       ApplicationConfig applicationConfig = new ApplicationConfig();
       applicationConfig.setName("calculator-provider");
       return applicationConfig;
     }
    
     @Bean
     public RegistryConfig registryConfig() {
       RegistryConfig registryConfig = new RegistryConfig();
       registryConfig.setAddress("N/A");
       return registryConfig;
     }
    
     @Bean
     public ProtocolConfig protocolConfig() {
       ProtocolConfig protocolConfig = new ProtocolConfig();
       protocolConfig.setId("dubbo");
       protocolConfig.setName("dubbo");
       protocolConfig.setPort(12345);
       return protocolConfig;
     }
    
    }
    

Finally, we will create the CalculatorService implementation as shown below.

import com.alibaba.dubbo.config.annotation.Service;
import com.calculatorapp.api.CalculatorService;

@Service(timeout = 5000, version = "0.1.0")
public class SimpleCalculator implements CalculatorService {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

}

The only important thing to note is the use of @Service annotation. We configured the service timeout as well as version of the service.

Now, we are down with implementation of our service. You can run the application from command-line by first building the project and then running the executable jar.

Please make sure Spring Boot plugin is enabled for provider module.

$ java -jar provider-1.0-SNAPSHOT.jar

The output will be as shown below.

:12345/com.calculatorapp.api.CalculatorService?anyhost=true&application=calculator-provider&bean.name=ServiceBean:com.calculatorapp.api.CalculatorService:0.1.0&bind.ip=172.20.1.107&bind.port=12345&dubbo=2.0.2&generic=false&interface=com.calculatorapp.api.CalculatorService&methods=add&pid=47997&revision=0.1.0&side=provider&timeout=5000&timestamp=1544845493665&version=0.1.0, dubbo version: 2.6.5, current host: 172.20.1.107
2018-12-14 20:44:53.868  INFO 47997 --- [           main] c.a.d.remoting.transport.AbstractServer  :  [DUBBO] Start NettyServer bind /0.0.0.0:12345, export /172.20.1.107:12345, dubbo version: 2.6.5, current host: 172.20.1.107
2018-12-14 20:44:53.885  INFO 47997 --- [           main] c.c.p.CalculatorProviderApplication      : Started CalculatorProviderApplication in 1.362 seconds (JVM running for 1.779)
2018-12-14 20:44:53.888  INFO 47997 --- [pool-1-thread-1] .d.c.e.AwaitingNonWebApplicationListener :  [Dubbo] Current Spring Boot Application is await...

Important thing to note is that service is available at 172.20.1.107:12345.

Step 3: Create the consumer application

Now, we will write the consumer side of this application. We will expose a REST API that will invoke the CalculatorService.

We will start by creating the CalculatorConsumerApplication

import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
@DubboComponentScan(basePackages = "calculator.consumer")
public class CalculatorConsumerApplication {

  public static void main(String[] args) {
    new SpringApplicationBuilder(CalculatorConsumerApplication.class)
      .run(args);
  }
}

Next, we will create the DubboConfig class

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.RegistryConfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DubboConfig {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("consumer-test");
        return applicationConfig;
    }

    @Bean
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setTimeout(3000);
        return consumerConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        return registryConfig;
    }

}

Finally, we will write a REST API that will invoke the service implementation.

import com.alibaba.dubbo.config.annotation.Reference;
import com.calculatorapp.api.CalculatorService;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CalculatorApi {

  @Reference(
    version = "0.1.0",
    application = "calculator-consumer",
    url = "dubbo://localhost:12345"
  )
  private CalculatorService calculatorService;

  @GetMapping(path = "/add")
  public int add(@RequestParam("a") int a, @RequestParam("b") int b) {
    return calculatorService.add(a, b);
  }

}

As you can see above, we referenced our CalculatorService. We provided the url at which service is available.

The rest controller gets input as query parameter which it passes it to CalculatorService and return the response to the user.

To run the consumer application, you need to first uncomment spring-boot plugin and then run the executable jar.

$ java -jar consumer-1.0-SNAPSHOT.jar

The output will be as shown below

2018-12-14 20:52:47.911  INFO 48658 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/add],methods=[GET]}" onto public int com.calculatorapp.consumer.CalculatorApi.add(int,int)
2018-12-14 20:52:47.914  INFO 48658 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-12-14 20:52:47.915  INFO 48658 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-12-14 20:52:47.939  INFO 48658 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-14 20:52:47.939  INFO 48658 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-14 20:52:48.097  INFO 48658 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-12-14 20:52:48.136  INFO 48658 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-14 20:52:48.140  INFO 48658 --- [           main] c.c.c.CalculatorConsumerApplication      : Started CalculatorConsumerApplication in 3.092 seconds (JVM running for 3.575)

Step 4: Test the application

Now, we can test the application. Please ensure both consumer and provider modules are running.

We can use cURL to make the REST API call.

$ curl http://localhost:8080/add?a=1&b=2
3

As you can see, we made a call to add 1 and 2, our REST API made an RPC call to SimpleCalculator to add two numbers.

Conclusion

Dubbo provides a lot of features that we can use to build Microservices based application. This is quick and dirty post on how to get started with Dubbo and Spring Boot. In the next post, we will dig deeper into Dubbo by building an end to end application.

1 thought on “Getting started with Apache Dubbo and Spring Boot

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s