【问题标题】:Graceful exception handling using DDD and Azure使用 DDD 和 Azure 进行优雅的异常处理
【发布时间】:2014-09-05 17:19:38
【问题描述】:

我正在使用域驱动设计开发一个 MVC 5 Web 应用程序。我的控制器基本上调用服务层,该服务层根据场景返回数据(实体或实体列表)或执行操作(业务流程)。

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

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

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

我目前的方法是创建一个特定于存储库层的异常和一个特定于服务层的异常,并且存储库层中发生的故障将冒泡到服务层,用户界面可以根据其判断来处理。

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

  1. 推荐的处理不同层之间错误的方法是什么?
  2. 在这种分层场景中添加日志记录的推荐位置是什么?

将天蓝色日志记录放在服务或存储库层似乎很糟糕,至少不使用包装类?

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

【问题讨论】:

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


    【解决方案1】:

    这里并没有真正确定的答案,但以下是我使用过几次并且效果很好的解决方案。 (不仅用于异常处理,还用于所有横切关注点)。

    一种可能的方法是使用装饰器模式。我写了一篇关于这个的帖子,你可以在这里查看: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 句柄(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'
        }
    }
    

    当你有这个时,你可以创建一个实现日志记录但包装(装饰)底层类的记录器类:

    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 容器的解决方案。

    【讨论】:

    • 我不确定我是否喜欢每次想使用命令或查询处理程序时调用异常处理程序的想法。将其称为其他名称会更好吗? +1。
    • 您不需要调用 ExceptionHandler。从消费者的角度来看,它只是一个 IQueryHandler&lt;CustomerQuery, List&lt;Customer&gt;&gt; 。具体实例将是一个异常处理程序,包裹在GetCustomersHandler 周围。您通常在组合根中处理此问题,如果您使用的是 DI 容器,则通常使用 DI 容器
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 2014-07-10
    • 2017-03-04
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    相关资源
    最近更新 更多