使用DDD和Azure进行优雅的异常处理

时间:2014-09-05 17:19:39

标签: c# azure exception-handling domain-driven-design

我正在使用Domain Driven Design开发MVC 5 Web应用程序。我的控制器基本上调用服务层,该服务层返回数据(实体或实体列表)或执行操作(业务流程),具体取决于场景。

这是我的困惑。我需要一种有效的策略来记录出于故障排除目的而发生的异常,同时向用户显示友好消息或在特定条件下根本不显示。

例如,假设服务层中的某些代码导致NullReferenceException,我希望在记录异常以进行故障排除时为用户优雅地处理此问题。另外,让我们说在存储库层中发生异常,例如在尝试访问数据库时发生连接错误。这将是另一种情况,我想以同样的方式处理。

在处理DDD时,这种情况的推荐方法是什么?我有我的存储库 - >服务层 - >控制器 - > UI。

我目前的方法是创建一个特定于存储库层的异常,一个特定于服务层的异常,并且存储库层中发生的故障将被冒泡到UI可以根据其自行决定处理的服务层。

但是,我想利用Azure日志记录将错误添加到日志文件中以供进一步调查。

  1. 处理各层之间错误的推荐方法是什么?
  2. 在此分层方案中添加日志记录的推荐位置是什么?
  3. 将azure日志记录放入服务或存储库层似乎是不好的,至少不使用包装类?

    是否有一种全局的方法来处理这个问题,而不必考虑每一个异常(对于任何可能会出现问题的异常都是一个问题)。

1 个答案:

答案 0 :(得分:4)

这里确实不是一个明确的答案,但以下是我已经使用了几次并且效果很好的解决方案。 (不仅适用于异常处理,还适用于所有横切问题)。

一种可能的方法是使用装饰器模式。我写了一篇关于此的帖子,你可以在这里查看:http://www.kenneth-truyers.net/2014/06/02/simplify-code-by-using-composition/

我还建议您查看Greg Young关于大致相同主题的视频:http://www.infoq.com/presentations/8-lines-code-refactoring

为了使用装饰器模式,您可以将返回数据和执行业务流程的方法转换为查询和命令处理程序。假设您有以下方法:

List<Customer> GetCustomers(string country, string orderBy)
{
    ...
}

void CreateInvoice(int customerId, decimal amount)
{
    ...
}

void CreateCustomer(string name, string address)
{
    ...
}

现在,这些方法不符合界面,因此您无法提取一个。但是,您可以将它们更改为查询和命令模式:

接口:     接口IQueryHandler     {         TResult Handle(TQuery查询);     }

interface ICommandHandler<T>
{
    Handle(T command);
}

现在您可以更改类,以便实现此界面:

class GetCustomersHandler : IQueryHandler<CustomerQuery, List<Customer>>
{
    List<Customer> Handle(CustomerQuery query)
    {
        // CustomerQuery is a simple message type class which contains country and orderby
        // just as in the original method, but now packed up in a 'message'
    }
}

class CreateInvoiceHandler : ICommandHandler<CreateInvoice>
{
    public void Handle(CreateInvoice command)
    {
        // CreateInvoice is a simple message type class which contains customerId and amount
        // just as in the original method, but now packed up in a 'message'
    }
}

如果有了这个,你可以创建一个实现日志记录的logger类,但包装(修饰)底层类:

class QueryExceptionHandler<TQuery, TResult> : IQueryHandler<TQuery, TResult>
{
    IQueryHandler<TQuery, TResult> _innerQueryHandler;
    public QueryLogHandler(IQueryHandler<TQuery, TResult> innerQueryHandler)
    {
        _innerQueryHandler = innerQueryHandler;
    }

    TResult Handle(TQuery query)
    {
         try
         {
             var result = _innerQueryHandler.Handle(query);
         }
         catch(Exception ex)
         {
              // Deal with exception here
         }
    }
}

当您想要使用它时,您可以像这样(从UI代码)实例化它。

IQueryHandler<CustomerQuery, List<Customer>> handler = 
    new QueryExceptionHandler<CustomerQuery, List<Customer>>(new GetCustomersHandler());

var customers = handler.Handle(new CustomerQuery {Country = "us", OrderBy = "Name"});

当然,这个queryExceptionHandler也可以重用于其他处理程序(例如):

IQueryHandler<InvoiceQuery, List<Invoice>> handler = 
    new QueryExceptionHandler<InvoiceQuery, List<Invoice>>(new GetCInvoicesHandler());

var invoices= handler.Handle(new InvoiceQuery {MinAmount= 100});

现在异常处理在一个班级完成,而你所有其他班级都不需要为此烦恼。同样的想法可以应用于业务操作(命令端)。

除此之外,在这种情况下,我只添加了一层用于异常处理。您也可以将异常处理程序包装在记录器中,从而在彼此之上构建各种装饰器。这样你就可以创建一个用于记录的类,一个用于异常处理,一个用于...

它不仅允许您将该行为与实际类分开,而且它允许您根据需要为每个不同的处理程序自定义它(使用异常包装客户处理程序并仅在日志记录中记录和发票处理程序)例如处理程序)

像上面示例中那样构建处理程序非常麻烦(特别是当您开始添加多个装饰器时),但它只是为了向您展示它们如何协同工作。

为此使用依赖注入会更好。您可以进行手动DI,功能性方法(参见Greg Young的视频)或使用DI容器。

我知道它看起来像一个非常复杂的样本,但您很快就会注意到,一旦您设置了小结构,它实际上很容易使用。您可以参考我的文章,您也可以在其中看到使用DI容器的解决方案。