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
@DubboComponentScan
annotation. -
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×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.
I think the pom.xml in root folder need to be correct. Its same as provider’s. Can you kindly update it.