王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人
去年我们在重构项目中落地了DDD,当时花了点时间研究了下阿里巴巴大淘宝技术发布的《阿里技术专家详解DDD系列》,其中第三讲《阿里技术专家详解DDD系列 第三讲 – Repository模式》中提到了一项技术–变更追踪。
简单来说,变更追踪是记录对象进行业务操作后发生的改变,通过这些改变来决定如何更新数据库,文章中提到了两种实现变更追踪方案:
- 基于Snapshot的方案:当数据从DB里取出来后,在内存中保存一份snapshot,然后在数据写入时和snapshot比较。常见的实现如Hibernate。
- 基于Proxy的方案:当数据从DB里取出来后,通过weaving的方式将所有setter都增加一个切面来判断setter是否被调用以及值是否变更,如果变更则标记为Dirty。在保存时根据Dirty判断是否需要更新。常见的实现如Entity Framework。
不过由于只给出了Snapshot方案的部分实现代码,导致很多读者对产生了疑惑。
我们在工程实践中借鉴了Snapshot方案的设计,并根据自身的业务情况做出了一些调整,下面就和大家分享我们在工程中的实践。
叠“BUFF”:
- 今天的主题是实现变更追踪而不是DDD,所以尽量不要把DDD的“战火”引过来;
- 以下代码未经过严格的测试,可能存在BUG,欢迎大家批评指正和讨论。
开始前的准备工作
聚合与Repository接口的定义
正式开始前,我们先做一些简单的准备工作,主要是DDD设计中的接口定义,首先是定义接口Aggregate和Identifier:
public interface Aggregate<ID extends Identifier> extends Serializable { ID getId();} public interface Identifier extends Serializable { Serializable value();}
接着定义Repository接口并提供3个基础能力:
public interface Repository<T extends Aggregate<ID>, ID extends Identifier> { /** * 保存 * @param aggregateRoot * @throws IllegalAccessException */ void save(T aggregateRoot) throws IllegalAccessException; /** * 删除 * @param aggregateRoot */ void remove(T aggregateRoot); /** * 查询 * @param identifier * @return */ T find(ID identifier);}
Repository是Service(业务逻辑)与DAO(Data Access Object,数据访问对象)间的“桥梁”,用于隔离业务逻辑与数据库之间的依赖,帮助我们屏蔽在数据库发生变更时对业务逻辑产生的影响,这点是DDD设计相关的内容,我们在这里不过多的讨论。
领域对象与Repository服务的定义
我们定义一个简单书籍和图片的实体:
@Getter@Setterpublic class Book implements Aggregate<BookId> { private BookId bookId; private String bookName; private String bookDesc; private Long words; private List<Image> images; private List<String> contents; @Override public BookId getId() { return this.bookId; }} @Getter@Setterpublic class BookId implements Identifier { private Long bookId; @Override public Serializable value() { return this.bookId; }} @Getter@Setterpublic class Image implements Aggregate<ImageId> { private ImageId imageId; private String imageUrl; @Override public ImageId getId() { return this.imageId; }} @Getter@Setterpublic class ImageId implements Identifier { private long imageId; @Override public Serializable value() { return this.imageId; }}
在有些DDD的实践规范中,实体中是不允许出现Getter方法和Setter方法的,这里为了方便提供测试数据,直接使用了lombok的注解添加Getter方法和Setter方法。
最后我们来定义实体Book的Repository服务:
public interface BookRepository extends Repository<Book, BookId> { } public class BookRepositoryImpl implements BookRepository { @Override public void save(Book aggregateRoot) { // 实现保存逻辑 } @Override public void remove(Book aggregateRoot) { // 实现删除逻辑 } @Override public Book find(BookId identifier) { Book book = new Book(); // 实现查询逻辑 return book; }}
BookRepository接口的意义是方便自定义Repository方法,BookRepositoryImpl是BookRepository具体的实现,这里我们只使用3个基础功能即可,具体的实现逻辑是调用DAO实现增删改查,并借助Convert工具实现DO与实体的转换,我们这里就省略这部分内容了,实际上是我懒得写了。
变更追踪的实现
RepositorySupport的实现
变更追踪的核心是在调用Repository的基础能力时进行实体对象的追踪,并在保存时对比实体对象的变化,具体的执行逻辑如下:
- 调用
Repository#find
时,复制实体对象的快照,添加的变更追踪的容器中; - 调用
Repository#save
时,对比当前实体对象与快照,返回两者间的差异; - 调用
Repository#remove
时,删除变更追踪容器中实体对象的快照。
在我们的工程实践中,核心设计采用了阿里巴巴在《阿里技术专家详解DDD系列 第三讲 – Repository模式》给出的方案,但在具体的实现细节上,我们做了一些调整,接下来就和大家分享下我们的设计。
首先来实现通用支撑类RepositorySupport,提供可复用的变更追踪能力:
public abstract class RepositorySupport<T extends Aggregate<ID>, ID extends Identifier> implements Repository<T, ID> { private final AggregateTracingManager<T, ID> aggregateTracingManager; public RepositorySupport() { this.aggregateTracingManager = new ThreadLocalTracingManager<>(); } /** * 由继承RepositorySupport的子类实现 */ protected abstract T onSelect(ID id); protected abstract void onInsert(T aggregate); protected abstract void onUpdate(T aggregate, AggregateDifference<T, ID> aggregateDifference); protected abstract void onDelete(T aggregate); /** * 主动追踪 * @param id * @return */ public void attach(T aggregate) { this.aggregateTracingManager.attach(aggregate); } /** * 差异对比 * @param aggregate * @return * @throws IllegalAccessException */ protected AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException { return this.aggregateTracingManager.different(aggregate); } /** * 解除追踪 * @param id * @return */ public void detach(T aggregate) { this.aggregateTracingManager.detach(aggregate); } @Override public T find(ID identifier) { T aggregate = this.onSelect(identifier); if (aggregate != null) { this.aggregateTracingManager.attach(aggregate); } return aggregate; } @Override public void save(T aggregate) throws IllegalAccessException { AggregateDifference<T, ID> aggregateDifference = this.aggregateTracingManager.different(aggregate); if (DifferenceTypeEnum.ADDED.equals(aggregateDifference.getDifferentType())) { this.onInsert(aggregate); } else { this.onUpdate(aggregate, aggregateDifference); } this.aggregateTracingManager.merge(aggregate); } @Override public void remove(T aggregate) { this.onDelete(aggregate); this.aggregateTracingManager.detach(aggregate); }}
我们依次对通用支撑类RepositorySupport中的成员变量和方法进行说明。
首先是RepositorySupport中唯一的成员变量AggregateTracingManager,该类的功能是完成变更追踪快照的管理,包括对象追踪,差异对比和解除追踪等。
接着是继承RepositorySupport的实现类需要重写的方法:
RepositorySupport#onSelect
,由RepositorySupport中实现的Repository#find
调用,与直接实现Repository#find
相同,通过DAO查询数据,并转换为实体对象;RepositorySupport#onInsert
,由RepositorySupport中实现的Repository#save
调用,与直接实现Repository#save
类似,通过DAO保存数据,此时为新增数据的保存;RepositorySupport#onUpdate
,由RepositorySupport中实现的Repository#save
调用,与直接实现Repository#save
类似,通过DAO保存数据,此时为修改数据的保存;RepositorySupport#onDelete
,由RepositorySupport中实现的Repository#remove
调用,与直接实现Repository#remove
相同,通过DAO删除数据。
接着是Repository中定义的提供变更追踪能力的方法:
RepositorySupport#attach
,主动追踪,当实体的Repository接口中自定义查询方法时,实现类可以通过该方法实现对象的变更追踪;RepositorySupport#different
,差异对比,当实体的Repository接口中自定义保存方法时,实现类可以通过该方法获取当前实体对象与快照的差异;RepositorySupport#detach
,解除追踪,当实体的Repository接口中自定义删除方法时,实现类可以通过该方法解除对象的变更追踪。
最后是RepositorySupport中对Repository接口的实现,实现中确定了RepositorySupport#onSelect
,RepositorySupport#onInsert
,RepositorySupport#onUpdate
和RepositorySupport#onDelete
方法的调用时机,并通过AggregateTracingManager来管理追踪对象:
RepositorySupport#find
的实现中,通过RepositorySupport#onSelect
查询实体对象,并决定是否调用AggregateTracingManager#attach
进行变更追踪;RepositorySupport#save
的实现中,调用AggregateTracingManager#different
获取当前实体对象与快照间的差异,并根据差异的类型选择执行RepositorySupport#onInsert
或RepositorySupport#onUpdate
,最后调用AggregateTracingManager#merge
将变更后的对象合并到变更追踪容器中;RepositorySupport#remove
的实现中,调用RepositorySupport#onDelete
删除数据,并调用AggregateTracingManager#detach
解除对象的追踪。
AggregateTracingManager的实现
AggregateTracingManager提供了管理变更追踪的能力,接口设计如下:
public interface AggregateTracingManager<T extends Aggregate<ID>, ID extends Identifier> { /** * 变更追踪 * @param aggregate */ void attach(T aggregate); /** * 解除追踪 * @param aggregate */ void detach(T aggregate); /** * 对比差异 * @param aggregate * @return */ AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException; /** * 合并变更 * @param aggregate */ void merge(T aggregate);}
接着提供一个AggregateTracingManager的实现类,我们的工程中同样选择了ThreadLocal来实现线程隔离:
public class ThreadLocalTracingManager<T extends Aggregate<ID>, ID extends Identifier> implements AggregateTracingManager<T, ID> { private final ThreadLocal<TraceContext<T, ID>> context; public ThreadLocalTracingManager() { this.context = ThreadLocal.withInitial(MapContext::new); } @Override public void attach(T aggregate) { this.context.get().tracing(aggregate.getId(), aggregate); } @Override public void detach(T aggregate) { this.context.get().remove(aggregate.getId()); } @Override public AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException { T snapshot = this.context.get().find(aggregate.getId()); return DifferentUtils.different(snapshot, aggregate); } @Override public void merge(T aggregate) { attach(aggregate); }}
最后是定义变更追踪中用于存储快照的容器TraceContext接口:
public interface TraceContext<T extends Aggregate<ID>, ID extends Identifier> { void add(ID id, T aggregate); T find(ID id); void remove(ID id);}
TraceContext的功能比较简单,提供了3个方法:
void add(ID id, T aggregate)
,添加追踪对象;T find(ID id)
,获取追踪对象的快照;void remove(ID id)
,删除追踪对象。
这里我提供一个使用HashMap做存储容器的简单实现:
public class MapContext<T extends Aggregate<ID>, ID extends Identifier> implements TraceContext<T, ID> { private final Map<ID, T> snapshots; public MapContext() { this.snapshots = new HashMap<>(); } @Override public void add(ID id, T aggregate) { T snapshot = SnapshotUtils.snapshot(aggregate); this.snapshots.put(aggregate.getId(), snapshot); } @Override public T find(ID id) { for (Map.Entry<ID, T> entry : this.snapshots.entrySet()) { ID entryId = entry.getKey(); if (id.getClass().equals(entryId.getClass()) && entryId.value().equals(id.value())) { return entry.getValue(); } } return snapshots.get(id); } @Override public void remove(ID id) { this.snapshots.remove(id); }}
至此,我们已经完成了变更追踪的整体框架。实际上我们在工程中实现的AggregateTracingManager和TraceContext会更加复杂,并添加了一些具有我司特色的功能,这里大家可以根据各自的情况做出不同的实现。
变更追踪中的工具类实现
由于《阿里技术专家详解DDD系列 第三讲 – Repository模式》文中的重点是介绍变更追踪这项技术,因此忽略了几个较为关键的工具类的实现,导致很多人在落地这项技术上遇到了困境,这里我结合工程中的实践,结合我个人的思考,给大家提供一个设计思路。
SnapshotUtils的实现
SnapshotUtils用于实现Aggregate的拷贝,因为在MapContext#find
方法的实现中是通过类型与值的对比来获取对象,因此我们在SnapshotUtils的实现中只需要实现深拷贝即可:
public class SnapshotUtils { @SuppressWarnings("unchecked") public static <T extends Aggregate<ID>, ID extends Identifier> T snapshot(T aggregate) throws IOException, ClassNotFoundException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(aggregate); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); T snapshot = (T) objectInputStream.readObject(); objectOutputStream.close(); byteArrayOutputStream.close(); objectInputStream.close(); byteArrayInputStream.close(); return snapshot; }}
据我推测阿里巴巴大淘宝技术在文中使用的SnapshotUtils中除了Identifier外的其余字段是深拷贝,我们的实践中允许Identifier也进行深拷贝,所以可以通过序列化与反序列化的方式进行深拷贝。
除了序列化的方式外,还有很多其他的方式可以实现深拷贝,我见过使用JSON工具来回倒腾实现深拷贝,或者可以使用BeanUtil等等。
Tips:有些工具的使用是有前提的,比如需要Getter和Setter方法,又或者使用序列化的方式需要继承Serializable接口。
使用Java Objec Diff实现DiffUtils
DiffUtils用于实现两个Java对象间的对比,因为此类需求较少所以市面上可供使用的开源工具并不是很多,相对来说Java Objec Diff是使用较为广泛的开源项目,不过该项目最新版本是2018年更新的0.95版本,作者应该是停止维护Java Object Diff了,或是由于该项目属于工具类项目,目前已经达到了较为完备的状态,不需要进行太多的维护工作了。
我们先来使用Java Objec Diff项目实现一个简单的Java对象对比工具,引入Java Objec Diff的依赖:
<dependency> <groupId>de.danielbechler</groupId> <artifactId>java-object-diff</artifactId> <version>0.95</version></dependency>
基于Java Objec Diff项目构建DiffUtils,这里给出一个简单的实现:
public class DiffUtils { public static EntityDiff diff(Object snapshot, Object obj) { DiffNode diffNode = ObjectDifferBuilder.buildDefault().compare(obj, snapshot); if (!diffNode.hasChanges()) { return EntityDiff.EMPTY; } EntityDiff entityDiff = new EntityDiff(); entityDiff.setHasChanges(true); diffNode.visit((node, visit) -> { boolean hasChanges = node.hasChanges(); Object objValue = node.canonicalGet(obj); Object snapshotValue = node.canonicalGet(snapshot); // 处理其他的逻辑和构建EntityDiff对象 }); return entityDiff; }} @Getter@Setterpublic class EntityDiff { public static final EntityDiff EMPTY = new EntityDiff(); private boolean hasChanges; // 省略其余属性的实现 public EntityDiff() { }}
EntityDiff的结构可以根据自身工程的需求进行定制化,我这里只是为了展示如何通过Java Objec Diff项目构建DiffUtils。
具有我司特色的DifferentUtils
接下来就该我来献丑了。
因为我们有一些定制化的需求(具体原因已经记不得了),所以当时没有选择使用Java Objec Diff项目而是实现了具有我司特色的Java对象的对比工具类DifferentUtils。
首先是我们定义的4种差异状态:
public enum DifferenceType { /** * 新增 */ ADDED(), /** * 删除 */ REMOVED(), /** * 修改 */ MODIFIED(), /** * 无变化 */ UNTOUCHED()}
接着我们对结果进行了封装,分为两层,第一层是标记Aggregate差异的AggregateDifference:
@Getter@Setterpublic class AggregateDifference<T extends Aggregate<ID>, ID extends Identifier> { /** * 快照对象 */ private T snapshot; /** * 追踪对象 */ private T aggregate; /** * 差异类型 */ private DifferenceType differentType; /** * 字段差异 */ private Map<String, FieldDifference> fieldDifferences;}
第二层是比较Aggregate字段差异的FieldDifference:
@Getter@Setterpublic class FieldDifference { /** * 字段名 */ private String name; /** * 字段类型 */ private Type type; /** * 快照值 */ private Object snapshotValue; /** * 当前值 */ private Object tracValue; /** * 差异类型 */ private DifferenceType differenceType;}
以及3个实现类,标记Java中原生类型的JavaTypeFieldDifference,标记集合类型的CollectionFieldDifference,以及标记实现Aggregate接口的AggregareFieldDifference:
public class JavaTypeFieldDifference extends FieldDifference {} @Getter@Setterpublic class CollectionFieldDifference extends FieldDifference { /** * 集合元素差异 */ private List<FieldDifference> elementDifference; public CollectionFieldDifference(String name, Type type, Object snapshotValue, Object tracValue) { super(name, type, snapshotValue, tracValue); this.elementDifference = new ArrayList<>(); } public CollectionFieldDifference(String name, Type type, Object snapshotValue, Object tracValue, DifferenceType differenceType) { super(name, type, snapshotValue, tracValue, differenceType); this.elementDifference = new ArrayList<>(); }} @Getter@Setterpublic class AggregareFieldDifference extends FieldDifference { private Map<String, FieldDifference> fieldDifferences; private final Identifier identifier; public AggregareFieldDifference(String name, Type type, Object snapshotValue, Object tracValue, DifferenceType differenceType, Identifier identifier) { super(name, type, snapshotValue, tracValue, differenceType); this.identifier = identifier; this.fieldDifferences = new HashMap<>(); }}
可以看到,我们在工程实践中并不支持Map类型的字段进行对比,这是因为在我们落地的DDD工程规范中,实现Aggregate接口的类中不允许出现Map类型的字段,只允许Java的8种基础类型(包装类型),String,List,值对象以及实体。
准备工作完成后,我们开始实现DifferentUtils,首先定义方法声明,与上面的DiffUtils#diff
存在一些差异,主要在泛型的使用上:
public class DifferentUtils { public static <T extends Aggregate<ID>, ID extends Identifier> AggregateDifference<T, ID> different(T snapshot, T aggregate) throws IllegalAccessException { // 待实现 }}
接着我们处理两个入参可能为null的情况进行处理,总计有4种情况:
snapsho == null && aggregate == null
,此时认为是DifferenceType.UNTOUCHED
;snapshot == null && aggregate != null
,此时认为是DifferenceType.ADDED
;snapshot != null && aggregate == null
,此时认为是DifferenceType.REMOVED
;snapshot != null && aggregate != null
,这种情况需要对比字段的差异。
此时我们可以得到用于入参为null时,返回DifferenceType的方法:
private static DifferenceType basicDifferentType(Object snapshot, Object aggregate) { if (snapshot == null && aggregate == null) { return DifferenceType.UNTOUCHED; } if (snapshot == null) { return DifferenceType.ADDED; } if (aggregate == null) { return DifferenceType.REMOVED; } return null;}
我们直接在DifferentUtils#different
中调用DifferentUtils#basicDifferentType
,并补充snapshot和aggregate均不为null时的处理:
public static <T extends Aggregate<ID>, ID extends Identifier> AggregateDifference<T, ID> different(T snapshot, T aggregate) throws IllegalAccessException { DifferenceType basicDifferenceType = basicDifferentType(snapshot, aggregate); if (basicDifferenceType != null) { return new AggregateDifference<>(snapshot, aggregate, basicDifferenceType); } Field[] fields = ReflectionUtils.getFields(aggregate); // 标记Aggregate DifferenceType aggregateDifferentType = aggregateDifferentType(fields, snapshot, aggregate); // 构建AggregateDifference对象 AggregateDifference<T, ID> aggregateDifference = new AggregateDifference<>(snapshot, aggregate, aggregateDifferentType); Map<String, FieldDifference> fieldDifferences = aggregateDifference.getFieldDifferences(); // 对比字段差异 setDifferences(snapshot, aggregate, fields, fieldDifferences); return aggregateDifference}
DifferentUtils#aggregateDifferentType
方法,该方法用于对Aggregate进行标记:
public static <T extends Aggregate<ID>, ID extends Identifier> DifferenceType aggregateDifferentType(Field[] fields, T snapshot, T aggregate) throws IllegalAccessException { DifferenceType differenceType = basicDifferentType(snapshot, aggregate); if (differenceType != null) { return differenceType; } boolean unchanged = true; for (Field field : fields) { field.setAccessible(true); // 处理需要跳过的情形 if (shouldSkipClass(field.getType())) { continue; } if (Collection.class.isAssignableFrom(field.getType())) { ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); Class<?> parameterizedClass = (Class<?>) parameterizedType.getActualTypeArguments()[0]; if (Aggregate.class.isAssignableFrom(parameterizedClass) || Map.class.isAssignableFrom(parameterizedClass)) { continue; } } // 对比字段差异 Object aggregateValue = field.get(aggregate); Object snapshotValue = field.get(snapshot); if (snapshotValue == null && aggregateValue == null) { continue; } else if (snapshotValue == null) { unchanged = false; continue; } unchanged = snapshotValue.equals(aggregateValue) & unchanged; } return unchanged ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;} private static boolean shouldSkipClass(Class<?> clazz) { return Identifier.class.isAssignableFrom(clazz) || Aggregate.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz);}
因为该方法需要在其它位置复用,所以开始时先调用了DifferentUtils#aggregateDifferentType
处理null的状态;接着是跳过需要特殊处理的类型,这些类型要么是单独处理,要么是不需要处理,以及当字段的类型为Collection时,某些泛型类型也不需要处理;最后是通过Object#equals
方法进行对比,并返回相应的修改状态。
DifferentUtils#setDifferences
的实现,该方法遍历Aggregate的字段,并对比每个字段的差异:
private static <T extends Aggregate<ID>, ID extends Identifier> void setDifferences(T snapshot, T aggregate, Field[] fields, Map<String, FieldDifference> fieldDifferences) throws IllegalAccessException { for (Field field : fields) { if (Identifier.class.isAssignableFrom(field.getType())) { continue; } String filedName = ReflectionUtils.getFieldName(field); field.setAccessible(true); Object snapshotValue = snapshot == null ? null : field.get(snapshot); Object aggregateValue = aggregate == null ? null : field.get(aggregate); if (snapshotValue == null && aggregateValue == null) { continue; } // 对比每个字段的差异 FieldDifference fieldDifference = compareFiled(field, snapshotValue, aggregateValue); fieldDifferences.put(filedName, fieldDifference); }}
DifferentUtils#compareFiled
的实现,该方法将字段进行分类对比:
@SuppressWarnings("unchecked")private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareFiled(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException { ComparableType comparableType = ComparableType.comparableType(aggregateValue == null ? snapshotValue : aggregateValue); if (ComparableType.AGGREGATE_TYPE.equals(comparableType)) { return compareAggregateType(field, (T) snapshotValue, (T) aggregateValue); } else if (ComparableType.COLLECTION_TYPE.equals(comparableType)) { return compareCollectionType(field, snapshotValue, aggregateValue); } else if (ComparableType.JAVA_TYPE.equals(comparableType)) { return compareJavaType(field, snapshotValue, aggregateValue); } else { throw new UnsupportedOperationException(); }} /** * 可比较的字段类型 */enum ComparableType { AGGREGATE_TYPE(), COLLECTION_TYPE(), JAVA_TYPE(), OTHER_TYPE(); public static ComparableType comparableType(@NonNull Object obj) { if (obj instanceof Aggregate) { return AGGREGATE_TYPE; } else if (obj instanceof Collection) { return COLLECTION_TYPE; } else if (obj instanceof Map) { return OTHER_TYPE; } else { return JAVA_TYPE; } }}
DifferentUtils#compareJavaType
的实现,该方法对比了Java类型字段的差异:
private static FieldDifference compareJavaType(Field field, Object snapshotValue, Object aggregateValue) { String filedName = ReflectionUtils.getFieldName(field); Type type = field.getGenericType(); DifferenceType differenceType = javaDifferentType(snapshotValue, aggregateValue); return new JavaTypeFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType);} public static DifferenceType javaDifferentType(Object snapshot, Object aggregate) { DifferenceType differenceType = basicDifferentType(snapshot, aggregate); if (differenceType != null) { return differenceType; } if (snapshot.equals(aggregate)) { return DifferenceType.UNTOUCHED; } else { return DifferenceType.MODIFIED; }}
DifferentUtils#compareAggregateType
的实现,该方法对比实现Aggregate接口的类型的字段进行对比,通过递归不断向下深入直到类型为Java类型:
private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareAggregateType(Field field, T snapshotValue, T aggregateValue) throws IllegalAccessException { String filedName = ReflectionUtils.getFieldName(field); Type type = field.getGenericType(); Aggregate<?> notNullValue = snapshotValue == null ? aggregateValue : snapshotValue; Field[] entityFields = ReflectionUtils.getFields(notNullValue); Identifier id = notNullValue.getId(); DifferenceType differenceType = aggregateDifferentType(entityFields, snapshotValue, aggregateValue); AggregareFieldDifference aggregareFieldDifference = new AggregareFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType, id); Map<String, FieldDifference> fieldDifferences = aggregareFieldDifference.getFieldDifferences(); setDifferences(snapshotValue, aggregateValue, entityFields, fieldDifferences); return aggregareFieldDifference;}
DifferentUtils#compareCollectionType
的实现,该方法用于对比集合类型的
@SuppressWarnings("unchecked")private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareCollectionType(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException { String filedName = ReflectionUtils.getFieldName(field); Type type = field.getGenericType(); ParameterizedType parameterizedType = (ParameterizedType) type; Class<?> genericityClass = (Class<?>) parameterizedType.getActualTypeArguments()[0]; // 处理泛型为Java类型的集合 if (!Aggregate.class.isAssignableFrom(genericityClass) && !Map.class.isAssignableFrom(genericityClass)) { Collection<?> snapshotValues = (Collection<?>) snapshotValue; Collection<?> aggregateValues = (Collection<?>) aggregateValue; DifferenceType differenceType = collectionDifferentType(genericityClass, snapshotValues, aggregateValues); return new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType); } // 处理泛型为实现Aggreagte接口的类型的集合 Collection<T> snapshotValues = (Collection<T>) snapshotValue; Collection<T> aggregateValues = (Collection<T>) aggregateValue; Map<Serializable, T> snapshotMap = snapshotValues.stream().collect(Collectors.toMap(snapshot -> snapshot.getId().value(), snapshot -> snapshot)); Map<Serializable, T> aggregateMap = aggregateValues.stream().collect(Collectors.toMap(aggregate -> aggregate.getId().value(), aggregate -> aggregate)); CollectionFieldDifference collectionFieldDifference = new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue); boolean unchanged = true; // snapshotMap与aggregateMap的交集,snapshotMap对aggregateMap的补集 for (Serializable key : snapshotMap.keySet()) { T snapshotElement = snapshotMap.get(key); T aggregateElement = aggregateMap.get(key); FieldDifference fieldDifferent = compareFiled(field, snapshotElement, aggregateElement); unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged; collectionFieldDifference.getElementDifference().add(fieldDifferent); } // aggregateMap对snapshotMap的补集 for (Serializable key : aggregateMap.keySet()) { if (snapshotMap.get(key) != null) { continue; } T aggregateElement = aggregateMap.get(key); FieldDifference fieldDifferent = compareFiled(field, null, aggregateElement); unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged; collectionFieldDifference.getElementDifference().add(fieldDifferent); } if (unchanged) { collectionFieldDifference.setDifferenceType(DifferenceType.UNTOUCHED); } else { collectionFieldDifference.setDifferenceType(DifferenceType.MODIFIED); } return collectionFieldDifference;} public static DifferenceType collectionDifferentType(Class<?> typeArguments, Collection<?> snapshot, Collection<?> aggregate) { if (CollectionUtils.isEmpty(snapshot) && CollectionUtils.isEmpty(aggregate)) { return DifferenceType.UNTOUCHED; } if (CollectionUtils.isEmpty(snapshot)) { return DifferenceType.ADDED; } if (CollectionUtils.isEmpty(aggregate)) { return DifferenceType.REMOVED; } if (specialHandingClass(typeArguments)) { return snapshot.size() == aggregate.size() ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED; } return snapshot.equals(aggregate) ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;} private static boolean specialHandingClass(Class<?> clazz) { return shouldSkipClass(clazz) || Collection.class.isAssignableFrom(clazz);}
我们将Collection类型的字段分为两类,泛型为Java类型的和泛型为实现Aggregate接口的。当集合的泛型为Java类型时,只需要使用Object#equals
方法进行对比即可;当集合的泛型为Collection或Aggregate时(集合的泛型不应该出现Map或Identifier),先对数量进行对比,标记整体的变化,接着来对比每个Aggregate的差异,并进行标记。
我的想法是,先将List<T>
转换为Map<Serializable, T>
,Map的key存储Id,value存储对象本身,这样可以得到两个Map:
Map<Serializable, T> snapshotMap
Map<Serializable, T> aggregateMap
先遍历snapshotMap,取出aggregateMap中Id与之对应的对象进行比较,并一一标记,这里处理的是snapshotMap与aggregateMap的交集,以及snapshotMap对aggregateMap的补集(即snapshotMap中有而aggregateMap中无的),实际上,我们这里处理的是snapshotMap的全集;再遍历aggregateMap,跳过snapshotMap中Id与之对应的对象,这里我们处理的是aggregateMap对snapshotMap的补集(即aggregateMap中有而snapshotMap中无的);这样,我们就处理完了两个集合中的元素,最后再根据每个元素对比的结果标记集合的差异类型即可。
好了,以上就是具有我司特色的DifferentUtils工具类的实现,因为没有研究过Java Object Diff的源码,因此不太清楚自己与大佬的差距究竟有多远,欢迎大家提出自己的想法一起讨论。
Tips:鉴于保密的原因,DifferentUtils及相关类都经过不同程度的修改,且修改后的实现并没有经过严格的评审和测试,可能会出现各种各样的BUG~~
ReflectionUtils的实现
变更追踪的实现中还有一个反射相关的工具类ReflectionUtils,该工具类的实现可大可小,往小了可以像我下面实现的这样:
public class ReflectionUtils { public static Field[] getFields(Object obj) { return obj.getClass().getDeclaredFields(); } public static String getFieldName(Field field) { return field.getName(); }}
往大了可以加入缓存等优化措施,例如ReflectionUtils#getFields
加入缓存Map<Class<?>, Field[]> fieldMap
,将首次获取到的结果添加到缓存中,以此来提高反射工具的性能。
结语
好了,到这里我们就一起实现了基于快照机制的变更追踪,文章中的代码还比较潦草,像是毛坯房,目的是和大家分享实现过程和设计,如果要真正的在生产环境中落地,还需要做“精装修”,这里举几个我们的“精装修”例子:
- TraceContext的实现中,容器我们选择了WeakHashMap,用于实现“自动”执行
AggregateTracingManager#detach
; - AggregateTracingManager中我们加入了配置项,实现某些功能的配置化,这里涉及定制业务就不过多展开了;
- ReflectionUtils中加入了缓存机制,以此提高反射的效率。
好了,今天就到这里了,Bye~~
推荐阅读
- 阿里技术专家详解DDD系列 第一讲- Domain Primitive
- 阿里技术专家详解DDD系列 第二讲 – 应用架构
- 阿里技术专家详解DDD系列 第三讲 – Repository模式
- 阿里技术专家详解DDD系列 第四讲 – 领域层设计规范
- 阿里技术专家详解DDD系列 第五讲:聊聊如何避免写流水账代码
如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!