首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【SpringCloud】服务拆分原则 && 工程搭建实操 && RestTemplate && REST API

【SpringCloud】服务拆分原则 && 工程搭建实操 && RestTemplate && REST API

原创
作者头像
lirendada
发布2026-02-11 10:25:48
发布2026-02-11 10:25:48
940
举报
文章被收录于专栏:JavaJava

Ⅰ. 开发环境安装

一、JDK

Spring Cloud 是基于 Spring Boot 进行开发的,Spring Boot 3.X 以下的版本,Spring 官方已不再进行维护(还可以继续使用),Spring Boot 3.X 的版本,使用的 JDK 版本基线为 JDK17

二、MySQL

在使用 Spring Cloud Azure 的场景(即 Spring Cloud 在 Azure 上对 MySQL 的支持)中,官方明确表示其 "当前版本" 支持 MySQL 5.78.0

对于 MySQL JDBC 驱动 MySQL Connector/J 的官方文档显示:

  • Connector/J 8.0 支持 MySQL 8.0 及以上。
  • Connector/J 9.4 支持 MySQL 8.0 及以上。

Ⅱ. 案例介绍

一、需求

实现一个电商平台。一个电商平台包含的内容非常多,以京东为例,仅从首页上就可以看到巨多的功能。

我们该如何实现呢?如果把这些功能全部写在一个服务里,这个服务将是巨大的。巨多的会员,巨大的流量,微服务架构是最好的选择。

而微服务应用开发的第一步,就是服务拆分。拆分后才能进行 "各自开发"。

二、服务拆分原则

微服务到底多小才算 "微",这个在业界并没有明确的标准。微服务并不是越小越好,服务越小,微服务架构的优点和缺点都会越来越明显。

服务越小,微服务的独立性就会越来越高,但同时,微服务的数量也会越多,管理这些微服务的难度也会提高。所以服务拆分也要考虑场景。

还是以企业管理为例:

企业中一个员工的工作内容与企业规模,项目规模等都有关系。

在小公司,一个员工可能需要负责很多部门的事情,大公司的话,一个部门的工作可能需要多个员工来处理。

拆分微服务一般遵循如下原则:

① 单一职责原则

单一职责原则原本是面向对象设计中的一个基本原则,它指的是一个类应该专注于单一功能。不要存在多于一个导致类变更的原因。

在微服务架构中,一个微服务只负责一个功能或业务领域,每个服务应该有清晰的定义和边界,只关注自己的特定业务领域

组织团队也是,一个人专注做一件事情的效率远高于同时关注多件事情。 比如一个人同时管理和维护一份代码,要比多个人同时维护多份代码的效率高。 比如电商系统:

② 服务自治

服务自治是指每个微服务都应该具备高度自治的能力,即每个服务要能做到独立开发、独立测试、独立构建、独立部署、独立运行。

以上面的电商系统为例,每一个微服务应该有自己的存储、配置,在进行开发、构建、部署、运行和测试时,并不需要过多关注其他微服务的状态和数据。

比如企业管理: 每个部分负责每个部门的事情,并且尽可能少的受其他团队影响。 研发部门只负责需求功能的开发,而不负责需求文档的书写和 UI 的设计。并且其他部门的人员变动,流程变更,也尽可能少的影响研发部门。部门和部门之间尽可能自治。

③ 单向依赖

微服务之间需要做到单向依赖,严禁循环依赖,双向依赖!

  • 循环依赖:A->B->C->A
  • 双向依赖:A->B,B->A

如果一些场景确实无法避免循环依赖或者双向依赖,可以考虑使用消息队列等其他方式来实现~

三、服务拆分示例

一个完整的电商系统是庞大的,这里重点关注如何使用 Spring Cloud 解决微服务架构中遇到的问题。

以订单列表为例:

简单来看,这个页面提供了以下信息:

  1. 订单列表
  2. 商品信息

根据服务的单一职责原则,我们把服务进行拆分为:订单服务、商品服务

  • 订单服务:提供订单 ID,获取订单详细信息
  • 商品服务:根据商品 ID,返回商品详细信息

