学习微服务架构, 不但要了解微服务中的基本概念和重要组件, 更重要的是实践. 本文将会以一个电商中的常见业务场景为例构建微服务. 在本文中, 主要使用最新的 SpringCloud(version:Finchley.SR1) 体系进行构建.
1 准备
在开始本文之前, 需要以下预备知识:
熟悉 Spring 和 SpringBoot
了解微服务
本文会使用 SpringCloud 中的一些组件进行开发:
Spring Cloud Netflix Eureka: 注册中心
Spring Cloud Netflix Zuul:API 网关
Spring Cloud OpenFeign: 服务调用工具
2 业务场景
为了更好的展示微服务, 本文将以电商业务场景中的 创建订单 为例. 现实系统中的下单非常复杂, 这里会进行简化, 主要是为了方便理解微服务. 所以, 这里假定, 下单过程中的两个主要操作:
根据下单用户的 id, 查询当前用户的信息
根据下单的商品 id, 查询当前商品的信息
最终, 根据这个业务场景, 设计的微服务架构图如下:
按照业务场景, 将不同的功能垂直划分成三个服务:
- UserService
- OrderService
- ProductService
其中, OrderService 会调用 UserService 和 ProductService 的相关服务, 在调用过程中, 并不是直接调用, 而是通过 Eureka 去调用它们. 外部调用不会直接调用具体的服务, 全部都是通过 Zuul 网关进行调用, 此处, Zuul 也是通过 Eureka 去调用具体的服务.
3 构建项目
根据架构图, 构建项目骨架. 创建一个 Maven 项目, 并且创建五个子模块, pom.xml:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.0.1.RELEASE</version>
- </parent>
- <groupId>com.no.one</groupId>
- <artifactId>microservice-base</artifactId>
- <packaging>pom</packaging>
- <version>1.0-SNAPSHOT</version>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>Finchley.SR1</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <modules>
- <module>discovery-server</module>
- <module>api-gateway-server</module>
- <module>order-service</module>
- <module>product-service</module>
- <module>user-service</module>
- </modules>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
在这个 pom 文件中, 我们定义了所使用的 SpringCloud 以及 SpringBoot 相关库的版本. 同时, 在各个模块中, 我们将使用 .properties 文件作为项目配置文件, 而不使用 .yml 文件. yml 文件虽然层次分明, 但是基于缩进的语法, 容易出问题. 关于这个文件格式的选择, 要考虑到个人习惯以及团队内的约定.
3.1 注册中心: Eureka
创建 Eureka 注册中心, 首先要引入依赖, 在配置文件 microservice-base\discovery-server\pom.xml :
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
- </dependencies>
然后创建主类, com.no.one.discovery.DiscoveryApplication
- @SpringBootApplication
- @EnableEurekaServer
- public class DiscoveryApplication {
- public static void main(String[] args) {
- SpringApplication.run(DiscoveryApplication.class, args);
- }
- }
这是一个 SpringBoot 应用, 关键就在于使用 @EnableEurekaServer 注解. 然后, 增加相关的配置:
- microservice-base\discovery-server\src\main\resources\bootstrap.properties:
- 1spring.application.name=discovery-server
- microservice-base\discovery-server\src\main\resources\application.properties:
- server.port=8761
- eureka.instance.hostname=localhost
- eureka.client.registerWithEureka=false
- eureka.client.fetchRegistry=false
- eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
这个配置文件中, 配置了 Eureka 服务器的参数.
3.2 API 网关: Zuul
引入 Zuul 的依赖, 在配置文件 microservice-base\api-gateway-server\pom.xml :
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
- </dependency>
- </dependencies>
注意, 在 Zuul 网关中, 调用上游服务时, 是通过调用 Eureka 进行的, 所以, 此处引入 eureka-client 的依赖.
创建主类 com.no.one.gateway.GatewayApplication.java
- @SpringBootApplication
- @EnableDiscoveryClient
- @EnableZuulProxy
- public class GatewayApplication {
- public static void main(String[] args) {
- SpringApplication.run(GatewayApplication.class, args);
- }
- }
这也是一个 SpringBoot 应用, 关键就在于使用 @EnableZuulProxy 注解, 该注解会启用反向代理, 后面会看到相关的路由配置. 同时, 通过 @EnableDiscoveryClient 注解, 集成对于 Eureka 的支持, 后续, 在配置路由时可以直接使用服务的名字进行.
然后, 增加相关配置 microservice-base\api-gateway-server\src\main\resources\bootstrap.properties :
- 1spring.application.name=api-gateway-server
- microservice-base\api-gateway-server\src\main\resources\application.properties:
- server.port=9002
- eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
- zuul.prefix=/api
- zuul.ignoredServices='*'
- zuul.routes.user-service.path=/user-service/**
- zuul.routes.user-service.serviceId=user-service
- zuul.routes.product-service.path=/product-service/**
- zuul.routes.product-service.serviceId=product-service
- zuul.routes.order-service.path=/order-service/**
- zuul.routes.order-service.serviceId=order-service
在此处, 除了 Zuul 网关的配置, 还配置好相关的路由.
3.3 业务服务
完成基础组件, 开始构建具体的业务服务. 三个业务组件都是 SpringBoot 应用, 构建方法类似. 同时, 为了简单起见, 支持使用内存数据库, 数据库的访问直接使用 SpingDataJPA. 以构建 OrderService 为例, 首先, 创建 microservice-base\order-service\pom.xml 文件, 并引入依赖:
- <dependencies>
- <!-- Spring Boot -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Spring Cloud -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <!-- Third parties -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- <dependency>
- <groupId>org.hsqldb</groupId>
- <artifactId>hsqldb</artifactId>
- </dependency>
- </dependencies>
主要的配置 microservice-base\order-service\src\main\resources\bootstrap.properties :
1spring.application.name=order-service
以上名字属性, 作为服务名注册到 Eureka 中.
- microservice-base\order-service\src\main\resources\application.properties :
- server.port = 9200
- eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
相关的主要 Java 类:
实体类 microservice-base\order-service\src\main\java\com\no\one\order\model\Order.java :
- @Data
- @Entity
- @Table(name = "ms_order")
- public class Order {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
- @Column(name = "product_name")
- private String productName;
- @Column(name = "user_name")
- private String userName;
- @Column(name = "price")
- private BigDecimal price;
- @Column(name ="created_time")
- private Date createdTime;
- }
数据访问 microservice-base\order-service\src\main\java\com\no\one\order\repo\OrderRepo.java :
- public interface OrderRepo extends JpaRepository<Order, Long> {
- }
业务接口 microservice-base\order-service\src\main\java\com\no\one\order\service\OrderService.java
- public interface OrderService {
- Order createOrder(Long userId, Long productId);
- List<Order> listOrder();
- }
在实现业务接口之前, 要实现对于 ProductService 和 UserService 的服务调用. 此处, 直接使用 Open Feign , 该库可以极大简化对于远程服务的调用, 并且, 使用 SpringMVC 里的各种常用注解.
调用 ProductService, microservice-base\order-service\src\main\java\com\no\one\order\client\ProductClient.java :
- @FeignClient("product-service")
- public interface ProductClient {
- @RequestMapping(method = RequestMethod.GET, value = "/products/{id}")
- ProductDto getProduct(@PathVariable("id") Long id);
- }
最后, 实现 OrderService 中的业务接口 microservice-base\order-service\src\main\java\com\no\one\order\service\impl\OrderServiceImpl.java
- @Service
- @RequiredArgsConstructor
- public class OrderServiceImpl implements OrderService {
- private final OrderRepo orderRepo;
- private final UserClient userClient;
- private final ProductClient productClient;
- @Override
- public Order createOrder(Long userId, Long productId) {
- Order order = new Order();
- UserDto user = userClient.getUser(userId);
- order.setUserName(user.getName());
- ProductDto product = productClient.getProduct(productId);
- order.setProductName(product.getName());
- order.setPrice(product.getPrice());
- order.setCreatedTime(new Date());
- return orderRepo.save(order);
- }
- @Override
- public List<Order> listOrder() {
- return orderRepo.findAll();
- }
- }
在 createOrder 方法中, 会调用两个远程的服务, 然后, 创建订单实体对象, 最终数据保存到数据库中.
创建 RestAPI, microservice-base\order-service\src\main\java\com\no\one\order\controller\OrderController.java
- @RequiredArgsConstructor
- @RestController
- @RequestMapping("/orders")
- public class OrderController {
- private final OrderService orderService;
- @PostMapping
- public Order createOrder(@RequestBody CreateOrderRequest request){
- return orderService.createOrder(request.getUserId(), request.getProductId());
- }
- @GetMapping
- public List<Order> listOrder(){
- return orderService.listOrder();
- }
- @Data
- public static class CreateOrderRequest {
- private Long userId;
- private Long productId;
- }
- }
ProductService 和 OrderService 的构建类似, 不过, 我们会给这两个服务增加一些数据, 他们会在项目启动时初始化到内存数据库中:
- microservice-base\user-service\src\main\resources\data.sql
- INSERT INTO ms_user VALUES (1, 'Zeus');
- INSERT INTO ms_user VALUES (2, 'Hera');
- INSERT INTO ms_user VALUES (3, 'Hades');
- microservice-base\product-service\src\main\resources\data.sql
- INSERT INTO ms_product VALUES (1, 'iPhone4', 99);
- INSERT INTO ms_product VALUES (2, 'iPhone6',999.99);
- INSERT INTO ms_product VALUES (3, 'iPhone8', 9999.98);
此时, 我们可以借助一些 RestAPI 测试工具, 测试创建订单的接口:
POST http://localhost:9002/api/order-service/orders
HTTP 请求的请求体:
- {
- "userId":1,
- "productId":1
- }
成功后, 返回数据:
- {
- "id": 1,
- "productName": "iPhone4",
- "userName": "Zeus",
- "price": 99,
- "createdTime": "2018-08-27T12:17:02.406+0000"
- }
4 小结
本文结合一个电商中常见的创建订单这个业务场景, 使用微服务架构进行构建. 结合之前的微服务架构基础系列文章, 理论联系实践, 可以更好的理解微服务架构. 当然, 这是一个简单的例子, 现实中的开发往往要更加复杂.
来源: http://www.tuicool.com/articles/aQJ7Bfb