Я читаю книгу Вона Вернона - Реализация доменного дизайна. Есть пример приложения для управления проектами. Существуют агрегаты, такие как BacklogItem, Sprint и т. Д. Если у меня определено BacklogItemNotFoundException на уровне домена. Должен ли мой адаптер Rest перехватить его и преобразовать в NotFoundHttpResult? Или любые другие неработающие инвариантные исключения, такие как: EmailPatternBrokenException или TooManyCharactersForNameException, или что-либо еще должно обрабатываться в адаптере Rest (архитектура портов и адаптеров) и преобразовываться в ответы остальных? Если да, это означает, что RestAdapter должен иметь ссылку на слой домена? Это то, что беспокоит меня ...

8
DmitriBodiu 21 Авг 2018 в 09:26

5 ответов

Лучший ответ

Вопрос в противоречии. Если это исключение домена, это означает, что оно выбрасывается доменом.

В любом случае исключения, создаваемые доменом, должны обрабатываться прикладным уровнем.

У меня есть декоратор обработчика исключений для командной шины, который перехватывает любое исключение домена и переводит его в исключение приложения.

Это исключение приложения выбрасывается в адаптеры.

Адаптеры знают об исключениях приложения, а не об исключениях домена.

ОБНОВЛЕНИЕ

Мое исключение домена - это абстрактный базовый класс, от которого наследуются конкретные исключения домена

public abstract class DomainException extends RuntimeException {

private static final long serialVersionUID = 1L;

private ErrorMessage mainErrorMessage;
private List<ErrorMessage> detailErrorMessages;

protected DomainException ( List<ErrorMessage> aDetailMessages, Object... aMainMessageArgs ) {
    this.mainErrorMessage = new ErrorMessage(this.getClass().getSimpleName(), aMainMessageArgs );
    this.detailErrorMessages = ( (aDetailMessages==null) ? new ArrayList<ErrorMessage>() : aDetailMessages );
}

public ErrorMessage mainErrorMessage() {
    return this.mainErrorMessage;
}

public List<ErrorMessage> detailErrorMessages() {
    return this.detailErrorMessages;
}
}

ErrorMessage имеет ключ и список аргументов. Сообщения находятся в файле свойств, где ключом является имя конкретного класса исключений домена.

Исключением для приложения является только один тип, который содержит конкретное текстовое сообщение.

public class ApplicationException extends Exception {

private static final long serialVersionUID = 1L;


private String mainMessage;
private String[] detailMessages = new String[0];


public ApplicationException ( String aMainMessage, Throwable aCause, String... aDetailMessages ) {
    super ("Main Message = "+aMainMessage+" - DetailMessages = "+Utils.toString(aDetailMessages), aCause );
    this.mainMessage = aMainMessage;
    this.detailMessages = ( (aDetailMessages==null) ? (new String[0]) : aDetailMessages );
}


public String mainMessage() {
    return this.mainMessage;
}

public boolean hasDetailMessages() {
    return (this.detailMessages.length > 0);
}

public String[] detailMessages() {
    return this.detailMessages;
}
}

У меня есть декоратор (оборачивает выполнение каждой команды) для обработки исключений домена:

public class DomainExceptionHandlerDecorator extends Decorator {

private final DomainExceptionHandler domainExceptionHandler;


public DomainExceptionHandlerDecorator (DomainExceptionHandler domainExceptionHandler) {
    this.domainExceptionHandler = domainExceptionHandler;
}


@Override
public <C extends Command> void decorateCommand(Mediator mediator, C command) throws ApplicationException {
    try {
        mediator.executeCommand(command);
    } catch ( DomainException de ) {
        this.domainExceptionHandler.handle (de);
    }
}
}

И у меня есть обработчик исключений домена, который принимает исключение домена, переводит его в исключение приложения, читая файл свойств (TextMessageService выполняет эту работу) и выдает исключение приложения.

public class TranslatorDomainExceptionHandler implements DomainExceptionHandler {

private final TextMessageService configurationService;

public TranslatorDomainExceptionHandler ( TextMessageService aConfigurationService ) {
    this.configurationService = aConfigurationService;
}

@Override
public void handle ( DomainException de ) throws ApplicationException {

    ErrorMessage mainErrorMessage = de.mainErrorMessage();
    List<ErrorMessage> detailErrorMessages = de.detailErrorMessages();

    String mainMessage = this.configurationService.mensajeDeError ( mainErrorMessage );

    String[] detailMessages = new String [ detailErrorMessages.size() ];

    int i = 0;
    for ( ErrorMessage aDetailErrorMessage : detailErrorMessages ) {
        detailMessages[i] = this.configurationService.mensajeDeError ( aDetailErrorMessage );
        i++;
    }
    throw new ApplicationException ( mainMessage, de, detailMessages);      
}
}

