学习微服务架构,不但要了解微服务中的基本概念和重要组件,更重要的是实践。本文将会以一个电商中的常见业务场景为例构建微服务。在本文中,主要使用最新的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
:
spring.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
:
spring.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
:
spring.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 小结
本文结合一个电商中常见的创建订单这个业务场景,使用微服务架构进行构建。结合之前的微服务架构基础系列文章,理论联系实践,可以更好的理解微服务架构。当然,这是一个简单的例子,现实中的开发往往要更加复杂。
5 微服务架构基础系列
更多精彩内容,关注公众号SeniorEngineer: