很多团队在单体架构向微服务演进的过程中,常常陷入两大困境:要么启动全量重写,陷入长达数月的“重构地狱”,业务迭代完全停滞;要么盲目拆分,最终得到一个耦合度更高、维护难度更大的“分布式单体”。而增量重构的核心价值,就是用最小的成本、最低的风险,实现架构的平滑演进,全程保障业务零中断。
一、单体架构演进的前置判断:什么时候才真的需要微服务?
微服务从来不是银弹,绝大多数场景下,单体架构依然是性价比最高的选择。只有当你的系统出现以下可量化的问题时,才具备启动微服务演进的核心前提,所有判断标准均基于康威定律与业界公认的架构设计原则。
- 组织协作效率严重下降:一个常规业务需求,需要3个及以上团队同时修改同一单体应用的代码,月度代码合并冲突率超过30%,需求交付周期从周级拉长到月级。
- 应用交付效率触顶:单体应用全量编译打包时长超过10分钟,全量回归测试周期超过3天,一次局部功能发布需要全量验证所有业务,无法实现局部独立发布。
- 资源利用率严重失衡:应用内不同模块的资源需求差异极大,比如秒杀模块需要10台机器抗流量,而用户管理模块仅需2台,受单体架构限制必须全量扩容,资源浪费率超过60%。
- 技术债务无法局部偿还:单体应用基于老旧技术栈开发,框架版本无法升级(升级会影响全量业务),但部分新兴业务场景必须依赖新的技术能力支撑。
二、增量重构的核心方法论与底层逻辑
增量重构的本质,是在不中断业务的前提下,将单体系统的业务能力按业务域逐步迁移到独立的微服务中,每一步都完成闭环验证,最终完成全量架构演进。其核心体系基于三个业界权威的架构模式构建。
1. 绞杀者模式(Strangler Fig Pattern)
该模式由Martin Fowler于2004年提出,灵感来自热带雨林的绞杀榕——这种植物会逐步缠绕宿主树,最终完成替代,全程不会直接砍倒宿主导致生态崩溃。
- 底层逻辑:不直接修改原有单体系统的核心逻辑,而是在单体外围逐步构建新的微服务,通过流量灰度切换,将业务能力逐步迁移到新服务,当全量流量切换完成后,再下线旧的单体逻辑。
- 核心优势:每一步操作都具备完整的回滚能力,不会出现全量重写的“发布悬崖”,全程保障业务连续性。
2. 领域驱动设计(DDD)限界上下文拆分原则
该原则来自Eric Evans的《领域驱动设计》,是微服务拆分的黄金标准,从根源上避免拆分后的服务出现强耦合。
- 底层逻辑:微服务的拆分边界,必须与业务的限界上下文完全对齐,一个限界上下文对应一个微服务,每个服务只负责对应业务域内的完整能力。
- 核心原则:高内聚,低耦合。服务内部的业务逻辑高度内聚,服务之间仅通过标准化接口通信,最小化跨服务依赖。
3. 防腐层(Anti-Corruption Layer, ACL)模式
该模式同样来自DDD体系,用于隔离新旧系统的模型差异,避免新服务被旧系统的不合理设计污染。
- 底层逻辑:在新旧系统之间搭建一层独立的适配层,负责旧系统模型与新系统领域模型的双向转换,同时实现流量路由、灰度管控、熔断降级等能力。
- 核心价值:实现新旧系统的无缝协同,同时保证新服务的领域模型纯粹性,不会为了兼容旧系统做出不合理的设计妥协。
三者的协同关系为:DDD限界上下文是拆分的核心依据,绞杀者模式是演进的核心方式,防腐层是新旧系统协同的核心保障,三者共同构成了增量重构的完整方法论体系。
三、增量重构的全流程落地步骤
增量重构的核心执行原则是:先易后难、先非核心后核心、先低风险后高风险、先独立后耦合,全程单能力闭环推进,绝对禁止多业务能力并行迁移。
步骤1:全量业务与架构梳理,完成拆分边界定义
这是整个重构过程的基础,边界定义错误,会直接导致后续拆分出现无法挽回的耦合问题。
- 核心动作1:梳理单体系统的全量业务流程,绘制业务域全景图,明确核心域、支撑域、通用域。
- 核心动作2:基于DDD方法论,识别每个业务域的限界上下文,明确每个上下文的核心职责、对外提供的能力、依赖的外部能力。
- 核心动作3:梳理单体系统的数据库表结构,明确每个表所属的业务域,识别表之间的关联关系,尤其是跨业务域的强关联。
- 核心动作4:梳理单体系统的代码结构,识别每个业务模块的代码边界、依赖关系、技术债务情况。
- 核心输出物:业务域全景图、限界上下文地图、数据依赖关系图、代码依赖关系图。
步骤2:拆分优先级排序,确定演进路线
合理的优先级排序,能将重构风险降到最低,同时快速拿到重构成果,验证演进方向的正确性。优先级排序基于4个核心维度,权重从高到低依次为:
- 业务独立性:模块与其他模块的耦合度越低,优先级越高。比如短信通知、文件存储这类通用模块,几乎无外部依赖,优先拆分。
- 业务变更频率:模块的迭代频率越高,优先级越高。比如营销活动模块,经常需要快速迭代,拆分后可独立发布,不影响其他业务。
- 业务风险等级:模块的核心程度越低,故障影响范围越小,优先级越高。比如日志查询、数据统计模块优先拆分,交易核心模块放在最后。
- 性能瓶颈等级:模块的流量压力越大,性能瓶颈越明显,优先级越高。比如商品查询模块,是系统流量入口,拆分后可独立扩容。
基于上述维度,通用的演进路线排序为: 第一优先级:通用能力模块 → 第二优先级:非核心业务模块 → 第三优先级:核心业务支撑模块 → 第四优先级:核心交易链路模块
步骤3:防腐层搭建,实现新旧系统隔离
防腐层是整个增量重构过程的核心枢纽,所有新旧系统的交互都必须经过防腐层,绝对禁止新老系统直接调用。 防腐层的核心职责包括4个方面:
- 模型转换:将旧单体的贫血数据模型,转换为新微服务的富领域模型,避免新系统被旧模型的不合理设计污染。
- 接口适配:将新微服务的标准化接口,适配为旧单体需要的接口格式,实现新旧系统的无缝调用,前端与其他依赖模块完全无感知。
- 流量管控:实现新旧系统之间的流量路由、灰度比例控制、熔断降级,出现异常时可快速切回旧系统,保障业务稳定性。
- 行为兼容:严格保证新旧系统的业务处理逻辑完全一致,避免出现逻辑差异导致的业务故障。
步骤4:单业务能力渐进式迁移,闭环验证
核心执行逻辑:一次只迁移一个最小业务能力,完成开发、测试、验证、上线全流程闭环后,再启动下一个能力的迁移,从根源上控制风险范围。
每个业务能力迁移的标准执行流程:
- 能力定义:明确要迁移的业务能力的输入、输出、业务规则、异常处理逻辑,与原有单体逻辑完全对齐,形成标准化的能力说明。
- 新服务开发:基于DDD领域模型开发新的微服务,实现对应的业务能力,通过单元测试、集成测试,保证处理结果与原有单体完全一致。
- 双写适配:在防腐层实现数据双写逻辑,对新增、修改、删除操作,同时写入旧单体数据库与新微服务数据库,保证两边数据基准一致。
- 一致性比对:通过自动化工具,持续比对新旧系统的处理结果与存储数据,确保100%一致,验证周期不低于一个完整的业务周期。
- 读流量切换:先将读操作流量,按比例灰度切换到新微服务,验证新服务的性能、稳定性、逻辑正确性,无异常再逐步提升灰度比例。
- 写流量切换:读流量全量切换完成后,再将写操作流量按比例灰度切换到新微服务,验证无误后全量切换,同时停止双写,将新微服务作为唯一数据源。
- 旧逻辑下线:所有流量完全切换到新微服务,稳定运行一个完整业务周期后,下线单体中对应的旧逻辑代码与数据依赖。
步骤5:全链路灰度与流量切换,保障业务零中断
流量切换绝对不能一刀切,必须采用渐进式灰度策略,每一步都有明确的验证标准与回滚方案。 核心灰度执行策略:
- 流量比例灰度:从1%流量开始,逐步提升到10%、30%、50%、100%,每一步都持续监控核心指标,出现异常立即回滚到上一比例。
- 白名单灰度:先将内部测试用户、指定灰度用户的流量切换到新服务,验证无误后,再放开到全量公网用户。
- 业务场景灰度:先切换低风险的业务场景(比如数据查询操作),验证稳定后,再切换高风险的业务场景(比如支付、订单创建操作)。
每一次灰度比例提升,必须验证的核心指标包括:业务成功率、错误率、接口耗时、数据一致性、服务CPU/内存使用率、数据库负载、异常日志数量。
步骤6:旧系统平滑下线,完成架构演进
旧系统逻辑下线必须渐进式执行,禁止一次性删除全量代码,避免出现遗漏依赖导致的业务故障。 标准下线流程:
- 流量清零确认:通过监控与日志,确认所有业务流量都已切换到新微服务,单体中对应的旧逻辑连续7天无任何流量进入。
- 依赖清理:清理单体中对旧逻辑的所有依赖,包括代码依赖、配置依赖、数据库依赖、中间件依赖。
- 数据归档:将旧数据库中对应的业务表数据,归档到离线存储,保留合规要求的回溯周期,避免数据丢失。
- 代码下线:删除单体中对应的旧逻辑代码,发布新版本的单体应用,验证全量业务无任何影响。
- 资源释放:释放旧系统占用的服务器、数据库、中间件等资源,完成最终的下线操作。
步骤7:微服务治理体系完善
微服务拆分完成不是演进的终点,而是治理的起点。需要逐步完善服务注册发现、配置中心、限流熔断、全链路追踪、日志监控、容器编排、DevOps流水线等治理能力,保障微服务架构的长期稳定运行。
四、全流程实战示例
本示例基于电商场景,完整演示从单体系统中增量拆分用户服务的全流程。
1. 原有单体系统核心代码
单体用户实体类
package com.legacy.shop.user.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "legacy_user")
public class LegacyUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String phone;
private String email;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public LegacyUser() {
}
public LegacyUser(Long id, String username, String password, String phone, String email, Integer status, LocalDateTime createTime, LocalDateTime updateTime) {
this.id = id;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}
单体用户数据访问层
package com.legacy.shop.user.repository;
import com.legacy.shop.user.entity.LegacyUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface LegacyUserRepository extends JpaRepository<LegacyUser, Long> {
Optional<LegacyUser> findByUsername(String username);
Optional<LegacyUser> findByPhone(String phone);
}
单体用户业务服务层
package com.legacy.shop.user.service;
import com.legacy.shop.user.entity.LegacyUser;
import com.legacy.shop.user.repository.LegacyUserRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class LegacyUserService {
private final LegacyUserRepository userRepository;
public LegacyUserService(LegacyUserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<LegacyUser> getUserById(Long id) {
return userRepository.findById(id);
}
public Optional<LegacyUser> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
public LegacyUser createUser(LegacyUser user) {
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setStatus(1);
return userRepository.save(user);
}
public LegacyUser updateUser(LegacyUser user) {
user.setUpdateTime(LocalDateTime.now());
return userRepository.save(user);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
单体用户接口层
package com.legacy.shop.user.controller;
import com.legacy.shop.user.entity.LegacyUser;
import com.legacy.shop.user.service.LegacyUserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/api/user")
public class LegacyUserController {
private final LegacyUserService userService;
public LegacyUserController(LegacyUserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<LegacyUser> getUserById(@PathVariable Long id) {
Optional<LegacyUser> user = userService.getUserById(id);
return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/username/{username}")
public ResponseEntity<LegacyUser> getUserByUsername(@PathVariable String username) {
Optional<LegacyUser> user = userService.getUserByUsername(username);
return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<LegacyUser> createUser(@RequestBody LegacyUser user) {
LegacyUser savedUser = userService.createUser(user);
return ResponseEntity.ok(savedUser);
}
@PutMapping
public ResponseEntity<LegacyUser> updateUser(@RequestBody LegacyUser user) {
LegacyUser updatedUser = userService.updateUser(user);
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
2. 防腐层核心实现
灰度路由配置类
package com.legacy.shop.acl.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "acl.user.route")
public class UserRouteConfig {
private Integer grayRatio = 0;
private Boolean enableDoubleWrite = false;
private Boolean enableNewServiceRead = false;
private Boolean enableNewServiceWrite = false;
public Integer getGrayRatio() { return grayRatio; }
public void setGrayRatio(Integer grayRatio) { this.grayRatio = grayRatio; }
public Boolean getEnableDoubleWrite() { return enableDoubleWrite; }
public void setEnableDoubleWrite(Boolean enableDoubleWrite) { this.enableDoubleWrite = enableDoubleWrite; }
public Boolean getEnableNewServiceRead() { return enableNewServiceRead; }
public void setEnableNewServiceRead(Boolean enableNewServiceRead) { this.enableNewServiceRead = enableNewServiceRead; }
public Boolean getEnableNewServiceWrite() { return enableNewServiceWrite; }
public void setEnableNewServiceWrite(Boolean enableNewServiceWrite) { this.enableNewServiceWrite = enableNewServiceWrite; }
}
流量路由决策工具类
package com.legacy.shop.acl.router;
import com.legacy.shop.acl.config.UserRouteConfig;
import org.springframework.stereotype.Component;
@Component
public class UserTrafficRouter {
private final UserRouteConfig routeConfig;
public UserTrafficRouter(UserRouteConfig routeConfig) {
this.routeConfig = routeConfig;
}
public boolean shouldRouteToNewService(Long userId) {
if (routeConfig.getGrayRatio() <= 0) {
return false;
}
if (routeConfig.getGrayRatio() >= 100) {
return true;
}
long hash = userId % 100;
return hash < routeConfig.getGrayRatio();
}
public boolean shouldRouteToNewService(String username) {
if (routeConfig.getGrayRatio() <= 0) {
return false;
}
if (routeConfig.getGrayRatio() >= 100) {
return true;
}
long hash = Math.abs(username.hashCode()) % 100;
return hash < routeConfig.getGrayRatio();
}
}
新服务Feign调用客户端
package com.legacy.shop.acl.client;
import com.legacy.shop.acl.client.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "new-user-service", url = "${acl.user.service.url:http://localhost:8081}")
public interface NewUserServiceClient {
@GetMapping("/api/v1/user/{userId}")
ResponseEntity<UserDTO> getUserById(@PathVariable("userId") Long userId);
@GetMapping("/api/v1/user/username/{username}")
ResponseEntity<UserDTO> getUserByUsername(@PathVariable("username") String username);
@PostMapping("/api/v1/user")
ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO);
@PutMapping("/api/v1/user")
ResponseEntity<UserDTO> updateUser(@RequestBody UserDTO userDTO);
@DeleteMapping("/api/v1/user/{userId}")
ResponseEntity<Void> deleteUser(@PathVariable("userId") Long userId);
}
传输对象DTO
package com.legacy.shop.acl.client.dto;
import java.time.LocalDateTime;
public class UserDTO {
private Long userId;
private String username;
private String password;
private String phone;
private String email;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public UserDTO() {
}
public UserDTO(Long userId, String username, String password, String phone, String email, Integer status, LocalDateTime createTime, LocalDateTime updateTime) {
this.userId = userId;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}
新旧模型转换器
package com.legacy.shop.acl.converter;
import com.legacy.shop.acl.client.dto.UserDTO;
import com.legacy.shop.user.entity.LegacyUser;
import org.springframework.stereotype.Component;
@Component
public class UserModelConverter {
public UserDTO toDTO(LegacyUser legacyUser) {
if (legacyUser == null) {
return null;
}
return new UserDTO(
legacyUser.getId(),
legacyUser.getUsername(),
legacyUser.getPassword(),
legacyUser.getPhone(),
legacyUser.getEmail(),
legacyUser.getStatus(),
legacyUser.getCreateTime(),
legacyUser.getUpdateTime()
);
}
public LegacyUser toLegacyEntity(UserDTO userDTO) {
if (userDTO == null) {
return null;
}
return new LegacyUser(
userDTO.getUserId(),
userDTO.getUsername(),
userDTO.getPassword(),
userDTO.getPhone(),
userDTO.getEmail(),
userDTO.getStatus(),
userDTO.getCreateTime(),
userDTO.getUpdateTime()
);
}
}
防腐层核心服务
package com.legacy.shop.acl.service;
import com.legacy.shop.acl.client.NewUserServiceClient;
import com.legacy.shop.acl.client.dto.UserDTO;
import com.legacy.shop.acl.config.UserRouteConfig;
import com.legacy.shop.acl.converter.UserModelConverter;
import com.legacy.shop.acl.router.UserTrafficRouter;
import com.legacy.shop.user.entity.LegacyUser;
import com.legacy.shop.user.service.LegacyUserService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserAclService {
private final LegacyUserService legacyUserService;
private final NewUserServiceClient newUserServiceClient;
private final UserRouteConfig routeConfig;
private final UserTrafficRouter trafficRouter;
private final UserModelConverter modelConverter;
public UserAclService(LegacyUserService legacyUserService, NewUserServiceClient newUserServiceClient, UserRouteConfig routeConfig, UserTrafficRouter trafficRouter, UserModelConverter modelConverter) {
this.legacyUserService = legacyUserService;
this.newUserServiceClient = newUserServiceClient;
this.routeConfig = routeConfig;
this.trafficRouter = trafficRouter;
this.modelConverter = modelConverter;
}
public Optional<LegacyUser> getUserById(Long id) {
if (routeConfig.getEnableNewServiceRead() && trafficRouter.shouldRouteToNewService(id)) {
ResponseEntity<UserDTO> response = newUserServiceClient.getUserById(id);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return Optional.of(modelConverter.toLegacyEntity(response.getBody()));
}
}
return legacyUserService.getUserById(id);
}
public Optional<LegacyUser> getUserByUsername(String username) {
if (routeConfig.getEnableNewServiceRead() && trafficRouter.shouldRouteToNewService(username)) {
ResponseEntity<UserDTO> response = newUserServiceClient.getUserByUsername(username);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return Optional.of(modelConverter.toLegacyEntity(response.getBody()));
}
}
return legacyUserService.getUserByUsername(username);
}
public LegacyUser createUser(LegacyUser user) {
LegacyUser savedLegacyUser = legacyUserService.createUser(user);
if (routeConfig.getEnableDoubleWrite()) {
UserDTO userDTO = modelConverter.toDTO(savedLegacyUser);
newUserServiceClient.createUser(userDTO);
}
return savedLegacyUser;
}
public LegacyUser updateUser(LegacyUser user) {
LegacyUser updatedLegacyUser = legacyUserService.updateUser(user);
if (routeConfig.getEnableDoubleWrite()) {
UserDTO userDTO = modelConverter.toDTO(updatedLegacyUser);
newUserServiceClient.updateUser(userDTO);
}
return updatedLegacyUser;
}
public void deleteUser(Long id) {
legacyUserService.deleteUser(id);
if (routeConfig.getEnableDoubleWrite()) {
newUserServiceClient.deleteUser(id);
}
}
}
适配后的单体接口层
package com.legacy.shop.user.controller;
import com.legacy.shop.acl.service.UserAclService;
import com.legacy.shop.user.entity.LegacyUser;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/api/user")
public class LegacyUserController {
private final UserAclService userAclService;
public LegacyUserController(UserAclService userAclService) {
this.userAclService = userAclService;
}
@GetMapping("/{id}")
public ResponseEntity<LegacyUser> getUserById(@PathVariable Long id) {
Optional<LegacyUser> user = userAclService.getUserById(id);
return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/username/{username}")
public ResponseEntity<LegacyUser> getUserByUsername(@PathVariable String username) {
Optional<LegacyUser> user = userAclService.getUserByUsername(username);
return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<LegacyUser> createUser(@RequestBody LegacyUser user) {
LegacyUser savedUser = userAclService.createUser(user);
return ResponseEntity.ok(savedUser);
}
@PutMapping
public ResponseEntity<LegacyUser> updateUser(@RequestBody LegacyUser user) {
LegacyUser updatedUser = userAclService.updateUser(user);
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userAclService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
3. 新用户微服务完整实现
微服务启动类
package com.newshop.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
领域模型
package com.newshop.user.domain.model;
import java.time.LocalDateTime;
public class User {
private final Long userId;
private final String username;
private final String password;
private final String phone;
private final String email;
private final UserStatus status;
private final LocalDateTime createTime;
private LocalDateTime updateTime;
public User(Long userId, String username, String password, String phone, String email, UserStatus status, LocalDateTime createTime, LocalDateTime updateTime) {
this.userId = userId;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public void enable() {
if (this.status == UserStatus.ENABLED) {
return;
}
this.updateTime = LocalDateTime.now();
}
public void disable() {
if (this.status == UserStatus.DISABLED) {
return;
}
this.updateTime = LocalDateTime.now();
}
public void updateBaseInfo(String phone, String email) {
this.updateTime = LocalDateTime.now();
}
public Long getUserId() { return userId; }
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getPhone() { return phone; }
public String getEmail() { return email; }
public UserStatus getStatus() { return status; }
public LocalDateTime getCreateTime() { return createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public enum UserStatus {
ENABLED(1), DISABLED(0);
private final int code;
UserStatus(int code) {
this.code = code;
}
public int getCode() { return code; }
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("无效的用户状态码");
}
}
}
领域仓库接口
package com.newshop.user.domain.repository;
import com.newshop.user.domain.model.User;
import java.util.Optional;
public interface UserRepository {
Optional<User> findById(Long userId);
Optional<User> findByUsername(String username);
User save(User user);
void deleteById(Long userId);
}
领域服务
package com.newshop.user.domain.service;
import com.newshop.user.domain.model.User;
import com.newshop.user.domain.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class UserDomainService {
private final UserRepository userRepository;
public UserDomainService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> getUserById(Long userId) {
return userRepository.findById(userId);
}
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
public User createUser(User user) {
User newUser = new User(
null,
user.getUsername(),
user.getPassword(),
user.getPhone(),
user.getEmail(),
User.UserStatus.ENABLED,
LocalDateTime.now(),
LocalDateTime.now()
);
return userRepository.save(newUser);
}
public User updateUser(User user) {
Optional<User> existUserOpt = userRepository.findById(user.getUserId());
if (existUserOpt.isEmpty()) {
throw new IllegalArgumentException("用户不存在");
}
User existUser = existUserOpt.get();
existUser.updateBaseInfo(user.getPhone(), user.getEmail());
return userRepository.save(existUser);
}
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
}
持久化实体
package com.newshop.user.infrastructure.persistence.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "new_user")
public class UserPO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String username;
private String password;
private String phone;
private String email;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public UserPO() {
}
public UserPO(Long userId, String username, String password, String phone, String email, Integer status, LocalDateTime createTime, LocalDateTime updateTime) {
this.userId = userId;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}
JPA仓库接口
package com.newshop.user.infrastructure.persistence.repository;
import com.newshop.user.infrastructure.persistence.entity.UserPO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserJpaRepository extends JpaRepository<UserPO, Long> {
Optional<UserPO> findByUsername(String username);
Optional<UserPO> findByPhone(String phone);
}
模型转换器
package com.newshop.user.infrastructure.converter;
import com.newshop.user.domain.model.User;
import com.newshop.user.infrastructure.persistence.entity.UserPO;
import org.springframework.stereotype.Component;
@Component
public class UserConverter {
public User toDomain(UserPO userPO) {
if (userPO == null) {
return null;
}
return new User(
userPO.getUserId(),
userPO.getUsername(),
userPO.getPassword(),
userPO.getPhone(),
userPO.getEmail(),
User.UserStatus.fromCode(userPO.getStatus()),
userPO.getCreateTime(),
userPO.getUpdateTime()
);
}
public UserPO toPO(User user) {
if (user == null) {
return null;
}
return new UserPO(
user.getUserId(),
user.getUsername(),
user.getPassword(),
user.getPhone(),
user.getEmail(),
user.getStatus().getCode(),
user.getCreateTime(),
user.getUpdateTime()
);
}
}
仓库实现类
package com.newshop.user.infrastructure.persistence;
import com.newshop.user.domain.model.User;
import com.newshop.user.domain.repository.UserRepository;
import com.newshop.user.infrastructure.converter.UserConverter;
import com.newshop.user.infrastructure.persistence.repository.UserJpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class UserRepositoryImpl implements UserRepository {
private final UserJpaRepository userJpaRepository;
private final UserConverter userConverter;
public UserRepositoryImpl(UserJpaRepository userJpaRepository, UserConverter userConverter) {
this.userJpaRepository = userJpaRepository;
this.userConverter = userConverter;
}
@Override
public Optional<User> findById(Long userId) {
return userJpaRepository.findById(userId).map(userConverter::toDomain);
}
@Override
public Optional<User> findByUsername(String username) {
return userJpaRepository.findByUsername(username).map(userConverter::toDomain);
}
@Override
public User save(User user) {
UserPO userPO = userConverter.toPO(user);
UserPO savedPO = userJpaRepository.save(userPO);
return userConverter.toDomain(savedPO);
}
@Override
public void deleteById(Long userId) {
userJpaRepository.deleteById(userId);
}
}
应用层DTO
package com.newshop.user.application.dto;
import java.time.LocalDateTime;
public class UserDTO {
private Long userId;
private String username;
private String password;
private String phone;
private String email;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public UserDTO() {
}
public UserDTO(Long userId, String username, String password, String phone, String email, Integer status, LocalDateTime createTime, LocalDateTime updateTime) {
this.userId = userId;
this.username = username;
this.password = password;
this.phone = phone;
this.email = email;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}
应用层转换器
package com.newshop.user.application.converter;
import com.newshop.user.application.dto.UserDTO;
import com.newshop.user.domain.model.User;
import org.springframework.stereotype.Component;
@Component
public class UserAppConverter {
public UserDTO toDTO(User user) {
if (user == null) {
return null;
}
return new UserDTO(
user.getUserId(),
user.getUsername(),
user.getPassword(),
user.getPhone(),
user.getEmail(),
user.getStatus().getCode(),
user.getCreateTime(),
user.getUpdateTime()
);
}
public User toDomain(UserDTO userDTO) {
if (userDTO == null) {
return null;
}
return new User(
userDTO.getUserId(),
userDTO.getUsername(),
userDTO.getPassword(),
userDTO.getPhone(),
userDTO.getEmail(),
User.UserStatus.fromCode(userDTO.getStatus()),
userDTO.getCreateTime(),
userDTO.getUpdateTime()
);
}
}
应用层服务
package com.newshop.user.application.service;
import com.newshop.user.application.converter.UserAppConverter;
import com.newshop.user.application.dto.UserDTO;
import com.newshop.user.domain.model.User;
import com.newshop.user.domain.service.UserDomainService;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserAppService {
private final UserDomainService userDomainService;
private final UserAppConverter userAppConverter;
public UserAppService(UserDomainService userDomainService, UserAppConverter userAppConverter) {
this.userDomainService = userDomainService;
this.userAppConverter = userAppConverter;
}
public Optional<UserDTO> getUserById(Long userId) {
return userDomainService.getUserById(userId).map(userAppConverter::toDTO);
}
public Optional<UserDTO> getUserByUsername(String username) {
return userDomainService.getUserByUsername(username).map(userAppConverter::toDTO);
}
public UserDTO createUser(UserDTO userDTO) {
User user = userAppConverter.toDomain(userDTO);
User savedUser = userDomainService.createUser(user);
return userAppConverter.toDTO(savedUser);
}
public UserDTO updateUser(UserDTO userDTO) {
User user = userAppConverter.toDomain(userDTO);
User updatedUser = userDomainService.updateUser(user);
return userAppConverter.toDTO(updatedUser);
}
public void deleteUser(Long userId) {
userDomainService.deleteUser(userId);
}
}
接口层Controller
package com.newshop.user.interfaces.controller;
import com.newshop.user.application.dto.UserDTO;
import com.newshop.user.application.service.UserAppService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
private final UserAppService userAppService;
public UserController(UserAppService userAppService) {
this.userAppService = userAppService;
}
@GetMapping("/{userId}")
public ResponseEntity<UserDTO> getUserById(@PathVariable Long userId) {
Optional<UserDTO> userDTO = userAppService.getUserById(userId);
return userDTO.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@GetMapping("/username/{username}")
public ResponseEntity<UserDTO> getUserByUsername(@PathVariable String username) {
Optional<UserDTO> userDTO = userAppService.getUserByUsername(username);
return userDTO.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
UserDTO savedUser = userAppService.createUser(userDTO);
return ResponseEntity.ok(savedUser);
}
@PutMapping
public ResponseEntity<UserDTO> updateUser(@RequestBody UserDTO userDTO) {
UserDTO updatedUser = userAppService.updateUser(userDTO);
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{userId}")
public ResponseEntity<Void> deleteUser(@PathVariable Long userId) {
userAppService.deleteUser(userId);
return ResponseEntity.noContent().build();
}
}
五、易混淆技术点的明确区分
1. 增量重构 vs 全量重写
- 增量重构:基于现有系统逐步迁移业务能力,每一步都有验证与回滚方案,业务零中断,风险可控,适合核心在线业务系统。
- 全量重写:完全抛弃现有系统从零开发,开发周期长、风险极高,仅适用于现有系统完全无法维护、业务模型发生根本性变化的场景。
2. 微服务 vs 分布式单体
- 微服务:每个服务拥有独立的业务域、独立的数据库、独立的发布周期,服务之间通过标准化接口通信,低耦合、高内聚。
- 分布式单体:将单体应用拆分为多个部署单元,但所有服务共享同一个数据库,服务之间强耦合,一个服务故障会影响全链路,维护难度远高于单体架构。
3. 绞杀者模式 vs 适配器模式
- 绞杀者模式:用于系统架构演进,核心是逐步替换旧系统,最终完成旧系统下线,是一个长期的动态演进过程。
- 适配器模式:用于接口兼容,核心是将一个接口转换为另一个接口,实现两个系统的协同,是静态的适配方案,不会替换原有系统。
4. 数据自治 vs 数据共享
- 数据自治:每个微服务拥有独立的数据库,服务之间只能通过接口交互数据,低耦合,可独立扩展,是微服务的核心原则。
- 数据共享:多个服务共享同一个数据库,通过数据库表关联实现数据交互,强耦合,无法独立扩展,是微服务架构的典型反模式。
5. 业务能力拆分 vs 技术层拆分
- 业务能力拆分:基于DDD限界上下文按业务域拆分,每个服务负责一个完整的业务能力,高内聚、低耦合,是微服务拆分的正确方式。
- 技术层拆分:按技术层将Controller、Service、DAO拆分为不同服务,强耦合,一个需求需要修改多个服务,是微服务架构的典型反模式。
六、重构过程中的核心风险与防控方案
1. 数据一致性风险
- 风险场景:双写过程中单边成功单边失败,导致两边数据不一致;流量切换过程中新旧系统同时写入,引发数据冲突。
- 防控方案:采用最终一致性方案,通过定时任务持续比对两边数据,修复不一致项;双写过程中以旧系统为基准,新系统写入失败不影响主流程;写流量全量切换到新系统后,再停止双写。
2. 业务中断风险
- 风险场景:新服务出现故障导致业务无法访问;灰度切换过程中,新服务逻辑与旧系统不一致,引发业务异常。
- 防控方案:防腐层实现熔断降级,新服务异常时自动切回旧系统;每一步灰度切换都制定明确的回滚方案,出现异常立即回滚;上线前完成全量回归测试,确保新旧系统逻辑完全一致。
3. 服务依赖混乱风险
- 风险场景:拆分过程中服务依赖关系越来越复杂,出现循环依赖,导致系统无法维护。
- 防控方案:严格按照DDD限界上下文拆分,明确每个服务的职责边界;定期绘制服务依赖地图,清理不合理依赖;采用事件驱动架构,通过消息中间件实现服务解耦。
4. 性能退化风险
- 风险场景:微服务之间的网络调用导致接口耗时增加,原本的本地调用变为远程调用,出现性能瓶颈。
- 防控方案:合理设计服务粒度,避免过度拆分导致调用链路过长;采用缓存、批量调用、异步调用等方案优化远程调用性能;通过全链路压测提前发现性能瓶颈并优化。
七、核心原则总结
单体到微服务的演进,本质上不是一个技术问题,而是一个业务与组织的协同问题。架构演进的最终目标,是更好地支撑业务发展,提升组织协作效率,降低系统维护成本,而不是为了使用微服务而微服务。
整个演进过程,必须坚守三个核心原则:
- 业务驱动:所有架构调整都必须以业务需求为核心,技术始终服务于业务。
- 小步快跑:一次只完成一个最小闭环的调整,验证无误后再推进下一步,将风险控制在最小范围。
- 持续验证:每一步操作都有明确的验证标准,全程保障业务逻辑的一致性与系统的稳定性。
只有遵循这些原则,才能真正实现从单体到微服务的平滑演进,避免陷入“重构地狱”,让架构真正为业务赋能。