DDD - 复合聚合序列化 - 设计问题

DDD - Composite Aggregate Serialization - design problem

我正在尝试将 DDD 应用于一个 Java 项目。这是我偶然发现的问题:

在域中,我有一个使用复合 OOP 模式实现的聚合。此聚合上的方法会生成一些需要序列化并通过网络发送的域对象。这些是我考虑过的选项:

  1. 在我域的应用程序服务部分,我正在聚合,调用它的方法,并尝试将结果序列化为 DTO。为了将其序列化为 DTO,我必须使用 instanceof 检查当前节点是 Composite 还是 Child 并继续序列化。由于 instanceof 是代码味道(我读到它违反了 Open/Close 原则等),我决定尝试使用访问者模式。

  2. 为了应用访问者模式,我的复合聚合必须实现访问者,这将 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.
    }
}

ANodeBNode不知道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 在第二种情况下,两者解决方案有一个 elsecatch 来检查何时将非托管类型传递给该方法。
100% 确定您管理的所有类型只能使用 @jaco0646 提出的解决方案来实现,据我所知,它会强制您管理所有类型,否则将无法编译。 考虑到它需要的样板数量,我只会在抛出 RuntimeException 会导致业务问题时使用,我不能保证它不会通过适当的测试抛出(除此之外我发现它非常有趣)。

听起来你把它复杂化了。如果您需要 typeof,则您的聚合不是 return 有效的域对象。 returning 的域对象太笼统了。为了解决这个问题,您可以将聚合方法分成两种方法;一个是 return 的子对象,另一个是 return 的复合对象。然后您的应用程序服务决定调用哪一个(如果可能)。

如果出于某种原因您需要聚合到 return 通用对象,我会重新考虑您选择的设计。

另一个 "hack" 是简单地在您的域对象上放置一个 属性 以指示它是复合对象还是子对象。我假设聚合会知道它是否存在并且能够准确地填充 属性。