DDD - 复合聚合序列化 - 设计问题
DDD - Composite Aggregate Serialization - design problem
我正在尝试将 DDD 应用于一个 Java 项目。这是我偶然发现的问题:
在域中,我有一个使用复合 OOP 模式实现的聚合。此聚合上的方法会生成一些需要序列化并通过网络发送的域对象。这些是我考虑过的选项:
在我域的应用程序服务部分,我正在聚合,调用它的方法,并尝试将结果序列化为 DTO。为了将其序列化为 DTO,我必须使用 instanceof
检查当前节点是 Composite 还是 Child 并继续序列化。由于 instanceof
是代码味道(我读到它违反了 Open/Close 原则等),我决定尝试使用访问者模式。
为了应用访问者模式,我的复合聚合必须实现访问者,这将 return DTO,然后 DTO 成为域层的一部分——这也不是好的设计(因为域应该只包含领域概念,而 DTO 不是其中的一部分)。 DTO 序列化只是技术细节,不应进入域层。
有没有不违背这些设计原则的方案?
有没有办法在 java 中为重载方法模拟动态绑定(instanceof
除外 - 因为这可以解决选项 1 的问题)?
如果访问者具有通用 return 类型,则访问者 类 不会与该类型耦合。
public interface Node {
<T> T accept(NodeVisitor<T> visitor);
}
public class ANode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public class BNode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public interface NodeVisitor<T> {
T visit(ANode aNode);
T visit(BNode bNode);
}
public class DtoNodeVisitor implements NodeVisitor<DTO> {
@Override
public DTO visit(ANode aNode) {
return new DTO(); //use ANode to build this.
}
@Override
public DTO visit(BNode bNode) {
return new DTO(); //use BNode to build.
}
}
ANode
和BNode
不知道DTO
这里。
首先,在第 2 点我不明白:
my Composite Aggregate has to implement Visitor
我想到的第一个问题是,为什么?
不能将访问者声明为接口,并将实现作为聚合的输入参数传递吗?
Is there a way to simulate dynamic binding in java for overloaded methods (other than instanceof - since this would solve my problem with option 1)?
是的,您可以使用反射来实现,但真正的问题是,您要使用它们吗?
我认为答案取决于您必须处理多少个案例以及它们更改的频率?
如果您有 "manageable" 种不同的情况,使用 instanceof
的解决方案可能是一个很好的交易:
public Something myMethod(Entity entity){
if (entity instanceof AnEntity){
//do stuffs
else if (entity instanceof AnotherEntity){
//do something else
...
else {
throw new RuntimeException("Not managed " + entity.getClass().getName());
}
}
否则,当你有更多的情况,想把代码拆分成自己的方法时,你可以使用Java MethodHandle
,让我post一个用法示例:
package virtualmethods;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MyObject {
public String doStuffs(Object i) throws Throwable {
try {
final MethodType type = MethodType.methodType(String.class, i.getClass());
return (String) MethodHandles.lookup()
.findVirtual(getClass(), "doStuffs", type)
.invoke(this, i);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Not managed " + i.getClass().getName(), e);
}
}
private String doStuffs(Integer i) {
return "You choose " + i;
}
private String doStuffs(Boolean b) {
return "You choose boolean " + b;
}
}
然后使用它:
package virtualmethods;
public class Main {
public static void main(String[] args) throws Throwable {
MyObject object = new MyObject();
System.out.println("Integer => " + object.doStuffs(5));
System.out.println("Boolean => " + object.doStuffs(true));
try {
System.out.println("String => " + object.doStuffs("something"));
}
catch (Throwable e) {
System.out.println("KABOOM");
e.printStackTrace();
}
}
}
MyObject
中的 public 方法采用 Object
将查找名为 doStuffs
的方法 String
结果和 i.getClass()
作为 class MyObject
中的输入(更多信息 here)。
使用这种方式,您可以在运行时分派方法(使用重载是编译时的静态链接)。
但是这两种方法都有一个问题,你不能确定你会管理所有类型 extends/implements Entity
在第一种情况下 and/or Object
在第二种情况下,两者解决方案有一个 else
或 catch
来检查何时将非托管类型传递给该方法。
100% 确定您管理的所有类型只能使用 @jaco0646 提出的解决方案来实现,据我所知,它会强制您管理所有类型,否则将无法编译。
考虑到它需要的样板数量,我只会在抛出 RuntimeException
会导致业务问题时使用,我不能保证它不会通过适当的测试抛出(除此之外我发现它非常有趣)。
听起来你把它复杂化了。如果您需要 typeof
,则您的聚合不是 return 有效的域对象。 returning 的域对象太笼统了。为了解决这个问题,您可以将聚合方法分成两种方法;一个是 return 的子对象,另一个是 return 的复合对象。然后您的应用程序服务决定调用哪一个(如果可能)。
如果出于某种原因您需要聚合到 return 通用对象,我会重新考虑您选择的设计。
另一个 "hack" 是简单地在您的域对象上放置一个 属性 以指示它是复合对象还是子对象。我假设聚合会知道它是否存在并且能够准确地填充 属性。
我正在尝试将 DDD 应用于一个 Java 项目。这是我偶然发现的问题:
在域中,我有一个使用复合 OOP 模式实现的聚合。此聚合上的方法会生成一些需要序列化并通过网络发送的域对象。这些是我考虑过的选项:
在我域的应用程序服务部分,我正在聚合,调用它的方法,并尝试将结果序列化为 DTO。为了将其序列化为 DTO,我必须使用
instanceof
检查当前节点是 Composite 还是 Child 并继续序列化。由于instanceof
是代码味道(我读到它违反了 Open/Close 原则等),我决定尝试使用访问者模式。为了应用访问者模式,我的复合聚合必须实现访问者,这将 return DTO,然后 DTO 成为域层的一部分——这也不是好的设计(因为域应该只包含领域概念,而 DTO 不是其中的一部分)。 DTO 序列化只是技术细节,不应进入域层。
有没有不违背这些设计原则的方案?
有没有办法在 java 中为重载方法模拟动态绑定(instanceof
除外 - 因为这可以解决选项 1 的问题)?
如果访问者具有通用 return 类型,则访问者 类 不会与该类型耦合。
public interface Node {
<T> T accept(NodeVisitor<T> visitor);
}
public class ANode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public class BNode implements Node {
@Override
public <T> T accept(NodeVisitor<T> visitor) {
return visitor.visit(this);
}
}
public interface NodeVisitor<T> {
T visit(ANode aNode);
T visit(BNode bNode);
}
public class DtoNodeVisitor implements NodeVisitor<DTO> {
@Override
public DTO visit(ANode aNode) {
return new DTO(); //use ANode to build this.
}
@Override
public DTO visit(BNode bNode) {
return new DTO(); //use BNode to build.
}
}
ANode
和BNode
不知道DTO
这里。
首先,在第 2 点我不明白:
my Composite Aggregate has to implement Visitor
我想到的第一个问题是,为什么? 不能将访问者声明为接口,并将实现作为聚合的输入参数传递吗?
Is there a way to simulate dynamic binding in java for overloaded methods (other than instanceof - since this would solve my problem with option 1)?
是的,您可以使用反射来实现,但真正的问题是,您要使用它们吗?
我认为答案取决于您必须处理多少个案例以及它们更改的频率?
如果您有 "manageable" 种不同的情况,使用 instanceof
的解决方案可能是一个很好的交易:
public Something myMethod(Entity entity){
if (entity instanceof AnEntity){
//do stuffs
else if (entity instanceof AnotherEntity){
//do something else
...
else {
throw new RuntimeException("Not managed " + entity.getClass().getName());
}
}
否则,当你有更多的情况,想把代码拆分成自己的方法时,你可以使用Java MethodHandle
,让我post一个用法示例:
package virtualmethods;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MyObject {
public String doStuffs(Object i) throws Throwable {
try {
final MethodType type = MethodType.methodType(String.class, i.getClass());
return (String) MethodHandles.lookup()
.findVirtual(getClass(), "doStuffs", type)
.invoke(this, i);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Not managed " + i.getClass().getName(), e);
}
}
private String doStuffs(Integer i) {
return "You choose " + i;
}
private String doStuffs(Boolean b) {
return "You choose boolean " + b;
}
}
然后使用它:
package virtualmethods;
public class Main {
public static void main(String[] args) throws Throwable {
MyObject object = new MyObject();
System.out.println("Integer => " + object.doStuffs(5));
System.out.println("Boolean => " + object.doStuffs(true));
try {
System.out.println("String => " + object.doStuffs("something"));
}
catch (Throwable e) {
System.out.println("KABOOM");
e.printStackTrace();
}
}
}
MyObject
中的 public 方法采用 Object
将查找名为 doStuffs
的方法 String
结果和 i.getClass()
作为 class MyObject
中的输入(更多信息 here)。
使用这种方式,您可以在运行时分派方法(使用重载是编译时的静态链接)。
但是这两种方法都有一个问题,你不能确定你会管理所有类型 extends/implements Entity
在第一种情况下 and/or Object
在第二种情况下,两者解决方案有一个 else
或 catch
来检查何时将非托管类型传递给该方法。
100% 确定您管理的所有类型只能使用 @jaco0646 提出的解决方案来实现,据我所知,它会强制您管理所有类型,否则将无法编译。
考虑到它需要的样板数量,我只会在抛出 RuntimeException
会导致业务问题时使用,我不能保证它不会通过适当的测试抛出(除此之外我发现它非常有趣)。
听起来你把它复杂化了。如果您需要 typeof
,则您的聚合不是 return 有效的域对象。 returning 的域对象太笼统了。为了解决这个问题,您可以将聚合方法分成两种方法;一个是 return 的子对象,另一个是 return 的复合对象。然后您的应用程序服务决定调用哪一个(如果可能)。
如果出于某种原因您需要聚合到 return 通用对象,我会重新考虑您选择的设计。
另一个 "hack" 是简单地在您的域对象上放置一个 属性 以指示它是复合对象还是子对象。我假设聚合会知道它是否存在并且能够准确地填充 属性。