Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

enty-framework 双模式框架使用说明

一个既可拆分为 微服务(Nacos + Feign)、也可聚合为 单体(单进程)的 Spring Cloud 框架。两种模式共用同一套业务代码,切换只靠一个配置:app.mode

  • 技术栈:Spring Boot 2.7 + JDK8 + Spring Cloud Alibaba 2021.0.5.0 + Nacos 2.x + OpenFeign
  • 示例模块:user(用户)、order(订单),order 通过 Feign 调用 user

一、整体架构

root-pom (enty-framework)
├── common                     # 公共能力:统一返回体、全局异常、双模式开关
├── user                       # 用户功能模块(聚合父)
│   ├── user-api               #   契约层:Feign 接口 + DTO
│   └── user-service           #   实现层:微服务(端口 8081)
├── order                      # 订单功能模块(聚合父)
│   ├── order-api              #   契约层:Feign 接口 + DTO
│   └── order-service          #   实现层:微服务(端口 8082),Feign 调用 user
└── app-monolith               # 单体聚合模块(端口 8090)

二、各模块功能作用

模块 作用 说明
common 通用公共层 统一返回体 R、统一返回码 ResultCode、业务异常 BusinessException、全局异常处理 GlobalExceptionHandler@RestControllerAdvice)、双模式开关 AppModeEnvironmentPostProcessor。被所有模块依赖
xxx-api(契约层) 跨模块接口契约 Feign 接口 + DTO,只被「调用方」和「实现方」依赖,不承载业务实现
xxx-service(实现层) 业务实现 业务逻辑 + REST 表现层 + 独立启动入口。微服务模式下可独立运行并注册 Nacos;单体模式下被 app-monolith 作为库聚合
app-monolith 单体聚合模块 依赖所有 -service 模块,单进程运行。app.mode: mono 时自动关闭 Feign / Nacos discovery,跨模块调用变为本地方法调用

包结构约定(以 user 为例)

com.enty.user
├── UserApplication            # 微服务入口(模块根包)
├── controller/
│   └── UserController         # REST 表现层,路径与契约一致
├── service/
│   └── UserClientImpl         # 业务实现 @Service
└── config/                    # 模式相关配置(仅需要时)
com.enty.user.api              # user-api 模块
├── client/
│   └── UserClient             # Feign 契约接口
└── dto/
    └── UserDTO                # 数据传输对象

三、核心机制:app.mode 双模式开关

场景 效果
micro 微服务 注册 Nacos、Feign 远程调用
mono 单体 关闭 Feign 与 Nacos discovery,跨模块变本地调用

一个属性自动推导两件事:

  1. Feignorder/config/OrderFeignConfig@ConditionalOnProperty(name = "app.mode", havingValue = "micro", matchIfMissing = true),mono 时不创建 Feign 代理。
  2. Nacos discoverycommonAppModeEnvironmentPostProcessor(Spring Boot 扩展点,经 META-INF/spring.factories 注册)读到 app.mode=mono 时自动注入 spring.cloud.nacos.discovery.enabled=falsespring.cloud.discovery.enabled=false,无需手写。

app.mode 未配置,默认按 micro 处理(matchIfMissing = true)。单体入口务必显式写 mono


四、如何新增一个功能模块

以新增 payment(支付)为例,复制 user 的结构:

1. 建目录与两个模块

payment/
├── payment-api               # 契约层
└── payment-service           # 实现层

2. 契约层 payment-api

  • 包:com.enty.payment.api.client / com.enty.payment.api.dto
  • 定义 DTO 与 Feign 契约接口:
// com.enty.payment.api.client.PaymentClient
@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/payment")
    PaymentDTO create(@RequestBody PaymentDTO payment);

    @GetMapping("/payment/{id}")
    PaymentDTO getById(@PathVariable("id") Long id);
}

@FeignClient(name) 必须等于提供方 spring.application.name(这里是 payment-service)。

3. 实现层 payment-service

  • 入口:com.enty.payment.PaymentApplication
@SpringBootApplication(scanBasePackages = "com.enty")   // 扫描 common 通用组件
public class PaymentApplication {
    public static void main(String[] args) { SpringApplication.run(PaymentApplication.class, args); }
}
  • REST 表现层:com.enty.payment.controller.PaymentController@RestController,路径与 PaymentClient 契约一致,委托给本地实现)
  • 业务实现:com.enty.payment.service.PaymentClientImpl@Service implements PaymentClient
