07 How to Invoke Services Outside of This Business Module Service Invocation

07 How to Invoke Services Outside of This Business Module - Service Invocation #

In the previous article, we introduced the basic components of Nacos and completed the service registration and discovery mechanism, which enables the centralized management and configuration of all services, facilitating service invocation between them. In this article, we will combine the requirements to achieve service invocation and complete the functionality development.

Several ways to invoke services #

There are two common ways to invoke services: RPC and HTTP. RPC, short for Remote Produce Call, is a remote procedure call that is fast and efficient. Early WebService interfaces and popular frameworks such as Dubbo, gRPC, Thrift, and Motan are typical representatives of RPC. If you are interested, you can search for related information to gain a deeper understanding.

HTTP protocol (HyperText Transfer Protocol) is the most widely used network transmission protocol on the Internet. All WWW files must comply with this standard. There are no language limitations for service providers and invokers, which is more in line with the language-independent concept of microservices. The popular development method in RESTful form is also implemented through the HTTP protocol.

In this case, considering simplicity and the basic features of Spring Cloud, we decide to use the HTTP form to interact with interfaces and achieve service invocation. The commonly used invocation methods in the Spring Cloud system are RestTemplate, Ribbon, and Feign.

RestTemplate is a client provided by Spring for accessing REST services. RestTemplate provides various convenient methods to access remote HTTP services, greatly improving the efficiency of client implementation.

Ribbon, produced by Netflix, is popularly known for client load balancing.

Feign, also produced by Netflix, is a more convenient HTTP client that allows invoking remote methods as if they were local methods. It also uses Ribbon internally to implement load balancing functionality.

Since Ribbon has been integrated into Feign, we will only introduce the usage of RestTemplate and Feign below.

Application of RestTemplate #

Functional requirement: When a member binds a mobile phone number, the corresponding points should be increased. The binding of the member’s mobile phone number is completed in the Member Service, and the increase of member points is completed in the Points Service. The request path is client->Member Service->Points Service.

The method that responds to client requests is as follows:

@RequestMapping(value = "/bindMobileUseRestTemplate", method = RequestMethod.POST)
public CommonResult<Integer> bindMobileUseRestTemplate(String json) throws BusinessException{
    CommonResult<Integer> result = new CommonResult<>();
    log.info("bind mobile param = " + json);
    int rtn = memberService.bindMobileUseRestTemplate(json);
    result.setRespData(rtn);
    return result;
}

Do the configuration work for RestTemplate, otherwise, it cannot be used properly.

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory simpleClientHttpRequestFactory){
        return new RestTemplate(simpleClientHttpRequestFactory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        return factory;
    }
}

Handle the request in the MemberService, and the logic is as follows:

@Autowired
RestTemplate restTemplate;

@Override
public int bindMobileUseRestTemplate(String json) throws BusinessException {
    Member member = JSONObject.parseObject(json, Member.class);
    int rtn = memberMapper.insertSelective(member);
    // invoke another service
    if (rtn > 0) {
        MemberCard card = new MemberCard();
        card.setMemberId(member.getId());
        card.setCurQty("50");

        MultiValueMap<String, String> requestMap = new LinkedMultiValueMap<String, String>();
        requestMap.add("json", JSONObject.toJSONString(card).toString());
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(
                requestMap, null);

        String jsonResult = restTemplate.postForObject("http://localhost:10461/card/addCard", requestEntity,
                String.class);

        log.info("creata member card suc!" + jsonResult);
    }

    return rtn;
}

Use the postForObject method to request the method for generating point records in the Points Service and pass the corresponding parameters. The method in the Points sub-service that accepts the invocation request is relatively simple:

@RequestMapping("card")
@RestController
@Slf4j
public class MemberCardController {

    @Autowired
    MemberCardService cardService;

    @RequestMapping(value = "/addCard", method = RequestMethod.POST)
    public CommonResult<Integer> addCard(String json) throws BusinessException {
        log.info("eclise service example: begin add member card = " + json);
        //log.info("jar service example: begin add member card = " + json);
        CommonResult<Integer> result = new CommonResult<>();
        int rtn = cardService.addMemberCard(json);
        result.setRespData(rtn);
        return result;
    }
}

The actual business logic processing is completed by the MemberCardService interface:

@Service
@Slf4j
public class MemberCardServiceImpl implements MemberCardService {

    @Autowired
    MemberCardMapper cardMapper;

    @Override
    public int addMemberCard(String json) throws BusinessException {
MemberCard card = JSONObject.parseObject(json, MemberCard.class);
log.info("add member card " + json);
return cardMapper.insertSelective(card);
}
}

Start the member service and the points service separately, and perform a simple test through the Swagger interface UI.

