Thực thể khung thực thể - Một số dữ liệu từ dịch vụ web - Kiến trúc tốt nhất?


10

Chúng tôi hiện đang sử dụng Entity Framework làm ORM trên một vài ứng dụng web và cho đến hiện tại, nó phù hợp với chúng tôi cũng như tất cả dữ liệu của chúng tôi được lưu trữ trong một cơ sở dữ liệu. Chúng tôi đang sử dụng mẫu kho lưu trữ và có các dịch vụ (lớp miền) sử dụng các mẫu này và trả trực tiếp các thực thể EF cho bộ điều khiển ASP.NET MVC.

Tuy nhiên, một yêu cầu đã được đưa ra để sử dụng API của bên thứ 3 (thông qua dịch vụ web) sẽ cung cấp cho chúng tôi thêm thông tin liên quan đến người dùng trong cơ sở dữ liệu của chúng tôi. Trong cơ sở dữ liệu Người dùng cục bộ của chúng tôi, chúng tôi sẽ lưu trữ ID bên ngoài mà chúng tôi có thể cung cấp API để nhận thêm thông tin. Có khá nhiều thông tin có sẵn, nhưng vì mục đích đơn giản, một trong số đó liên quan đến công ty của người dùng (tên, người quản lý, phòng, chức danh công việc, địa điểm, v.v.). Thông tin này sẽ được sử dụng ở nhiều nơi trong các ứng dụng web của chúng tôi - trái ngược với việc được sử dụng ở một nơi duy nhất.

Vì vậy, câu hỏi của tôi là, nơi nào tốt nhất để cư trú và truy cập thông tin này? Vì nó được sử dụng ở nhiều nơi khác nhau, nên không thể tìm nạp nó trên cơ sở đặc biệt ở bất cứ nơi nào chúng tôi sử dụng trong ứng dụng web - vì vậy việc trả lại dữ liệu bổ sung này từ lớp miền là điều hợp lý.

Suy nghĩ ban đầu của tôi chỉ là tạo ra một lớp mô hình trình bao bọc chứa thực thể EF (EFUser) và một lớp 'ApiUser' mới chứa thông tin mới - và khi chúng tôi có được một người dùng, chúng tôi sẽ nhận được EFUser, và sau đó nhận thêm thông tin từ API và điền vào đối tượng ApiUser. Tuy nhiên, trong khi điều này sẽ tốt cho việc có được một người dùng, nó sẽ rơi khi có nhiều người dùng. Chúng tôi không thể nhấn API khi nhận danh sách người dùng.

Suy nghĩ thứ hai của tôi chỉ là thêm một phương thức đơn lẻ vào thực thể EFUser trả về ApiUser và chỉ cần điền nó khi cần. Điều này giải quyết vấn đề trên vì chúng tôi chỉ truy cập nó khi chúng tôi cần.

Hoặc ý nghĩ cuối cùng là giữ một bản sao dữ liệu cục bộ trong cơ sở dữ liệu của chúng tôi và đồng bộ hóa nó với API khi người dùng đăng nhập. Đây là công việc tối thiểu vì nó chỉ là một quá trình đồng bộ hóa - và chúng tôi không có chi phí đánh DB và API mỗi lần chúng tôi muốn lấy thông tin người dùng. Tuy nhiên, điều này có nghĩa là lưu trữ dữ liệu ở hai nơi và cũng có nghĩa là dữ liệu đã hết hạn đối với bất kỳ người dùng nào chưa đăng nhập trong một thời gian.

Có ai có lời khuyên hay đề nghị nào về cách tốt nhất để xử lý loại kịch bản này không?


it's not really sensible to fetch it on an ad-hoc basis-- Tại sao? Vì lý do hiệu suất?
Robert Harvey

Tôi không có nghĩa là đánh API trên cơ sở đặc biệt - Tôi chỉ có nghĩa là giữ cấu trúc thực thể hiện có, và sau đó gọi API ad-hoc trong ứng dụng web khi cần - Tôi chỉ có nghĩa là điều này sẽ không hợp lý vì nó sẽ cần phải được thực hiện ở nhiều nơi.
stevehayter

