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:
- Interface based remote call
Fault tolerance and load balancing
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:
- Eureka: Service registry
- Ribbon: Client side load balancer
- Zuul: Server side load balancing and routing
- 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:
- It is a Spring Boot application as it is annotated with
@SpringBootApplication -
We have enabled Dubbo’s component scan using the
@DubboComponentScanannotation. -
In our main method, we are telling Spring Boot that we do not want run this application as web application by passing
WebApplicationType.NONEflag.Next, we will create
DubboConfigclass. 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
providermodule.
$ 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×tamp=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.
Discover more from Shekhar Gulati
Subscribe to get the latest posts sent to your email.
I think the pom.xml in root folder need to be correct. Its same as provider’s. Can you kindly update it.