一光年

[SpringCloud-极简示例] 2. 从单体应用到微服务

2019.06.20

作为一个单体应用,两个模块直接最简单直接的调用关系就是通过jar引用。

如下,使用Spring Initializr生成ws-consumer-a和ws-provider-c两个项目。因为只是微服务的示例,没有涉及数据库层面操作。

单体应用

demo1-monolithic

在单体应用阶段,所有子模块都作为jar包被引入工程中。例如有一个模块Consumer,需要调用模块Provider的方法取得一条Product记录。最简单的办法就是引入Consumer模块的jar,直接调用相关Service方法即可。

Step1:创建Provider工程

// Product实体类
package com.example.springcloud.provider.entity;

public class Product {
	
    private Integer id;   // 商品ID
    private String name;  // 商品名称
	
    // Getters & Setters
    ...
}
// Product服务类,模拟DB操作
package com.example.springcloud.provider.service;

...

@Service
public class ProductService {

    public Product getById(Integer id) {
		
        // 模拟数据库操作
        Product product = new Product();
        product.setId(id);
        product.setName("Name-" + id);
        return product;
    }
}

Step2:创建Consumer工程

直接在controller中引入ProductService即可调用getById方法取得商品数据。

package com.example.springcloud.comsumer.web;
...

@RestController
@RequestMapping("")
public class DemoController {
	
    @Autowired
    private ProductService productService;

    @GetMapping("/product/{id}")
    public Product getProvider(@PathVariable Integer id) {
        return productService.getById(id);
    }
}

注意在主程序类中,要加上扫描包路径的设置。

package com.example.springcloud.comsumer;
...

@SpringBootApplication(scanBasePackages = {"com.example.springcloud"})
public class WsConsumerAApplication {

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

在pom中,加入对Provider项目的引用。

...
  <dependency>
    <groupId>com.example.springcloud</groupId>
    <artifactId>ws-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </dependency>
...

Step3:启动并测试

启动Consumer的主程序,在浏览器中输入http://localhost:8080/product/1,即可看到返回结果为:

{"id":1,"name":"Name-1"}

分拆为微服务

demo1-microservice

这里只是分拆为了两个微服务的雏形,但已经与单体应用有了本质的区别。理论上说,这两个服务可以独立部署和运行在不同的环境和主机上,两者直接仅通过RPC或HTTP协议进行通信。

Step1:为Provider实现RestAPI功能。

package com.example.springcloud.provider.web;
...

@RestController
@RequestMapping("")
public class ProductController {
	
    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product findById(@PathVariable Integer id) {
        return productService.getById(id);
    }
}

因为Consumer已经使用了8080端口,修改Provider的application.yml,端口设置为8081。

server:
  port: 8081

在浏览器中输入:http://localhost:8081/1,即可看到结果:

{"id":1,"name":"Name-1"}

Step2:Consumer中去除对Provider的引用

package com.example.springcloud.comsumer;
...

@SpringBootApplication() // 不再扫描对应package路径
public class WsConsumerAApplication {

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

pom.xml中也去除对ws-provider包的引入。

Step3:Consumer中添加RestTemplate

RestTemplate是一个Restful请求模板类,方便应用对RestfulApi发送请求。

package com.example.springcloud.comsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication(scanBasePackages = {"com.example.springcloud"})
public class WsConsumerAApplication {

    /**
     * 添加使用RestTemplate的Bean声明
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

}

修改Controller,不再直接引用Provider的jar代码。

package com.example.springcloud.comsumer.web;
...

@RestController
@RequestMapping("")
public class DemoController {
	
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/product/{id}")
    public Object getProvider(@PathVariable Integer id) {
        return restTemplate.getForObject("http://localhost:8081/" + id, Object.class);
    }
}

Step4:测试一下

  1. 启动Provider
  2. 启动Consumer
  3. 浏览器中输入 http://localhost:8080/product/1

{"id":1,"name":"Name-1"}

总结

虽然只是将单体应用的两个模块,分拆为了两个独立的应用服务,但整个架构已经发生了根本性的变化。

分拆前,所有代码在同一个进程中执行同一运行环境中执行。当分拆为两个独立的应用服务后,单体应用时代很多不是问题的问题就被暴露出来了。例如原子事务和网络延时的问题,等等。