// controller/PaymentController.java
@RestController
public class PaymentController {
    @Autowired
    private PaymentClient paymentClient;

    @GetMapping("/payment/{id}")
    public PaymentDTO getById(@PathVariable("id") Long id) {
        return paymentClient.getById(id);
    }
}
// service/PaymentClientImpl.java
@Service
public class PaymentClientImpl implements PaymentClient {
    @Override
    public PaymentDTO getById(Long id) { /* 业务逻辑 */ }
}
  • application.yml(微服务模式):
server:
  port: 8083
app:
  mode: micro
spring:
  application:
    name: payment-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

4. 接入聚合

  • pom.xml<modules><module>payment</module><dependencyManagement>payment-api / payment-service
  • payment/pom.xml:父 POM 引用根 POM,<modules>payment-apipayment-service
  • payment-service/pom.xml<parent> 指向 payment,依赖 common + payment-api + web/openfeign/nacos/loadbalancer
  • app-monolith/pom.xml:加依赖 <artifactId>payment-service</artifactId>(想让它进单体时)

5. 若支付模块要远程调用别人(如 user)

payment-service 加一个条件 Feign 配置(模仿 OrderFeignConfig):

package com.enty.payment.config;

@Configuration
@EnableFeignClients(clients = UserClient.class)                 // 只注册要调用的远程接口
@ConditionalOnProperty(name = "app.mode", havingValue = "micro", matchIfMissing = true)
public class PaymentFeignConfig {
}

6. 放进单体运行(app-monolith)

想让新模块也在单体模式下被聚合,只需在 app-monolith/pom.xml 加一行依赖:

<dependency>
    <groupId>com.enty</groupId>
    <artifactId>payment-service</artifactId>
</dependency>

除此之外什么都不用改,因为单体装配是自动的:

单体装配环节 为什么自动生效
组件扫描 MonolithApplicationscanBasePackages = "com.enty"com.enty.payment.* 天然被扫到
入口类排除 排除规则 .*\.Application 已把 PaymentApplication 挡在扫描外,避免入口副作用
Feign 关闭 PaymentFeignConfig 依赖 app.mode=micro,单体下 mono 自动不启用
Nacos 关闭 AppModeEnvironmentPostProcessormono 自动关闭 discovery

前提:新模块包必须在 com.enty 之下(com.enty.payment.*),否则扫不到。

配置:单体运行时只认 app-monolith/application.yml,所以支付模块需要的业务配置(数据源、自定义属性等)要写进这里,不能写在各 -service 自己的 yml。

构建与运行

mvn clean install            # 或 mvn clean package
java -jar app-monolith/target/app-monolith-1.0.0.jar

运行后支付模块的 REST 端点(如 /payment/{id})和 user/order 一样暴露在单体应用上。

注意(bean 名冲突):Spring 默认 bean 名 = 类名首字母小写,单体下所有模块的类在同一个容器里,不同模块不要用相同的类名(比如两个模块都叫 OrderMapper),否则 bean 名会冲突。


五、DTO / VO 的位置

类型 位置 说明
DTO(传输对象) com.enty.xxx.api.dto 跨模块传输的数据模型,放在契约层(api),调用方与实现方共用
VO(视图对象) com.enty.xxx.controller.vo(约定) 对外接口的请求/响应视图对象。当前模板未内置 VO,如需对外层与内部模型解耦,建议放 controller 子包 vo,由 Controller 负责 DTO/VO 互转

约定:跨模块传参一律走 api 层 DTO,不要用各模块内部实体(Entity/DO)直接对外。


六、如何远程调用别的模块

  1. 引入被调用方的 api 依赖:在 pom.xml<artifactId>xxx-api</artifactId>
  2. @Autowired 注入契约接口,直接调方法:
@Service
public class OrderClientImpl implements OrderClient {
    @Autowired
    private UserClient userClient;          // 跨模块调用点

    @Override
    public OrderDTO getOrderWithBuyer(Long id) {
        OrderDTO order = getById(id);
        UserDTO buyer = userClient.getById(order.getUserId());   // 远程调用 user-service
        order.setBuyerName(buyer.getName());
        return order;
    }
}
  1. 微服务模式userClient 是 Feign 代理,经 Nacos 负载均衡走 HTTP 调 http://user-service/...
  2. 单体模式userClient 是本进程内的 UserClientImpl,直接本地方法调用。

