【发布时间】:2015-08-06 21:17:42
【问题描述】:
我目前正在构建一个 Web 应用程序,并尝试按照良好的 MVC 和面向服务的架构来设计它。
然而,我在连接表示层(即我的控制器)和后端服务时遇到了一些困难,同时仍然保持良好的错误/验证报告给用户。
我阅读了一篇非常好的 SO 帖子 here,介绍了如何将验证逻辑与服务层分离,并且在大多数情况下,这一切都是有道理的。 然而,如果你可以这么说的话,有一个“缺陷”,在这个模型中让我很恼火:在查找验证器和服务都需要的对象时,如何避免重复工作?
我认为用一个相当简单的例子来解释会更容易:
假设我有一个允许用户共享代码 sn-ps 的应用程序。现在,我决定添加一个新功能,允许用户将他们的 GitHub 帐户附加到他们在我网站上的帐户(即建立个人资料)。 出于本示例的目的,我将简单地假设我的所有用户都是值得信赖的,并且只会尝试添加他们自己的 GitHub 帐户,而不是其他任何人的 :)
根据上述 SO 文章,我设置了一个基本的 GitHub 服务来检索 GitHub 用户信息。
interface IGitHubUserService {
GitHubUser FindByUserName(string username);
}
GitHubUserService 的具体实现对https://api.github.com/users/{0} 进行了昂贵的调用,以提取用户信息。
同样,按照本文的模型,我实现了以下命令来将用户帐户链接到 GitHub 用户:
// Command for linking a GitHub account to an internal user account
public class GitHubLinkCommand {
public int UserId { get; set; }
public string GitHubUsername { get; set }
};
我的验证器需要验证用户输入的用户名是有效的 GitHub 帐户。这非常简单:在GitHubUserService 上调用FindByUserName 并确保结果不为空:
public sealed class GitHubLinkCommandValidator : Validator<GitHubLinkCommand> {
private readonly IGitHubUserService _userService;
public GitHubLinkCommandValidator(IGitHubUserService userService) {
this._userService = userService;
}
protected override IEnumerable<ValidationResult> Validate(GitHubLinkCommand command) {
try {
var user = this._userService.FindByUserName(command.GitHubUsername);
if (user == null)
yield return new ValidationResult("Username", string.Format("No user with the name '{0}' found on GitHub's servers."));
}
catch(Exception e) {
yield return new ValidationResult("Username", "There was an error contacting GitHub's API.");
}
}
}
好的,那太好了!验证器非常简单且有意义。现在是时候制作GitHubLinkCommandHandler:
public class GitHubLinkCommandHandler : ICommandHandler<GitHubLinkCommand>
{
private readonly IGitHubUserService _userService;
public GitHubLinkCommandHandler(IGitHubUserService userService)
{
this._userService = userService;
}
public void Handle(GitHubLinkCommand command)
{
// Get the user details from GitHub:
var user = this._userService.FindByUserName(command.GitHubUsername);
// implementation of this entity isn't really relevant, just assume it's a persistent entity to be stored in a backing database
var entity = new GitHubUserEntity
{
Name = user.Login,
AvatarUrl = user.AvatarUrl
// etc.
};
// store the entity:
this._someRepository.Save(entity);
}
}
同样,这看起来非常简洁明了。但是有一个明显的问题:对IGitHubUserService::FindByUserName 的重复调用,一个来自验证器,一个来自服务。
在糟糕的日子里,如果没有服务器端缓存,这样的调用可能需要 1-2 秒,这使得复制成本太高,无法使用这种架构模型。
有没有其他人在围绕外部 API 编写验证器/服务时遇到过这样的问题?除了在具体类中实现缓存之外,您是如何减少重复工作的?
【问题讨论】:
-
我通常会将
IGitHubUserService与我的应用程序分离。我会在那里放置一个缓存,即使它经常像一个代理(带有一些装饰)它也可能成为一个适配器(如果GitHub接口更改)甚至是一个Bridge(如果你想让它足够通用,也可以与CodePlex、Google Code...一起使用) -
@AdrianoRepetti 这几乎是我想出的唯一解决方案,但它并不是真正的“解决方案”。我可以看到它适用于一个大型项目,但对于像示例中的这样一个简单实现的功能来说,这似乎是一笔巨大的投资。
-
带有缓存的 IGitHubService 代理并不比 GitHubLinkCommandHandler 多多少行代码(假设您只需要公开 FindUserByName)但是是的,我同意如果有 something i> 自动化这个样板代码(就像我们对 AOP 所做的那样)。
-
据我所知,您有 2 个选项缓存选项,是一个烦人的样板。但也许更好的选择是不为验证器设置单独的类。或者更确切地说,验证器只处理琐碎的问题,发送空值等。但是对于任何需要在句柄方法中进行验证的实际时间......这很烦人,但在可读性方面可能是最好的选择代码和效率?
-
@DanielSlater 我同意。我目前这样做的方法是完全建立它;一个“输入验证器”(基本上只是验证用户请求的基本要素是否符合模型的示意图;可为空的字段、数字字段等),然后在我的服务运行操作时将业务层验证传递给我的服务.我遇到的主要问题是提出了一种将错误暴露给调用者(即控制器)的正确方法,这就是我调查我在原始问题中提到的 SO 文章的原因。越来越混乱了!
标签: c# asp.net-mvc service-layer