#StackBounty: #spring-boot #microservices #spring-cloud #clean-architecture #client-library Client libraries in Spring Boot microservices

Bounty: 50

Three years ago I was participating as a developer on my first microservices project. I didn’t know anything about microservices conceptions. That project was building as Spring Boot microservices. In general nothing special but all projects applied quite controversial way of integration between microservices based on client libraries. I think those client libraries were made by naive way. I’ll try to give their main idea.

There are three modules in project: *-api, *-client and *-impl. The *-impl is a full-fledged REST-service and *-client is a client library for this REST-service. *-impl and *-client modules depend on the *-api (they import *-api as a maven dependency). The *-api in turn contains Java interfaces which should be implemented by @RestController classes from the *-impl module and by classes which implement functionality of client library for this REST-service (via RestTemplate or FeignClient). Also the *-api usually contains DTOs which may be covered by Bean Validation and Swagger annotations. In some cases those interfaces may contain @RequestMapping annotations from Spring-MVC. Thus implementation of @RestController and a FeignClient at the same time inherit that @RequestMapping.

*-api

@ApiModel
class DTO {
  @NotNull
  private String field;
  // getters & setters
}

interface Api {
  @RequestMapping("/api")
  void method(DTO dto)
}

*-client

@FeignClient("api")
interface Client extends Api {
  // void method(DTO) is inherited and implemented at runtime by Spring Cloud Feign
}

*-impl

@RestController
class ApiImpl implements Api {
  void method(@Validated DTO dto) {
    // implementation
  }
}

Not hard to guess if some other microservice will pull *-client dependency it may get unpredictable transitive dependencies in their classpath. Also appears tightly coupling between microservices.

I decide to dedicate some time for researching this issue and discovered some concepts. First of all I got acquainted with widespread opinions like this one or from Sam Newman’s famous Building Microservices book (chapter “Client Libraries”). Also I got knew about Consumer Driven Contracts and their implementations – Pact and Spring Cloud Contract. I decided if I will start a new project with Spring Boot microservices I’ll try not to make client libraries and couple microservices by Consumer Driven Contracts only. Thus I hope to reach minimum of coupling.

After that project I was participating in the other one and it was building nearly by the same way as the first one regarding client libraries. I tried to share my researching with a team but I didn’t get any feedback and all the team continued to make client libraries. After several months I left project.

Recently I became a developer on my third microservices project where Spring Boot is used too. And I faced that there also used the same way with client libraries as on prevous two projects. There I also couldn’t get any feedback about Consumer Driven Contracts using.

I would like to know an opinion of community. Which way do you use on your projects? Is the above mentioned way with client libraries reasonable?

Appendix 1.

@JRichardsz’s questions:

  1. What do you mean by client? client of rest api is a kind of sdk provided by api owner to allow clients to consume it in an easy way
    instead http low level implementations.
  2. what do you mean with integrations? is test integrations what you need?
  3. I think your requirement is related to how organize source code between several apis. Is it correct?

Answers:

  1. Here I consider only Spring/Spring Cloud. If I build a microservice with Spring Boot and I want to interact/integrate (this is what I mean by “integrations”) with another (micro)service I can use RestTemplate (it’s a kind of a client library, isn’t it?). If I would build a microservice with Spring Boot + Spring Cloud I could use
    Spring Cloud OpenFeign for interactions (or integration) with another (micro)service. I think Spring Cloud OpenFeign is also a kind of a client library, isn’t it?
    In my general question I talk about custom client libraries which were created by teams where I worked. For example there are two projects: microserviceA and microserviceB. Each of these projects contain three maven modules: *-api, *-client and *-impl. It’s implied that *-client maven module includes *-api maven module. Also *-api maven module used as a dependency in the *-impl maven module. When the microserviceA (microserviceA-impl maven module) wants to interact with the microserviceB it will import the microserviceB-client maven module. Thus microserviceA and microserviceB are tightly coupled.

  2. By integrations I mean interactions between microservices. For example, microserviceA interacts/integrates with microserviceB.

  3. My point concludes in opinion that microserviceA and microserviceB must not to have common source code (via client library). And that’s why I ask these questions:

Which way do you use on your projects? Is the above mentioned way with
client libraries reasonable?

Appendix 2.

I’ll try to explain in details and with examples.

Introduction.

When I participated in projects which were built as microservices they used the same way to implement interactions between microservices namely “client libraries”. They are not the client libraries which incapsulate low level http interactions, serializing/deserializing of http body (and so on) as RestTemplate or FeighClient. They are custom client libraries which have the only purpose – to make interactions (request/response) with the only microservice. For example, there is some microservice-b which offers some microservice-b-client.jar (it’s a custom client library) and microservice-a should use this jar for interact with microservice-b. It’s very similar to RPC implementation.

Example.

microservice-b project

microservice-b-api maven module

pom.xml:

<artifactId>microservice-b-api</artifactId>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

HelloController interface:

@Api("Hello API")
@RequestMapping("/hello")
public interface HelloController {
    @PostMapping
    HelloResponse hello(@RequestBody HelloRequest request);
}

HelloRequest dto:

@Getter
@Setter
@ApiModel("request model")
public class HelloRequest {
    @NotNull
    @ApiModelProperty("name property")
    private String name;
}