同一个 @Autowired UserClient,两种模式下行为自动切换,调用方代码零改动。

提供方必须把对应接口暴露为 REST:UserController 的路径要与 UserClient@GetMapping 路径一致(微服务模式下 Feign 请求的就是这些端点)。


七、微服务模式

7.1 启动

# 需先启动 Nacos(本项目示例用 Docker:nacos/nacos-server:v2.2.3,standalone 模式,暴露 8848/9848/9849)
java -jar user/user-service/target/user-service-1.0.0.jar      # 8081
java -jar order/order-service/target/order-service-1.0.0.jar   # 8082

每个 -service 模块的 jar 即 fat jar(可直接 java -jar)。若服务模块恢复 <classifier>exec</classifier> 配置,则运行 xxx-service-1.0.0-exec.jar

7.2 配置要点

配置 说明
app.mode micro 微服务模式(默认值,可不写)
spring.application.name 服务名 必须等于他人 @FeignClient(name) 的值
spring.cloud.nacos.discovery.server-addr Nacos 地址 127.0.0.1:8848
server.port 各服务独立端口 8081 / 8082 …

Nacos 地址可按环境覆盖:java -jar xxx.jar --spring.cloud.nacos.discovery.server-addr=10.0.0.5:8848

7.3 数据库配置(接入约定)

框架当前未内置 ORM / 数据源依赖,以下为接入约定。每个微服务各自连接自己的库,配置在各自服务的 application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: xxxx

每个 -service 一个数据源、一个库,天然隔离,无需多数据源路由。

7.4 数据库表规范

  • 库名:<模块名>_db,如 order_dbuser_dbpayment_db
  • 表名:<模块前缀>_<业务名>,前缀与库同名,如 order_t_orderorder_t_order_itemuser_t_user
  • 主键统一 id BIGINT,公共字段建议 create_time / update_time / deleted(逻辑删除)

前缀规则是「单体合并库」的前提,务必从第一天起遵守(见 8.3)。


八、单体模式

8.1 启动

java -jar app-monolith/target/app-monolith-1.0.0.jar   # 8090,不需要 Nacos

8.2 配置要点

app-monolith/src/main/resources/application.yml

server:
  port: 8090
app:
  mode: mono                 # 关键:单体模式,自动关 Feign + Nacos discovery
spring:
  application:
    name: monolith-app

只有这一份配置文件生效。各 -service 自己的 application.yml 在单体运行时不会被采用(它们被包进嵌套 jar,Spring Boot 不读取)。因此单体模式下新增的公共配置(如数据库)要写在这里。

8.3 数据库配置(接入约定)

单体模式约定单库:所有模块共用一个数据库(合并库),配置集中在 app-monolithapplication.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mono_all_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: xxxx

单进程单数据源,不需要 @DS 多数据源,跨模块调用天然是本地事务。

8.4 数据库表规范

  • 库名:合并库,如 mono_all_db
  • 表名:必须保留模块前缀,如 order_t_orderuser_t_user——这是合并后不撞名的关键
  • 「微服务多库 → 单体单库」的数据准备:把各 -servicedump 出来,按前缀规则导入合并库即可

8.5 两种模式数据库对比

微服务 单体
每服务一个库(order_db…) 一个合并库(mono_all_db
数据源配置位置 -service 的 application.yml app-monolith 的 application.yml
表前缀 order_t_xxx 同一套前缀(合并不撞名)
跨模块事务 分布式(需 Seata/补偿) 本地事务

九、注意事项

  1. 构建/安装:内部模块只在 reactor 中存在,单独构建 app-monolith(或 IDEA 解析单模块)需先执行 mvn install 装进本地仓库。日常从根目录 mvn clean installmvn clean package
  2. 异常处理:全局异常集中在 commonGlobalExceptionHandler不要在某个 -service 里另写 @RestControllerAdvice,否则单体模式下多个 advice 会竞争。
  3. 依赖去重:Maven 对同版本依赖只保留一份,common 等公共依赖不会因多个模块引用而重复。
  4. 配置生效范围:单体运行时只认 app-monolith/application.yml;微服务运行时各服务认各自的。
  5. app.mode 未配置时默认微服务,单体入口必须显式 mono

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages