Skip to content

Commit 444816c

Browse files
author
gaopeng71
committed
refactor to 2.0.0-SNAPSHOT
1 parent dbd044b commit 444816c

File tree

153 files changed

+2474
-857
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+2474
-857
lines changed

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ javadoc:install
1818
@mvn javadoc:javadoc -Pinstall
1919
@open target/site/apidocs/index.html
2020

21+
mutation:install
22+
@mvn eu.stamp-project:pitmp-maven-plugin:run
23+
@open dddplus-test/target/pit-reports/
24+
25+
jdepend:
26+
@mvn site
27+
@find . -name jdepend-report.html
28+
2129
deploy:
2230
@mvn clean deploy verify -Possrh -e
2331

dddplus-buddy/pom.xml

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0"?>
2+
<project
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4+
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.github.dddplus</groupId>
8+
<artifactId>dddplus</artifactId>
9+
<version>2.0.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>dddplus-buddy</artifactId>
13+
<name>DDDplus :: Buddy</name>
14+
<packaging>jar</packaging>
15+
<version>2.0.0-SNAPSHOT</version>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>io.github.dddplus</groupId>
20+
<artifactId>dddplus-runtime</artifactId>
21+
<version>${project.version}</version>
22+
</dependency>
23+
</dependencies>
24+
25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>org.apache.maven.plugins</groupId>
29+
<artifactId>maven-javadoc-plugin</artifactId>
30+
<version>2.10.4</version>
31+
<configuration>
32+
<encoding>UTF-8</encoding>
33+
<additionalJOption>-Xdoclint:none</additionalJOption>
34+
</configuration>
35+
<executions>
36+
<execution>
37+
<id>attach-javadocs</id>
38+
<goals>
39+
<goal>jar</goal>
40+
</goals>
41+
</execution>
42+
</executions>
43+
</plugin>
44+
<plugin>
45+
<groupId>org.apache.maven.plugins</groupId>
46+
<artifactId>maven-source-plugin</artifactId>
47+
<configuration>
48+
<attach>true</attach>
49+
</configuration>
50+
<executions>
51+
<execution>
52+
<id>attach-sources</id>
53+
<goals>
54+
<goal>jar-no-fork</goal>
55+
</goals>
56+
</execution>
57+
</executions>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package io.github.dddplus.buddy;
2+
3+
import lombok.NonNull;
4+
5+
import java.util.LinkedList;
6+
import java.util.List;
7+
import java.util.Optional;
8+
import java.util.stream.Collectors;
9+
10+
/**
11+
* 脏数据备忘录,服务于聚合根,帮助它对内部变化的对象进行追踪.
12+
*
13+
* <p>DDD里,entity在内存操作,改变状态,最后{@code repo.save(entity);} </p>
14+
* <p>{@code DirtyMemento}被Entity用来(主动)通知Repository哪些状态改变了(变脏了)</p>
15+
* <p>通过这个机制,{@code repo.save(entity);}可以通过一个save方法支持所有的状态改变场景</p>
16+
*/
17+
public final class DirtyMemento {
18+
private final List<IDirtyHint> hints = new LinkedList<>();
19+
20+
/**
21+
* 注册一个脏数据通知.
22+
*
23+
* @param hint the dirty hint with necessary data
24+
*/
25+
public void registerHint(@NonNull IDirtyHint hint) {
26+
hints.add(hint);
27+
}
28+
29+
/**
30+
* 合并脏数据通知:如未注册则注册,否则与现有那一个hint合并.
31+
*
32+
* <p>保证备忘录里,该{@link IMergeableDirtyHint}只保留一个</p>
33+
* <p>Merge on Write</p>
34+
*
35+
* @param hint 脏数据通知
36+
*/
37+
public void registerOrMerge(@NonNull IMergeableDirtyHint hint) {
38+
for (IDirtyHint existingHint : hints) {
39+
if (!existingHint.getClass().equals(hint.getClass())) {
40+
continue;
41+
}
42+
43+
IMergeableDirtyHint existingMergeableHint = (IMergeableDirtyHint) existingHint;
44+
if (hint.getId().equals(existingMergeableHint.getId())) {
45+
// found it, trigger the hook
46+
hint.onMerge(existingMergeableHint);
47+
return;
48+
}
49+
}
50+
51+
// not found
52+
registerHint(hint);
53+
}
54+
55+
/**
56+
* 当前的所有脏数据.
57+
*
58+
* @return all dirty hints
59+
*/
60+
public List<IDirtyHint> dirtyHints() {
61+
return hints;
62+
}
63+
64+
public void clear() {
65+
hints.clear();
66+
}
67+
68+
public boolean isEmpty() {
69+
return hints.isEmpty();
70+
}
71+
72+
public int size() {
73+
return hints.size();
74+
}
75+
76+
/**
77+
* 根据指定hint class type,找到已经注册的第一个脏数据通知.
78+
*
79+
* @param hintClass concrete type of {@link IDirtyHint}
80+
* @param <T> hint class type
81+
* @return null if not found. ONLY returns the first registered hint of specified type
82+
*/
83+
public <T extends IDirtyHint> T firstHintOf(Class<T> hintClass) {
84+
Optional<IDirtyHint> dirtyHint = hints.stream().filter(hintClass::isInstance).findFirst();
85+
if (dirtyHint.isPresent()) {
86+
return (T) dirtyHint.get();
87+
} else {
88+
return null;
89+
}
90+
}
91+
92+
/**
93+
* 根据指定hint class type,找到已经注册的最后一个脏数据通知.
94+
*
95+
* <p>例如,在一个请求内状态多次修改,只希望获取最新(最后)状态.</p>
96+
*
97+
* @param hintClass concrete type of {@link IDirtyHint}
98+
* @param <T> hint class type
99+
* @return null if not found. ONLY returns the last registered hint of specified type
100+
*/
101+
public <T extends IDirtyHint> T lastHintOf(Class<T> hintClass) {
102+
List<T> dirtyHints = dirtyHintsOf(hintClass);
103+
if (dirtyHints.isEmpty()) {
104+
return null;
105+
}
106+
107+
return dirtyHints.get(dirtyHints.size() - 1);
108+
}
109+
110+
/**
111+
* 根据指定hint class type,找到其所有的脏数据通知.
112+
*
113+
* @param hintClass concrete type of {@link IDirtyHint}
114+
* @param <T> hint class type
115+
* @return will never returns null, but might be an empty list
116+
*/
117+
@NonNull
118+
public <T extends IDirtyHint> List<T> dirtyHintsOf(Class<T> hintClass) {
119+
return (List<T>) hints.stream().filter(hintClass::isInstance).collect(Collectors.toList());
120+
}
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.dddplus.buddy;
2+
3+
/**
4+
* 脏数据提示.
5+
*
6+
* <p>{@link IDirtyHint} is part of our DDD building block.</p>
7+
* <p>它是聚合根改变状态时,由聚合根显式传递给{@code Repository}的待落库的脏数据携带者.</p>
8+
* <p>每个{@code IDirtyHint}包含了2层内容:</p>
9+
* <ul>
10+
* <li>业务语义,通过类名表达,{@code Repository}可以翻译成INSERT/UPDATE/DELETE数据库操作</li>
11+
* <li>携带的数据,{@code Repository}可以翻译成对应的表和字段</li>
12+
* </ul>
13+
* <p>业界有两个主流的变更追踪方案:</p>
14+
* <ul>
15+
* <li>基于snapshot方案:从数据库加载时内存保存一份snapshot,落库时(通过反射)与snapshot比较出变更的对象,全量diff</li>
16+
* <li>基于proxy方案:自动拦截setter方法,但需要框架支持,例如Entity Framework</li>
17+
* </ul>
18+
* <p>权衡下,我们使用了{@code IDirtyHint}方案,理解起来简单,性能有保障,风险低,也不依赖外部框架,但增加了开发成本</p>
19+
*/
20+
public interface IDirtyHint {
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.dddplus.buddy;
2+
3+
import java.io.Serializable;
4+
5+
/**
6+
* 可以合并的脏数据提示.
7+
*
8+
* @param <ID> 该hint的唯一标识
9+
*/
10+
public interface IMergeableDirtyHint<ID extends Serializable> extends IDirtyHint, IdentifiableDomainObject<ID> {
11+
12+
/**
13+
* Merge预留的hook.
14+
*
15+
* <p>注意:合并过程中要改变状态,要改变{@code thatHint}入参的状态,而不是改变{@code this}</p>
16+
*
17+
* @param thatHint {@link DirtyMemento}里现存的该hint
18+
*/
19+
default void onMerge(IDirtyHint thatHint) {
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.dddplus.buddy;
2+
3+
/**
4+
* Marker interface for native reusable and scenario-agnostic workflow.
5+
*
6+
* <p>{@link INativeFlow} is part of our DDD building block.</p>
7+
* <p>用于标识场景无关的、内在的原生的、可复用的流程片段.</p>
8+
* <p>这是一种代码的结构化(数字化)的手段,可以使核心能力显性化,有助于代码分析和可视化.</p>
9+
*/
10+
public interface INativeFlow {
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.github.dddplus.buddy;
2+
3+
import org.springframework.lang.Nullable;
4+
5+
import java.io.Serializable;
6+
7+
8+
/**
9+
* Interface for domain objects that can be uniquely identified.
10+
*
11+
* @param <ID> the ID type.
12+
*/
13+
public interface IdentifiableDomainObject<ID extends Serializable> {
14+
15+
/**
16+
* Returns the ID of this domain object.
17+
*
18+
* @return the ID or {@code null} if an ID has not been assigned yet.
19+
*/
20+
@Nullable
21+
ID getId();
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.github.dddplus.buddy.specification;
2+
3+
/**
4+
* Abstract base implementation of composite {@link ISpecification} with default
5+
* implementations for {@code and}, {@code or}.
6+
*/
7+
public abstract class AbstractSpecification<T> implements ISpecification<T> {
8+
9+
@Override
10+
public final boolean isSatisfiedBy(T candidate) {
11+
return isSatisfiedBy(candidate, Notification.build());
12+
}
13+
14+
public AbstractSpecification<T> and(final ISpecification<T> specification) {
15+
return new AndSpecification<T>(this, specification);
16+
}
17+
18+
public AbstractSpecification<T> or(final ISpecification<T> specification) {
19+
return new OrSpecification<T>(this, specification);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.dddplus.buddy.specification;
2+
3+
/**
4+
* AND specification, used to create a new specification that is the AND of two other specifications.
5+
*/
6+
public class AndSpecification<T> extends AbstractSpecification<T> {
7+
8+
private ISpecification<T> left;
9+
private ISpecification<T> right;
10+
11+
/**
12+
* Create a new AND specification based on two other spec.
13+
*
14+
* @param left Specification one.
15+
* @param right Specification two.
16+
*/
17+
public AndSpecification(final ISpecification<T> left, final ISpecification<T> right) {
18+
this.left = left;
19+
this.right = right;
20+
}
21+
22+
@Override
23+
public boolean isSatisfiedBy(T candidate, Notification notification) {
24+
return left.isSatisfiedBy(candidate, notification) && right.isSatisfiedBy(candidate, notification);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.github.dddplus.buddy.specification;
2+
3+
/**
4+
* Specification interface,归约模式(Specification Pattern).
5+
* <p>
6+
* <p>{@link ISpecification} is part of our DDD building block.</p>
7+
* <p>Specifications are small, single‐purpose classes(SRP), similar to policies.</p>
8+
* <ul>不是Always-Valid Model吗?为什么还把规约提出来?不违反该原则吗?
9+
* <li>if validation depends exclusively on an entity's data then it stays inside the entity</li>
10+
* <li>if validation depends on information from multiple entities or external services, use Specification</li>
11+
* </ul>
12+
*/
13+
public interface ISpecification<T> {
14+
15+
/**
16+
* Check if {@code candidate} is satisfied by the specification.
17+
*
18+
* @param candidate
19+
* @return
20+
*/
21+
boolean isSatisfiedBy(T candidate);
22+
23+
/**
24+
* Same as {@link #isSatisfiedBy(Object)} except that it accepts {@link Notification} to pass more detailed error info.
25+
*
26+
* @param candidate
27+
* @param notification
28+
* @return
29+
*/
30+
boolean isSatisfiedBy(T candidate, Notification notification);
31+
}

0 commit comments

Comments
 (0)