DDD可配置上下文验证:文档聚合根元数据动态必填字段方案问询
更优方案推荐及可配置场景处理
首先,咱们先拆解下现有两个方案的问题:
- 方案一(继承Metadata):值对象的核心是属性的不可变集合,继承会破坏值对象的语义——不同用户的必填规则是业务验证逻辑,不是值对象本身的结构差异。而且后续新增用户类型时,会导致子类爆炸,扩展性很差。
- 方案二(聚合根工厂做验证):会让Document聚合根的职责过重,把验证逻辑耦合到聚合根中,后续规则变化时需要修改聚合根,违反开闭原则,也不利于单元测试。
更优方案:策略模式分离验证逻辑
我推荐用策略模式把验证逻辑从值对象和聚合根中抽离出来,具体实现步骤:
- 保持
Metadata作为单一值对象,只负责封装数据,不包含任何验证逻辑:
class Metadata { // 20个字段的getter,构造方法保证所有字段被初始化(允许为空) public String getFieldA() { /*...*/ } public String getFieldB() { /*...*/ } // 其他字段的getter }
- 定义验证策略接口,抽象不同用户类型的验证行为:
interface MetadataValidationStrategy { void validate(Metadata metadata) throws InvalidMetadataException; } // 自定义领域异常,用于抛出验证失败信息 class InvalidMetadataException extends RuntimeException { public InvalidMetadataException(String message) { super(message); } }
- 为两类用户分别实现策略类(初期可硬编码规则,后续可扩展为动态配置):
class OperatorMetadataValidation implements MetadataValidationStrategy { @Override public void validate(Metadata metadata) { List<String> missingFields = new ArrayList<>(); if (metadata.getFieldA() == null || metadata.getFieldA().isEmpty()) missingFields.add("fieldA"); if (metadata.getFieldC() == null || metadata.getFieldC().isEmpty()) missingFields.add("fieldC"); if (metadata.getFieldE() == null || metadata.getFieldE().isEmpty()) missingFields.add("fieldE"); if (metadata.getFieldG() == null || metadata.getFieldG().isEmpty()) missingFields.add("fieldG"); if (!missingFields.isEmpty()) { throw new InvalidMetadataException("缺失必填字段:" + String.join(", ", missingFields)); } } } class UserMetadataValidation implements MetadataValidationStrategy { @Override public void validate(Metadata metadata) { // 检查10个必填字段的逻辑,结构同上 } }
- 在Document的工厂方法中注入对应策略,完成验证后创建聚合根:
class DocumentFactory { public Document createDocument(Metadata metadata, MetadataValidationStrategy strategy) { strategy.validate(metadata); return new Document(metadata); } }
这种方案的优势:
- 单一职责:Metadata只管存数据,验证逻辑由专门的策略类处理,聚合根只负责业务核心逻辑。
- 扩展性强:新增用户类型时,只需要新增一个策略类,无需修改现有代码。
- 可测试性高:每个策略类可以独立编写单元测试,验证逻辑的正确性。
处理必填字段可配置的场景(配置来自数据库)
当规则需要从数据库动态加载时,我们可以把策略类改成可配置化的,核心思路是把硬编码的字段列表换成从数据库读取的配置:
- 定义配置仓库接口,负责从数据库读取不同用户类型的必填字段:
// 枚举用户类型 enum UserType { OPERATOR, USER } interface ValidationConfigRepository { List<String> getRequiredFields(UserType userType); }
- 实现一个通用的可配置验证策略,依赖配置仓库动态获取规则:
class ConfigurableMetadataValidationStrategy implements MetadataValidationStrategy { private final UserType userType; private final ValidationConfigRepository configRepo; // 本地缓存,避免每次验证都查询数据库 private final LoadingCache<UserType, List<String>> fieldCache = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .build(key -> configRepo.getRequiredFields(key)); public ConfigurableMetadataValidationStrategy(UserType userType, ValidationConfigRepository configRepo) { this.userType = userType; this.configRepo = configRepo; } @Override public void validate(Metadata metadata) { try { List<String> requiredFields = fieldCache.get(userType); List<String> missingFields = new ArrayList<>(); // 这里可以用反射或者提前维护字段映射表来检查字段值 // 示例:假设我们有一个字段映射工具类 for (String field : requiredFields) { Object value = FieldAccessor.getValue(metadata, field); if (value == null || (value instanceof String && ((String) value).isEmpty())) { missingFields.add(field); } } if (!missingFields.isEmpty()) { throw new InvalidMetadataException("缺失必填字段:" + String.join(", ", missingFields)); } } catch (ExecutionException e) { throw new RuntimeException("加载验证配置失败", e); } } }
- 调整工厂方法,根据用户类型创建对应的可配置策略:
class DocumentFactory { private final ValidationConfigRepository configRepo; public DocumentFactory(ValidationConfigRepository configRepo) { this.configRepo = configRepo; } public Document createDocument(UserType userType, Metadata metadata) { MetadataValidationStrategy strategy = new ConfigurableMetadataValidationStrategy(userType, configRepo); strategy.validate(metadata); return new Document(metadata); } }
额外注意点:
- 缓存优化:数据库配置不会频繁变更,所以可以添加本地缓存或分布式缓存,减少数据库查询次数,同时设置合理的过期时间。
- 配置更新:如果需要支持配置实时生效,可以监听数据库的配置变更事件,主动刷新缓存。
- 字段映射:用反射检查字段时要注意性能,或者提前维护字段名称到Metadata getter的映射表,避免反射的开销。
内容的提问来源于stack exchange,提问作者msmani
相关产品推荐
相关产品推荐

