Làm thế nào để tránh giao diện trò chuyện


10

Bối cảnh: Tôi đang thiết kế một ứng dụng máy chủ và tạo các dll riêng cho các hệ thống con khác nhau. Để đơn giản hóa mọi thứ, giả sử tôi có hai hệ thống con: 1) Users2)Projects

Giao diện công cộng của người dùng có một phương pháp như:

IEnumerable<User> GetUser(int id);

Và giao diện công cộng của dự án có một phương pháp như:

IEnumerable<User> GetProjectUsers(int projectId);

Vì vậy, ví dụ, khi chúng ta cần hiển thị người dùng cho một dự án nhất định, chúng ta có thể gọi GetProjectUsersvà điều đó sẽ trả lại các đối tượng có đủ thông tin để hiển thị trong một datagrid hoặc tương tự.

Vấn đề: Lý tưởng nhất là Projectshệ thống con cũng không nên lưu trữ thông tin người dùng và nó chỉ nên lưu Id của người dùng tham gia dự án. Để phục vụ công tác GetProjectUsers, nó cần phải gọi GetUsercủa Usershệ thống cho mỗi id người dùng được lưu trong cơ sở dữ liệu riêng của mình. Tuy nhiên, điều này đòi hỏi rất nhiều GetUsercuộc gọi riêng biệt , gây ra rất nhiều truy vấn sql riêng bên trong Userhệ thống con. Tôi chưa thực sự thử nghiệm điều này nhưng việc có thiết kế trò chuyện này sẽ ảnh hưởng đến khả năng mở rộng của hệ thống.

Nếu tôi đặt sự tách biệt của các hệ thống con sang một bên, tôi có thể lưu trữ tất cả thông tin trong một lược đồ duy nhất có thể truy cập được bởi cả hai hệ thống và Projectscó thể chỉ cần thực hiện JOINđể có được tất cả người dùng dự án trong một truy vấn duy nhất. Projectscũng cần biết cách tạo Userđối tượng từ kết quả truy vấn. Nhưng điều này phá vỡ sự tách biệt có nhiều lợi thế.

Câu hỏi: Bất cứ ai cũng có thể đề xuất một cách để giữ sự tách biệt trong khi tránh tất cả các GetUsercuộc gọi cá nhân này trong thời gian GetProjectUsers?


Ví dụ: một ý tưởng tôi có là Người dùng cung cấp cho các hệ thống bên ngoài khả năng "gắn thẻ" người dùng bằng cặp giá trị nhãn và yêu cầu người dùng có một giá trị nhất định, ví dụ:

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

Sau đó, hệ thống Dự án có thể gắn thẻ cho từng người dùng khi họ được thêm vào dự án:

AddUserTag(userId,"project id", myProjectId.ToString());

và trong GetProjectUsers, nó có thể yêu cầu tất cả người dùng dự án trong một cuộc gọi:

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

Phần tôi không chắc chắn về điều này là: có, Người dùng không biết về các dự án nhưng thực sự thông tin về tư cách thành viên dự án được lưu trữ trong hệ thống Người dùng, không phải Dự án. Tôi chỉ không cảm thấy tự nhiên nên tôi đang cố xác định xem có bất lợi lớn nào ở đây không.

Câu trả lời:


10

Những gì còn thiếu trong hệ thống của bạn là bộ nhớ cache.

Bạn nói:

Tuy nhiên, điều này đòi hỏi rất nhiều GetUsercuộc gọi riêng biệt , gây ra rất nhiều truy vấn sql riêng bên trong Userhệ thống con.

Số lượng cuộc gọi đến một phương thức không nhất thiết phải giống với số lượng truy vấn SQL. Bạn nhận được thông tin về người dùng một lần, tại sao bạn lại truy vấn cho cùng một thông tin nếu nó không thay đổi? Rất có thể, bạn thậm chí có thể lưu trữ tất cả người dùng trong bộ nhớ, điều này sẽ dẫn đến các truy vấn SQL bằng không (trừ khi người dùng thay đổi).

Mặt khác, bằng cách thực hiện Projectstruy vấn hệ thống con cả dự án và người dùng INNER JOIN, bạn đưa ra một vấn đề khác: bạn đang truy vấn cùng một thông tin ở hai vị trí khác nhau trong mã của bạn, khiến việc vô hiệu hóa bộ đệm trở nên vô cùng khó khăn. Hậu quả là:

  • Hoặc bạn sẽ không giới thiệu bộ đệm bất cứ lúc nào,

  • Hoặc bạn sẽ dành hàng tuần hoặc hàng tháng để nghiên cứu những gì sẽ bị vô hiệu khi một phần thông tin thay đổi,

  • Hoặc bạn sẽ thêm tính năng vô hiệu hóa bộ đệm trong các vị trí đơn giản, quên các vị trí khác và dẫn đến khó tìm lỗi.


