DDD中Diff的应用(JAVERS)的封装

背景

在一些应用程序开发中,我们往往只能获取到当前对象的值,而无法获取到之前对象的值,以及当前对象的值那些值是修改的。如果我们知道了,我们就可以对这些修改操作作统一的封装,而不是一个对象往往要通过自己的应用程序去判断,然后调用不同的add、update、delete方法。这种做法就好比hibernate,我们在操作一个对象的时候,无需关注对象本身,我们只需要将修改后的对象丢到一个封装好的方法中,由框架自行给我们判断该对象是新增还是修改删除等。我们在领域驱动设计中,新增了聚合根这一角色,我们也希望借鉴hibernate类似的方式,将聚合根的所有crud封装起来,让我们的crud都无需关注过多的细节,更多的关注我们自己的业务操作。为了实现这种效果,我们引入了JAVERS这个框架,所以接下来我们先来一起学习一下JAVERS这个框架

说明

JAVERS 主要用于两个对象的比对,简化ORM操作

官网

Github

构造diff

如果我们要比较两个对象,我们需要先构建一个比较器出来,即diff
构造方式如下:

Javers javers = JaversBuilder.javers()
          .withListCompareAlgorithm(LEVENSHTEIN_DISTANCE)
          .build();

这里需要注意的是 List comparing algorithms这个参数,即比较算法,官方推荐使用的是LEVENSHTEIN_DISTANCE.通过看源码我们可以看到有如下三种比较器
在这里插入图片描述

  • Levenshtein: 是最智能的比较算法,一般推荐使用。但是需要注意的是如果比较元素超过300个以上效率就会变得很慢
  • Simple:主要优点是速度快,计算复杂度线性,主要缺点是输出过于冗长
  • Set: 如果您不关心集合的顺序,请选择 Set 算法。在进行比较之前,JaVers 会将所有 list 转换为 set。此算法产生最简洁的输出(只有 ValueAdded 和 ValueRemoved)

这里虽然有三种,但是我们最为常用的还是Levenshtein

测试

这里我们用一个例子测试看看

  • Student.class
@Data
@ToString
public class Student {

    @Id
    private int id;
    private String name;
    private int age;
    List<Teacher> teachers;
    
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

}

这里需要注意的是Studetn.class 中有一个 @Id注解,如果没有id,就没法区分对象比对的时候是否为新增对象了,只能比对出是修改和删除,即使我们想要比较出的是新增,实际也比较不出。所以这里需要注意,所以如果我们在新增对象比较的时候,如果没有id,框架又会报错,所以新增的对象我们可以给他一个随机的负数id

  • Teacher.class
@Data
public class Teacher {
    private String name;
    private String age;
}

测试修改

