从单体到微服务:零中断增量重构的核心方法论与全链路实战

0 阅读22分钟

很多团队在单体架构向微服务演进的过程中,常常陷入两大困境:要么启动全量重写,陷入长达数月的“重构地狱”,业务迭代完全停滞;要么盲目拆分,最终得到一个耦合度更高、维护难度更大的“分布式单体”。而增量重构的核心价值,就是用最小的成本、最低的风险,实现架构的平滑演进,全程保障业务零中断。

一、单体架构演进的前置判断:什么时候才真的需要微服务?

微服务从来不是银弹,绝大多数场景下,单体架构依然是性价比最高的选择。只有当你的系统出现以下可量化的问题时,才具备启动微服务演进的核心前提,所有判断标准均基于康威定律与业界公认的架构设计原则。

  1. 组织协作效率严重下降:一个常规业务需求,需要3个及以上团队同时修改同一单体应用的代码,月度代码合并冲突率超过30%,需求交付周期从周级拉长到月级。
  2. 应用交付效率触顶:单体应用全量编译打包时长超过10分钟,全量回归测试周期超过3天,一次局部功能发布需要全量验证所有业务,无法实现局部独立发布。
  3. 资源利用率严重失衡:应用内不同模块的资源需求差异极大,比如秒杀模块需要10台机器抗流量,而用户管理模块仅需2台,受单体架构限制必须全量扩容,资源浪费率超过60%。
  4. 技术债务无法局部偿还:单体应用基于老旧技术栈开发,框架版本无法升级(升级会影响全量业务),但部分新兴业务场景必须依赖新的技术能力支撑。

二、增量重构的核心方法论与底层逻辑

增量重构的本质,是在不中断业务的前提下,将单体系统的业务能力按业务域逐步迁移到独立的微服务中,每一步都完成闭环验证,最终完成全量架构演进。其核心体系基于三个业界权威的架构模式构建。

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个核心维度,权重从高到低依次为:

  1. 业务独立性:模块与其他模块的耦合度越低,优先级越高。比如短信通知、文件存储这类通用模块,几乎无外部依赖,优先拆分。
  2. 业务变更频率:模块的迭代频率越高,优先级越高。比如营销活动模块,经常需要快速迭代,拆分后可独立发布,不影响其他业务。
  3. 业务风险等级:模块的核心程度越低,故障影响范围越小,优先级越高。比如日志查询、数据统计模块优先拆分,交易核心模块放在最后。
  4. 性能瓶颈等级:模块的流量压力越大,性能瓶颈越明显,优先级越高。比如商品查询模块,是系统流量入口,拆分后可独立扩容。

基于上述维度,通用的演进路线排序为: 第一优先级:通用能力模块 → 第二优先级:非核心业务模块 → 第三优先级:核心业务支撑模块 → 第四优先级:核心交易链路模块

步骤3:防腐层搭建,实现新旧系统隔离

防腐层是整个增量重构过程的核心枢纽,所有新旧系统的交互都必须经过防腐层,绝对禁止新老系统直接调用。 防腐层的核心职责包括4个方面:

  1. 模型转换:将旧单体的贫血数据模型,转换为新微服务的富领域模型,避免新系统被旧模型的不合理设计污染。
  2. 接口适配:将新微服务的标准化接口,适配为旧单体需要的接口格式,实现新旧系统的无缝调用,前端与其他依赖模块完全无感知。
  3. 流量管控:实现新旧系统之间的流量路由、灰度比例控制、熔断降级,出现异常时可快速切回旧系统,保障业务稳定性。
  4. 行为兼容:严格保证新旧系统的业务处理逻辑完全一致,避免出现逻辑差异导致的业务故障。

步骤4:单业务能力渐进式迁移,闭环验证

核心执行逻辑:一次只迁移一个最小业务能力,完成开发、测试、验证、上线全流程闭环后,再启动下一个能力的迁移,从根源上控制风险范围。

每个业务能力迁移的标准执行流程:

  1. 能力定义:明确要迁移的业务能力的输入、输出、业务规则、异常处理逻辑,与原有单体逻辑完全对齐,形成标准化的能力说明。
  2. 新服务开发:基于DDD领域模型开发新的微服务,实现对应的业务能力,通过单元测试、集成测试,保证处理结果与原有单体完全一致。
  3. 双写适配:在防腐层实现数据双写逻辑,对新增、修改、删除操作,同时写入旧单体数据库与新微服务数据库,保证两边数据基准一致。
  4. 一致性比对:通过自动化工具,持续比对新旧系统的处理结果与存储数据,确保100%一致,验证周期不低于一个完整的业务周期。
  5. 读流量切换:先将读操作流量,按比例灰度切换到新微服务,验证新服务的性能、稳定性、逻辑正确性,无异常再逐步提升灰度比例。
  6. 写流量切换:读流量全量切换完成后,再将写操作流量按比例灰度切换到新微服务,验证无误后全量切换,同时停止双写,将新微服务作为唯一数据源。
  7. 旧逻辑下线:所有流量完全切换到新微服务,稳定运行一个完整业务周期后,下线单体中对应的旧逻辑代码与数据依赖。

步骤5:全链路灰度与流量切换,保障业务零中断

