В настоящее время я создаю веб-приложение и пытаюсь его спроектировать с помощью хорошей MVC и сервис-ориентированной архитектуры.
Однако я ударил немного стены при подключении уровня представления (например, моих контроллеров) и внутренних служб, сохраняя при этом хорошую отчетность об ошибках/валидации обратно пользователю.
Я прочитал действительно хорошую публикацию SO здесь о том, как отделить логику проверки от уровня сервиса и большая часть все это имело смысл. Однако был один "недостаток", если можно так выразиться, в этой модели, которая на меня набросилась: как избежать дублирования усилий при поиске объектов, требуемых как валидатором, так и службой?
Я думаю, что было бы проще объяснить с помощью достаточно простого примера:
Скажем, у меня есть приложение, которое позволяет пользователям обмениваться фрагментами кода. Теперь я решил добавить новую функцию, которая позволяет пользователю присоединить свою учетную запись 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
и убедитесь, что результат не равен null:
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 servers."));
}
catch(Exception e) {
yield return new ValidationResult("Username", "There was an error contacting GitHub 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 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-интерфейсов и как вы уменьшили дублирование усилий за пределами реализации кеша в своем конкретном классе?