HelloResponse dto:

@Getter
@Setter
@ApiModel("response model")
public class HelloResponse {
    @ApiModelProperty("greeting property")
    private String greeting;
}

microservice-b-client maven module

pom.xml:

<artifactId>microservice-b-client</artifactId>

<dependencies>
    <dependency>
        <groupId>my.rinat</groupId>
        <artifactId>microservice-b-api</artifactId>
        <version>0.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

HelloClient interface:

@FeignClient(value = "hello", url = "http://localhost:8181")
public interface HelloClient extends HelloController {
}

microservice-b-impl maven module

pom.xml:

<artifactId>microservice-b-impl</artifactId>

<dependencies>
    <dependency>
        <groupId>my.rinat</groupId>
        <artifactId>microservice-b-client</artifactId>
        <version>0.0</version>
    </dependency>
</dependencies>

MicroserviceB class:

@EnableFeignClients
@EnableSwagger2
@SpringBootApplication
public class MicroserviceB {
    public static void main(String[] args) {
        SpringApplication.run(MicroserviceB.class, args);
    }
}

HelloControllerImpl class:

@RestController
public class HelloControllerImpl implements HelloController {
    @Override
    public HelloResponse hello(HelloRequest request) {
        var hello = new HelloResponse();
        hello.setGreeting("Hello " + request.getName());
        return hello;
    }
}

application.yml:

server:
  port: 8181

microservice-a project

pom.xml:

<artifactId>microservice-a</artifactId>

<dependencies>
    <dependency>
        <groupId>my.rinat</groupId>
        <artifactId>microservice-b-client</artifactId>
        <version>0.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

MicroserviceA class:

@Slf4j
@EnableFeignClients(basePackageClasses = HelloClient.class)
@SpringBootApplication
public class MicroserviceA {

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

    @Bean
    CommandLineRunner hello(HelloClient client) {
        return args -> {
            var request = new HelloRequest();
            request.setName("StackOverflow");
            var response = client.hello(request);
            log.info(response.getGreeting());
        };
    }
}

Result of MicroserviceA run:

2020-01-02 10:06:20.623  INFO 22288 --- [           main] com.example.microservicea.MicroserviceA  : Hello StackOverflow

Here you can see full example

Question.

I think this way of integration between microservices (via custom client libraries) is a wrong way. First of all microservices become tightly-coupled. Second – client library brings undesirable dependencies. Despite this argument teams where I worked use this odd way to make integration between microservices. I would like to know is this way to make integration of microservices reasonable (correct)? Which is the best practice to make integrations between microservices?

P.S. In my opinion Spring Boot microservices should be coupled by Consumer Driven Contracts (Spring Cloud Contract or Pact) and nothing else. How do you think is it right way?


Get this bounty!!!

#StackBounty: #java #nginx #spring-cloud #nginx-reverse-proxy #spring-cloud-gateway Gateway timeout with Sping cloud gateway and Nginx …

Bounty: 100

I created API gateway for my application and it will act as a front controller for other microservices.
In my production setup I user Nginx as a reverse proxy for my gateway

API gateway is running on port 8080

Nginx config as below

gateway-api.conf:

server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://localhost:30010/;
        keepalive_timeout 500s;
    }
    keepalive_timeout 500s;
    access_log /var/log/nginx/api.log;  
    error_log /var/log/nginx/api_error.log;
}

timeout setting in nginx.conf

proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;

Spring cloud gateway gradle file:

compile('org.springframework.cloud:spring-cloud-starter-gateway')
 compile('org.springframework.cloud:spring-cloud-starter-openfeign')
 compile("org.springframework.boot:spring-boot-starter-actuator")
 compile('org.springframework.boot:spring-boot-starter-security')

springBootVersion=2.0.3.RELEASE
springDMPVersion=1.0.4.RELEASE
springPlatformBomVersion=Cairo-SR2
springCloudVersion=Finchley.RELEASE

Gateway application:

@SpringBootApplication
@ComponentScan(basePackages = {"com.example"})
@EntityScan(basePackages = {"com.example"})
@EnableFeignClients(basePackages = "com.example")
public class GatewayApplication {

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

Problem statement:

In one of my microservice, one REST API take more than 3 minutes to complete.
If I call this API via nginx(api.example.com), it fails exactly after 1 min and gives HTTP status 504

curl:

curl --request GET 
  --url http://api.example.com/hellomicroservice/api/take/moretime

error:

504 Timeout while reading the response from Server

No error logs in nginx and API gateway

Access log from nginx:

203.129.213.102 - - [01/Apr/2019:08:14:33 +0000] "GET hellomicroservice/api/take/moretime HTTP/1.1" 499 0 "-" "PostmanRuntime/7.3.0"

But when I make a call of the same API directly to the gateway(on gateway port 8080), request process successfully

curl with gateway port:

curl --request GET 
  --url http://api.example.com:8080/hellomicroservice/api/take/moretime

Edit:
If I apply the Nginx timeout settings as less than 60 Seconds(For example 30 seconds), the request gets timed out in a specified time interval. But if I set the Nginx timeout to be more than 60 seconds, let’s 300 Seconds, the request gets timed out after 60 seconds.


Get this bounty!!!