规则是控制器不应具有业务逻辑,而应将其委托给服务。但是当我们这样做时,我们无法处理所有可能的情况并返回适当的HTTP响应。
让我们看一个例子。假设我们正在构建某种社交网络,并且需要创建一个端点来对帖子进行评分(喜欢或不喜欢)。
首先让我们看一个委派逻辑到服务的例子,这是我们的控制器操作:
您在这里看到了问题吗?如果没有带有给定ID的文章,我们该如何返回未找到响应?如果用户没有权限对文章进行评分,我们该如何返回禁止响应?
我的建议是,在控制器中处理这个逻辑。因为在我看来,这应该是控制器的责任,要在执行操作之前检查所有权限。所以这是我会这样做的:
我需要进一步解释为什么这是不好的吗?
让我们看一个例子。假设我们正在构建某种社交网络,并且需要创建一个端点来对帖子进行评分(喜欢或不喜欢)。
首先让我们看一个委派逻辑到服务的例子,这是我们的控制器操作:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
您在这里看到了问题吗?如果没有带有给定ID的文章,我们该如何返回未找到响应?如果用户没有权限对文章进行评分,我们该如何返回禁止响应?
PostsService.Rate
只能返回一个新的 PostRating
,但其他情况呢?我们可以抛出异常,但是我们需要创建很多自定义异常,以便将它们映射到适当的 HTTP 响应。我不喜欢使用异常来处理这个问题,我认为有一种更好的方法可以处理这些情况,而不是使用异常。因为我认为,当文章不存在和用户没有权限时,并不是例外情况,它们只是普通情况,就像成功地对文章进行评分一样。我的建议是,在控制器中处理这个逻辑。因为在我看来,这应该是控制器的责任,要在执行操作之前检查所有权限。所以这是我会这样做的:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var post = _postsRepository.GetByIdWithRatings(postId);
if (post == null)
return NotFound();
if (!_permissionService.CanRate(user, post))
return Forbidden();
PostRating newPostRating = new PostRating
{
Post = post,
Author = user,
Type = ratingType
};
_postRatingsRepository.Save(newPostRating);
return Created(newPostRating);
}
我认为应该这样做,但我敢打赌有人会说这对控制器来说太过逻辑化,或者你不应该在其中使用存储库。
如果你不喜欢在控制器中使用存储库,那么你会把获取或保存文章的方法放在哪里?在服务中吗?这样就会有 PostsService.GetByIdWithRatings
和 PostsService.Save
,它们除了调用 PostsRepository.GetByIdWithRatings
和 PostsRepository.Save
之外什么也不做。这是非常不必要的,只会导致模板代码。
更新: 也许有人会建议使用 PostsService 检查权限,然后调用 PostsService.Rate。这样做很糟糕,因为它涉及到更多不必要的数据库访问。例如,可能会像这样:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
if(_postsService.Exists(postId))
return NotFound();
if(!_postsService.CanUserRate(user, postId))
return Forbidden();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
我需要进一步解释为什么这是不好的吗?