Attualmente sto creando un'applicazione Web e sto tentando di progettarla seguendo il buon MVC e l'architettura orientata ai servizi.Separazione di validatore e servizio con chiamate API esterne
Ho, tuttavia, colpito un po 'di un muro nel collegare il livello di presentazione (cioè i miei controller) e i servizi di back-end, pur mantenendo un buon rapporto di errore/convalida di nuovo all'utente.
Ho letto un SO post davvero valido here su come separare la logica di convalida dal livello di servizio e per la maggior parte tutto aveva senso. Tuttavia c'era un "difetto", se così si può chiamare, in questo modello che mi ha ridestato: come evitare di duplicare gli sforzi quando si cercano oggetti richiesti sia dal validatore che dal servizio?
penso che sarebbe stato più facile da spiegare con un ragionevolmente semplice esempio:
Diciamo Ho un'applicazione che consente agli utenti di condividere frammenti di codice in giro. Ora, ho deciso di aggiungere una nuova funzionalità che consente a un utente di collegare il proprio account GitHub al proprio account sul mio sito (ad esempio per creare un profilo). Ai fini di questo esempio assumerò semplicemente che tutti i miei utenti sono affidabili e tenterebbero solo di aggiungere i propri account GitHub, non quelli di altri :)
In seguito al suddetto articolo SO che ho impostato un servizio GitHub di base per il recupero delle informazioni utente GitHub.
interface IGitHubUserService {
GitHubUser FindByUserName(string username);
}
L'attuazione concreta di GitHubUserService effettua una chiamata costoso per https://api.github.com/users/{0}
per tirare le informazioni dell'utente. Anche in questo caso, seguendo il modello del articolo che ho implementato il seguente comando per collegare un account utente a un utente 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 }
};
mio validatore ha bisogno di verificare che il nome utente immesso dall'utente è un account GitHub valido. Questo è molto semplice: chiamare FindByUserName
sulla GitHubUserService
e assicurarsi che il risultato non è nullo:
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.");
}
}
}
Va bene che è grande! Il validatore è davvero semplice e sensato. Ora è il momento di fare il 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);
}
}
Ancora una volta, questo sembra veramente pulito e lineare. Tuttavia c'è un problema lampante: le chiamate duplicate a IGitHubUserService::FindByUserName
, una dal validatore e una dal servizio. In una giornata negativa, una chiamata del genere può richiedere 1-2 secondi senza il caching sul lato server, rendendo la duplicazione troppo costosa per utilizzare questo modello architettonico.
Qualcun altro ha riscontrato un tale problema durante la scrittura di validatori/servizi intorno a API esterne e in che modo è stata ridotta la duplicazione degli sforzi al di fuori dell'implementazione di una cache nella classe concreta?
Di solito disaccordo 'IGitHubUserService' dalla mia applicazione. Lì metterei una cache e anche se spesso si comporta come un ** Proxy ** (con qualche _decoration_) potrebbe anche diventare un ** Adapter ** (se l'interfaccia di GitHub cambia) o anche un ** Bridge ** (in caso vuoi renderlo abbastanza generico da essere usato anche con CodePlex, Google Code ...) –
@AdrianoRepetti Questa è praticamente l'unica soluzione che ho trovato, ma non è davvero una gran "soluzione". Potrei vederlo funzionare per un grande progetto, ma sembra un enorme investimento per una funzionalità così banalmente implementata come quella nell'esempio. –
Proxy con cache per IGitHubService non è molto più linee di codice di GitHubLinkCommandHandler (supponendo che sia necessario esporre solo FindUserByName) ma sì, sono d'accordo sarebbe bello se ci fosse _qualcosa_ per automatizzare questo codice boilerplate (come faremmo con AOP). –