Các tập hợp DDD có thực sự là một ý tưởng tốt trong một Ứng dụng Web không?


40

Tôi đang đi sâu vào Thiết kế hướng tên miền và một số khái niệm tôi đang tìm thấy rất có ý nghĩa trên bề mặt, nhưng khi tôi nghĩ về chúng nhiều hơn, tôi phải tự hỏi liệu đó có thực sự là một ý tưởng hay không.

Ví dụ, khái niệm về Uẩn có ý nghĩa. Bạn tạo các miền sở hữu nhỏ để bạn không phải đối phó với toàn bộ mô hình miền.

Tuy nhiên, khi tôi nghĩ về điều này trong ngữ cảnh của một ứng dụng web, chúng tôi thường xuyên nhấn vào cơ sở dữ liệu để lấy lại các tập hợp dữ liệu nhỏ. Chẳng hạn, một trang chỉ có thể liệt kê số lượng đơn đặt hàng, với các liên kết để nhấp vào để mở đơn hàng và xem id đơn hàng của nó.

Nếu tôi Uẩn chánh kiến, tôi thường sẽ sử dụng mô hình kho để trả lại một OrderAggregate rằng sẽ chứa các thành viên GetAll, GetByID, Delete, và Save. Được, nghe tuyệt đấy. Nhưng...

Nếu tôi gọi GetAll để liệt kê tất cả các đơn đặt hàng của tôi, thì dường như mẫu này sẽ yêu cầu toàn bộ danh sách thông tin tổng hợp được trả về, hoàn thành đơn hàng, dòng đơn hàng, v.v ... Khi tôi chỉ cần một tập hợp nhỏ thông tin đó (chỉ cần thông tin tiêu đề).

Tui bỏ lỡ điều gì vậy? Hoặc có một số mức độ tối ưu hóa bạn sẽ sử dụng ở đây? Tôi không thể tưởng tượng rằng bất cứ ai sẽ ủng hộ việc trả lại toàn bộ thông tin khi bạn không cần.

Chắc chắn, người ta có thể tạo các phương thức trên kho lưu trữ của bạn như thế nào GetOrderHeaders, nhưng điều đó dường như đánh bại mục đích sử dụng một mẫu giống như kho lưu trữ ở nơi đầu tiên.

Bất cứ ai có thể làm rõ điều này cho tôi?

CHỈNH SỬA:

Sau nhiều nghiên cứu hơn, tôi nghĩ rằng việc ngắt kết nối ở đây là một mẫu Kho lưu trữ thuần túy khác với những gì hầu hết mọi người nghĩ về Kho lưu trữ.

Fowler định nghĩa một kho lưu trữ là một kho lưu trữ dữ liệu sử dụng ngữ nghĩa của bộ sưu tập và thường được lưu trong bộ nhớ. Điều này có nghĩa là tạo ra một biểu đồ toàn bộ đối tượng.

Evans thay đổi Kho lưu trữ để bao gồm Rễ tổng hợp, và do đó, kho lưu trữ bị cắt bỏ để chỉ hỗ trợ các đối tượng trong Tổng hợp.

Hầu hết mọi người dường như nghĩ về các kho lưu trữ như là Đối tượng truy cập dữ liệu được tôn vinh, nơi bạn chỉ cần tạo các phương thức để lấy bất kỳ dữ liệu nào bạn muốn. Đó dường như không phải là mục đích như được mô tả trong Mô hình kiến ​​trúc ứng dụng doanh nghiệp của Fowler.

Vẫn còn những người khác nghĩ về một kho lưu trữ như một sự trừu tượng đơn giản được sử dụng chủ yếu để làm cho việc kiểm tra và chế nhạo dễ dàng hơn, hoặc để tách rời sự kiên trì từ phần còn lại của hệ thống.

Tôi đoán câu trả lời là đây là một khái niệm phức tạp hơn nhiều so với lần đầu tiên tôi nghĩ.


4
"Tôi đoán câu trả lời là đây là một khái niệm phức tạp hơn nhiều so với lần đầu tiên tôi nghĩ." Điều này rất đúng.
quentin-starin

đối với tình huống của bạn, bạn có thể tạo proxy cho đối tượng gốc tổng hợp có thể truy xuất và lưu trữ dữ liệu chỉ khi nó được yêu cầu
Steven A. Lowe

Đề nghị của tôi là thực hiện tải lười biếng trong các hiệp hội tổng hợp gốc. Vì vậy, bạn có thể lấy danh sách các gốc mà không cần tải quá nhiều đối tượng.
Juliano