Ⅲ. 数据准备

根据服务自治原则,每个服务都应有自己独立的数据库

订单服务:

代码语言:javascript
复制
-- 建库
create database if not exists cloud_order charset utf8mb4;
use cloud_order;

-- 订单表
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
  `product_id` BIGINT(20) NULL COMMENT '产品id',
  `num` INT(10) NULL DEFAULT 0 COMMENT '下单数量',
  `price` BIGINT(20) NOT NULL COMMENT '实付款',
  `delete_flag` TINYINT(4) NULL DEFAULT 0,
  `create_time` DATETIME DEFAULT now(),
  `update_time` DATETIME DEFAULT now(),
  PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '订单表';

-- 数据初始化
insert into order_detail (user_id,product_id,num,price) values
(2001, 1001,1,99), 
(2002, 1002,1,30), 
(2001, 1003,1,40),
(2003, 1004,3,58), 
(2004, 1005,7,85), 
(2005, 1006,7,94);

商品服务:

代码语言:javascript
复制
create database if not exists cloud_product charset utf8mb4;
use cloud_product;

-- 产品表
DROP TABLE IF EXISTS product_detail;
CREATE TABLE product_detail (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '产品id',
  `product_name` varchar(128) NULL COMMENT '产品名称',
  `product_price` BIGINT(20) NOT NULL COMMENT '产品价格',
  `state` TINYINT(4) NULL DEFAULT 0 COMMENT '产品状态 0-有效 1-下架',
  `create_time` DATETIME DEFAULT now(),
  `update_time` DATETIME DEFAULT now(),
  PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '产品表';

-- 数据初始化
insert into product_detail (id, product_name,product_price,state) values
(1001,"T恤", 101, 0), 
(1002, "短袖",30, 0), 
(1003, "短裤",44, 0),
(1004, "卫衣",58, 0), 
(1005, "马甲",98, 0),
(1006,"羽绒服", 101, 0),
(1007, "冲锋衣",30, 0), 
(1008, "袜子",44, 0), 
(1009, "鞋子",58, 0),
(10010, "毛衣",98, 0)

Ⅳ. 工程搭建

一、构建父子工程

① 创建父工程

  1. 创建一个空的 maven 项目,删除所有代码,只保留 pom.xml 文件:
  1. 完善 pom.xml 文件:
    1. 使用 properties 来进行版本号的统一管理
    2. 使用 dependencyManagement 来管理依赖
    3. 声明父工程的打包方式为 pom
    代码语言:javascript
    复制
    <?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>
        <groupId>com.liren</groupId>
        <artifactId>spring-cloud-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>pom</packaging>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.1.6</version>
            <relativePath/> <!-- 从远程仓库下载拉取,而不在本地找父POM -->
        </parent>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <java.version>17</java.version>
            <mybatis.version>3.0.3</mybatis.version>
            <mysql.version>8.0.33</mysql.version>
            <spring-cloud.version>2022.0.3</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>${mybatis.version}</version>
                </dependency>
                <dependency>
                    <groupId>com.mysql</groupId>
                    <artifactId>mysql-connector-j</artifactId>
                    <version>${mysql.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter-test</artifactId>
                    <version>${mybatis.version}</version>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    </project>

② 创建子项目 -- 订单服务

然后声明项目依赖和项目构建插件:

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

③ 创建子项目 -- 商品服务

创建商品服务 product-service 的过程和上面是一样的!

然后声明项目依赖和项目构建插件:

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

二、完善订单服务

① 完善启动类、配置文件

  • 启动类:
    代码语言:javascript
    复制
    @SpringBootApplication
    public class OrderServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderServiceApplication.class, args);
        }
    }
  • 配置文件:
    代码语言:javascript
    复制
    server:
      port: 8080
    
    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    
    mybatis:
      configuration:
        # 配置打印 MyBatis 日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        map-underscore-to-camel-case: true  #配置驼峰自动转换

