将验证程序和服务与外部API调用分开

问题描述 投票:11回答:2

我目前正在构建一个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和面向服务的体系结构进行设计。但是,我在连接表示层时遇到了一些麻烦(...

c# asp.net-mvc service-layer
2个回答
1
投票

从我的角度来看,问题在于,LinkCommandHandler和LinkCommandValidator都不应该首先检索GitHub用户。如果您考虑“单一责任原则”,则验证器具有单个作业以验证用户的存在,而LinkCommandHanlder则具有单个作业以将实体加载到存储库中。他们两个都不应该从GitHub提取实体/用户。


0
投票

我的答案要比彼得·兰格短,但我认为这只是您的UserCommandValidator应该验证UserCommand是否有效,而不是User。

© www.soinside.com 2019 - 2024. All rights reserved.