DDD - Составная совокупная сериализация - проблема проектирования - PullRequest
1 голос
/ 21 сентября 2019

Я пытаюсь применить DDD к одному проекту Java.Вот проблема, с которой я столкнулся:

В домене у меня есть Агрегат, который реализован с использованием шаблона составного ООП.Методы этого агрегата создают некоторые доменные объекты, которые необходимо сериализовать и отправить по проводам.Вот варианты, о которых я подумал:

  1. В части службы приложений моего домена я собираю агрегат, вызываю его методы и пытаюсь сериализовать результаты в DTO.Чтобы сериализовать его в DTO, я должен использовать instanceof, чтобы проверить, является ли текущий узел составным или дочерним, и продолжить сериализацию.Поскольку instanceof является запахом кода (поскольку я читал, что он нарушает принцип Open / Close и т. Д.), Я решил попробовать использовать шаблон Visitor.

  2. Чтобы применить шаблон Visitor,мой составной агрегат должен реализовать Visitor, который будет возвращать DTO, а затем DTO становиться частью доменного уровня - что также не является хорошим дизайном (поскольку домен должен содержать только концепции домена, а DTO не являются его частью).Сериализация DTO - это только техническая деталь, которая не должна переходить на уровень домена.

Есть ли какое-либо другое решение, которое не противоречит этим принципам проектирования?

Есть лиспособ имитировать динамическое связывание в Java для перегруженных методов (кроме instanceof - так как это решило бы мою проблему с вариантом 1)?

Ответы [ 3 ]

1 голос
/ 21 сентября 2019

Если у посетителя есть универсальный тип возвращаемого значения, то посещенные классы не связаны с этим типом.

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 здесь.

0 голосов
/ 21 сентября 2019

Звучит так, будто вы слишком усложняете это.Если вам нужен typeof, то ваш агрегат не возвращает допустимый объект домена.Доменный объект, который он возвращает, слишком общий.Чтобы противостоять этому, вы можете разделить ваш агрегатный метод на два метода;один, который возвращает Child, и другой, который возвращает Composite.Затем ваша прикладная служба решает, какую из них следует вызвать (если это возможно).

Если по какой-то причине вам нужен агрегат для возврата универсального объекта, я бы пересмотрел выбранный вами дизайн.

Еще один «хак» - просто добавить свойство в свой объект домена, которое указывает, является ли оно составным или дочерним.Я предполагаю, что агрегат будет знать, если это так, и сможет точно заполнить это свойство.

0 голосов
/ 21 сентября 2019

Во-первых, в пункте 2 я не получаю:

мой составной агрегат должен реализовать Visitor

Первый вопрос, который приходит мне в голову: почему?Разве вы не можете объявить посетителя как интерфейс и передать реализацию как входной параметр вашего агрегата?

Есть ли способ имитировать динамическое связывание в java для перегруженных методов (кроме instanceof -так как это решило бы мою проблему с вариантом 1)?

Да, вы можете сделать это с помощью Reflections, но реальный вопрос заключается в том, хотите ли вы их использовать?

Я думаю, что ответ зависит от того, сколько случаев вы должны решатьи как часто они меняются?

Если у вас есть «управляемое» количество различных случаев, решение с 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, позвольте мне опубликовать пример использования:

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();
        }
    }

}

Метод public в MyObject, принимающий Object, будет искать метод с именем doStuffs с результатом String и i.getClass() в качестве входных данных в классе MyObject(дополнительная информация здесь ).
Используя этот способ, вы можете отправлять методы во время выполнения (использование перегрузки - статическое связывание во время компиляции).Но оба метода имеют проблему, состоящую в том, что вы не можете быть уверены, что будете управлять ВСЕМИ типами, которые расширяют / реализуют Entity в первом случае и / или Object во втором, оба решения имеют else илиcatch чтобы проверить, когда в метод передается неуправляемый тип.
Будьте на 100% уверены, что вы управляете всеми типами, может быть достигнуто только с помощью решения, предложенного @ jaco0646, поскольку, насколько я знаю, оно заставляет васуправлять всеми типами, иначе он не скомпилируется.Учитывая необходимое количество шаблонов, которые я бы использовал, я бы использовал только при броске, что RuntimeException вызовет бизнес-проблему, и я не могу гарантировать, что его не выбрасывают с помощью надлежащего тестирования (помимо этого я нашел это очень увлекательным).

...