如何在 Web API 控制器中使用分层架构处理服务器错误
Posted
技术标签:
【中文标题】如何在 Web API 控制器中使用分层架构处理服务器错误【英文标题】:How to handle server errors with layered architecture in web API controller 【发布时间】:2018-10-26 09:29:35 【问题描述】:编辑:
我刚刚意识到 SaveChangesAsync 返回 0 并不意味着它失败了,实体框架在失败时总是会抛出异常,因此检查 SaveChanges == 0 是否是多余的!在下面的示例中,保存更改应始终返回 1,如果失败则抛出异常。
但是在某些情况下,使用了其他东西并且它不是实体框架,所以这个问题也是如此。
服务器可能会出现故障,当我将所有数据访问代码放入控制器时,我可以这样处理:
[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
await _dbContext.AddAsync(item);
if (await _dbContext.SaveChangesAsync() == 0)
return StatusCode(StatusCodes.Status500InternalServerError);
return CreatedAtAction(nameof(GetAsync), new id = item.Id , item);
当我的数据访问被封装在服务层时,我应该如何处理?
public class ItemsService
public async Task<Item> CreateAsync(Item item)
await _dbContext.AddAsync(item);
if (await _dbContext.SaveChangesAsync() == 0)
return null;
return item;
那么它会这样使用:
[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
// model state validation skipped for the sake of simplicity,
// that would return BadRequest or some more valuable information
var item = await _itemsService.CreateAsync(item);
if (item == null)
return StatusCode(StatusCodes.Status500InternalServerError);
return CreatedAtAction(nameof(GetAsync), new id = item.Id , item);
也许这适用于精细创建,因为只有 2 个状态代码,但让我们考虑更新可能有超过 2 个可能错误的地方,例如:
未找到 (404) 内部服务器错误(500) 好的 (200)没有服务的代码:
[HttpPut("id")]
public async Task<ActionResult<Item>> UpdateAsync(int id, [FromBody] Item itemToUpdate)
var item = await _dbContext.Items.FindAsync(id);
if (item == null)
return NotFound();
// update item with itemToUpdate
//...
await _dbContext.Update(item);
if (await _dbContext.SaveChangesAsync() == 0)
return StatusCode(StatusCodes.Status500InternalServerError);
return item;
现在服务无法正确处理:
public class ItemsService
public async Task<Item> UpdateAsync(Item updateItem)
var item = await _dbContext.Items.FindAsync(id);
if (item == null)
return null;
//change some properties and update
//...
_dbContext.Items.Update(item);
if (await _dbContext.SaveChangesAsync() == 0)
// what now?
return item;
因为它总是返回 null 并且无法判断该项目是否未找到或保存失败。
如何正确处理?
注意:我没有添加 DTO 或类似的东西来保持这个例子的简单。
【问题讨论】:
你有没有想过会冒泡层而不是返回值的异常来解决硬件/io/传输特定问题,例如“互联网/服务器不可用”、“磁盘已满” @k3b 你的意思是在 try-catch 中包装代码吗?或者你到底是什么意思? 【参考方案1】:您的服务负责捕获它知道如何处理的所有异常并处理这些异常。所有其他异常都应通过某种IServiceResult<T>
报告,同时提供bool IsSuccessful
和AggregatedException Exceptions get;
。所以与其抛出你让最高层来决定如何反应。从许多角度来看,这实际上是最好的方法,包括:功能的纯度(这是易于并行化的关键概念,对服务器来说不重要吗?),干净且可维护的代码(服务的消费者不应该知道/非常关心:他们只是验证结果是否成功并使用结果;否则挖掘聚合异常;实现您自己类型的异常提供适合您项目需求的方法可能是有意义的),能够继续进行更高阶-负责实际错误处理的函数;以rX
为例:.OnError(Action<Exception> handler)
。远不止这些,但我不想让答案变得更长。
作为旁注。确保您阅读了来自Haskell
编程语言的关于Maybe
和Either
monads 的介绍性杠杆文章;如果您更喜欢F#
,可以尝试分别寻找Some
和Either
。在那里您可以找到有用的方法,例如 .Map(...)
,并查看以有效方式组合/聚合这些方法的好方法。
祝你好运。
【讨论】:
AggregatedException 应该是什么样子,应该如何填充? 不幸的是,我对 Haskell 和 F# 了解不多,因为我从未使用过它们,但我知道响应式编程,但如果这就是您所说的rX
@Konrad AggException
是标准的 .NET
类。由于并发和并行计算的本质,它在多线程编程中无处不在。如果您(还)不熟悉,那么花点时间尝试一下绝对是有意义的。但是,现在您可能会跳过那些“高级”方法;我上面推荐的解决方案将与这些想法完全兼容,您可以尽快或永远不要回复它们。【参考方案2】:
这实际上是一个相当大的话题,有很多选择。
我更喜欢抛出异常。您可以为此创建自己的类或使用 .net 类。你可以有 NotFoundException、InternalServerError 等。每个类都可以有 StatusCode 和 Message 字段或任何你想要的。接下来,您需要为 asp.net 实现异常过滤器,它将处理所有异常并返回带有可以从异常类中检索的状态码的响应。
另一种选择可能更简单。您可以创建一个包含 StatusCode 和对象作为结果的类,以便从您的方法中返回它。这是一种从您的方法中返回更多内容的方法,而不仅仅是 null 或对象。
几篇好文章: Exceptions or error codes
https://www.hanselman.com/blog/GoodExceptionManagementRulesOfThumb.aspx
http://codebetter.com/karlseguin/2006/04/05/understanding-and-using-exceptions/
【讨论】:
【参考方案3】:服务的代码要好得多。控制器将业务逻辑委托给服务层。
为了更好地实现服务层,您应该:
当您的服务发生错误时抛出自定义异常。示例:抛出ItemNotFoundExcption
而不是返回null
。 ItemUpdateException
更新失败时。
在您的控制器中,您需要捕获所有可能发生的自定义异常并执行需要完成的操作,例如为ItemNotFoundException
返回NotFound()
,为ItemUpdateException
返回BadRequest()
。
后一点可以通过自定义操作过滤器来实现,它可以帮助您使控制器的操作更精简。
【讨论】:
服务甚至不应该知道.BadRequest()
的存在。服务现在应该很热......服务;)让服务知道响应肯定是错误的职责分离,导致整体架构不佳。如果由于任何因素无法提供服务,则必须将此特定因素报告给呼叫者。而已;然后假定调用者而不是服务决定下一步该做什么。
@SerejaBogolubov 我在哪一行说 service do BadRequest()
?老兄重读了我的答案。服务仅抛出控制器操作捕获的自定义异常。然后操作可以解释自定义异常以返回 BadRequest、NotFound 或属于该层的任何内容。
确实如此。然后纯度是唯一的问题,但是我回滚我的 -1 使其变为 +1 ))))以上是关于如何在 Web API 控制器中使用分层架构处理服务器错误的主要内容,如果未能解决你的问题,请参考以下文章