《柒柒架构》DDD领域驱动设计--领域模型(四)

本文探讨了如何在DDD中简化值对象设计,通过注解进行单属性验证,以及使用业务验证对象处理多属性联合校验,减少工作量,提高开发灵活性。同时介绍了如何在Spring Boot中利用AspectJ实现属性赋值时的自动验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

前面我们跳过了值对象的部分,是因为,值对象的设计其实并不影响DDD的核心功能。而且在值对象的设计上,一千个读者有一千个哈姆雷特。因此在这篇文章中,我讲下我自己对值对象的理解,当然也只是作为参考,各位在真正使用DDD的值对象时,还需根据自身的情况及需要,设计自己的值对象。

值对象的定义(Value Object)

从任何其他事物发展而来,初级的形成或生长的早期阶段,非Entity的值对象。 就好像 Integer、String 是所有编程语言的Primitive一样,在 DDD 里,VO 可以说是一切模型、方法、架构的基础,而就像 Integer、String 一样, DVO又是无所不在的。
我们看看严格的VO定义是什么样子的:
在这里插入图片描述
这个例子是将Entity某一属性,使用JAVA对象进行包装,并且将其行为及其验证逻辑进行封装在一起。然后由多个值对象来组装成Entity。
所以,Entity和VO是多对多的关系,每个Entity是由多个值对象组装得到。DDD严格定义,希望Entity每个属性(多个关联属性,比如地址:省、市、区、街道等)都定义成值对象,分别将值对象的属性、行为、验证进行封装,保证每个值对象的定义是合法的,这样Entity中的VO就可以保证,一定是符合逻辑的,无需再Entity中再去验证栏位属性的合法性。
因此值对象有如下行为特征:

  • 将隐性的概念显性化
  • 将隐性的上下文显性化
  • 封装多对象行为
    及其特点:
  • 不可变性
  • 是一个完整的概念整体,拥有精准定义
  • 使用业务域的原生语言
  • 是业务域的最小组成部分、可构建复杂组合

我的理解

实际操作起来,按照VO的严格定义,代码虽说会很规整,但是会对开发带来很大的工作量及开发阻力

值对象的目的:是将实体的栏位进行业务含义校验,确保一个合理的属性值或者一组属性值

因此为了满足这个目的,我们可以选择针对一些部分属性组合成值对象,在属性上强制做验证,如上述例子。

保证Entity属性的合法性

我们在使用springboot对入参进行验证的时候,都是使用@Valid注解结合DTO上的@Length等注解完成栏位验证的,示例如下:
在这里插入图片描述
在这里插入图片描述
如果不添加@Valid注解,是不会进行栏位验证的,而且只能在方法的参数上使用。
那为了满足,在Entity的属性上对其赋值的严格合法性,不依赖这些限制。我使用了Aspectj进行set方法的切入,在对Entity属性值进行赋值时,验证@Length等相关配置,这样就无需添加@Valid,且没有必须作为方法参数的限制。
在这里插入图片描述
在这里插入图片描述

业务验证

第二个问题是,每个属性的验证现在可以满足了,多栏位的联合校验怎么处理呢,注解是无法处理这种情况的。下面我就介绍下,柒柒架构中,多属性的联合校验如何处理的。

业务验证的目的是要保证实体或者聚合内,属性的业务属性必需保证合法性,同时也是对值对象业务含义验证的一种辅助手段
  1. 定义业务验证注解
public @interface ValidFailMemo {
    String value() default "验证失败";
}

标记一个对象是 业务验证对象。其Value值,是业务验证失败时,失败的memo信息。

  1. 定义业务验证接口
@ValidFailMemo
public interface DddValidation<T extends Aggregate> {
    boolean isValid(T t);
}

业务验证对象必须实现该接口,并且实现isValid方法。
3. 应用启动时,扫描所有bean,做聚合与业务验证类的映射

@Component
public class DddValidationManager {
    private Map<Class<? extends Aggregate>, List<DddValidation<Aggregate>>> validationMap = new HashMap<>();