4
Gần 6 năm sau, vẫn là một câu hỏi hay. Sau khi đọc chương trong sách đỏ, tôi sẽ nói: đừng làm cho tổng hợp của bạn quá lớn. Thật hấp dẫn khi chọn một số khái niệm cấp cao nhất từ ​​tên miền của bạn và tuyên bố nó là Root để cai trị tất cả trừ DDD ủng hộ các tập hợp nhỏ hơn. Và giảm thiểu sự thiếu hiệu quả như những gì bạn mô tả ở trên.
Cpt. Senkfuss

Tập hợp của bạn phải càng nhỏ càng tốt trong khi tự nhiên và hiệu quả đối với miền (một thách thức!). Hơn nữa, nó là hoàn toàn tốt và mong muốn cho repos của bạn có phương pháp rất cụ thể .
Timo

Câu trả lời:


30

Không sử dụng Mô hình miền và tổng hợp của bạn để truy vấn.

Trong thực tế, những gì bạn đang hỏi là một câu hỏi đủ phổ biến rằng một tập hợp các nguyên tắc và mô hình đã được thiết lập để tránh điều đó. Nó được gọi là CQRS .


2
@Mystere Man: Không, nó để cung cấp dữ liệu tối thiểu cần thiết. Đó là một trong những mục đích lớn của mô hình đọc tách biệt. Nó cũng giúp giải quyết một số vấn đề tương tranh lên phía trước. CQRS có một số lợi ích khi áp dụng cho DDD.
quentin-starin

2
@Mystere: Tôi khá ngạc nhiên khi bạn nhớ điều đó nếu bạn đọc bài viết tôi liên kết đến. Xem phần có tiêu đề "Truy vấn (báo cáo)": "Khi một ứng dụng đang yêu cầu dữ liệu ... việc này sẽ được thực hiện trong một cuộc gọi đến lớp Truy vấn và đổi lại, nó sẽ nhận được một DTO duy nhất chứa tất cả dữ liệu cần thiết. .. Lý do cho điều này là dữ liệu thường được truy vấn nhiều lần hơn so với hành vi tên miền đang được thực thi, vì vậy bằng cách tối ưu hóa điều này, bạn sẽ nâng cao hiệu suất nhận thức của hệ thống. "
quentin-starin

4
Đó là lý do tại sao chúng tôi sẽ không sử dụng Kho lưu trữ để đọc dữ liệu trong hệ thống CQRS. Chúng tôi sẽ viết một lớp đơn giản để đóng gói một truy vấn (sử dụng bất kỳ công nghệ nào thuận tiện hoặc cần thiết, thường là ADO.Net hoặc Linq2Sql hoặc SubSonic phù hợp ở đây), trả lại (tất cả) dữ liệu cần thiết cho nhiệm vụ trong tay, để tránh kéo dữ liệu qua tất cả các lớp bình thường của Kho lưu trữ DDD. Chúng tôi sẽ chỉ sử dụng Kho lưu trữ để truy xuất Tổng hợp nếu chúng tôi muốn gửi lệnh đến miền.
quentin-starin

9
"Tôi không thể tưởng tượng rằng bất cứ ai sẽ ủng hộ việc trả lại toàn bộ thông tin khi bạn không cần nó." Tôi đang cố gắng nói rằng bạn hoàn toàn chính xác với tuyên bố này. Không lấy toàn bộ thông tin khi bạn không cần. Đây là cốt lõi của CQRS áp dụng cho DDD. Bạn không cần tổng hợp để truy vấn. Nhận dữ liệu thông qua một cơ chế khác, và sau đó thực hiện điều đó một cách nhất quán.
quentin-starin

2
@qes Thật vậy, giải pháp tốt nhất là không sử dụng DDD cho các truy vấn (đọc) :) Nhưng bạn vẫn sử dụng DDD trong phần Lệnh, tức là để lưu trữ hoặc cập nhật dữ liệu. Vì vậy, tôi có một câu hỏi cho bạn, bạn có luôn sử dụng Kho lưu trữ với Thực thể khi bạn cần cập nhật dữ liệu trong DB không? Hãy nói rằng bạn chỉ cần thay đổi một giá trị nhỏ trong cột (chuyển đổi một số loại), bạn vẫn tải toàn bộ Thực thể trong lớp Ứng dụng, thay đổi một giá trị (thuộc tính) và sau đó lưu toàn bộ Thực thể trở lại DB? Một chút quá mức, quá?
Andrew

