Quick notes of microservice in action
This is quick notes of learning Spring microservice in action(2nd edition). To be clarified, Spring-Boot and Spring-Cloud version are little different from book.
Here are versions used:
- Spring-Boot version
2.6.13
- Spring-Cloud version
2021.0.5
Whereas the guide book Spring Microservice In Action(2nd edition)
uses 2.2.3.RELEASE
for spring-boot
and Hoxton.SR1
for spring-cloud. Which introduces some version 'traps'.
1) No spring.config.import property has been defined #
While configuring licensing-service
as Spring Cloud Config Client, I encountered this error. After googled,
I found that was a new version of spring with old configuration style.
You're getting this error because you're using a new version of Spring Boot and Spring Cloud, but you're trying to configure it in the old way.
Here is the full answer link: https://stackoverflow.com/questions/67507452/no-spring-config-import-property-has-been-defined
Spring Boot 2.4(later) introduced a new way to import config data.
Here is official doc: https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_spring_cloud_config_client
2 solutions were provided to solve problem.
2) Spring cloud config Git backend with ssh authentication #
Use command
ssh-keygen -m PEM -t rsa -b 4096 -f config_server_deploy_key.rsa
to gen rsa key pair.
Then config spring cloud config server like this:
1 spring:
2 cloud:
3 config:
4 server:
5 git:
6 uri: git@gitserver.com:team/repo1.git
7 ignoreLocalSshSettings: true
8# hostKey: someHostKey
9# hostKeyAlgorithm: ssh-rsa
10 privateKey: |
11 -----BEGIN RSA PRIVATE KEY-----
12 MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX
13 IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF
14 ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud
15 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i
16 oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W
17 DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd
18 fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b
19 BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG
20 EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj
21 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8
22 +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe
23 pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG
24 ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ
25 xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW
26 dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi
27 PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX
28 VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z
29 FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R
30 gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4
31 VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV
32 cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee
33 KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N
34 CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs
35 q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J
36 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT
37 -----END RSA PRIVATE KEY-----
Note: hostKey
and hostkeyAlgorithm
are no necessary configuration items.
Do Not forget to add public key to your gitHub settings.
By the way, if you already have an ssh key-pair in your local machine, just use it!
Referring official guide to learn more about git backend: https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_spring_cloud_config_server
3) Config sensitive info as env variables in Docker Compose #
Use System Environment Variable.
1docker compose run -e KEY_OF_ENV=value web python app.py
4) Debug Micro Service locally with docker and IDEA #
Docker services:
- config server
- eureka server
- database
IDEA services:
- licensing service
- organization service
IDEA services need some extra configurations(program arguments) to override config server's backend configuration.
Use --key=value
to do that.
1--eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka
2--spring.datasource.url=jdbc:postgresql://localhost:5432/ostock_dev
3--spring.config.import=optional:configserver:http://localhost:8071
5) Spring-Cloud discovery client and loadbalancer #
After introducing eureka client to licensing service, we also introduced spring-cloud-starter-loadbalancer
and spring-cloud-starter-openfeign
, which provide load-balance and microservice remote invoking ability to licensing service.
There 3 ways to achieve remote call, introduced by the book(microservice in action).
- DiscoveryClient (Without load balance)
- RestTemplate
- OpenFeign
5.1 DiscoveryClient (Not recommended) #
To Use DiscoveryClient
in Spring Cloud, just inject DiscoveryClient
bean into services.
1@Component
2public class OrganizationDiscoveryClient {
3 @Autowired
4 private DiscoveryClient discoveryClient;
5
6 /**
7 * No LoadBalanced
8 */
9 public Organization getOrganization(String organizationId) {
10 RestTemplate restTemplate = new RestTemplate();
11 List<ServiceInstance> instances = discoveryClient.getInstances("organization-service");
12
13 if (instances.isEmpty()) return null;
14 String serviceUri = String.format("%s/v1/organization/%s",instances.get(0).getUri().toString(), organizationId);
15
16 ResponseEntity< Organization > restExchange =
17 restTemplate.exchange(
18 serviceUri,
19 HttpMethod.GET,
20 null, Organization.class, organizationId);
21
22 return restExchange.getBody();
23 }
24}
DiscoveryClient
actually get all service instances by service name you set in eureka client.
The service name was set by spring.application,name
property.
And you could find out that DiscoveryClient
was used to get remote service's url, and service was invoked by RestTemplate
.
As you can see, there are still some 'hard code' in there, and DiscoveryClient
always get the first service's url, which means
Spring Cloud load-balance did not work in this scenario.
5.2 RestTemplate with @LoadBalanced
#
If you want your RestTemplate
run as load-balanced client, you need to config it with @LoadBalanced
.
1@LoadBalanced
2@Bean
3public RestTemplate getRestTemplate(){
4 return new RestTemplate();
5}
Then you can use RestTemplate
like this:
1@Component
2public class OrganizationRestTemplateClient {
3
4 @Autowired
5 RestTemplate restTemplate;
6
7 public Organization getOrganization(String organizationId){
8 ResponseEntity<Organization> restExchange =
9 restTemplate.exchange(
10 "http://organization-service/v1/organization/{organizationId}",
11 HttpMethod.GET,
12 null, Organization.class, organizationId);
13
14 return restExchange.getBody();
15 }
16}
If you have more than 1 instances of organization service, the RestTemplate
will query each instances Round-Robbin.
5.3 OpenFeign #
Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
By using OpenFeign integrated with Spring Cloud, it already supported a load-balanced http-client.
The way to use OpenFeign
is simple:
1@FeignClient("organization-service")
2public interface OrganizationFeignClient {
3
4 @RequestMapping(
5 method= RequestMethod.GET,
6 value="/v1/organization/{organizationId}",
7 consumes="application/json")
8 Organization getOrganization(@PathVariable("organizationId") String organizationId);
9}
Do not forget to annotate Main-class with @EnableFeignClients
.
5.4 Other approaches #
Except ways mentioned above, there are other approaches to achieve RPC in Spring of course.
Spring WebFlux is a alternative:
More information, read:
- https://docs.spring.io/spring-cloud-commons/docs/3.1.8/reference/html/#spring-cloud-loadbalancer
- https://docs.spring.io/spring-cloud-openfeign/docs/3.1.9/reference/html/
Using a ReactiveLoadBalanced WebClients #
Filter UserContextFunctionFilter
uses to transmit correlationId and other HTTP Headers like JWT while micro service invoking.
1@Autowired
2ReactorLoadBalancerExchangeFilterFunction lbFunction;
3/**
4 * {@link WebClient} load balancer HTTP client
5 */
6@Bean
7@LoadBalanced
8public WebClient.Builder getWebclient() {
9 return WebClient.builder().filters(f -> {
10 f.add(new UserContextFunctionFilter());
11 f.add(lbFunction);
12 });
13}
And the UserContextFunctionFilter
looks like:
1public class UserContextFunctionFilter implements ExchangeFilterFunction {
2 @Override
3 public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
4 ClientRequest buildRequest = ClientRequest.from(request).headers(h -> {
5 h.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());
6 h.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());
7 }).build();
8 return next.exchange(buildRequest);
9 }
10
11}
There is a warning info by spring BeanPostProcessorAutoConfiguration:
12024-12-17 08:29:57.580 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker :Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
22024-12-17 08:29:57.594 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
32024-12-17 08:29:57.605 INFO 1 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'reactorDeferringLoadBalancerExchangeFilterFunction' of type [org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
This ...is not eligible for getting processed by all BeanPostProcessors
info always causes by
circle dependency.
This info Spring Cloud did not fix it officially yet.