    @PostConstruct
    private void init() {
        final String[] beanDefinitionNames = SpringContextUtil.getApplicationContext().getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            Object beanName1 = SpringContextUtil.getBean(beanName);
            if (beanName1 == null) {
                continue;
            }
            if (DddValidation.class.isAssignableFrom(beanName1.getClass())) {
                Class aClass = (Class) ((ParameterizedType) beanName1.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
                List<DddValidation<Aggregate>> dddValidations = validationMap.get(aClass);
                if (dddValidations == null) {
                    dddValidations = new ArrayList<>();
                }
                dddValidations.add((DddValidation) beanName1);
                validationMap.put((Class<? extends Aggregate>) aClass, dddValidations);
            }
        }
    }
  1. 定义验证方法
public List<ValidResult> valid(Aggregate aggregate) {
        List<ValidResult> validResults = new ArrayList<>();
        List<DddValidation<Aggregate>> dddValidations = validationMap.get(aggregate.getClass());
        if (dddValidations == null || dddValidations.size() == 0) {
            return validResults;
        }
        for (DddValidation<Aggregate> dddValidation : dddValidations) {
            boolean valid = dddValidation.isValid(aggregate);
            if (valid == false) {
                Class<? extends DddValidation> dddValidationClass = dddValidation.getClass();
                ValidFailMemo annotationsByType = dddValidation.getClass().getAnnotation(ValidFailMemo.class);
                String value = annotationsByType.value();
                validResults.add(new ValidResult(dddValidationClass, value));
            }
        }
        return validResults;
    }

3和4结合组成了DddValidationManager 对象,定义了初始化及验证时的方法。
5. 具体验证类

@ValidFailMemo("聚合根与实体的聚合值不一致")
@Component
public class AggregateKeyMustSame implements DddValidation<CustomInfo> {

    @Override
    public boolean isValid(CustomInfo customInfo) {
        String idValue = customInfo.idValue();
        String positionIdValue = customInfo.getPosition().getUserId();

        if (idValue.equals(positionIdValue)) {
            return true;
        } else {
            return false;
        }
    }
}

目录结构:
在这里插入图片描述
只需要按照上述Demo形式定义业务验证对象,并添加到Spring容器中即可,柒柒架构会自动扫描所有bean并添加到以Aggregate为key的map中。
那么柒柒架构是在何时进行处理业务验证的呢?

public interface BaseRepository {
    /**
     * @param aggregate,c 传入的聚合class类型
     */
    @SneakyThrows
    static <T extends Aggregate> void save(Aggregate aggregate, Class<T> c) {
        DddValidationManager dddValidationManager = SpringContextUtil.getBean(DddValidationManager.class);
        List<DddValidationManager.ValidResult> valid = dddValidationManager.valid(aggregate);
        if (valid == null || valid.size() == 0) {
            String className = c.getSimpleName();
            Repository repository = (Repository) SpringContextUtil.getBean(className.substring(0, 1).toLowerCase().concat(className.substring(1)) + "RepositoryImpl");
            if (repository == null) {
                repository = (Repository) SpringContextUtil.getBean("generalRepositoryImpl");
            }
            repository.save(aggregate, c);
        } else {
            throw new Exception(valid.toString());
        }
    }
}

在使用BaseRepository 的save方法前,会去做相应的验证,如果业务验证失败,则不会执行save方法,保证了聚合属性的逻辑一致性。

小结

  • 使用严格的DDD进行VO的设计,会比较繁琐,对开发带来很大的工作量及开发阻力
  • 值对象是为了保证属性值的合法性,对有关联的属性进行组装,并且包含值对象的相关行为
  • 我对值对象进行了简化,使用注解方式对Entity单个属性进行栏位校验
  • 使用业务验证对象,对联合属性进行业务校验

这样就减少了对VO的过多设计,否则按照严格的VO设计会很复杂,且涉及到DO、PO、DTO的转换时,也是非常负责,因此考虑到这些因素,我不建议对VO进行过度复杂的设计。
当然,这仅仅代表个人观点,VO的设计见仁见智,各位在使用DDD的时候,可以根据自身的具体情况自行设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值