8

Tôi đã vật lộn, và vẫn đang vật lộn, với cách sử dụng tốt nhất mẫu kho lưu trữ trong Thiết kế hướng tên miền. Sau khi sử dụng nó lần đầu tiên, tôi đã nghĩ ra các cách sau:

  1. Một kho lưu trữ nên đơn giản; nó chỉ chịu trách nhiệm lưu trữ các đối tượng miền và truy xuất chúng. Tất cả các logic khác phải ở trong các đối tượng khác, như các nhà máy và dịch vụ tên miền.

  2. Một kho lưu trữ hoạt động giống như một bộ sưu tập như thể nó là một bộ sưu tập các gốc tổng hợp.

  3. Kho lưu trữ không phải là DAO chung, mỗi kho lưu trữ có giao diện hẹp và duy nhất. Một kho lưu trữ thường có các phương thức tìm cụ thể cho phép bạn tìm kiếm bộ sưu tập theo tên miền (ví dụ: cung cấp cho tôi tất cả các đơn đặt hàng mở cho người dùng X). Bản thân kho lưu trữ có thể được thực hiện với sự trợ giúp của DAO chung.

  4. Lý tưởng nhất là các phương thức tìm sẽ chỉ trả về các gốc tổng hợp. Nếu điều đó không hiệu quả, nó cũng có thể trả về các đối tượng chỉ đọc giá trị hơn chứa chính xác những gì bạn cần (mặc dù đó là điểm cộng nếu các đối tượng giá trị này cũng có thể được biểu thị theo tên miền). Như một phương sách cuối cùng, kho lưu trữ cũng có thể được sử dụng để trả về các tập hợp con hoặc tập hợp các tập hợp con của một gốc tổng hợp.

  5. Các lựa chọn như thế này phụ thuộc vào các công nghệ được sử dụng, vì bạn cần tìm cách thể hiện hiệu quả nhất mô hình miền của mình với các công nghệ được sử dụng.


Nó chắc chắn là một chủ đề phức tạp cho chắc chắn. Thật khó để biến lý thuyết thành thực tiễn nhất là khi nó kết hợp hai lý thuyết riêng biệt và riêng biệt thành một thực tiễn duy nhất.
Sebastian Patten

6

Tôi không nghĩ rằng phương thức GetOrderHeaders của bạn đánh bại mục đích của kho lưu trữ.

DDD có liên quan (trong số những thứ khác) với việc đảm bảo rằng bạn có được những gì bạn cần bằng cách sử dụng gốc tổng hợp (ví dụ: bạn sẽ không có OrderDetailsRep repository), nhưng nó không giới hạn bạn theo cách bạn đang đề cập.

Nếu OrderHeader là một khái niệm Miền, thì bạn nên định nghĩa nó như vậy và có các phương thức lưu trữ thích hợp để truy xuất chúng. Chỉ cần đảm bảo rằng bạn đang đi qua gốc tổng hợp chính xác khi bạn làm.


Có lẽ tôi đang nhầm lẫn các khái niệm ở đây, nhưng sự hiểu biết của tôi về mẫu kho lưu trữ là tách rời sự bền bỉ khỏi miền, bằng cách sử dụng giao diện chuẩn để duy trì. Nếu bạn phải thêm các phương thức tùy chỉnh cho một tính năng cụ thể, dường như điều đó sẽ được ghép lại mọi thứ một lần nữa.
Erik Funkenbusch

1
chế kiên trì được tách rời khỏi miền, nhưng không phải là những gì đang được duy trì. Nếu bạn thấy mình nói những điều như "chúng tôi cần liệt kê các Tiêu đề đơn hàng tại đây", thì bạn cần lập mô hình OrderHeader trong Miền của bạn và cung cấp cách lấy chúng từ kho lưu trữ của bạn.
Eric King

Ngoài ra, không được treo lên "giao diện chuẩn cho sự bền bỉ". Không có thứ gọi là mẫu kho lưu trữ chung sẽ đủ cho tất cả các ứng dụng có thể. Mỗi ứng dụng sẽ có nhiều phương thức lưu trữ ngoài tiêu chuẩn "GetById", "Lưu", v.v. Những phương thức đó là điểm bắt đầu, không phải điểm kết thúc.
Eric King

4