Câu trả lời:


3

Trường hợp của bạn

Trong trường hợp của bạn, cả ba lựa chọn đều khả thi. Tôi nghĩ rằng tùy chọn tốt nhất có lẽ là đồng bộ hóa nguồn dữ liệu của bạn ở một nơi nào đó mà ứng dụng asp.net thậm chí không nhận ra. Đó là, tránh hai lần tìm nạp ở nền trước mỗi lần, đồng bộ hóa API với db một cách im lặng). Vì vậy, nếu đó là một lựa chọn khả thi trong trường hợp của bạn - tôi nói hãy làm điều đó.

Một giải pháp mà bạn thực hiện tìm nạp 'một lần' như câu trả lời khác cho thấy dường như không khả thi vì nó không duy trì phản hồi ở bất cứ đâu và ASP.NET MVC sẽ chỉ thực hiện tìm nạp cho mọi yêu cầu lặp đi lặp lại.

Tôi sẽ tránh những người độc thân, tôi không nghĩ rằng đó là một ý tưởng tốt vì nhiều lý do thông thường.

Nếu tùy chọn thứ ba không khả thi - một tùy chọn là lười tải nó. Đó là, có một lớp mở rộng thực thể và yêu cầu API phải dựa trên cơ sở. Đó là một sự trừu tượng rất nguy hiểm mặc dù nó thậm chí còn kỳ diệu hơn và trạng thái không rõ ràng.

Tôi đoán nó thực sự sôi lên với một số câu hỏi:

  • API thường thay đổi dữ liệu cuộc gọi như thế nào? Không thường xuyên? Lựa chọn thứ ba. Thường xuyên? Đột nhiên lựa chọn thứ ba không quá khả thi. Tôi không chắc chắn tôi cũng chống lại các cuộc gọi đặc biệt như bạn.
  • Cuộc gọi API đắt thế nào? Bạn có trả tiền cho mỗi cuộc gọi? Họ có nhanh không? Miễn phí? Nếu chúng nhanh, thực hiện cuộc gọi mỗi lần có thể hoạt động, nếu chúng chậm, bạn cần phải có một số loại dự đoán và thực hiện cuộc gọi. Nếu họ tốn tiền - đó là một động lực lớn cho bộ nhớ đệm.
  • Thời gian đáp ứng phải nhanh như thế nào? Rõ ràng nhanh hơn là tốt hơn, nhưng hy sinh tốc độ cho đơn giản có thể đáng giá trong một số trường hợp, đặc biệt là nếu nó không trực tiếp đối mặt với người dùng.
  • Dữ liệu API khác với dữ liệu của bạn như thế nào? Có phải họ là hai thứ khác nhau về khái niệm? Nếu vậy, có thể tốt hơn nữa là chỉ để lộ API bên ngoài thay vì trả lại kết quả API trực tiếp với kết quả và để phía bên kia thực hiện cuộc gọi thứ hai và xử lý việc quản lý nó.

Một hoặc hai từ về sự tách biệt mối quan tâm

Cho phép tôi tranh luận với những gì Bobson đang nói về việc tách các mối quan tâm ở đây. Vào cuối ngày - đưa logic đó vào các thực thể như thế vi phạm sự phân tách các mối quan tâm cũng tệ như vậy.

Có một kho lưu trữ như vậy vi phạm sự phân tách các mối quan tâm cũng tệ như vậy bằng cách đặt logic trung tâm trình bày trong lớp logic kinh doanh. Kho lưu trữ của bạn bây giờ đột nhiên biết về những thứ liên quan đến bản trình bày như cách bạn hiển thị người dùng trong bộ điều khiển mvc asp.net của bạn.

Trong câu hỏi liên quan này, tôi đã hỏi về việc truy cập các thực thể trực tiếp từ bộ điều khiển. Cho phép tôi trích dẫn một trong những câu trả lời ở đó:

"Chào mừng bạn đến với BigPizza, cửa hàng Pizza tùy chỉnh, tôi có thể nhận đơn đặt hàng của bạn không?" "Chà, tôi muốn có một chiếc Pizza với ô liu, nhưng sốt cà chua ở trên và phô mai ở phía dưới và nướng trong lò trong 90 phút cho đến khi nó đen và cứng như một tảng đá granit phẳng." "OK, thưa ngài, pizza tùy chỉnh là nghề nghiệp của chúng tôi, chúng tôi sẽ làm cho nó."

Nhân viên thu ngân đi vào bếp. "Có một kẻ tâm thần ở quầy, anh ta muốn có một chiếc Pizza với ... đó là một tảng đá granit với ... chờ đã ... chúng ta cần phải có tên trước", anh nói với đầu bếp.

"Không!", Đầu bếp hét lên, "không phải lần nữa! Bạn biết chúng tôi đã thử điều đó rồi." Anh ta lấy một chồng giấy với 400 trang, "ở đây chúng tôi có đá granit từ năm 2005, nhưng ... nó không có ô liu, nhưng thay vào đó là paprica ... hoặc đây là cà chua hàng đầu ... nhưng khách hàng muốn nó nướng chỉ nửa phút. " "Có lẽ chúng ta nên gọi nó là TopTomatoGraniteRock Special?" "Nhưng nó không đưa phô mai ở phía dưới vào tài khoản ..." Nhân viên thu ngân: "Đó là điều đặc biệt được cho là thể hiện." "Nhưng có hòn đá Pizza hình thành như một kim tự tháp cũng sẽ rất đặc biệt", đầu bếp trả lời. "Hmmm ... thật khó ...", nhân viên thu ngân nói.

"PIZZA CỦA TÔI CÓ PHẢI LÀ Ở OVEN KHÔNG?", Đột nhiên nó hét qua cửa bếp. "Hãy dừng cuộc thảo luận này lại, chỉ cho tôi biết cách làm Pizza này, chúng tôi sẽ không có Pizza lần thứ hai", đầu bếp quyết định. "OK, đó là Pizza với ô liu, nhưng sốt cà chua ở trên và phô mai ở phía dưới và nướng trong lò trong 90 phút cho đến khi nó đen và cứng như một tảng đá granit phẳng."

(Đọc phần còn lại của câu trả lời, nó thực sự rất hay).

Thật ngây thơ khi bỏ qua thực tế là có một cơ sở dữ liệu - có một cơ sở dữ liệu và cho dù bạn có muốn trừu tượng đến mức nào đi chăng nữa thì nó cũng không đi đến đâu. Ứng dụng của bạn sẽ được biết về nguồn dữ liệu. Bạn sẽ không thể 'trao đổi nóng'. Các ORM rất hữu ích nhưng chúng bị rò rỉ do vấn đề phức tạp mà chúng giải quyết và vì nhiều lý do hiệu suất (ví dụ như Chọn n + 1).


Cảm ơn bạn đã phản hồi rất kỹ lưỡng @Benjamin. Ban đầu tôi bắt đầu tạo một nguyên mẫu bằng giải pháp của Bobson ở trên (ngay cả trước khi anh ấy đăng câu trả lời của mình), nhưng bạn nêu ra một số điểm quan trọng. Để trả lời câu hỏi của bạn ,: - Cuộc gọi API không quá tốn kém (chúng miễn phí và cũng nhanh). - Một số phần của dữ liệu sẽ thay đổi khá thường xuyên (một số thậm chí cứ sau vài giờ). - Tốc độ khá quan trọng, nhưng đối tượng của ứng dụng là việc giảm tải nhanh không phải là một yêu cầu tuyệt đối.
stevehayter 17/12/13

@stevehayter Trong trường hợp đó rất có thể tôi sẽ thực hiện các cuộc gọi đến API từ phía máy khách. Nó rẻ hơn và nhanh hơn, và nó mang lại cho bạn sự kiểm soát chi tiết hơn.
Benjamin Gruenbaum

