You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DDD架构下跨聚合根的User实体管理方案咨询

DDD模块化单体中User跨模块关联的解决方案

别把User实体移到Shared模块

Shared模块的定位是存放通用工具类、全局值对象、跨模块通用接口这类无业务属性的内容,User是独立业务域的聚合根,包含用户注册、权限、状态等专属业务逻辑,移到Shared会直接破坏单一职责原则:

  • Shared模块会逐渐臃肿成“大杂烩”,后续User的业务变更会影响所有依赖Shared的模块;
  • 违背DDD聚合根的边界设计,User的业务逻辑会被分散到各个模块,难以维护。

用用户ID值对象实现无依赖关联

在Shared模块定义一个UserId值对象,封装ID的格式校验、类型约束等逻辑,所有需要关联用户的模块(Article、后续的资源模块)都用这个UserId来存储创建者/所有者标识,而非直接引用User实体:

  • Article模块只依赖Shared的UserId,完全不依赖User模块,符合六边形架构的依赖倒置规则;
  • User模块也无需依赖任何业务模块,专注自身业务逻辑。

跨模块数据交互用端口-适配器解耦

如果业务需要从Article模块获取用户的展示信息(比如作者名称),绝对不能直接调用User模块的服务,而是用六边形架构的端口-适配器模式:

  1. 在Article模块内部定义用户信息查询端口(比如UserInfoQueryPort),这个端口属于Article的输入端口,仅定义Article需要的查询能力;
  2. 在User模块实现这个端口的适配器(比如UserInfoQueryAdapter),适配器内部调用User模块的仓储或服务获取数据;
  3. 通过依赖注入把适配器注入到Article模块,Article只依赖自己定义的端口,完全解耦于User模块。

示例代码(Java):

// Shared模块:UserId值对象
public record UserId(String value) {
    public UserId {
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
    }
}

// Article模块:用户信息查询端口
public interface UserInfoQueryPort {
    String getUserNameById(UserId userId);
}

// Article模块:Article实体
public class Article {
    private final String id;
    private final UserId creatorId;
    private String title;

    public Article(String id, UserId creatorId, String title) {
        this.id = id;
        this.creatorId = creatorId;
        this.title = title;
    }

    // 通过端口获取作者名称
    public String getCreatorName(UserInfoQueryPort queryPort) {
        return queryPort.getUserNameById(this.creatorId);
    }
}

// User模块:端口适配器实现
public class UserInfoQueryAdapter implements UserInfoQueryPort {
    private final UserRepository userRepo;

    public UserInfoQueryAdapter(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    @Override
    public String getUserNameById(UserId userId) {
        return userRepo.findById(userId.value())
                .map(User::getUserName)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
    }
}

跨模块业务规则的处理

如果有跨模块的业务规则(比如仅激活用户可创建文章),不要在Article模块内判断用户状态,推荐两种方案:

  • 跨领域服务:定义一个ArticleCreationService,同时依赖User模块的状态查询端口和Article模块的创建端口,在服务内完成用户状态校验,再触发文章创建;
  • 事件驱动:User模块发布用户状态变更事件(比如UserActivatedEvent),Article模块订阅事件并缓存必要状态,或者在创建时通过User端口实时查询状态。

内容的提问来源于stack exchange,提问作者Jazi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 21:44:55