我目前正在构建一个Web应用程序,并尝试遵循良好的MVC和面向服务的体系结构进行设计。
但是,在连接表示层(即我的控制器)和后端服务时,我遇到了一点麻烦,同时仍然保持良好的错误/验证报告给用户。
我读了一篇非常不错的SO帖子here,内容涉及如何将验证逻辑与服务层分开,并且在大多数情况下,这都是有意义的。但是,在这个模型中,有一个“缺陷”(如果可以这样称呼),它让我ni不安:查找验证器和服务所需的对象时,如何避免重复工作?
我认为用一个相当简单的例子来解释会更容易:
假设我有一个允许用户共享代码段的应用程序。现在,我决定添加一项新功能,该功能允许用户将其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帐户。这非常简单:在FindByUserName
上调用GitHubUserService
并确保结果不为空:
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编写验证器/服务时,还有其他人遇到过这样的问题吗?您如何减少在具体类中实现缓存之外的重复工作?
我目前正在构建一个Web应用程序,并尝试遵循良好的MVC和面向服务的体系结构进行设计。但是,我在连接表示层时遇到了一些麻烦(...
从我的角度来看,问题在于,LinkCommandHandler和LinkCommandValidator都不应该首先检索GitHub用户。如果您考虑“单一责任原则”,则验证器具有单个作业以验证用户的存在,而LinkCommandHanlder则具有单个作业以将实体加载到存储库中。他们两个都不应该从GitHub提取实体/用户。
我的答案要比彼得·兰格短,但我认为这只是您的UserCommandValidator应该验证UserCommand是否有效,而不是User。