理论必须结合实践才能发挥价值。本章将以一个经典的**"用户注册"**场景为例,演示如何使用 COLA 架构从零开始构建并开发一个功能。我们将遵循由外向内定义接口、由内向外实现核心的开发路径。
5.1 第一步:脚手架生成(初始化)
COLA 最大的便利之一是提供了标准的 Maven Archetype。你不需要手动创建那四个模块,直接运行一条命令即可生成标准工程。
Maven命令(基于COLA 4.0):
codeBash
mvn archetype:generate \
-DgroupId=com.alibaba.cola.demo \
-DartifactId=demo-web \
-Dversion=1.0.0-SNAPSHOT \
-Dpackage=com.alibaba.demo \
-DarchetypeArtifactId=cola-framework-archetype-service \
-DarchetypeGroupId=com.alibaba.cola \
-DarchetypeVersion=4.0.1
执行完毕后,你将得到一个标准的父子工程结构:
-
demo-web-adapter
-
demo-web-app
-
demo-web-client
-
demo-web-domain
-
demo-web-infrastructure
5.2 第二步:定义业务契约(Client层)
在 COLA 中,开发通常从"定义外部如何与我们交互"开始。
在 client 模块中,我们需要定义 DTO 和 API。
codeJava
// 1. 定义命令请求 (UserRegisterCmd.java)
public class UserRegisterCmd extends Command {
@NotNull
private String userName;
@NotNull
private String password;
// ... getters/setters
}
// 2. 定义服务接口 (UserServiceI.java)
public interface UserServiceI {
Response register(UserRegisterCmd cmd);
}
5.3 第三步:构建领域模型(Domain层)
这是最关键的一步。忘掉数据库表结构,专注于业务实体。
在 domain 模块中:
codeJava
// 1. 定义领域实体 (User.java)
// 这是一个充血模型,包含业务行为
public class User extends Entity {
private String userName;
private String password; // 可能是加密后的
// 业务行为:加密密码
public void encryptPassword() {
this.password = DigestUtils.md5Hex(this.password);
}
}
// 2. 定义网关接口 (UserGateway.java)
// Domain层只定义接口,不关心数据存在MySQL还是Redis
public interface UserGateway {
void save(User user);
}
5.4 第四步:实现基础设施(Infrastructure层)
在 infrastructure 模块中,我们需要落地数据的持久化。这里会涉及 COLA 开发中常见的一个痛点:对象转换。
-
定义 DO (Data Object): 对应数据库表结构(MyBatis-Plus/JPA实体)。
-
实现 Mapper: 数据库访问接口。
-
实现 Gateway:
codeJava
@Component
public class UserGatewayImpl implements UserGateway {
@Autowired
private UserMapper userMapper;
@Override
public void save(User user) {
// 核心步骤:将 Domain Entity 转换为 Infra DO
UserDO userDO = UserConvertor.toDO(user);
userMapper.insert(userDO);
}
}
注:你需要编写一个 Convertor/Assembler 类来处理 User <-> UserDO 的互相转换,这是解耦的必要成本。
5.5 第五步:编排业务逻辑(App层)
在 app 模块中,通过 Executor 来串联业务流程。
codeJava
@Component
@CommandHandler
public class UserRegisterCmdExe implements CommandExecutorI<Response, UserRegisterCmd> {
@Autowired
private UserGateway userGateway;
@Override
public Response execute(UserRegisterCmd cmd) {
// 1. 转换 DTO -> Entity
User user = new User();
user.setUserName(cmd.getUserName());
user.setPassword(cmd.getPassword());
// 2. 执行领域业务逻辑
user.encryptPassword(); // 调用领域能力
// 3. 调用基础设施持久化
userGateway.save(user);
// 4. 返回结果
return Response.buildSuccess();
}
}
5.6 第六步:暴露接口(Adapter层)
最后,在 adapter 模块的 Controller 中暴露 HTTP 接口。
codeJava
@RestController
public class UserController {
@Autowired
private UserServiceI userService; // 注入 Client 层的接口
@PostMapping("/user/register")
public Response register(@RequestBody UserRegisterCmd cmd) {
return userService.register(cmd);
}
}
5.7 开发心得与注意事项
-
关于对象转换: 你会发现 COLA 架构中存在大量的对象转换(DTO <-> Entity <-> DO)。很多初学者觉得繁琐,但切记:DTO 是为了契约的稳定性,DO 是为了数据库的映射,Entity 才是业务的核心。不混用这些对象,是防止"大泥球"腐烂的第一道防线。可以使用 MapStruct 等工具简化这一过程。
-
依赖原则: 永远检查你的 pom.xml。如果 domain 模块引用了 mybatis 或 spring-web,说明你的架构已经"漏气"了,请立即修正。
-
扩展点使用: 如果后续涉及"VIP用户注册"和"普通用户注册"逻辑不同,可以引入 COLA 的 Extension 机制,在 App 层通过 BizCode 动态路由到不同的 Executor 实现,这也是 COLA 应对复杂业务的杀手锏。
传统做法是在 Service 层写大量的 if (userType == "VIP") else if ...,导致核心代码臃肿不堪。COLA 通过 Extension(扩展点) 机制,利用多态思想完美解决了这个问题。
第一步:定义扩展点接口(Domain层)
首先,我们在 Domain 层定义一个扩展点接口,继承 ExtensionPointI。这个接口定义了那些"因人而异"的逻辑钩子。
codeJava
package com.alibaba.demo.domain.user.gateway;
import com.alibaba.cola.extension.ExtensionPointI;
// 定义注册流程中的差异化钩子
public interface UserRegisterExtPt extends ExtensionPointI {
// 譬如:不同用户有不同的校验逻辑
void validate(String userName);
// 譬如:注册成功后的后置处理(VIP送积分,普通用户发邮件)
void afterRegister(String userName);
}
第二步:实现不同身份的逻辑(App层)
在 App 层(或者专门的 extension 包),我们为不同的身份编写具体的实现类,并使用 @Extension 注解进行标记。
场景 A:普通用户实现
codeJava
@Extension(bizId = "COMMON_USER") // 标记业务身份 ID
public class CommonUserRegisterExt implements UserRegisterExtPt {
@Override
public void validate(String userName) {
if (userName.length() > 10) {
throw new BizException("普通用户名长度不能超过10");
}
}
@Override
public void afterRegister(String userName) {
System.out.println("发送欢迎邮件给普通用户:" + userName);
}
}
场景 B:VIP 用户实现
codeJava
@Extension(bizId = "VIP_USER") // 标记业务身份 ID
public class VipUserRegisterExt implements UserRegisterExtPt {
@Override
public void validate(String userName) {
// VIP 用户特权:名字可以很长,不做限制
// 仅做简单的敏感词过滤
}
@Override
public void afterRegister(String userName) {
System.out.println("增加VIP积分并分配专属客服给:" + userName);
}
}
第三步:在业务编排中调用扩展点(App层)
回到我们的 UserRegisterCmdExe(命令执行器)。现在,我们不再直接写逻辑,而是通过 COLA 提供的 ExtensionExecutor 来分发请求。
codeJava
@Component
@CommandHandler
public class UserRegisterCmdExe implements CommandExecutorI<Response, UserRegisterCmd> {
@Autowired
private UserGateway userGateway;
@Autowired
private ExtensionExecutor extensionExecutor; // 注入扩展点执行器
@Override
public Response execute(UserRegisterCmd cmd) {
// 1. 构建业务场景 (BizScenario)
// 这里的 bizId 通常来自前端参数 cmd.getUserType(),例如 "VIP_USER" 或 "COMMON_USER"
BizScenario scenario = BizScenario.valueOf(cmd.getUserType());
// 2. 执行扩展点:校验逻辑
// executeVoid 方法会自动根据 scenario 找到对应的实现类 (Common 或 Vip) 并执行
extensionExecutor.executeVoid(UserRegisterExtPt.class, scenario,
ext -> ext.validate(cmd.getUserName()));
// 3. 通用逻辑:领域对象转换与密码加密 (所有用户都一样)
User user = new User();
user.setUserName(cmd.getUserName());
user.setPassword(cmd.getPassword());
user.encryptPassword();
// 4. 持久化
userGateway.save(user);
// 5. 执行扩展点:后置处理
extensionExecutor.executeVoid(UserRegisterExtPt.class, scenario,
ext -> ext.afterRegister(cmd.getUserName()));
return Response.buildSuccess();
}
}
5.8 总结与实战心得
通过上述的"多身份"实战,我们可以清晰地看到 COLA 架构的威力:
-
开闭原则(OCP)的完美体现: 如果未来新增了"企业用户",我们只需要新增一个 EnterpriseUserRegisterExt 类并打上注解,无需修改 UserRegisterCmdExe 中的任何一行代码。系统越复杂,这种优势越明显。
-
业务逻辑清晰分层:
-
App Layer:负责流程编排,决定"先做什么,再做什么"(先校验,再保存,再后置处理)。
-
Domain Layer:负责核心模型行为(如密码加密)。
-
Extension:负责处理差异化逻辑(不同用户的特殊规则)。
-
-
避免"大泥球": 所有的 if-else 都被多态取代了,代码的可读性和可维护性得到了质的飞跃。
欢迎关注,一起交流,一起进步~