Việc sử dụng DDD của tôi có thể không được coi là DDD "thuần túy" nhưng tôi đã điều chỉnh các chiến lược trong thế giới thực sau đây bằng cách sử dụng DDD chống lại kho lưu trữ dữ liệu DB.

  • Một gốc tổng hợp có một kho lưu trữ liên quan
  • Kho lưu trữ liên quan chỉ được sử dụng bởi gốc tổng hợp đó (nó không có sẵn công khai)
  • Một kho lưu trữ có thể chứa các cuộc gọi truy vấn (ví dụ: GetAllActiveOrder, GetOrderItemsForOrder)
  • Một dịch vụ hiển thị một tập hợp con công khai của kho lưu trữ và các hoạt động không chính xác khác (ví dụ: Chuyển tiền từ tài khoản ngân hàng này sang tài khoản ngân hàng khác, LoadById, Tìm kiếm / Tìm kiếm, TạoEntity, v.v.).
  • Tôi sử dụng Root -> Dịch vụ -> Kho lưu trữ. Dịch vụ DDD chỉ được sử dụng cho bất kỳ thứ gì mà Thực thể không thể tự trả lời (ví dụ LoadById, TransferMoneyFromAccountToAccount), nhưng trong thế giới thực, tôi có xu hướng dính vào các dịch vụ liên quan đến CRUD khác (Lưu, Xóa, Truy vấn) ngay cả khi root nên có thể "trả lời / thực hiện" chính chúng. Lưu ý rằng không có gì sai khi cấp quyền truy cập thực thể cho dịch vụ gốc tổng hợp khác! Tuy nhiên, hãy nhớ rằng bạn sẽ không bao gồm trong một dịch vụ (GetOrderItemsForOrder) mà sẽ bao gồm điều đó trong kho lưu trữ để Root tổng hợp có thể sử dụng dịch vụ đó. Lưu ý rằng một dịch vụ không nên để lộ bất kỳ truy vấn mở nào như kho lưu trữ có thể.
  • Tôi thường định nghĩa một Kho lưu trữ một cách trừu tượng trong mô hình miền (thông qua giao diện) và cung cấp một triển khai cụ thể riêng biệt. Tôi xác định đầy đủ một dịch vụ trong mô hình miền tiêm vào kho lưu trữ cụ thể để sử dụng.

** Bạn không phải mang lại toàn bộ tổng hợp. Tuy nhiên, nếu bạn muốn nhiều hơn, bạn phải hỏi root chứ không phải một số dịch vụ hoặc kho lưu trữ khác. Đây là tải lười biếng và có thể được thực hiện thủ công với người nghèo tải lười biếng (đưa kho lưu trữ / dịch vụ phù hợp vào thư mục gốc) hoặc sử dụng và ORM hỗ trợ việc này.

Trong ví dụ của bạn, tôi có thể sẽ cung cấp một cuộc gọi kho lưu trữ chỉ mang đến các tiêu đề đơn hàng nếu tôi muốn tải các chi tiết trên một cuộc gọi riêng biệt. Lưu ý rằng bằng cách có "Người đặt hàng", chúng tôi thực sự đang giới thiệu một khái niệm bổ sung vào miền.


3

Mô hình miền của bạn chứa logic kinh doanh của bạn ở dạng tinh khiết nhất. Tất cả các mối quan hệ và hoạt động hỗ trợ hoạt động kinh doanh. Cái bạn thiếu trong bản đồ khái niệm của bạn là ý tưởng về Lớp dịch vụ ứng dụng, lớp dịch vụ bao bọc xung quanh mô hình miền và cung cấp một cái nhìn đơn giản hóa về miền doanh nghiệp (một phép chiếu nếu bạn muốn) cho phép mô hình Miền thay đổi khi cần mà không ảnh hưởng trực tiếp đến các ứng dụng sử dụng lớp dịch vụ.

Đi xa hơn. Ý tưởng của tổng hợp là có một đối tượng, gốc tổng hợp, chịu trách nhiệm duy trì tính nhất quán của tổng hợp. Trong ví dụ của bạn, đơn hàng sẽ chịu trách nhiệm thao túng các dòng đơn hàng của nó.

Ví dụ của bạn, lớp dịch vụ sẽ hiển thị một hoạt động như GetOrderForCustomer sẽ chỉ trả về những gì cần thiết để xem danh sách tóm tắt các đơn đặt hàng (khi bạn gọi chúng là OrderHeaders).

Cuối cùng, mẫu Kho lưu trữ không CHỈ LÀ một bộ sưu tập, nhưng cũng cho phép truy vấn khai báo. Trong C #, bạn có thể sử dụng LINQ làm Đối tượng truy vấn hoặc hầu hết các O / RM khác cũng cung cấp đặc tả Đối tượng truy vấn.

