Sunday, June 23, 2019

Reactive web application with SpringBoot

0. Intro

Event-driven asynchronous approach is getting more and more popular.
When we're talking about REACTIVE we means that we should react somehow on something. When it comes to web applications, most often that means: we should react by showing some new information on the page on some event,

With traditional approach web page should poll(send request, receive response) some rest service every time it want to get new portion of data:




With reactive - we don't have to poll, we just have to subscribe to some "event stream".

 


1. SpringBoot reactive web application

Let's now create "reactive" springBoot application.

build.gradle:

plugins {
 id 'org.springframework.boot' version '2.1.5.RELEASE'
 id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.demien'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
 developmentOnly
 runtimeClasspath {
  extendsFrom developmentOnly
 }
}

repositories {
 mavenCentral()
}

dependencies {
 implementation 'org.springframework.boot:spring-boot-starter-webflux'
 developmentOnly 'org.springframework.boot:spring-boot-devtools'
 testImplementation 'org.springframework.boot:spring-boot-starter-test'
 testImplementation 'io.projectreactor:reactor-test'
}

- we have to add dependency "webflux" here.



Main application runner: 

package com.demien.reactweb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ReactwebApplication {

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

}


- nothing interesting is here, just start of spring boot application.

Reactive rest controller: 

package com.demien.reactweb;

import java.time.Duration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;

@RestController
public class RandomNumberController {

 private final Log log = LogFactory.getLog(RandomNumberController.class);

 @RequestMapping("/random")
 public Flux<Integer> random() {
  return Flux.interval(Duration.ofSeconds(5)).map(i -> {
   this.log.info("iteration:" + i);
   return generateRandomNumber();
  }).log();
 }

 private int generateRandomNumber() {
  return (int) (Math.random() * 1000);
 }

}

-magic is happening here ! First of all, we're returning Flux<Integer> - that means, we're actually returning "stream" of integers. Second - we're using delay of 5 seconds between emitting elements.

2. HTML Web page

Here we have just one button. But than we're pressing it, it subscribes as to events which are coming from rest endpoint "/random" which we created at previous step. Also we're defining the "handler" (stringEventSource.onmessage) - what should we do when new event arrives: we're just adding one more list item.


<html>

<head>

    <script>
        function registerEventSourceAndAddResponseTo(uri, elementId) {
            var stringEvents = document.getElementById(elementId);
            var stringEventSource = new EventSource(uri);
            stringEventSource.onmessage = function (e) {
                var newElement = document.createElement("li");
                newElement.innerHTML = e.data;
                stringEvents.appendChild(newElement);
            }
        }

        function subscribe() {
            registerEventSourceAndAddResponseTo("http://localhost:8080/random","display");
        }

    </script>

</head>

<body>

    <p>
        <button id="subscribe-button" onclick="subscribe()">Subscribe to random numbers</button>
        <ul id="display"></ul>
    </p>


</body>

</html>

3. Let's run it! 

I'm running my springBoot application and opening the web page:


Nothing happen so far. We have to press the button for being subscribed to new event. Let's press it:



And now it's much better! Events are coming from server side and UI shows them!

4. The end. 

Full source code can be downloaded from here.

Wednesday, June 12, 2019

Microservices with Spring Boot 2

0. Intro

This is some kind of refreshment of one of my previous posts: "Microservices with spring boot"
updates:
- codebase migrated to version of spring boot: 2.1.4
- used"feign client" instead of "rest template" for micro-services interaction
- used "zipkin" and "sleuth.sampler" for application monitoring

I will not be providing a lot of details - they are in my previous post. DRY - don't repeat yourself :)

1. Architecture

Actually, architecture is the same as in previous post. Client is communicating with 3 micro-services:





But apart from them, we also have some "infrastructure micro-services":



Next picture is taken from zipkin - to show micro-services communication flow: we're calling edge-server and providing it with details of server we actually want to call. Below, we're calling from edge-server - cart-service(method cart/test), which calling through edge-server  user-service  2 times (user/login and user/byToken)  and again through edge-server -  item-service (item/getAll):


2. Discovery server 

The same stuff as in my previous post - I'm using Eureka:



3. Micro-services interaction

As I mentioned at the beginning, I'm using "feign client" for micro-services interaction. I'll show it on example of "cart-service" - it should call "user-service" to get user details and also it should get some items details from "item-service".

