网站首页 > 技术文章 正文
引言
在多年的软件架构实践中,我发现一个普遍存在的问题:业务模块划分与技术架构分层这两个概念经常被混淆使用,导致架构设计混乱、团队沟通困难、代码组织无序。
本文将从第一性原理出发,深度剖析这两个概念的本质区别,并通过实际案例展示如何在企业级应用中正确应用这两种架构思维。
概念本质:两个不同的架构维度
业务模块划分:水平切分的业务视角
定义:按照业务功能和领域边界,将系统划分为相对独立的业务模块。
核心特征:
- 业务导向:以业务能力和领域知识为划分依据
- 水平切分:在同一抽象层次上进行功能分割
- 领域边界:遵循DDD的限界上下文原则
- 业务完整性:每个模块包含完整的业务闭环
技术架构分层:垂直切分的技术视角
定义:按照技术职责和抽象层次,将系统划分为不同的技术层次。
核心特征:
- 技术导向:以技术职责和依赖关系为划分依据
- 垂直切分:在不同抽象层次上进行职责分离
- 依赖方向:严格遵循单向依赖原则
- 技术内聚:每层专注特定的技术关注点
深度对比:两种架构思维的差异
维度 | 业务模块划分 | 技术架构分层 |
切分方向 | 水平切分(功能维度) | 垂直切分(抽象维度) |
关注焦点 | 业务能力和领域边界 | 技术职责和依赖关系 |
设计目标 | 业务内聚、模块解耦 | 职责分离、层次清晰 |
变化驱动 | 业务需求变化 | 技术架构演进 |
团队视角 | 产品经理、业务分析师 | 架构师、技术负责人 |
生命周期 | 相对稳定,随业务发展调整 | 相对固定,技术架构确定后很少变化 |
正交关系:两个维度的组合应用
在实际的企业级应用中,这两种架构思维是正交关系,需要同时应用:
关键洞察:每个业务模块内部都包含完整的技术分层,这就是现代软件架构的二维设计空间。
实践案例:短信管理平台的架构设计
让我们以一个真实的短信管理平台为例,展示如何正确应用这两种架构思维。
第一步:业务模块划分(水平维度)
基于业务能力和领域边界,我们识别出以下核心业务模块:
核心业务模块
├── 短信发送管理模块
│ ├── 发送申请创建
│ ├── 发送任务执行
│ └── 发送结果跟踪
├── 流程审批管理模块
│ ├── 审批流程定义
│ ├── 审批任务处理
│ └── 审批历史查询
├── 账号资源管理模块
│ ├── 短信账号管理
│ ├── 签名模板管理
│ └── 余额费用管理
└── 系统配置管理模块
├── 系统参数配置
├── 权限角色管理
└── 操作日志管理
第二步:技术架构分层(垂直维度)
每个业务模块内部都采用DDD四层架构:
// 以短信发送管理模块为例
src/main/java/com/gwm/sms/sending/
├── presentation/ # 表现层
│ ├── controller/
│ │ └── SmsController.java
│ └── dto/
│ ├── SmsTaskCreateRequest.java
│ └── SmsTaskVO.java
├── application/ # 应用层
│ ├── service/
│ │ └── SmsApplicationService.java
│ └── command/
│ └── SmsTaskCreateCommand.java
├── domain/ # 领域层
│ ├── entity/
│ │ └── SmsTask.java
│ ├── service/
│ │ └── SmsTaskDomainService.java
│ └── repository/
│ └── SmsTaskRepository.java
└── infrastructure/ # 基础设施层
├── repository/
│ └── SmsTaskRepositoryImpl.java
└── mapper/
└── SmsTaskMapper.java
第三步:模块间协作设计
不同业务模块之间通过应用层服务进行协作,避免跨模块的直接依赖:
/**
* 短信发送应用服务
* 负责编排本模块内的业务流程,以及与其他模块的协作
*/
@Service
@Slf4j
public class SmsApplicationService {
private final SmsTaskDomainService smsTaskDomainService;
private final WorkflowApplicationService workflowApplicationService; // 跨模块协作
private final AccountApplicationService accountApplicationService; // 跨模块协作
/**
* 创建短信发送申请
* 展示跨模块协作的正确方式
*/
@Transactional(rollbackFor = Exception.class)
public String createSmsTask(SmsTaskCreateCommand command) {
// 1. 本模块的领域逻辑
SmsTask task = smsTaskDomainService.createTask(command);
// 2. 跨模块协作:验证账号资源
accountApplicationService.validateAccountResource(
command.getAccountId(),
task.getEstimatedCount()
);
// 3. 跨模块协作:启动审批流程
String workflowId = workflowApplicationService.startApprovalProcess(
task.getId(),
command.getApplicantId()
);
// 4. 更新本模块状态
smsTaskDomainService.updateWorkflowId(task.getId(), workflowId);
return task.getId();
}
}
常见反模式与解决方案
反模式1:业务模块与技术分层混淆
错误做法:将业务模块当作技术分层
src/main/java/com/gwm/sms/
├── controller/ # 所有模块的Controller放在一起
├── service/ # 所有模块的Service放在一起
├── entity/ # 所有模块的Entity放在一起
└── mapper/ # 所有模块的Mapper放在一起
正确做法:先按业务模块划分,再按技术分层组织
src/main/java/com/gwm/sms/
├── sending/ # 短信发送模块
│ ├── presentation/
│ ├── application/
│ ├── domain/
│ └── infrastructure/
├── workflow/ # 流程管理模块
│ ├── presentation/
│ ├── application/
│ ├── domain/
│ └── infrastructure/
└── account/ # 账号管理模块
├── presentation/
├── application/
├── domain/
└── infrastructure/
反模式2:跨层直接调用
错误做法:Controller直接调用Repository
@RestController
public class SmsController {
@Autowired
private SmsTaskMapper smsTaskMapper; // 跨层调用
public Result<List<SmsTask>> getTasks() {
return Result.success(smsTaskMapper.selectAll());
}
}
正确做法:严格遵循分层依赖
@RestController
public class SmsController {
private final SmsApplicationService smsApplicationService;
public Result<List<SmsTaskVO>> getTasks() {
List<SmsTaskDTO> tasks = smsApplicationService.getTasks();
return Result.success(SmsTaskConverter.toVO(tasks));
}
}
反模式3:模块间直接依赖
错误做法:领域层直接依赖其他模块
@Component
public class SmsTaskDomainService {
@Autowired
private WorkflowDomainService workflowDomainService; // 跨模块依赖
}
正确做法:通过应用层进行模块协作
@Service
public class SmsApplicationService {
private final SmsTaskDomainService smsTaskDomainService;
private final WorkflowApplicationService workflowApplicationService; // 应用层协作
}
架构决策指导原则
1. 业务模块划分原则
- 高内聚:模块内部业务逻辑紧密相关
- 低耦合:模块间依赖关系清晰且最小化
- 完整性:每个模块包含完整的业务闭环
- 稳定性:模块边界相对稳定,不频繁变化
2. 技术分层设计原则
- 单一职责:每层只处理特定类型的技术关注点
- 依赖倒置:高层不依赖低层,都依赖抽象
- 接口隔离:层间通过明确的接口进行交互
- 开闭原则:对扩展开放,对修改关闭
3. 模块协作设计原则
- 应用层协作:跨模块调用只能发生在应用层
- 事件驱动:复杂的跨模块协作优先考虑事件机制
- 接口契约:模块间通过稳定的接口契约进行交互
- 数据隔离:每个模块拥有独立的数据存储
不同架构模式的应用
单体应用架构
单个部署单元包含所有业务模块:
┌─────────────────────────────────────┐
│ 短信管理平台 │
├─────────────────────────────────────┤
│ 短信发送 │ 流程管理 │ 账号管理 │ 配置管理 │
│ 模块 │ 模块 │ 模块 │ 模块 │
├─────────────────────────────────────┤
│ 共享的技术基础设施 │
└─────────────────────────────────────┘
微服务架构
每个业务模块独立部署:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 短信发送服务 │ │ 流程管理服务 │ │ 账号管理服务 │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ 四层架构 │ │ 四层架构 │ │ 四层架构 │
└─────────────┘ └─────────────┘ └─────────────┘
实施建议
1. 团队组织建议
- 业务模块负责制:每个模块指定专门的负责人
- 架构师统筹:确保技术分层的一致性和规范性
- 跨模块协作机制:建立清晰的模块间协作流程
2. 代码管理建议
- 模块独立性:每个模块可以相对独立地开发和测试
- 接口版本管理:模块间接口变更需要版本控制
- 代码审查重点:重点关注跨层调用和跨模块依赖
3. 演进策略建议
- 渐进式重构:从单体架构向模块化架构逐步演进
- 边界调整:根据业务发展适时调整模块边界
- 技术升级:技术分层的升级不影响业务模块划分
总结
业务模块划分与技术架构分层是软件架构设计中的两个核心维度:
- 业务模块划分解决的是"做什么"的问题,关注业务能力和领域边界
- 技术架构分层解决的是"怎么做"的问题,关注技术职责和依赖关系
正确理解和应用这两种架构思维,能够帮助我们设计出既满足业务需求又具备良好技术特性的软件系统。在实际项目中,要避免将这两个概念混淆,而是要将它们作为正交的设计维度,共同构建清晰、可维护、可扩展的软件架构。
记住:优秀的架构师不是选择其中一种思维,而是能够熟练地运用两种思维,在二维设计空间中找到最适合当前业务和技术环境的架构方案。
猜你喜欢
- 2025-09-04 单体架构回潮:企业为何不再纠结技术焦虑
- 2025-09-04 Spring Boot 3 中 Jar 的运行原理及解压后文件含义
- 2025-09-04 互联网运维必知!最新技术架构全解析
- 2025-09-04 Injob in产品实践:Agent 效果差?先别怪模型——可能是你的“上下文”被污染了
- 2025-09-04 从微服务到单体:究竟是什么让架构走“回头路”?
- 2025-09-04 股份有限公司的分权与制衡_股份有限公司怎么分股权
- 2025-09-04 拆解Power'by必经之路:安托规划-治理-开发-服务四层架构全揭秘
- 2025-09-04 中小型企业适合实现微服务架构吗?
- 2025-09-04 30年架构师谈25时代IT行业入行:选择适合你的技术路径与职业规划
- 2025-09-04 Agent杂谈:Agent的能力上下限及「Agent构建」核心技术栈调研分享~
- 最近发表
-
- count(*)、count1(1)、count(主键)、count(字段) 哪个更快?
- 深入探索 Spring Boot3 中 MyBatis 的 association 标签用法
- js异步操作 Promise fetch API 带来的网络请求变革—仙盟创梦IDE
- HTTP状态码超详细说明_http 状态码有哪些
- 聊聊跨域的原理与解决方法_跨域解决方案及原理
- 告别懵圈!产品新人的接口文档轻松入门指南
- 在Javaweb中实现发送简单邮件_java web发布
- 优化必备基础:Oracle中常见的三种表连接方式
- Oracle常用工具使用 - AWR_oracle工具有哪些
- 搭载USB 3.1接口:msi 微星 发布 990FXA Gaming 游戏主板
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)