Kho lưu trữ làm trung gian giữa các lớp ánh xạ dữ liệu và miền, hoạt động như một bộ sưu tập đối tượng miền trong bộ nhớ. Các đối tượng khách hàng xây dựng các đặc tả truy vấn khai báo và gửi chúng đến Kho lưu trữ để thỏa mãn. (từ trang Kho lưu trữ của Fowler )

Thấy rằng bạn có thể tạo các truy vấn đối với kho lưu trữ, việc cung cấp các phương thức tiện lợi xử lý các truy vấn phổ biến cũng có ý nghĩa. Tức là nếu bạn chỉ muốn các tiêu đề của đơn đặt hàng của mình, bạn có thể tạo một truy vấn chỉ trả về tiêu đề và hiển thị nó từ một phương thức tiện lợi trong kho lưu trữ của bạn.

Hy vọng điều này sẽ giúp làm rõ mọi thứ.


0

Tôi biết đây là một câu hỏi cũ nhưng tôi dường như đã đi đến một câu trả lời khác.

Khi tôi tạo một Kho lưu trữ, nó thường bao bọc một số truy vấn được lưu trữ .

Fowler định nghĩa một kho lưu trữ là một kho lưu trữ dữ liệu sử dụng ngữ nghĩa của bộ sưu tập và thường được lưu trong bộ nhớ. Điều này có nghĩa là tạo ra một biểu đồ toàn bộ đối tượng.

Giữ những kho lưu trữ trong ram máy chủ của bạn. Họ không chỉ chuyển qua các đối tượng đến cơ sở dữ liệu!

Nếu tôi đang ở trong một ứng dụng web có đơn đặt hàng liệt kê trang, bạn có thể nhấp vào để xem chi tiết, rất có thể tôi sẽ muốn trang danh sách đơn hàng của mình có thông tin chi tiết về các đơn hàng (ID, Tên, Số tiền, Ngày) để giúp người dùng quyết định xem họ muốn xem cái nào.

Tại thời điểm này, bạn có hai lựa chọn.

  1. Bạn có thể truy vấn cơ sở dữ liệu và lấy lại chính xác những gì bạn cần để tạo danh sách, sau đó truy vấn lại để lấy các chi tiết riêng lẻ bạn cần xem trên trang chi tiết.

  2. Bạn có thể thực hiện 1 truy vấn lấy lại tất cả thông tin và lưu trữ nó. Trên trang tiếp theo yêu cầu bạn đọc từ ram máy chủ thay vì cơ sở dữ liệu. Nếu người dùng quay lại hoặc chọn trang tiếp theo, bạn vẫn không thực hiện các chuyến đi đến cơ sở dữ liệu.

Trong thực tế, cách bạn thực hiện nó chỉ là như vậy, và chi tiết thực hiện. Nếu người dùng lớn nhất của tôi có 10 đơn hàng, tôi có thể muốn đi với tùy chọn 2. Nếu tôi đang nói 10.000 đơn hàng thì tùy chọn 1 là cần thiết. Trong cả hai trường hợp trên và trong nhiều trường hợp khác, tôi muốn kho lưu trữ ẩn chi tiết triển khai đó.

Đi về phía trước nếu tôi nhận được một vé để cho người dùng biết họ đã chi bao nhiêu cho các đơn hàng ( dữ liệu tổng hợp ) trong tháng trước trên trang danh sách đơn hàng, tôi có nên viết logic để tính toán điều đó trong SQL và thực hiện một chuyến đi khứ hồi khác DB hoặc bạn muốn tính toán nó bằng cách sử dụng dữ liệu đã có trong ram máy chủ?

Theo kinh nghiệm tổng hợp tên miền của tôi cung cấp lợi ích rất lớn.

  • Họ là một phần tái sử dụng mã lớn mà thực sự hoạt động.
  • Họ đơn giản hóa mã bằng cách giữ logic kinh doanh của bạn ngay trong lớp lõi thay vì phải đi sâu qua lớp cơ sở hạ tầng để có được máy chủ sql thực hiện.
  • Họ cũng có thể tăng tốc đáng kể thời gian phản hồi của bạn bằng cách giảm số lượng truy vấn bạn cần thực hiện vì bạn có thể dễ dàng lưu trữ chúng.
  • SQL mà tôi đang viết thường dễ bảo trì hơn vì tôi thường chỉ yêu cầu mọi thứ và tính toán phía máy chủ.
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.