img

img

RestTemplate relies on the JDK to provide the ability to connect to HTTP by default. It provides different methods for HTTP requests, making it an improvement over native HTTP requests. However, it is still not elegant enough. Is it possible to call third-party services like calling local interfaces? Below introduces the application of Feign, which will definitely make you like the way Feign is called.

Application of Feign #

The biggest convenience of calling Fegin lies in shielding the underlying connection logic, allowing you to call third-party services like calling local interfaces, with less code and a more elegant style. Of course, it must be coordinated under the service registry center to complete the service call normally. However, RestTemplate does not care if the service registry is running properly.

Introducing Feign #

Feign is another open-source framework developed by Netflix for load balancing. It encapsulates Ribbon and RestTemplate, realizes the interface-oriented programming of WebService, and further reduces the coupling of the project, because it encapsulates Ribbon and RestTemplate, so it has the functions of these two frameworks. In the pom.xml of the member module, add the jar reference:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

In the module startup class, add the @EnableFeignClients annotation to enable the Feign related functions, and start to scan the @FeignClient annotations by scanning the package.

//#client directory is the directory where the feign interface is located
@EnableFeignClients(basePackages = "com.mall.parking.member.client")

The frontend request response method:

@RequestMapping(value = "/bindMobile", method = RequestMethod.POST)
public CommonResult<Integer> bindMobile(String json) throws BusinessException{
    CommonResult<Integer> result = new CommonResult<>();
    log.info("bind mobile param = " + json);
    int rtn = memberService.bindMobile(json);
    result.setRespData(rtn);
    return result;
}

Interface writing #

Write MemberCardClient, which is used when calling the points service. The interfaces in it are one-to-one with the relevant methods in the points service.

@FeignClient(value = "card-service")
public interface MemberCardClient {
    @RequestMapping(value = "/card/addCard", method = RequestMethod.POST)
    public CommonResult<Integer> addCard(@RequestParam(value = "json") String json) throws BusinessException;

    @RequestMapping(value = "/card/updateCard", method = RequestMethod.POST)
    public CommonResult<Integer> updateCard(@RequestParam(value = "json") String json) throws BusinessException;
}

Note: It is @RequestParam not @PathVariable. @PathVariable is used to get values ​​from the path, while @RequestParam is used to get values ​​from the parameters. They have different usage.

When using, just autowired it like using a local interface, and you can use it as you did. With this, the code writing is completed. Let’s verify the accuracy of the logic.

  1. Make sure nacos-server is running.
  2. Start the parking-member and parking-card services separately.
  3. Use the swagger-ui of parking-member to call the member binding mobile number interface (or use the Postman tool)

Under normal circumstances, the data tables in both park-member and park-card will have generated data.

img

img

So, when does fallback come into play? It is very easy to verify. When the points service is closed, and then call the points generation method in the points service again, you will find that the method in MemberCardServiceFallback is called directly and responded to the caller, avoiding long waiting when the timeout occurs.

Load Balancing #

As mentioned earlier, Feign has integrated Ribbon by default, so load balancing can be implemented directly through Feign. Start two card-service instances, open the Nacos console, and find that the instances have been registered successfully. Then, visit bindMobile multiple times through the swagger-ui or Postman tool. Based on the console log output, you will find that the requests are executed alternately in the two card-service instances.

img

How to change the default load balancing strategy? Understand how many load balancing strategies Ribbon provides: random, round-robin, retry, weighted response time, and best available. They correspond to the following:

com.netflix.loadbalancer.RandomRule com.netflix.loadbalancer.RoundRobinRule com.netflix.loadbalancer.RetryRule com.netflix.loadbalancer.WeightedResponseTimeRule com.netflix.loadbalancer.BestAvailableRule

Since it is client-side load balancing, you must configure it in the service calling project to achieve the adjustment.

card-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Or you can adjust it through Java code, placing it below the project startup class.

@Bean
public IRule ribbonRule() {
    // Load balancing rule, change to random
    return new RandomRule();
//        return new BestAvailableRule();
//        return new WeightedResponseTimeRule();
//        return new RoundRobinRule();
//        return new RetryRule();
}

With this, we have completed a “bind mobile number for members and add corresponding points” function through two methods of service invocation. We have also tested the configuration and use of client-side load balancing.

Extra Exercises #

Having mastered service invocation, and without considering non-business functions, most of the business logic code in this case can be completed. Referring to the user stories you have broken down or the main business flowchart, start now and perfect the code. Service invocation is a function often used between microservices, and choosing a convenient invocation method is particularly important. As usual, let me leave you with a thinking question: This article uses client-side load balancing technology. How is it different from load balancing technology we often mention?