Tôi hiện đang xây dựng một ứng dụng web và cố gắng thiết kế ứng dụng theo cấu trúc MVC và kiến trúc hướng dịch vụ tốt.Tách trình xác thực và dịch vụ với các cuộc gọi API bên ngoài
Tuy nhiên, tôi đã nhấn một chút tường khi kết nối lớp trình bày (tức là bộ điều khiển của tôi) và dịch vụ back-end trong khi vẫn duy trì báo cáo lỗi/xác thực tốt báo cáo lại cho người dùng.
Tôi đã đọc một bài đăng SO thực sự tốt here về cách tách logic Xác thực khỏi lớp dịch vụ và phần lớn tất cả đều có ý nghĩa. Tuy nhiên, có một "lỗ hổng", nếu bạn có thể gọi nó, trong mô hình này đã cười khúc khích với tôi: Làm thế nào để tránh trùng lặp nỗ lực khi tra cứu các đối tượng được yêu cầu bởi cả trình xác nhận và dịch vụ?
Tôi nghĩ rằng nó sẽ được dễ dàng hơn để giải thích với một ví dụ khá đơn giản:
Hãy nói rằng tôi có một ứng dụng cho phép người dùng chia sẻ các đoạn mã xung quanh. Bây giờ, tôi đã quyết định thêm một tính năng mới cho phép người dùng đính kèm tài khoản GitHub của họ vào tài khoản của họ trên trang web của tôi (tức là xây dựng một hồ sơ). Với mục đích của ví dụ này tôi sẽ chỉ đơn giản giả định rằng tất cả người dùng của tôi đều đáng tin cậy và chỉ cố gắng thêm tài khoản GitHub của riêng họ, chứ không phải của ai khác :)
Làm theo bài viết SO đã đề cập ở trên một dịch vụ GitHub cơ bản để truy xuất thông tin người dùng GitHub.
interface IGitHubUserService {
GitHubUser FindByUserName(string username);
}
Việc triển khai cụ thể GitHubUserService thực hiện cuộc gọi tốn kém đến https://api.github.com/users/{0}
để lấy thông tin người dùng. Một lần nữa, theo mô hình của bài viết tôi thực hiện lệnh sau để liên kết một tài khoản người dùng cho một người dùng 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 }
};
validator của tôi cần phải xác nhận rằng tên người dùng nhập vào bởi người sử dụng là một tài khoản GitHub hợp lệ. Điều này rất đơn giản: gọi FindByUserName
trên GitHubUserService
và chắc chắn rằng kết quả không phải là 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's servers."));
}
catch(Exception e) {
yield return new ValidationResult("Username", "There was an error contacting GitHub's API.");
}
}
}
Được rồi đó là tuyệt vời! Trình xác thực thực sự đơn giản và có ý nghĩa. Bây giờ là lúc để làm cho 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);
}
}
Một lần nữa, điều này có vẻ thực sự gọn gàng và đơn giản. Tuy nhiên có một vấn đề rõ ràng: Bản sao gọi tới IGitHubUserService::FindByUserName
, một từ trình xác thực và một từ dịch vụ. Vào một ngày tồi tệ, cuộc gọi như vậy có thể mất 1-2 giây mà không có bộ nhớ đệm phía máy chủ, làm cho quá trình sao chép quá tốn kém để sử dụng mô hình kiến trúc này.
Có ai khác gặp phải vấn đề như vậy khi viết trình xác thực/dịch vụ xung quanh các API bên ngoài và làm cách nào bạn giảm sự cố gắng sao chép bên ngoài triển khai bộ đệm trong lớp bê tông của bạn?
Tôi thường muốn tách 'IGitHubUserService' khỏi ứng dụng của mình. Ở đó tôi sẽ đặt một bộ nhớ cache và thậm chí nếu nó thường hoạt động như ** Proxy ** (với một số _decoration_) nó cũng có thể trở thành ** Adapter ** (nếu giao diện GitHub thay đổi) hoặc thậm chí là ** ** ** (trong trường hợp bạn muốn làm cho nó đủ phổ biến để được sử dụng cũng với CodePlex, Google Code ...) –
@AdrianoRepetti Đó là khá nhiều giải pháp duy nhất tôi đã đưa ra, nhưng nó không thực sự nhiều của một "giải pháp". Tôi có thể thấy nó hoạt động cho một dự án lớn, nhưng nó có vẻ như là một khoản đầu tư lớn cho một tính năng được thực hiện tầm thường như một ví dụ. –
Proxy với bộ nhớ cache cho IGitHubService không phải là nhiều dòng mã hơn GitHubLinkCommandHandler (giả sử bạn cần để lộ chỉ FindUserByName) nhưng có, tôi đồng ý nó sẽ là tốt đẹp nếu có _something_ để tự động hóa mã boilerplate này (như chúng ta sẽ làm với AOP). –