流量切换绝对不能一刀切,必须采用渐进式灰度策略,每一步都有明确的验证标准与回滚方案。 核心灰度执行策略:

  1. 流量比例灰度:从1%流量开始,逐步提升到10%、30%、50%、100%,每一步都持续监控核心指标,出现异常立即回滚到上一比例。
  2. 白名单灰度:先将内部测试用户、指定灰度用户的流量切换到新服务,验证无误后,再放开到全量公网用户。
  3. 业务场景灰度:先切换低风险的业务场景(比如数据查询操作),验证稳定后,再切换高风险的业务场景(比如支付、订单创建操作)。

每一次灰度比例提升,必须验证的核心指标包括:业务成功率、错误率、接口耗时、数据一致性、服务CPU/内存使用率、数据库负载、异常日志数量。

步骤6:旧系统平滑下线,完成架构演进

旧系统逻辑下线必须渐进式执行,禁止一次性删除全量代码,避免出现遗漏依赖导致的业务故障。 标准下线流程:

  1. 流量清零确认:通过监控与日志,确认所有业务流量都已切换到新微服务,单体中对应的旧逻辑连续7天无任何流量进入。
  2. 依赖清理:清理单体中对旧逻辑的所有依赖,包括代码依赖、配置依赖、数据库依赖、中间件依赖。
  3. 数据归档:将旧数据库中对应的业务表数据,归档到离线存储,保留合规要求的回溯周期,避免数据丢失。
  4. 代码下线:删除单体中对应的旧逻辑代码,发布新版本的单体应用,验证全量业务无任何影响。
  5. 资源释放:释放旧系统占用的服务器、数据库、中间件等资源,完成最终的下线操作。

步骤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<LegacyUsergetUserById(Long id) {
        return userRepository.findById(id);
    }

    public Optional<LegacyUsergetUserByUsername(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<LegacyUsergetUserById(@PathVariable Long id) {
        Optional<LegacyUseruser = userService.getUserById(id);
        return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping("/username/{username}")
    public ResponseEntity<LegacyUsergetUserByUsername(@PathVariable String username) {
        Optional<LegacyUseruser = userService.getUserByUsername(username);
        return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<LegacyUsercreateUser(@RequestBody LegacyUser user) {
        LegacyUser savedUser = userService.createUser(user);
        return ResponseEntity.ok(savedUser);
    }

    @PutMapping
    public ResponseEntity<LegacyUserupdateUser(@RequestBody LegacyUser user) {
        LegacyUser updatedUser = userService.updateUser(user);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<VoiddeleteUser(@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<LegacyUsergetUserById(@PathVariable Long id) {
        Optional<LegacyUseruser = userAclService.getUserById(id);
        return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping("/username/{username}")
    public ResponseEntity<LegacyUsergetUserByUsername(@PathVariable String username) {
        Optional<LegacyUseruser = userAclService.getUserByUsername(username);
        return user.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<LegacyUsercreateUser(@RequestBody LegacyUser user) {
        LegacyUser savedUser = userAclService.createUser(user);
        return ResponseEntity.ok(savedUser);
    }

    @PutMapping
    public ResponseEntity<LegacyUserupdateUser(@RequestBody LegacyUser user) {
        LegacyUser updatedUser = userAclService.updateUser(user);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<VoiddeleteUser(@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<UsergetUserById(Long userId) {
        return userRepository.findById(userId);
    }

    public Optional<UsergetUserByUsername(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<UserDTOgetUserById(@PathVariable Long userId) {
        Optional<UserDTOuserDTO = userAppService.getUserById(userId);
        return userDTO.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping("/username/{username}")
    public ResponseEntity<UserDTOgetUserByUsername(@PathVariable String username) {
        Optional<UserDTOuserDTO = userAppService.getUserByUsername(username);
        return userDTO.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<UserDTOcreateUser(@RequestBody UserDTO userDTO) {
        UserDTO savedUser = userAppService.createUser(userDTO);
        return ResponseEntity.ok(savedUser);
    }

    @PutMapping
    public ResponseEntity<UserDTOupdateUser(@RequestBody UserDTO userDTO) {
        UserDTO updatedUser = userAppService.updateUser(userDTO);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{userId}")
    public ResponseEntity<VoiddeleteUser(@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. 性能退化风险

  • 风险场景:微服务之间的网络调用导致接口耗时增加,原本的本地调用变为远程调用,出现性能瓶颈。
  • 防控方案:合理设计服务粒度,避免过度拆分导致调用链路过长;采用缓存、批量调用、异步调用等方案优化远程调用性能;通过全链路压测提前发现性能瓶颈并优化。

七、核心原则总结

单体到微服务的演进,本质上不是一个技术问题,而是一个业务与组织的协同问题。架构演进的最终目标,是更好地支撑业务发展,提升组织协作效率,降低系统维护成本,而不是为了使用微服务而微服务。

整个演进过程,必须坚守三个核心原则:

  1. 业务驱动:所有架构调整都必须以业务需求为核心,技术始终服务于业务。
  2. 小步快跑:一次只完成一个最小闭环的调整,验证无误后再推进下一步,将风险控制在最小范围。
  3. 持续验证:每一步操作都有明确的验证标准,全程保障业务逻辑的一致性与系统的稳定性。

只有遵循这些原则,才能真正实现从单体到微服务的平滑演进,避免陷入“重构地狱”,让架构真正为业务赋能。