1
Dựa trên những câu trả lời này, tôi ít nghiêng về việc giữ một bản sao dữ liệu cục bộ. Tôi thực sự nghiêng về việc phơi bày API riêng biệt và xử lý dữ liệu bổ sung theo cách đó. Tôi nghĩ rằng đây có thể là một sự thỏa hiệp tốt giữa sự đơn giản của giải pháp @ Bobson, nhưng cũng thêm một mức độ tách biệt mà tôi thấy thoải mái hơn một chút. Tôi sẽ xem xét chiến lược này trong nguyên mẫu của mình và báo cáo lại với những phát hiện của tôi (và trao tiền thưởng!).
stevehayter 17/12/13

@BenjaminGruenbaum - Tôi không chắc là tôi làm theo lập luận của bạn. Làm thế nào để đề xuất của tôi làm cho các kho lưu trữ nhận thức về trình bày? Chắc chắn, nó biết rằng một trường được hỗ trợ API đã được truy cập, nhưng điều đó không liên quan gì đến những gì chế độ xem đang làm với thông tin đó.
Bobson

1
Tôi đã chọn chuyển tất cả mọi thứ sang phía máy khách - nhưng như một phương thức mở rộng trên EFUser (tồn tại trong lớp trình bày, trong một cụm riêng biệt). Phương thức chỉ đơn giản trả về dữ liệu từ API và đặt một singleton để nó không bị lặp lại. Thời gian tồn tại của các đối tượng quá ngắn đến nỗi tôi không gặp vấn đề gì khi sử dụng singleton ở đây. Cách này có một số mức độ tách biệt, nhưng tôi vẫn có được sự thuận tiện khi làm việc với thực thể EFUser. Cảm ơn tất cả những người được hỏi vì sự giúp đỡ của họ. Chắc chắn là một cuộc thảo luận thú vị :).
stevehayter

2

Với sự phân tách các mối quan tâm thích hợp , không có gì ở trên mức Entity Framework / API thậm chí sẽ nhận ra dữ liệu đến từ đâu. Trừ khi lệnh gọi API đắt tiền (về thời gian hoặc xử lý), việc truy cập dữ liệu sử dụng nó phải minh bạch như truy cập dữ liệu từ cơ sở dữ liệu.

Sau đó, cách tốt nhất để thực hiện điều này là thêm các thuộc tính bổ sung vào EFUserđối tượng lười tải dữ liệu API khi cần. Một cái gì đó như thế này:

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

Bên ngoài, lần đầu tiên CompanyNamehoặc JobTitleđược sử dụng sẽ có một lệnh gọi API được thực hiện (và do đó có độ trễ nhỏ), nhưng tất cả các cuộc gọi tiếp theo cho đến khi đối tượng bị hủy sẽ nhanh chóng và dễ dàng như truy cập cơ sở dữ liệu.


Cảm ơn @Bobson ... đây thực sự là tuyến đường ban đầu tôi bắt đầu đi xuống (với một số phương thức mở rộng được thêm vào để tải hàng loạt chi tiết cho danh sách người dùng - ví dụ: hiển thị tên công ty cho danh sách người dùng). Nó có vẻ phù hợp với nhu cầu của tôi cho đến nay - nhưng Benjamin dưới đây nêu ra một số điểm quan trọng, vì vậy tôi sẽ tiếp tục đánh giá trong tuần này.
stevehayter 17/12/13

0

Một ý tưởng là sửa đổi ApiUser để không phải lúc nào cũng có thêm thông tin. Thay vào đó, bạn đặt một phương thức trên ApiUser để tìm nạp nó:

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

Bạn cũng có thể sửa đổi điều này một chút để sử dụng tải dữ liệu bổ sung một cách lười biếng, do đó bạn không phải trích xuất UserExtraData từ đối tượng ApiUser:

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

Theo cách này, khi bạn có một danh sách, dữ liệu bổ sung sẽ không được tìm nạp theo mặc định. Nhưng bạn vẫn có thể truy cập nó trong khi duyệt qua danh sách!


Không thực sự chắc chắn những gì bạn muốn nói ở đây - trong backend.load (), chúng tôi đã thực hiện tải - vì vậy chắc chắn điều đó sẽ tải dữ liệu API?
stevehayter

Ý tôi là bạn nên chờ thực hiện tải thêm cho đến khi được yêu cầu rõ ràng - lười tải dữ liệu api.
Alexander Torstling
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.