② 业务代码

  1. 实体类
    代码语言:javascript
    复制
    @Data
    public class OrderInfo {
        private Integer id;
        private Integer userId;
        private Integer productId;
        private Integer num;
        private Integer price;
        private Integer deleteFlag;
        private Date createTime;
        private Date updateTime;
    }
  2. Controller
    代码语言:javascript
    复制
    @RequestMapping("/order")
    @RestController
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @RequestMapping("/{orderId}")
        public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){
            return orderService.selectOrderById(orderId);
        }
    }
  3. Service
    代码语言:javascript
    复制
    @Service
    public class OrderService {
        @Autowired
        private OrderMapper orderMapper;
    
        public OrderInfo selectOrderById(Integer orderId) {
            OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
            return orderInfo;
        }
    }
  4. Mapper
    代码语言:javascript
    复制
    @Mapper
    public interface OrderMapper {
        @Select("select * from order_detail where id=#{orderId}")
        OrderInfo selectOrderById(Integer orderId);
    }

③ 测试

访问 url:http://127.0.0.1:8080/order/1

页面正常返回结果:

三、完善商品服务(同上)

① 完善启动类,配置文件

  • 启动类:
    代码语言:javascript
    复制
    @SpringBootApplication
    public class ProductServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProductServiceApplication.class, args);
        }
    }
  • 配置文件:
    代码语言:javascript
    复制
    server:
      port: 9090 # 后面需要多个服务一起启动,所以设置为不同的端口号
    
    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    
    mybatis:
      configuration:  # 配置打印 MyBatis 日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        map-underscore-to-camel-case: true  #配置驼峰自动转换

② 业务代码

  1. 实体类
    代码语言:javascript
    复制
    @Data
    public class ProductInfo {
        private Integer id;
        private String productName;
        private Integer productPrice;
        private Integer state;
        private Date createTime;
        private Date updateTime;
    }
  2. Controller
    代码语言:javascript
    复制
    @RequestMapping("/product")
    @RestController
    public class ProductController {
        @Autowired
        private ProductService productService;
    
        @RequestMapping("/{productId}")
        public ProductInfo getProductById(@PathVariable("productId") Integer productId){
            System.out.println("收到请求,Id:"+productId);
            return productService.selectProductById(productId);
        }
    }
  3. Service
    代码语言:javascript
    复制
    @Service
    public class ProductService {
        @Autowired
        private ProductMapper productMapper;
    
        public ProductInfo selectProductById(Integer id){
            return productMapper.selectProductById(id);
        }
    }
  4. Mapper
    代码语言:javascript
    复制
    @Mapper
    public interface ProductMapper {
        @Select("select * from product_detail where id=#{id}")
        ProductInfo selectProductById(Integer id);
    }

③ 测试

访问 url:http://127.0.0.1:9090/product/1001

页面正常返回结果:

四、远程调用

① 需求

根据订单查询订单信息时,根据订单中的产品 ID,获取产品的详细信息。

② 实现

实现思路:order-service 服务向 product-service 服务发送一个 http 请求,把得到的返回结果,和订单结果融合在一起,返回给调用方。

实现方式:采用 Spring 提供的 RestTemplate