3.1. Properties

file application.properties:

spring.application.name=cart-service
server.port=8100
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

- here we're defining "coordinates" of our discovery service to get information about services we may need (user and item services)

file bootstrap.properties:

spring.zipkin.base-url=http://localhost:9411/
spring.sleuth.sampler.probability=1

- here we're defining "coordinates" of zipkin application - we 'll be sending there requests traces.

3.2 Feign clients 

In feign clients we're defining our "edge-server". Also we're defining "ribbon-client" for service we want to call - it can be different instances of one service, so ribbon client needed for load-balancing. And finally in methods, we're defining the exact rest-services we want to call.

Item service feign client: 

package com.demien.sprcloud.cartservice.controller;
import java.util.List;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.demien.sprcloud.cartservice.domain.Item;

@FeignClient(contextId = "itemClient", name = "edge-server")
@RibbonClient(name = "item-service")
public interface ItemServiceProxy {

 @RequestMapping(value = "/item-service/item/{itemId}", method = RequestMethod.GET)
 public Item getById(@PathVariable("itemId") String itemId);

 @RequestMapping(value = "/item-service/item/getAll", method = RequestMethod.GET)
 public List<Item> getAll();

}



User service feign client: 

package com.demien.sprcloud.cartservice.controller;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(contextId = "userClient", name = "edge-server")
@RibbonClient(name = "user-service")
public interface UserServiceProxy {

 @RequestMapping(value = "/user-service/user/login", method = RequestMethod.POST)
 public String login(@RequestParam("userId") String userId, @RequestParam("userPassword") String userPassword);

 @RequestMapping(value = "/user-service/user/byToken/{tokenId}", method = RequestMethod.GET)
 public String userByToken(@PathVariable("tokenId") String tokenId);

}

3.3. Test method with interaction 

Next method is just for emulation of some process where user is logging in, adding some items into cart: our cart-service will be communicating with item and user services. Now we can "autowire" our feign clients - and just call them! Lines with calls - are in bold.

 @Autowired
 private UserServiceProxy userServiceProxy;

 @Autowired
 private ItemServiceProxy itemServiceProxy;

 public String getDefaultResponse() {
  return "Something is wrong. Please try again later";

 }

 @HystrixCommand(fallbackMethod = "getDefaultResponse")
 @RequestMapping(method = RequestMethod.GET, value = "/test")
 public String test() {
  final StringBuilder result = new StringBuilder();
  result.append("Logging in into userService as user1/pasword <br/> ");
  final String tokenId = this.userServiceProxy.login("user1", "password1");
  result.append("Received Token: " + tokenId + "<br/><br/>");
  result.append("Getting user details from userService by token <br/>");
  final String userDetails = this.userServiceProxy.userByToken(tokenId);
  result.append("Reseived UserDetails: " + userDetails + "<br/><br/>");

  result.append("Getting item list from itmService <br/>");
  final List<Item> items = this.itemServiceProxy.getAll();
  result.append("Reseived items: <br/>");
  items.forEach(item -> result.append("    " + item.toString() + " <br/>"));

  return result.toString();
 }

Now to test it we can open in browser URL: http://localhost:8765/cart-service/cart/test
and result should be:


Logging in into userService as user1/pasword 
Received Token: ec1a5d78-a8c5-4392-90bb-cbca7d8c9244

Getting user details from userService by token
Reseived UserDetails: {"id":"user1","name":"First User","address":"First Address"}

Getting item list from itmService
Reseived items:
Item(itemId=I6S, itemName=IphoNovatekne 6s, price=400.00)
Item(itemId=I7, itemName=Iphone 7, price=500.00)
Item(itemId=N5, itemName=Samsung galaxy note 5, price=450.00) 



- so we successfully called 2 micro-services!


4. Zipkin 

Zipkin lives here. It's a distributing trace system. To use it I just downloaded JAR file and started it by "java -jar zipkinFileName.jar"

My micro-services are already configured for using trace information to zipkin (bootstrap.propeties at #3.1). So when zipking is started it's possible to monitor them:




Now we can drill-down to details and found the picture I shown at the beginning:
I think this picture makes much more sense now - it's a "map" of execution of my test rest service from #3.3.


5. The end. 

As I mentioned at the beginning, a lot of details were omitted, because they are present in my previous post. Full source code can be downloaded from here.