Đọc lại câu hỏi của bạn, tôi nhận thấy một từ khóa tôi đã bỏ lỡ lần đầu tiên: khả năng mở rộng . Theo nguyên tắc thông thường, bạn có thể theo mô hình tiếp theo:

  1. Hãy tự hỏi liệu hệ thống có chậm không (tức là nó vi phạm yêu cầu hiệu năng phi chức năng, hoặc đơn giản là một cơn ác mộng khi sử dụng).

    Nếu hệ thống không chậm, đừng bận tâm về hiệu suất. Phiền về mã sạch, dễ đọc, bảo trì, kiểm tra, bảo hiểm chi nhánh, thiết kế sạch, tài liệu chi tiết và dễ hiểu, nhận xét mã tốt.

  2. Nếu có, tìm kiếm nút cổ chai. Bạn làm điều đó không phải bằng cách đoán, mà bằng cách định hình . Bằng cách định hình, bạn xác định vị trí chính xác của nút cổ chai (với điều kiện là khi bạn đoán , gần như mỗi lần bạn sẽ hiểu sai) và bây giờ có thể tập trung vào phần đó của mã.

  3. Một khi nút cổ chai được tìm thấy, tìm kiếm giải pháp. Bạn làm điều đó bằng cách đoán, đo điểm chuẩn, định hình, viết thay thế, hiểu tối ưu hóa trình biên dịch, hiểu tối ưu hóa tùy thuộc vào bạn, đặt câu hỏi về Stack Overflow và chuyển sang các ngôn ngữ cấp thấp (bao gồm Trình biên dịch, khi cần thiết).

Vấn đề thực tế với Projectshệ thống con yêu cầu thông tin cho Usershệ thống con là gì?

Vấn đề khả năng mở rộng trong tương lai cuối cùng? Đây không phải là một vấn đề. Khả năng mở rộng có thể trở thành một cơn ác mộng nếu bạn bắt đầu hợp nhất mọi thứ vào một giải pháp nguyên khối hoặc truy vấn cùng một dữ liệu từ nhiều vị trí (như được giải thích dưới đây, vì khó giới thiệu bộ đệm).

Nếu đã có một vấn đề hiệu suất đáng chú ý, thì, bước 2, tìm kiếm nút cổ chai.

Nếu có vẻ như, thực sự, nút cổ chai tồn tại và là do thực tế là Projectsyêu cầu người dùng thông qua Usershệ thống con (và nằm ở cấp truy vấn cơ sở dữ liệu), chỉ sau đó bạn mới nên tìm kiếm một giải pháp thay thế.

Cách thay thế phổ biến nhất là triển khai bộ nhớ đệm, giảm đáng kể số lượng truy vấn. Nếu bạn đang ở trong tình huống mà bộ nhớ đệm không giúp ích, thì việc lược tả thêm có thể cho bạn thấy rằng bạn cần giảm số lượng truy vấn hoặc thêm (hoặc xóa) chỉ mục cơ sở dữ liệu hoặc ném thêm phần cứng hoặc thiết kế lại toàn bộ hệ thống .


Trừ khi tôi hiểu nhầm bạn, bạn sẽ nói "giữ các cuộc gọi GetUser riêng lẻ, nhưng sử dụng bộ nhớ đệm để tránh vòng tròn db".
Eren Ersönmez

@ ErenErsönmez : GetUser, thay vì truy vấn cơ sở dữ liệu, sẽ tìm trong bộ đệm. Điều này có nghĩa là thực sự không quan trọng số lần bạn gọi GetUser, vì nó sẽ tải dữ liệu từ bộ nhớ thay vì cơ sở dữ liệu (trừ khi bộ đệm đã bị vô hiệu).
Arseni Mourzenko

Đây là một gợi ý hay, vì tôi đã không làm tốt công việc nêu bật vấn đề chính, đó là "để thoát khỏi tình trạng hỗn độn mà không hợp nhất các hệ thống thành một hệ thống". Ví dụ về Người dùng và Dự án của tôi đương nhiên sẽ khiến bạn tin rằng có một số lượng người dùng tương đối ít và hiếm khi thay đổi. Có lẽ một ví dụ tốt hơn sẽ là Tài liệu và Dự án. Hãy tưởng tượng bạn có một vài triệu tài liệu, hàng ngàn tài liệu được thêm vào mỗi ngày và hệ thống Dự án sử dụng hệ thống Tài liệu để lưu trữ tài liệu của nó. Bạn vẫn sẽ đề nghị lưu trữ sau đó? Có lẽ là không, phải không?
Eren Ersönmez

@ ErenErsönmez: bạn càng có nhiều dữ liệu, bộ nhớ đệm càng quan trọng xuất hiện. Theo nguyên tắc thông thường, hãy so sánh số lần đọc với số lần ghi. Nếu "hàng ngàn" tài liệu được thêm vào mỗi ngày và có hàng triệu selecttruy vấn mỗi ngày, bạn nên sử dụng bộ nhớ đệm tốt hơn. Mặt khác, nếu bạn thêm hàng tỷ thực thể vào cơ sở dữ liệu nhưng chỉ nhận được vài nghìn selectgiây với các s rất chọn lọc where, bộ nhớ đệm có thể không hữu ích.
Arseni Mourzenko

bạn có thể đúng - tôi có thể đang cố gắng khắc phục sự cố mà tôi chưa có. Tôi có thể sẽ thực hiện như hiện tại và cố gắng cải thiện sau này nếu cần. Nếu bộ nhớ đệm không phù hợp bởi vì, ví dụ, các thực thể có khả năng chỉ được đọc 1-2 lần sau khi được thêm vào, bạn có nghĩ rằng giải pháp tôi có thể tôi đã thêm vào câu hỏi có thể hoạt động không? Bạn có thấy một vấn đề lớn với điều đó?
Eren Ersönmez
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.