实现 http 请求的方式,有很多,可参考:https://zhuanlan.zhihu.com/p/670101467

  1. 定义 RestTemplate
    代码语言:javascript
    复制
    @Configuration
    public class BeanConfig {
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
  2. 将商品模块中的 ProductInfo 复制到订单模块中,然后修改 OrderInfo(这是错误示范,这里只是为了方便演示)
  1. 修改 order-service 中的 OrderService
    代码语言:javascript
    复制
    @Service
    public class OrderService {
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private RestTemplate restTemplate;
    
        public OrderInfo selectOrderById(Integer orderId) {
            OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
            String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
            ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
            orderInfo.setProductInfo(productInfo);
            return orderInfo;
        }
    }
  2. 最后将两个模块都启动起来

③ 测试

访问 url:http://127.0.0.1:8080/order/1

页面返回结果:

Ⅴ. RestTemplate

RestTemplate 是 Spring 提供的一个同步 HTTP 客户端,用来在 Java 程序中发送 HTTP 请求,调用远程 REST 接口。

简单说,它是:

  • Java 世界里的一个 HTTP 工具类
  • 用于 远程调用(远程服务 / 第三方 API)
  • 由 Spring Boot 自带(starter-web 包含)

常用方法如下所示:

方法名

功能

类似于

getForObject()

发送 GET 请求并获取响应体

axios.get / fetch

getForEntity()

获取完整响应(含 headers、status)

更底层

postForObject()

发送 POST 请求

axios.post

put()

PUT 请求

axios.put

delete()

DELETE 请求

axios.delete

exchange()

高级通用方法,可自定义 header、method

通用 API

不过虽然它是 "调用远程接口" 的利器,但在 Spring Cloud 中,通常会被 Feign 进一步封装替代。下面是不同的方案的区别:

特性

RestTemplate

WebClient

Feign

调用方式

同步阻塞

异步非阻塞

声明式接口

复杂度

简单

稍复杂

最简单(写接口就行)

Spring Cloud 集成

✅ 有

✅ 有

✅ 强烈推荐

推荐场景

普通 HTTP 调用

高并发异步调用

微服务间调用

拓展:什么是 REST API

一、概念

RESTful API(Representational State Transfer API)是一种基于 HTTP 协议、面向资源的接口设计风格

简单说:

  • 它不是一种技术;
  • 而是一种 接口设计规范
  • 让前端和后端通过统一的规则进行通信。

二、通俗解释

假如系统中有个 "用户模块":

  • 前端要获取用户列表
  • 查看某个用户
  • 创建新用户
  • 修改或删除用户

传统的接口可能会写成:

代码语言:javascript
复制
/getUserList
/getUserById
/createUser
/updateUser
/deleteUser

RESTful 风格 认为这些动作其实都是针对一个 "资源(Resource)" —— 比如 "用户"。所以 RESTful 把接口 "资源化":

操作

URL(资源路径)

HTTP 方法

获取所有用户

/users

GET

获取单个用户

/users/{id}

GET

创建用户

/users

POST

修改用户

/users/{id}

PUT

删除用户

/users/{id}

DELETE

是不是瞬间感觉清爽多了?没有那些 "动词堆叠" 的接口名,全靠 HTTP 方法表达语义。

三、六个核心设计原则

原则

含义

1️⃣ 以资源为中心(Resource Oriented)

接口操作的是“资源”,而不是“动作”

2️⃣ 使用标准 HTTP 方法

GET、POST、PUT、DELETE 等

3️⃣ 通过 URL 标识资源

每个资源都有唯一 URI

4️⃣ 使用 JSON 表达资源状态

客户端与服务端交换 JSON

5️⃣ 无状态(Stateless)

每次请求都独立,不依赖上一次

6️⃣ 统一响应格式

一般定义通用结构 {code, msg, data}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 开发环境安装
    • 一、JDK
    • 二、MySQL
  • Ⅱ. 案例介绍
    • 一、需求
    • 二、服务拆分原则
      • ① 单一职责原则
      • ② 服务自治
      • ③ 单向依赖
    • 三、服务拆分示例
  • Ⅲ. 数据准备
  • Ⅳ. 工程搭建
    • 一、构建父子工程
      • ① 创建父工程
      • ② 创建子项目 -- 订单服务
      • ③ 创建子项目 -- 商品服务
    • 二、完善订单服务
      • ① 完善启动类、配置文件
      • ② 业务代码
      • ③ 测试
    • 三、完善商品服务(同上)
      • ① 完善启动类,配置文件
      • ② 业务代码
      • ③ 测试
    • 四、远程调用
      • ① 需求
      • ② 实现
      • ③ 测试
  • Ⅴ. RestTemplate
  • 拓展:什么是 REST API
    • 一、概念
    • 二、通俗解释
    • 三、六个核心设计原则
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档