@Test
    public void changeTest() {
        Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build();
        Student old = new Student(1, "韩信", 22);
        Teacher teacher = new Teacher();
        teacher.setName("韩信的老师");
        teacher.setAge("33");
        old.setTeachers(Lists.newArrayList(teacher));

        Student newObj = new Student(1, "阿离", 32);
        Teacher teacher1 = new Teacher();
        teacher1.setName("阿离的老师");
        teacher1.setAge("44");
        newObj.setTeachers(Lists.newArrayList(teacher1));

        Diff diff = javers.compare(old, newObj);
        System.out.println(diff.prettyPrint());


        Changes changes = diff.getChanges();
        for (Change change : changes) {
            if ((change instanceof NewObject)) {
                System.out.println("新增改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);
                System.out.println("新增");

            }

            if ((change instanceof ObjectRemoved)) {
                System.out.println("删除改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);
            }

            if ((change instanceof ValueChange)) {
                System.out.println("修改改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);
            }

        }

在这里插入图片描述
可以看到我们拿到了所有的变化值

这里我们改变的对象一边有如下三种

  • NewObject 新增
  • ObjectRemoved 删除
  • PropertyChange 修改

其中PropertyChange又细分为:

  • ContainerChange 集合修改
  • MapChange map修改
  • ReferenceChange 实体引用修改
  • ValueChange 普通值的修改

测试新增

我们将上面的代码作一行改动

        Diff diff = javers.compare(old, null);

运行结果:
在这里插入图片描述

集合比较

对于集合的比对,我们一般使用

javers.compareCollections()
@Test
    public void compareCollections() {
        Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build();
        Student old = new Student(1, "韩信", 22);
        Teacher teacher = new Teacher();
        teacher.setName("韩信的老师");
        teacher.setAge("33");
        old.setTeachers(Lists.newArrayList(teacher));

        Student newObj = new Student(1, "阿离", 32);
        Teacher teacher1 = new Teacher();
        teacher1.setName("阿离的老师");
        teacher1.setAge("44");
        newObj.setTeachers(Lists.newArrayList(teacher1));

        Student newObj2 = new Student(2, "奏", 18);
        Teacher teacher2 = new Teacher();
        teacher2.setName("奏的老师");
        teacher2.setAge("22");
        newObj2.setTeachers(Lists.newArrayList(teacher2));

        Diff diff = javers.compareCollections(Lists.newArrayList(old),
                Lists.newArrayList(newObj, newObj2), Student.class);
        System.out.println(diff.prettyPrint());


        Changes changes = diff.getChanges();
        for (Change change : changes) {
            if ((change instanceof NewObject)) {
                System.out.println("新增改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);

            }

            if ((change instanceof ObjectRemoved)) {
                System.out.println("删除改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);
            }

            if ((change instanceof ValueChange)) {
                System.out.println("修改改动: " + change);
//                change.getAffectedObject().ifPresent(System.out::println);
            }

        }

    }

在这里插入图片描述

有了上面的这些方法,我们就可以基于上面的改动对我们聚合根CRUD的一些封装。

封装CRUD

public <T> void objectChangeFunction(Consumer<T> addConsume,
                                         Consumer<T> updateConsume,
                                         Consumer<T> deleteConsume,
                                         Class<T> clazz) {
        Changes changes = this.diff.getChanges();
        for (Change change : changes) {
            if ((change instanceof NewObject && Objects.nonNull(addConsume))) {
                change.getAffectedObject().ifPresent(item -> addConsume.accept((T) item));
                return;
            }
            if ((change instanceof ObjectRemoved && Objects.nonNull(deleteConsume))) {
                change.getAffectedObject().ifPresent(item -> deleteConsume.accept((T) item));
                return;
            }
            if ((change instanceof ValueChange && Objects.nonNull(updateConsume))) {
                change.getAffectedObject().ifPresent(item -> updateConsume.accept((T) item));
                return;
            }
        }
    }

使用也非常简单

Diff plutusDiff = javers.compare(old, new);
objectChangeFunction(add -> qualityCheckDAO.saveStudent(student),
                update -> qualityCheckDAO.updateStudent(student),
                null, Student.class);

这里我们主要是介绍JAVERS的基本使用,所以对封装成为工具类这一块就不作过多说明,自己可以基于JAVERS去为自己项目作一些工具类的封装

自定义比较器

有时候我们需要自定义一些字段类型的比较器,JAVERS也提供了扩展接口,只需要CustomValueComparator接口即可
这里我们自定义一个BigDecimal比较器,来控制比较的小数位数

public class CustomBigDecimalComparator implements CustomValueComparator<BigDecimal> {
    private int significantDecimalPlaces;

    public CustomBigDecimalComparator(int significantDecimalPlaces) {
        this.significantDecimalPlaces = significantDecimalPlaces;
    }

    @Override
    public boolean equals(BigDecimal a, BigDecimal b) {
        return round(a).equals(round(b));
    }

    @Override
    public String toString(BigDecimal value) {
        return round(value).toString();
    }

    private BigDecimal round(BigDecimal val) {
        return val.setScale(significantDecimalPlaces, RoundingMode.HALF_DOWN);
    }
}

使用

@Test
    public void test() {
        Javers javers = JaversBuilder.javers()
                .registerValue(BigDecimal.class,
                        new CustomBigDecimalComparator(2)).build();
        int size = javers.compare(new ValueObject(new BigDecimal("1.123")),
                new ValueObject(new BigDecimal("1.124"))).getChanges().size();
        System.out.println(size == 0);
    }

    static class ValueObject {
        BigDecimal value;
        List<BigDecimal> values;

        public ValueObject(BigDecimal value) {
            this.value = value;
        }
    }

在这里插入图片描述

这里的CustomValueComparator的设计有点类似于Mybatis的类型转换Handler。javaer还可以自定义属性比较器,实现接口CustomPropertyComparator即可,这里感兴趣自己去了解,就不做过多讲解

注解

JAVERS提供的一些注解总共有如下几种

类级别

@Entity

将给定的类(及其所有子类)声明为 Entity 类型

@ValueObject

将给定的类(及其所有子类)声明为 Value Object 类型。

@Value
@DiffIgnore
@ShallowReference
@IgnoreDeclaredProperties
@TypeName

属性

@Id

声明一个实体的 id 属性,没有id无法比对出是否为新增对象

@DiffIgnore

比对时可以忽略的属性

@DiffInclude
@ShallowReference

声明一个属性为浅引用。只能用于实体类型属性。目标 Entity 实例的所有属性(Id 除外)都被忽略。

@PropertyName

官网提供了这么多注解,实际我也没用过这么多,用的比较多的还是@Id和`@DiffIgnore,限于篇幅,就不作过多解释,在实际中需要使用时可参考官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值