我的域异常是否应该从应用程序层抛出?

时间:2018-08-21 06:26:05

标签: exception-handling domain-driven-design hexagonal-architecture

我正在阅读Vaughn Vernon的书-实现域驱动设计。有一个项目管理应用程序的示例。有聚合,例如BacklogItem,Sprint等。如果我在Domain层中定义了BacklogItemNotFoundException。我的Rest适配器应该抓住它并转换为NotFoundHttpResult吗?还是任何其他破碎的不变异常,例如:EmailPatternBrokenException或TooManyCharactersForNameException或应在Rest适配器(端口和适配器体系结构)中处理的任何东西,然后重新转换为Rest响应?如果是,这意味着RestAdapter应该具有对Domain层的引用?这就是困扰我的地方...

5 个答案:

答案 0 :(得分:2)

我将在通用错误处理之上加上2美分,而不是专门添加到DDD中。

例外情况是您向消费者公开的合同的一部分。例如,如果希望将商品添加到购物车中,则可能明确抛出的异常包括itemNotAvailable,shoppingCartNotExisting等...

另一方面,技术异常不是合同的一部分,它们可能发生,但不应明确处理,因为没人能对此做任何事情,它们必须暗示操作中断(以及当前单位的回滚)工作)。

rest接口是对资源进行操作的协定。通过http使用rest时,合同条款与http协议相关。

上述典型操作(添加,即在购物车资源上发布商品)将转换为例如,对于shoppingCartNotExisting为404,对于itemNotAvailable为409(冲突,即由于某些状态而不再可能进行资源更新)同时更改了。)

因此,是的,所有“域”异常(作为合同一部分的预期异常)都应由rest适配器显式映射,所有未检查的异常都将导致500错误。

答案 1 :(得分:1)

我尽我所能避免域异常,而宁愿使无效状态不可达。第一个原因是异常是针对异常的意外事件,第二个原因是我不希望我的代码因可能会出错的每一个小商业问题而被细粒度的try / catching弄乱。

  

BacklogItemNotFoundException

对我来说,这通常是您的存储库或查询服务,返回空值或空列表。无需域例外。

  

EmailPatternBrokenException

     

TooManyCharactersForNameException

我让我的Web框架的验证功能处理这些问题。您也可以在“域”中检查它,但很少达到这一点,并且您真的不需要专门处理此类错误。

因此,两种典型情况是:

+-----------------------+--------------------+-------------------------------------------------+
| Domain                | Application        | Presentation                                    |
+-----------------------+--------------------+-------------------------------------------------+
| Expected failure case | Return Result.Fail | Clean error message                             |
+-----------------------+--------------------+-------------------------------------------------+
| Exception             | -                  | Caught in catch-all clause > 500 error or other |
+-----------------------+--------------------+-------------------------------------------------+

答案 2 :(得分:1)

这个问题是矛盾的。如果它是域异常,则表示它是由域抛出的。

无论如何,域引发的异常应由应用程序层处理。

我有一个用于命令总线的异常处理程序装饰器,它可以捕获任何域异常并将其转换为应用程序异常。

此应用程序异常被抛出到适配器。

适配器了解应用程序异常,而不是域异常。

更新

我的域异常是抽象基类,具体的域异常从该基类继承

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

适配器(例如UI)将捕获应用程序异常并向用户显示其消息。但是它不知道域异常。

答案 3 :(得分:0)

TLDR;如果“应用程序”或“表示层”对“域”层具有依赖性,则可以,但是不建议采用其他方法。

理想地,不应存在从一层到另一层的任何依赖关系,但这是不可能的,否则该软件将无法使用。相反,您应该尝试最小化依赖项的数量和方向。干净体系结构的一般规则或最佳做法是使域层与基础结构或应用程序层无关。域对象(聚合,值对象等)不应关心特定的持久性或Rest或HTTP或MVC,就像域专家并不关心这些事情一样。

在现实世界中,域层可能会受到技术(例如框架)的影响。例如,我们放置注释以将某些Domain对象标记为持久存在时的某些特定行为,而不是仅仅因为手头上的情况而使用外部XML或JSON文件,因此维护起来更加容易。但是,我们需要将这些影响降到最低。

答案 4 :(得分:0)

应用程序层是业务特定的域本身。因此,您的应用程序层应根据应用程序/业务的期望来处理域异常。该应用程序(例如,面向客户端的Web应用程序,移动设备,内部CRM应用程序或后端对前端API)可能不是域层的唯一客户端(例如,其余api,jar库)。您可能不想将某些域异常公开给最终用户,因此应用程序必须专门包装这些异常或全局处理异常。

相关问题