Адаптер (например, пользовательский интерфейс) перехватит исключение приложения и покажет его сообщение пользователю. Но он не знает об исключениях домена.

7
choquero70 4 Сен 2018 в 16:55

Я добавлю свои 2 цента об обработке ошибок, не связанных конкретно с DDD.

Исключение составляют части договора, которые вы выставляете потребителю. Если вы ожидаете, например, добавить элемент в корзину, исключение, которое вы можете явно сгенерировать, включает itemNotAvailable, shoppingCartNotExisting и т. Д ...

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

Интерфейс отдыха - это контракт на операцию над ресурсом. При использовании rest over http условия договора связаны с протоколом http.

Типичная операция, описанная выше (добавление, т. Е. Публикация элемента в ресурсе корзины), будет переведена, например, на 404 для shoppingCartNotExisting и 409 для itemNotAvailable (конфликт, т. Е. Обновление ресурса больше невозможно, поскольку какое-то состояние изменилось между тем ) .

Так что да, все «доменные» исключения (ожидаемые исключения как часть контракта) должны быть явно отображены оставшимся адаптером, все непроверенные должны привести к ошибке 500.

5
Gab 30 Янв 2020 в 08:16

TLDR ; Это нормально, если уровень Application или Presentation имеет зависимость от уровня Domain, другой способ не рекомендуется.

В идеале не должно быть никакой зависимости от одного уровня к другому, но это невозможно, иначе программное обеспечение не будет пригодно для использования. Вместо этого вы должны попытаться свести к минимуму количество и направление зависимостей. Общее правило или наилучшая практика для чистой архитектуры - сохранять уровень домена независимым от инфраструктуры или уровня приложений. Объекты домена (агрегаты, объекты значений и т. Д.) Не должны заботиться об определенной персистентности, или Rest, или HTTP или MVC, точно так же, как эксперты домена не заботятся об этих вещах.

В реальном мире уровень домена может зависеть от технологий (таких как фреймворки). Например, мы помещаем аннотации, чтобы пометить некоторые объекты Домена как поведение определенным образом, когда они сохраняются, вместо того, чтобы использовать внешние файлы XML или JSON только потому, что они под рукой, их легче поддерживать. Нам необходимо, однако, ограничить это влияние до минимума.

1
Constantin Galbenu 21 Авг 2018 в 06:52

Прикладной уровень сам по себе является специфическим для бизнеса Таким образом, ваш прикладной уровень должен обрабатывать исключение домена в зависимости от того, что ожидает приложение / бизнес. Приложение (например, клиентское веб-приложение, мобильное приложение, внутреннее приложение CRM или API backend-for-frontend), вероятно, является не единственным клиентом уровня домена (например, остальные API, библиотека JAR). Могут быть определенные исключения домена, которые вы не хотите показывать конечному пользователю, поэтому приложение должно специально обернуть эти исключения или обрабатывать исключения глобально.

0
alltej 21 Авг 2018 в 13:46

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

Backlog ItemNotFoundException

Для меня это обычно ваш репозиторий или служба запросов, возвращающая ноль или пустой список. Нет необходимости в исключении домена.

EmailPatternBrokenException

TooManyCharactersForNameException

Я позволил функции проверки моего веб-фреймворка справиться с этим. Вы также можете проверить это в Домене, но он редко достигнет этой точки, и вам действительно не нужно специально обрабатывать такого рода ошибки.

В результате, два типичных сценария:

+-----------------------+--------------------+-------------------------------------------------+
| Domain                | Application        | Presentation                                    |
+-----------------------+--------------------+-------------------------------------------------+
| Expected failure case | Return Result.Fail | Clean error message                             |
+-----------------------+--------------------+-------------------------------------------------+
| Exception             | -                  | Caught in catch-all clause > 500 error or other |
+-----------------------+--------------------+-------------------------------------------------+
3
guillaume31 21 Авг 2018 в 12:05
51942769