Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

About Spring 5 Reactive Microservices

DZone's Guide to

About Spring 5 Reactive Microservices

The Spring initiative with support for reactive programming seems promising, but it's still in the early stages of development.

Free Resource

Build APIs from SQL and NoSQL or Salesforce data sources in seconds. Read the Creating REST APIs white paper, brought to you in partnership with CA Technologies.

The Spring team has announced support for reactive programming model from 5.0 release. The new Spring version will probably be released in March. Fortunately, milestone and snapshot versions with these changes are now available on public Spring repositories. There is a new Spring Web Reactive project with support for reactive @Controller and a new WebClient with client-side reactive support. Today, I'm going to take a closer look at solutions suggested by Spring team.

Following this documentation, the Spring Framework uses Reactor internally for its own reactive support. Reactor is a Reactive Streams implementation that further extends the basic Reactive Streams Publisher contract with the Flux and  Mono composable API types to provide declarative operations on data sequences of 0..N and 0..1. On the server side, Spring supports annotation based and functional programming models. Annotation model use @Controller and the other annotations supported also with Spring MVC. Reactive controller will be very similar to standard REST controller for synchronous services instead of it uses Flux,  Monoand  Publisher objects. Today I'm going to show you how to develop simple reactive microservices using annotation model and MongoDB reactive module. Sample application source code is available here.

For our example, we need to use snapshots of Spring Boot 2.0.0 and Spring Web Reactive 0.1.0. The main pom.xml fragment and single microservice pom.xml are below. In our microservices, we use Netty instead of default Tomcat server.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
    <dependency>
      <groupId>org.springframework.boot.experimental</groupId>
      <artifactId>spring-boot-dependencies-web-reactive</artifactId>
      <version>0.1.0.BUILD-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot.experimental</groupId>
    <artifactId>spring-boot-starter-web-reactive</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>io.projectreactor.ipc</groupId>
    <artifactId>reactor-netty</artifactId>
  </dependency>
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
  </dependency>
  <dependency>
    <groupId>pl.piomin.services</groupId>
    <artifactId>common</artifactId>
    <version>${project.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.projectreactor.addons</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

We have two microservices: account-service and customer-service. Each of them has its own MongoDB database and they are exposing simple reactive API for searching and saving data. Also, customer service interacts with account service to get all customer accounts and return them in via the customer service API method. Here's our account controller code.

@RestController
public class AccountController {

  @Autowired
  private AccountRepository repository;

  @GetMapping(value = "/account/customer/{customer}")
  public Flux<Account> findByCustomer(@PathVariable("customer") Integer customerId) {
    return repository.findByCustomerId(customerId)
    .map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
  }

  @GetMapping(value = "/account")
  public Flux<Account> findAll() {
  return repository.findAll().map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
  }

  @GetMapping(value = "/account/{id}")
  public Mono<Account> findById(@PathVariable("id") Integer id) {
    return repository.findById(id)
    .map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
  }

  @PostMapping("/person")
  public Mono<Account> create(@RequestBody Publisher<Account> accountStream) {
    return repository
    .save(Mono.from(accountStream)
    .map(a -> new pl.piomin.services.account.model.Account(a.getNumber(), a.getCustomerId(),
    a.getAmount())))
    .map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
  }

}

In all API methods, we also perform mapping from Account entity (MongoDB @Document) to Account DTO available in our common module. Here's account repository class. It uses ReactiveMongoTemplate for interacting with Mongo collections.

@Repository
public class AccountRepository {

  @Autowired
  private ReactiveMongoTemplate template;

  public Mono<Account> findById(Integer id) {
  return template.findById(id, Account.class);
  }

  public Flux<Account> findAll() {
  return template.findAll(Account.class);
  }

  public Flux<Account> findByCustomerId(String customerId) {
  return template.find(query(where("customerId").is(customerId)), Account.class);
  }

  public Mono<Account> save(Mono<Account> account) {
  return template.insert(account);
  }

}

In our Spring Boot main or @Configuration class we should declare spring beans for MongoDB with connection settings.

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
  }

  public @Bean MongoClient mongoClient() {
  return MongoClients.create("mongodb://192.168.99.100");
  }

  public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
  return new ReactiveMongoTemplate(mongoClient(), "account");
  }

}

I used docker MongoDB container for working on this sample.

docker run -d --name mongo -p 27017:27017 mongo

In customer service we call endpoint /account/customer/{customer} from account service. I declared  @Bean   WebClient  in our main class.

@Autowired
private WebClient webClient;

@GetMapping(value = "/customer/accounts/{pesel}")
public Mono<Customer> findByPeselWithAccounts(@PathVariable("pesel") String pesel) {
return repository.findByPesel(pesel).flatMap(customer -> webClient.get().uri("/account/customer/{customer}", customer.getId()).accept(MediaType.APPLICATION_JSON)
.exchange().flatMap(response -> response.bodyToFlux(Account.class))).collectList().map(l -> {return new Customer(pesel, l);});
}

We can test GET calls using a web browser or REST clients. With POST, it's not so simple. Here are two simple test cases for adding new customers and getting customers with accounts. Test getCustomerAccounts need account service running on port 2222.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CustomerTest {

  private static final Logger logger = Logger.getLogger("CustomerTest");

  private WebClient webClient;

  @LocalServerPort
  private int port;

  @Before
  public void setup() {
  this.webClient = WebClient.create("http://localhost:" + this.port);
  }

  @Test
  public void getCustomerAccounts() {
    Customer customer = this.webClient.get().uri("/customer/accounts/234543647565")
    .accept(MediaType.APPLICATION_JSON).exchange().then(response -> response.bodyToMono(Customer.class))
    .block();
    logger.info("Customer: " + customer);
  }

  @Test
  public void addCustomer() {
    Customer customer = new Customer(null, "Adam", "Kowalski", "123456787654");
    customer = webClient.post().uri("/customer").accept(MediaType.APPLICATION_JSON)
    .exchange(BodyInserters.fromObject(customer)).then(response -> response.bodyToMono(Customer.class))
    .block();
    logger.info("Customer: " + customer);
  }

}

Conclusion

The Spring initiative with support for reactive programming seems promising, but now it's in early stages of development. There is no availability to use it with popular projects from Spring Cloud like Eureka, Ribbon, or Hystrix. When I tried to add this dependency to pom.xml, my service failed to start. I hope that in the near future such functionalities like service discovery and load balancing will be available also for reactive microservices same as for synchronous REST microservices. Spring has also support for a reactive model in Spring Cloud Stream project. It's more stable than WebFlux framework. I'll try to use it in the future.

The Integration Zone is brought to you in partnership with CA Technologies.  Use CA Live API Creator to quickly create complete application backends, with secure APIs and robust application logic, in an easy to use interface.

Topics:
integration ,reactive microservices ,spring

Published at DZone with permission of Piotr Mińkowski. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}