DDD - quy tắc mà các Thực thể không thể truy cập trực tiếp vào Kho


185

Trong Domain Driven Design, có vẻ là rất nhiều các thỏa thuận rằng các thực thể không nên truy cập trực tiếp Repositories.

Điều này đến từ cuốn sách Thiết kế hướng của Eric Evans , hay nó đến từ nơi khác?

Đâu là một số giải thích tốt cho lý do đằng sau nó?

chỉnh sửa: Để làm rõ: Tôi không nói về thực tiễn OO cổ điển về việc tách quyền truy cập dữ liệu thành một lớp riêng biệt khỏi logic nghiệp vụ - Tôi đang nói về sự sắp xếp cụ thể theo đó trong DDD, Các thực thể không được phép nói chuyện với dữ liệu lớp truy cập hoàn toàn (nghĩa là chúng không được phép giữ tham chiếu đến các đối tượng Kho lưu trữ)

cập nhật: Tôi đã đưa tiền thưởng cho BacceSR vì câu trả lời của anh ấy có vẻ gần nhất, nhưng tôi vẫn rất đẹp trong bóng tối về điều này. Nếu đó là một nguyên tắc quan trọng như vậy, chắc chắn sẽ có một số bài viết hay về nó trực tuyến ở đâu đó, chắc chắn?

cập nhật: Tháng 3 năm 2013, các câu hỏi được nêu lên có nghĩa là có rất nhiều mối quan tâm trong vấn đề này, và mặc dù có rất nhiều câu trả lời, tôi vẫn nghĩ có nhiều chỗ hơn nếu mọi người có ý tưởng về điều này.


Hãy xem stackoverflow câu hỏi của tôi.com/q/8269784/235715 , nó cho thấy một tình huống khi khó nắm bắt logic, mà không có Thực thể có quyền truy cập vào kho lưu trữ. Mặc dù tôi nghĩ rằng các thực thể không nên có quyền truy cập vào kho lưu trữ và có một giải pháp cho tình huống của tôi khi mã có thể được viết lại mà không cần tham chiếu kho lưu trữ, nhưng hiện tại tôi không thể nghĩ ra bất kỳ.
Alex Burtsev

Không biết nó đến từ đâu. Suy nghĩ của tôi: Tôi nghĩ rằng sự hiểu lầm này đến từ mọi người như thế nào không hiểu DDD là gì. Cách tiếp cận này không phải để thực hiện phần mềm mà là để thiết kế nó (miền .. thiết kế). Trước đây, chúng tôi đã có kiến ​​trúc sư và người thực hiện, nhưng bây giờ chỉ có các nhà phát triển phần mềm. DDD có nghĩa là cho các kiến ​​trúc sư. Và khi một kiến ​​trúc sư đang thiết kế phần mềm, anh ta cần một số công cụ hoặc mẫu để thể hiện bộ nhớ hoặc cơ sở dữ liệu cho các nhà phát triển sẽ thực hiện thiết kế đã chuẩn bị. Nhưng bản thân thiết kế (từ góc độ kinh doanh) không có hoặc không cần một kho lưu trữ.
berhalak

Câu trả lời:


47

Có một chút nhầm lẫn ở đây. Kho lưu trữ truy cập rễ tổng hợp. Rễ tổng hợp là các thực thể. Lý do cho điều này là sự tách biệt các mối quan tâm và phân lớp tốt. Điều này không có ý nghĩa đối với các dự án nhỏ, nhưng nếu bạn thuộc một nhóm lớn mà bạn muốn nói, "Bạn truy cập một sản phẩm thông qua Kho lưu trữ sản phẩm. Sản phẩm là một gốc tổng hợp cho một tập hợp các thực thể, bao gồm cả đối tượng ProductCatalog. Nếu bạn muốn cập nhật ProductCatalog, bạn phải thông qua ProductRep repository. "

Bằng cách này, bạn có sự tách biệt rất, rất rõ ràng về logic kinh doanh và nơi mọi thứ được cập nhật. Bạn không có một đứa trẻ nào đó tự mình viết và viết toàn bộ chương trình này để thực hiện tất cả những điều phức tạp này vào danh mục sản phẩm và khi tích hợp nó vào dự án ngược dòng, bạn đang ngồi đó nhìn nó và nhận ra nó tất cả phải bị bỏ rơi. Nó cũng có nghĩa là khi mọi người tham gia nhóm, thêm các tính năng mới, họ biết nơi để đi và cách cấu trúc chương trình.

Nhưng chờ đã! Kho lưu trữ cũng đề cập đến lớp lưu giữ lâu bền, như trong Mẫu lưu trữ. Trong một thế giới tốt hơn, Kho lưu trữ của Eric Evans và Mẫu Kho lưu trữ sẽ có các tên riêng biệt, vì chúng có xu hướng chồng chéo lên nhau khá nhiều. Để có được mẫu kho lưu trữ, bạn phải đối chiếu với các cách khác để truy cập dữ liệu, với một bus dịch vụ hoặc hệ thống mô hình sự kiện. Thông thường khi bạn đạt đến cấp độ này, định nghĩa Kho lưu trữ của Eric Evans đi bên cạnh và bạn bắt đầu nói về một bối cảnh bị ràng buộc. Mỗi bối cảnh giới hạn về cơ bản là ứng dụng riêng của nó. Bạn có thể có một hệ thống phê duyệt tinh vi để đưa mọi thứ vào danh mục sản phẩm. Trong thiết kế ban đầu của bạn, sản phẩm là phần trung tâm nhưng trong bối cảnh giới hạn này, danh mục sản phẩm là. Bạn vẫn có thể truy cập thông tin sản phẩm và cập nhật sản phẩm qua xe buýt dịch vụ,

Quay lại câu hỏi ban đầu của bạn. Nếu bạn đang truy cập vào một kho lưu trữ từ bên trong một thực thể, điều đó có nghĩa là thực thể đó không thực sự là một thực thể kinh doanh mà có lẽ là thứ gì đó nên tồn tại trong một lớp dịch vụ. Điều này là do các thực thể là đối tượng kinh doanh và nên quan tâm đến việc giống như DSL (ngôn ngữ cụ thể của miền) càng nhiều càng tốt. Chỉ có thông tin kinh doanh trong lớp này. Nếu bạn đang khắc phục sự cố về hiệu suất, bạn sẽ biết tìm ở nơi khác vì chỉ có thông tin doanh nghiệp mới có ở đây. Nếu đột nhiên, bạn gặp sự cố ứng dụng ở đây, bạn đang rất khó khăn để mở rộng và duy trì một ứng dụng, đó thực sự là trái tim của DDD: tạo ra phần mềm có thể bảo trì.

Trả lời Nhận xét 1 : Đúng, câu hỏi hay. Vì vậy, không phải tất cả xác nhận xảy ra trong lớp miền. Sharp có một thuộc tính "DomainSignature" thực hiện những gì bạn muốn. Đó là nhận thức bền bỉ, nhưng là một thuộc tính giữ cho lớp miền sạch sẽ. Nó đảm bảo rằng bạn không có một thực thể trùng lặp với, trong ví dụ của bạn cùng tên.

Nhưng hãy nói về các quy tắc xác nhận phức tạp hơn. Giả sử bạn là Amazon.com. Bạn đã bao giờ đặt hàng một cái gì đó với một thẻ tín dụng hết hạn? Tôi có, nơi tôi chưa cập nhật thẻ và mua một cái gì đó. Nó chấp nhận đơn đặt hàng và giao diện người dùng thông báo cho tôi rằng mọi thứ đều đào hoa. Khoảng 15 phút sau, tôi sẽ nhận được một email nói rằng có vấn đề với đơn đặt hàng của tôi, thẻ tín dụng của tôi không hợp lệ. Điều xảy ra ở đây là, lý tưởng nhất, có một số xác nhận regex trong lớp miền. Đây có phải là số thẻ tín dụng chính xác? Nếu có, hãy kiên trì đặt hàng. Tuy nhiên, có xác nhận bổ sung ở lớp tác vụ ứng dụng, trong đó một dịch vụ bên ngoài được truy vấn để xem liệu thanh toán có thể được thực hiện trên thẻ tín dụng hay không. Nếu không, đừng thực sự giao hàng, tạm dừng đơn hàng và chờ khách hàng.

Đừng ngại tạo các đối tượng xác nhận ở lớp dịch vụ có thể truy cập kho lưu trữ. Chỉ cần giữ nó ra khỏi lớp miền.


15
Cảm ơn. Nhưng tôi nên cố gắng để có được càng nhiều logic kinh doanh càng tốt vào các thực thể (và các nhà máy và thông số kỹ thuật liên quan của họ, v.v.), phải không? Nhưng nếu không ai trong số họ được phép lấy dữ liệu qua Kho lưu trữ, làm thế nào tôi có thể viết bất kỳ logic kinh doanh (khá phức tạp) nào? Ví dụ: Người dùng phòng chat không được phép thay đổi tên của họ thành tên đã được người khác sử dụng. Tôi muốn quy tắc đó được xây dựng bởi thực thể ChatUser, nhưng nó không dễ thực hiện nếu bạn không thể truy cập kho lưu trữ từ đó. Vậy tôi phải làm sao?
codeulike

Phản hồi của tôi lớn hơn hộp bình luận sẽ cho phép, xem phần chỉnh sửa.
kertosis

6
Bạn nên biết cách bảo vệ bản thân khỏi bị tổn hại. Điều này bao gồm đảm bảo nó không thể vào trạng thái không hợp lệ. Những gì bạn đang mô tả với người dùng phòng Trò chuyện là logic nghiệp vụ nằm trong BỔ SUNG cho logic mà thực thể phải giữ cho nó hợp lệ. Logic kinh doanh giống như những gì bạn muốn thực sự thuộc về dịch vụ Chatroom, không phải thực thể ChatUser.
Alec

9
Cảm ơn Alec. Đó là một cách rõ ràng để thể hiện nó. Nhưng đối với tôi, dường như quy tắc vàng tập trung vào miền của Evans về 'tất cả logic kinh doanh nên đi trong lớp miền' là mâu thuẫn với quy tắc 'các thực thể không nên truy cập vào kho lưu trữ'. Tôi có thể sống với điều đó nếu tôi hiểu tại sao lại như vậy, nhưng tôi không thể tìm thấy bất kỳ lời giải thích trực tuyến nào về lý do tại sao các thực thể không nên truy cập vào kho lưu trữ. Evans dường như không đề cập đến nó một cách rõ ràng. Nó từ đâu đến? Nếu bạn có thể đăng câu trả lời chỉ vào một số tài liệu hay, bạn có thể tự bỏ túi 50pt tiền thưởng :)
codeulike

4
"anh ấy không có ý nghĩa gì với việc nhỏ" Đây là một sai lầm lớn mà các đội làm ... đó là một dự án nhỏ, vì vậy tôi có thể làm điều này và ... ngừng suy nghĩ như vậy. Nhiều dự án nhỏ mà chúng tôi làm việc cùng, cuối cùng trở nên lớn, do yêu cầu kinh doanh. Nếu bạn làm một cái gì đó khô héo dù nhỏ hay lớn, hãy làm đúng.
MeTitus

35

Lúc đầu, tôi đã thuyết phục cho phép một số thực thể của tôi truy cập vào kho lưu trữ (tức là lười tải mà không có ORM). Sau đó tôi đã đi đến kết luận rằng tôi không nên và tôi có thể tìm ra những cách khác:

  1. Chúng ta nên biết ý định của mình trong một yêu cầu và những gì chúng ta muốn từ miền, do đó chúng ta có thể thực hiện các cuộc gọi kho lưu trữ trước khi xây dựng hoặc gọi hành vi Tổng hợp. Điều này cũng giúp tránh vấn đề về trạng thái trong bộ nhớ không nhất quán và nhu cầu tải lười biếng (xem bài viết này ). Mùi là bạn không thể tạo một thể hiện trong bộ nhớ của thực thể của mình nữa mà không lo lắng về việc truy cập dữ liệu.
  2. CQS (Tách truy vấn lệnh) có thể giúp giảm nhu cầu muốn gọi kho lưu trữ cho những thứ trong các thực thể của chúng tôi.
  3. Chúng ta có thể sử dụng một đặc tả để đóng gói và truyền đạt nhu cầu logic miền và chuyển nó đến kho lưu trữ (một dịch vụ có thể sắp xếp những thứ này cho chúng ta). Các đặc điểm kỹ thuật có thể đến từ các thực thể chịu trách nhiệm duy trì bất biến đó. Kho lưu trữ sẽ diễn giải các phần của đặc tả thành triển khai truy vấn của riêng nó và áp dụng các quy tắc từ đặc tả trên kết quả truy vấn. Điều này nhằm mục đích giữ logic miền trong lớp miền. Nó cũng phục vụ Ngôn ngữ Ubiquitous và hoa hồng tốt hơn. Hãy tưởng tượng nói "đặc tả đơn hàng quá hạn" so với nói "thứ tự bộ lọc từ tbl_order trong đó được đặt_at ít hơn 30 phút trước sysdate" (xem câu trả lời này ).
  4. Nó làm cho lý luận về hành vi của các thực thể trở nên khó khăn hơn vì Nguyên tắc Trách nhiệm Đơn lẻ bị vi phạm. Nếu bạn cần giải quyết vấn đề lưu trữ / kiên trì, bạn biết nơi nào nên đi và nơi không nên đi.
  5. Nó tránh được nguy cơ cho phép thực thể truy cập hai chiều vào trạng thái toàn cầu (thông qua kho lưu trữ và dịch vụ miền). Bạn cũng không muốn phá vỡ ranh giới giao dịch của mình.

Vernon Vaughn trong cuốn sách đỏ Thực hiện Thiết kế hướng tên miền đề cập đến vấn đề này ở hai nơi mà tôi biết (lưu ý: cuốn sách này được Evans chứng thực hoàn toàn như bạn có thể đọc trong lời nói đầu). Trong Chương 7 về Dịch vụ, anh ta sử dụng dịch vụ tên miền và thông số kỹ thuật để giải quyết nhu cầu tổng hợp để sử dụng kho lưu trữ và tổng hợp khác để xác định xem người dùng có được xác thực hay không. Ông được trích dẫn nói:

Theo nguyên tắc thông thường, chúng ta nên cố gắng tránh sử dụng Kho (12) từ bên trong Tập hợp, nếu có thể.

Vernon, Vaughn (2013 / 02-06). Triển khai thiết kế hướng tên miền (Địa điểm Kindle 6089). Giáo dục Pearson. Phiên bản Kindle.

Và trong Chương 10 về Uẩn, trong phần có tiêu đề "Điều hướng mẫu" , ông nói (ngay sau khi ông khuyến nghị sử dụng ID duy nhất toàn cầu để tham chiếu các gốc tổng hợp khác):

Tham chiếu theo danh tính không hoàn toàn ngăn chặn điều hướng thông qua mô hình. Một số sẽ sử dụng Kho lưu trữ (12) từ bên trong Tổng hợp để tra cứu. Kỹ thuật này được gọi là Mô hình miền bị ngắt kết nối và thực sự là một dạng lười tải. Tuy nhiên, có một cách tiếp cận được đề xuất khác: Sử dụng Kho lưu trữ hoặc Dịch vụ miền (7) để tìm kiếm các đối tượng phụ thuộc trước khi gọi hành vi Tổng hợp. Dịch vụ ứng dụng khách có thể kiểm soát điều này, sau đó gửi tới Tổng hợp:

Anh ta đi vào hiển thị một ví dụ về điều này trong mã:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

Ông cũng tiếp tục đề cập đến một giải pháp khác về cách sử dụng dịch vụ tên miền trong phương thức lệnh Tổng hợp cùng với việc gửi hai lần . (Tôi không thể khuyên bạn nên đọc cuốn sách của anh ấy có lợi như thế nào.

Sau đó, tôi đã có một số cuộc thảo luận với Marco Pivetta luôn luôn duyên dáng @Ocramius , người đã chỉ cho tôi một chút mã về việc rút ra một đặc tả từ tên miền và sử dụng điều đó:

1) Điều này không được khuyến khích:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) Trong một dịch vụ tên miền, điều này là tốt:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}

1
Câu hỏi: Chúng tôi luôn được dạy không tạo đối tượng ở trạng thái không hợp lệ hoặc không nhất quán. Khi bạn tải người dùng từ kho lưu trữ, và sau đó bạn gọi getFriends()trước khi làm bất cứ điều gì khác, nó sẽ trống hoặc tải lười biếng. Nếu trống thì đối tượng này nằm và ở trạng thái không hợp lệ. Bất kỳ suy nghĩ về điều này?
Jimbo

Kho lưu trữ gọi Miền để tạo một thể hiện mới. Bạn không nhận được một phiên bản Người dùng mà không thông qua Tên miền. Vấn đề câu trả lời này là ngược lại. Trường hợp Tên miền đang tham chiếu Kho lưu trữ và điều này nên tránh.
prograhammer

28

Đó là một câu hỏi rất hay. Tôi sẽ mong đợi một số cuộc thảo luận về điều này. Nhưng tôi nghĩ rằng nó đã được đề cập trong một số cuốn sách DDD và Jimmy nilssons và Eric Evans. Tôi đoán nó cũng có thể nhìn thấy qua các ví dụ về cách sử dụng mô hình reposistory.

NHƯNG hãy thảo luận. Tôi nghĩ rằng một suy nghĩ rất hợp lệ là tại sao một thực thể nên biết về cách tồn tại một thực thể khác? Quan trọng với DDD là mỗi thực thể có trách nhiệm quản lý "phạm vi tri thức" của riêng mình và không nên biết bất cứ điều gì về cách đọc hoặc viết các thực thể khác. Chắc chắn bạn có thể chỉ cần thêm giao diện kho lưu trữ vào Thực thể A để đọc Thực thể B. Nhưng rủi ro là bạn tiết lộ kiến ​​thức về cách duy trì B. Thực thể A cũng sẽ xác thực trên B trước khi duy trì B thành db?

Như bạn có thể thấy thực thể A có thể tham gia nhiều hơn vào vòng đời của thực thể B và điều đó có thể tăng thêm độ phức tạp cho mô hình.

Tôi đoán (không có ví dụ nào) rằng kiểm tra đơn vị sẽ phức tạp hơn.

Nhưng tôi chắc chắn sẽ luôn có những tình huống mà bạn muốn sử dụng kho lưu trữ thông qua các thực thể. Bạn phải xem xét từng kịch bản để đưa ra đánh giá hợp lệ. Ưu và nhược điểm. Nhưng theo tôi, giải pháp thực thể kho lưu trữ bắt đầu với rất nhiều Nhược điểm. Đó phải là một kịch bản rất đặc biệt với Ưu điểm cân bằng Nhược điểm ....


1
Điểm tốt. Mô hình miền trường học cũ có thể sẽ có Thực thể B chịu trách nhiệm về việc xác thực chính nó trước khi nó tự tồn tại, tôi đoán vậy. Bạn có chắc Evans đề cập đến các Thực thể không sử dụng Kho lưu trữ? Tôi mới đọc được một nửa cuốn sách và nó chưa đề cập đến nó ...
codeulike

Vâng, tôi đã đọc cuốn sách này vài năm trước (cũng 3 ...) và trí nhớ của tôi làm tôi thất vọng. Tôi không thể nhớ lại nếu anh ấy nói chính xác nó NHƯNG tuy nhiên tôi tin rằng anh ấy đã minh họa điều này thông qua các ví dụ. Bạn cũng có thể tìm thấy một diễn giải cộng đồng về ví dụ về hàng hóa của anh ấy (từ cuốn sách của anh ấy) tại dddsamplenet.codeplex.com . Tải về dự án mã (xem dự án Vanilla - ví dụ từ cuốn sách). Bạn sẽ thấy rằng các kho lưu trữ chỉ được sử dụng trong lớp Ứng dụng để truy cập các thực thể miền.
Magnus Backeus

1
Tải xuống ví dụ DDD SmartCA từ cuốn sách p2p.wrox.com/, bạn sẽ thấy một cách tiếp cận khác (mặc dù đây là ứng dụng khách RIA windows) nơi các kho lưu trữ được sử dụng trong các dịch vụ (không có gì lạ ở đây) nhưng các dịch vụ được sử dụng bên trong. Đây là điều tôi sẽ không làm NHƯNG tôi là một anh chàng ứng dụng webb. Đưa ra kịch bản cho ứng dụng SmartCA nơi bạn phải có khả năng làm việc ngoại tuyến, có thể thiết kế ddd sẽ trông khác.
Magnus Backeus

Ví dụ về SmartCA nghe có vẻ thú vị, đó là chương nào? (việc tải mã được sắp xếp theo chương)
codeulike

1
@codeulike Tôi hiện đang thiết kế và triển khai một khung sử dụng các khái niệm ddd. Đôi khi thực hiện xác nhận cần truy cập vào cơ sở dữ liệu và truy vấn nó (ví dụ: truy vấn để kiểm tra chỉ mục duy nhất nhiều cột). Với sự tôn trọng điều này và thực tế là các truy vấn nên được viết trong lớp kho lưu trữ. giao diện kho lưu trữ của chúng trong lớp mô hình miền để đặt xác thực hoàn toàn trong lớp mô hình miền. Vì vậy, cuối cùng có ổn cho các thực thể miền có quyền truy cập vào kho không?
Karamafrooz

13

Tại sao phải tách ra truy cập dữ liệu?

Từ cuốn sách, tôi nghĩ hai trang đầu tiên của chương Thiết kế hướng mẫu cho một số lý do tại sao bạn muốn trừu tượng hóa các chi tiết triển khai kỹ thuật từ việc triển khai mô hình miền.

  • Bạn muốn giữ một kết nối chặt chẽ giữa mô hình miền và mã
  • Phân tách mối quan tâm kỹ thuật giúp chứng minh mô hình là thiết thực để thực hiện
  • Bạn muốn ngôn ngữ có mặt khắp nơi thấm vào thiết kế của hệ thống

Đây dường như là tất cả cho mục đích tránh một "mô hình phân tích" riêng biệt trở nên ly dị với việc triển khai thực tế của hệ thống.

Từ những gì tôi hiểu về cuốn sách, nó nói rằng "mô hình phân tích" này cuối cùng có thể được thiết kế mà không cần xem xét triển khai phần mềm. Một khi các nhà phát triển cố gắng thực hiện mô hình được hiểu bởi phía doanh nghiệp, họ hình thành sự trừu tượng của riêng họ do sự cần thiết, gây ra một bức tường trong giao tiếp và hiểu biết.

Theo hướng khác, các nhà phát triển đưa quá nhiều mối quan tâm kỹ thuật vào mô hình miền cũng có thể gây ra sự phân chia này.

Vì vậy, bạn có thể xem xét rằng thực hành phân tách các mối quan tâm như sự kiên trì có thể giúp bảo vệ chống lại các thiết kế này một mô hình phân tích phân kỳ. Nếu cảm thấy cần thiết phải giới thiệu những thứ như sự kiên trì vào mô hình thì đó là một lá cờ đỏ. Có thể mô hình không thực tế để thực hiện.

Trích dẫn:

"Mô hình duy nhất làm giảm khả năng xảy ra lỗi, vì thiết kế hiện là kết quả trực tiếp của mô hình được xem xét cẩn thận. Thiết kế, và thậm chí chính mã, có khả năng giao tiếp của mô hình."

Cách tôi diễn giải điều này, nếu bạn kết thúc với nhiều dòng mã xử lý những thứ như truy cập cơ sở dữ liệu, bạn sẽ mất khả năng giao tiếp đó.

Nếu nhu cầu truy cập cơ sở dữ liệu là dành cho những việc như kiểm tra tính duy nhất, hãy xem:

Udi Dahan: những sai lầm lớn nhất mà các đội mắc phải khi áp dụng DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-appending-ddd/

trong "Tất cả các quy tắc không được tạo ra bằng nhau"

Sử dụng mẫu mô hình miền

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

trong "Kịch bản không sử dụng mô hình miền", chạm vào cùng một chủ đề.

Cách tách quyền truy cập dữ liệu

Đang tải dữ liệu qua giao diện

"Lớp truy cập dữ liệu" đã được trừu tượng hóa thông qua một giao diện mà bạn gọi để lấy dữ liệu cần thiết:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Ưu điểm: Giao diện tách mã hệ thống ống nước "truy cập dữ liệu", cho phép bạn vẫn viết bài kiểm tra. Truy cập dữ liệu có thể được xử lý theo từng trường hợp cho phép hiệu suất tốt hơn so với chiến lược chung.

Nhược điểm: Mã gọi phải giả định những gì đã được tải và những gì chưa.

Nói GetOrderLines trả về các đối tượng OrderLine với thuộc tính ProductInfo null vì lý do hiệu năng. Nhà phát triển phải có kiến ​​thức sâu sắc về mã đằng sau giao diện.

Tôi đã thử phương pháp này trên các hệ thống thực. Cuối cùng, bạn thay đổi phạm vi của những gì được tải mọi lúc để cố gắng khắc phục các sự cố về hiệu suất. Cuối cùng, bạn nhìn trộm phía sau giao diện để xem mã truy cập dữ liệu để xem những gì đang và không được tải.

Bây giờ, việc phân tách các mối quan tâm sẽ cho phép nhà phát triển tập trung vào một khía cạnh của mã cùng một lúc, càng nhiều càng tốt. Kỹ thuật giao diện loại bỏ CÁCH dữ liệu này được tải, nhưng không tải dữ liệu NHIỀU, KHI được tải và KHI được tải ở đâu.

Kết luận: Khá tách biệt!

Tải lười biếng

Dữ liệu được tải theo yêu cầu. Các lệnh gọi để tải dữ liệu được ẩn trong chính biểu đồ đối tượng, trong đó việc truy cập một thuộc tính có thể khiến truy vấn sql thực thi trước khi trả về kết quả.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Ưu điểm: Quyền truy cập dữ liệu 'WHEN, WHERE và CÁCH' bị ẩn khỏi nhà phát triển tập trung vào logic miền. Không có mã nào trong tổng hợp liên quan đến việc tải dữ liệu. Lượng dữ liệu được tải có thể là số lượng chính xác theo yêu cầu của mã.

Nhược điểm: Khi bạn gặp phải vấn đề về hiệu năng, thật khó để khắc phục khi bạn có giải pháp "một kích thước phù hợp với tất cả" chung chung. Tải chậm có thể gây ra hiệu suất tổng thể tồi tệ hơn và thực hiện tải lười biếng có thể khó khăn.

Giao diện vai trò / Tìm nạp háo hức

Mỗi trường hợp sử dụng được thực hiện rõ ràng thông qua Giao diện vai trò được thực hiện bởi lớp tổng hợp, cho phép các chiến lược tải dữ liệu được xử lý cho mỗi trường hợp sử dụng.

Chiến lược tìm nạp có thể trông như thế này:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);
    
        return order;
    }

}
   

Sau đó, tổng hợp của bạn có thể trông như:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrargety được sử dụng để xây dựng tổng hợp, và sau đó tổng hợp thực hiện công việc của nó.

Ưu điểm: Cho phép mã tùy chỉnh cho mỗi trường hợp sử dụng, cho phép hiệu suất tối ưu. Là nội tuyến với Nguyên tắc phân chia giao diện . Không có yêu cầu mã phức tạp. Các bài kiểm tra đơn vị tổng hợp không phải bắt chước chiến lược tải. Chiến lược tải chung có thể được sử dụng cho hầu hết các trường hợp (ví dụ: chiến lược "tải tất cả") và chiến lược tải đặc biệt có thể được thực hiện khi cần thiết.

Nhược điểm: Nhà phát triển vẫn phải điều chỉnh / xem lại chiến lược tìm nạp sau khi thay đổi mã miền.

Với cách tiếp cận chiến lược tìm nạp, bạn vẫn có thể thấy mình thay đổi mã tìm nạp tùy chỉnh để thay đổi quy tắc kinh doanh. Nó không phải là một sự tách biệt hoàn hảo của các mối quan tâm nhưng cuối cùng sẽ dễ bảo trì hơn và tốt hơn so với lựa chọn đầu tiên. Chiến lược tìm nạp sẽ gói gọn dữ liệu CÁCH, KHI và WHERE được tải. Nó có sự phân tách mối quan tâm tốt hơn, mà không mất tính linh hoạt như một kích thước phù hợp với tất cả các phương pháp tải lười biếng.


Cảm ơn, tôi sẽ kiểm tra các liên kết. Nhưng trong câu trả lời của bạn có phải bạn đang nhầm lẫn 'tách mối quan tâm' với 'hoàn toàn không có quyền truy cập vào nó'? Chắc chắn hầu hết mọi người sẽ đồng ý rằng lớp bền bỉ nên được tách biệt khỏi lớp mà các Thực thể đang ở. Nhưng điều đó khác với cách nói 'các thực thể không thể nhìn thấy lớp kiên trì, thậm chí thông qua một thuyết bất khả thi rất chung chung giao diện '.
codeulike

Tải dữ liệu qua giao diện hay không, bạn vẫn quan tâm đến việc tải dữ liệu trong khi thực hiện các quy tắc kinh doanh. Tôi đồng ý rằng rất nhiều người vẫn gọi đây là sự phân tách mối quan tâm, có lẽ nguyên tắc trách nhiệm duy nhất sẽ là một thuật ngữ tốt hơn để sử dụng.
ttg

1
Không hoàn toàn chắc chắn làm thế nào để phân tích bình luận cuối cùng của bạn, nhưng tôi nghĩ rằng bạn đang đề xuất rằng dữ liệu không nên được tải trong khi xử lý các quy tắc kinh doanh? Tôi thấy điều đó sẽ làm cho các quy tắc 'tinh khiết hơn'. Nhưng nhiều loại quy tắc kinh doanh sẽ cần phải tham khảo các dữ liệu khác - bạn có gợi ý rằng nó nên được tải trước bởi một đối tượng riêng biệt không?
codeulike

@codeulike: Tôi đã cập nhật câu trả lời của mình. Bạn vẫn có thể tải dữ liệu trong các quy tắc kinh doanh nếu bạn cảm thấy bạn hoàn toàn phải làm vậy, nhưng điều đó không yêu cầu thêm các dòng mã truy cập dữ liệu vào mô hình miền của bạn (ví dụ: tải lười biếng). Trong các mô hình miền mà tôi đã thiết kế, dữ liệu thường chỉ được tải trước như bạn đã nói. Tôi đã thấy rằng việc chạy các quy tắc kinh doanh thường không yêu cầu quá nhiều dữ liệu.
ttg


12

Thật là một câu hỏi xuất sắc. Tôi đang trên con đường khám phá tương tự, và hầu hết các câu trả lời trên internet dường như mang lại nhiều vấn đề như chúng mang lại giải pháp.

Vì vậy, (có nguy cơ viết một cái gì đó mà tôi không đồng ý với một năm kể từ bây giờ) đây là những khám phá của tôi cho đến nay.

Trước hết, chúng tôi thích một mô hình miền phong phú , mang lại cho chúng tôi khả năng khám phá cao (về những gì chúng tôi có thể làm với tổng hợp) và khả năng đọc (gọi phương thức biểu cảm).

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

Chúng tôi muốn đạt được điều này mà không cần tiêm bất kỳ dịch vụ nào vào hàm tạo của thực thể, bởi vì:

  • Giới thiệu một hành vi mới (sử dụng dịch vụ mới) có thể dẫn đến thay đổi hàm tạo, nghĩa là thay đổi đó ảnh hưởng đến mọi dòng khởi tạo thực thể !
  • Các dịch vụ này không phải là một phần của mô hình , nhưng phương thức xây dựng sẽ gợi ý rằng chúng là.
  • Thông thường một dịch vụ (thậm chí giao diện của nó) là một chi tiết triển khai chứ không phải là một phần của miền. Mô hình miền sẽ có sự phụ thuộc hướng ngoại .
  • Nó có thể gây nhầm lẫn tại sao thực thể không thể tồn tại mà không có những phụ thuộc này. (Một dịch vụ ghi chú tín dụng, bạn nói? Tôi thậm chí sẽ không làm bất cứ điều gì với ghi chú tín dụng ...)
  • Nó sẽ làm cho nó khó khởi tạo, do đó khó kiểm tra .
  • Vấn đề dễ dàng lan rộng, bởi vì các thực thể khác có chứa thực thể này sẽ có cùng các phụ thuộc - mà chúng có thể trông giống như các phụ thuộc rất không tự nhiên .

Làm thế nào, sau đó, chúng ta có thể làm điều này? Kết luận của tôi cho đến nay là phụ thuộc phương thức và công văn kép cung cấp một giải pháp tốt.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote()bây giờ yêu cầu một dịch vụ chịu trách nhiệm tạo ghi chú tín dụng. Nó sử dụng công văn kép , giảm tải hoàn toàn công việc cho dịch vụ chịu trách nhiệm, trong khi vẫn duy trì khả năng khám phá từ Invoicethực thể.

SetStatus()bây giờ có một phụ thuộc đơn giản vào một logger, rõ ràng sẽ thực hiện một phần của công việc .

Để sau này, để làm cho mọi thứ dễ dàng hơn trên mã máy khách, thay vào đó chúng ta có thể đăng nhập thông qua một IInvoiceService. Rốt cuộc, việc ghi nhật ký hóa đơn có vẻ khá nội tại đối với hóa đơn. Một đơn như vậy IInvoiceServicegiúp tránh sự cần thiết cho tất cả các loại dịch vụ nhỏ cho các hoạt động khác nhau. Nhược điểm là nó trở nên tối nghĩa chính xác những gì dịch vụ sẽ làm . Nó thậm chí có thể bắt đầu trông giống như công văn kép, trong khi hầu hết các công việc thực sự vẫn được thực hiện trong SetStatus()chính nó.

Chúng tôi vẫn có thể đặt tên cho tham số là 'logger', với hy vọng tiết lộ ý định của chúng tôi. Có vẻ hơi yếu, mặc dù.

Thay vào đó, tôi sẽ chọn yêu cầu IInvoiceLogger(như chúng ta đã làm trong mẫu mã) và đã IInvoiceServicetriển khai giao diện đó. Mã khách hàng có thể chỉ cần sử dụng mã duy nhất IInvoiceServicecho tất cả các Invoicephương thức yêu cầu bất kỳ một "dịch vụ nhỏ" nội tại hóa đơn cụ thể nào, trong khi chữ ký phương thức vẫn làm rõ rất nhiều những gì họ đang yêu cầu.

Tôi nhận thấy rằng tôi đã không giải quyết các kho lưu trữ một cách nhanh chóng. Vâng, logger là hoặc sử dụng một kho lưu trữ, nhưng tôi cũng cung cấp một ví dụ rõ ràng hơn. Chúng ta có thể sử dụng cùng một cách tiếp cận, nếu kho lưu trữ là cần thiết chỉ trong một hoặc hai phương thức.

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

Trong thực tế, điều này cung cấp một giải pháp thay thế cho tải lười biếng luôn rắc rối .

Cập nhật: Tôi đã để lại văn bản dưới đây cho mục đích lịch sử, nhưng tôi khuyên bạn nên tránh xa việc tải lười biếng 100%.

Đối với sự thật, tải lười biếng bất động sản có trụ sở, tôi làm hiện đang sử dụng constructor injection, nhưng theo một cách bền bỉ-ngu dốt.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

Một mặt, kho lưu trữ tải Invoicetừ cơ sở dữ liệu có thể có quyền truy cập miễn phí vào một chức năng sẽ tải các ghi chú tín dụng tương ứng và đưa chức năng đó vào Invoice.

Mặt khác, mã tạo ra một cái mới thực sự Invoicesẽ chỉ truyền một hàm trả về một danh sách trống:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(Một phong tục ILazy<out T>có thể loại bỏ chúng ta về dàn diễn viên xấu xí IEnumerable, nhưng điều đó sẽ làm phức tạp cuộc thảo luận.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

Tôi rất vui khi nghe ý kiến, sở thích và cải tiến của bạn!


3

Đối với tôi điều này dường như là thực hành tốt liên quan đến 3M chứ không phải là cụ thể đối với DDD.

Những lý do mà tôi có thể nghĩ đến là:

  • Tách biệt các mối quan tâm (Các thực thể nên được tách biệt khỏi cách chúng tồn tại. Vì có thể có nhiều chiến lược trong đó cùng một thực thể sẽ được duy trì tùy thuộc vào kịch bản sử dụng)
  • Theo logic, các thực thể có thể được nhìn thấy ở một mức dưới mức mà kho lưu trữ hoạt động. Các thành phần cấp thấp hơn không nên có kiến ​​thức về các thành phần cấp cao hơn. Do đó, các mục không nên có kiến ​​thức về Kho.

2

chỉ đơn giản là Vernon Vaughn đưa ra một giải pháp:

Sử dụng một kho lưu trữ hoặc dịch vụ miền để tìm kiếm các đối tượng phụ thuộc trước khi gọi hành vi tổng hợp. Một dịch vụ ứng dụng khách có thể kiểm soát điều này.


Nhưng không phải từ một thực thể.
thợ rèn

Từ nguồn Vernon Vaughn IDDD: public class Lịch kéo dài EventSourcedRootEntity {... public CalendarEntry scheduleCalendarEntry (CalendarIdentityService aCalendarIdentityService,
Teimuraz

kiểm tra giấy của anh ấy @Teimuraz
Alireza Rahmani Khalili

1

Tôi đã học cách viết mã lập trình hướng đối tượng trước khi tất cả buzz tầng riêng biệt này xuất hiện và các đối tượng / lớp DID đầu tiên của tôi ánh xạ trực tiếp đến cơ sở dữ liệu.

Cuối cùng, tôi đã thêm một lớp trung gian vì tôi phải di chuyển sang một máy chủ cơ sở dữ liệu khác. Tôi đã thấy / nghe về cùng một kịch bản nhiều lần.

Tôi nghĩ rằng tách biệt quyền truy cập dữ liệu (còn gọi là "Kho lưu trữ") khỏi logic kinh doanh của bạn, là một trong những điều đó, đã được phát minh lại nhiều lần, cho rằng cuốn sách Thiết kế hướng tên miền, khiến nó trở nên "ồn ào".

Tôi hiện đang sử dụng 3 lớp (GUI, Logic, Truy cập dữ liệu), giống như nhiều nhà phát triển, vì đây là một kỹ thuật tốt.

Tách dữ liệu, thành một Repositorylớp (còn gọi là Data Accesslớp), có thể được xem như một kỹ thuật lập trình tốt, không chỉ là một quy tắc, để tuân theo.

Giống như nhiều phương pháp, bạn có thể muốn bắt đầu, bằng cách KHÔNG triển khai, và cuối cùng, cập nhật chương trình của bạn, một khi bạn hiểu chúng.

Trích dẫn: Iliad hoàn toàn không được phát minh bởi Homer, Carmina Burana hoàn toàn không được phát minh bởi Carl Orff, và trong cả hai trường hợp, người đưa người khác làm việc, tất cả đều là người nhận được tín dụng ;-)


1
Cảm ơn, nhưng tôi không hỏi về việc tách quyền truy cập dữ liệu khỏi logic kinh doanh - đó là một điều rất rõ ràng rằng có một thỏa thuận rất rộng. Tôi đang hỏi về lý do tại sao trong các kiến ​​trúc DDD như S # arp, các Thực thể không được phép 'nói chuyện' với lớp truy cập dữ liệu. Đó là một sự sắp xếp thú vị mà tôi chưa thể tìm thấy nhiều cuộc thảo luận.
codeulike

0

Điều này đến từ cuốn sách Thiết kế hướng của Eric Evans, hay nó đến từ nơi khác?

Đó là những thứ cũ. Cuốn sách của Eric chỉ làm cho nó nổi hơn một chút.

Đâu là một số giải thích tốt cho lý do đằng sau nó?

Lý trí rất đơn giản - tâm trí con người trở nên yếu đuối khi phải đối mặt với nhiều bối cảnh liên quan mơ hồ. Chúng dẫn đến sự mơ hồ (Mỹ ở Nam / Bắc Mỹ có nghĩa là Nam / Bắc Mỹ), sự mơ hồ dẫn đến việc lập bản đồ thông tin liên tục bất cứ khi nào tâm trí "chạm vào nó" và đó là tổng năng suất và lỗi kém.

Logic kinh doanh nên được phản ánh rõ ràng nhất có thể. Khóa ngoài, chuẩn hóa, ánh xạ quan hệ đối tượng là từ miền hoàn toàn khác nhau - những thứ đó là kỹ thuật, liên quan đến máy tính.

Tương tự: nếu bạn đang học cách viết tay, bạn không nên cảm thấy khó hiểu về việc bút được sản xuất ở đâu, tại sao mực lại giữ trên giấy, khi giấy được phát minh và những phát minh nổi tiếng khác của Trung Quốc là gì.

chỉnh sửa: Để làm rõ: Tôi không nói về thực tiễn OO cổ điển về việc tách quyền truy cập dữ liệu thành một lớp riêng biệt khỏi logic nghiệp vụ - Tôi đang nói về sự sắp xếp cụ thể theo đó trong DDD, Các thực thể không được phép nói chuyện với dữ liệu lớp truy cập hoàn toàn (nghĩa là chúng không được phép giữ tham chiếu đến các đối tượng Kho lưu trữ)

Lý do vẫn giống như tôi đã đề cập ở trên. Đây chỉ là một bước nữa. Tại sao các thực thể nên kiên trì một phần không biết gì nếu chúng có thể (ít nhất là gần với) hoàn toàn? Ít quan tâm đến miền không liên quan đến mô hình của chúng tôi - phòng thở hơn tâm trí của chúng tôi nhận được khi nó phải diễn giải lại nó.


Đúng. Vậy, làm thế nào mà một Thực thể không biết gì về sự kiên trì hoàn toàn thực hiện Logic Kinh doanh nếu nó thậm chí không được phép nói chuyện với lớp kiên trì? Nó làm gì khi cần xem xét các giá trị trong các thực thể khác tùy ý?
codeulike

Nếu thực thể của bạn cần xem xét các giá trị trong các thực thể khác tùy ý, bạn có thể có một số vấn đề về thiết kế. Có lẽ xem xét phá vỡ các lớp học để họ gắn kết hơn.
cdaq

0

Để trích dẫn Carolina Lilientahl, "Các mẫu nên ngăn chặn chu kỳ" https://www.youtube.com/watch?v=eJjadzMRQAk , trong đó cô đề cập đến sự phụ thuộc theo chu kỳ giữa các lớp. Trong trường hợp các kho lưu trữ bên trong các tập hợp, có một sự cám dỗ để tạo ra các phụ thuộc theo chu kỳ ngoài sự triệu tập của điều hướng đối tượng là lý do duy nhất. Mẫu được đề cập ở trên bởi prograhammer, được đề xuất bởi Vernon Vaughn, trong đó các tập hợp khác được tham chiếu bởi id thay vì các trường hợp gốc, (có tên nào cho mẫu này không?) Gợi ý một giải pháp thay thế có thể hướng dẫn các giải pháp khác.

Ví dụ về sự phụ thuộc theo chu kỳ giữa các lớp (thú nhận):

(Time0): Hai lớp, Mẫu và Giếng, tham chiếu lẫn nhau (phụ thuộc theo chu kỳ). Giếng liên quan đến Mẫu và Mẫu đề cập đến Giếng, vì sự tiện lợi (đôi khi là các mẫu lặp, đôi khi lặp tất cả các giếng trong một tấm). Tôi không thể tưởng tượng các trường hợp Mẫu không tham chiếu trở lại Giếng nơi nó được đặt.

(Time1): Một năm sau, nhiều trường hợp sử dụng được triển khai .... và hiện tại có những trường hợp Mẫu không nên tham chiếu lại Giếng được đặt. Có những tấm tạm thời trong một bước làm việc. Ở đây một cái giếng đề cập đến một mẫu, trong đó lần lượt đề cập đến một cái giếng trên một tấm khác. Bởi vì điều này, hành vi kỳ lạ đôi khi xảy ra khi ai đó cố gắng thực hiện các tính năng mới. Mất thời gian để thâm nhập.

Tôi cũng đã được giúp đỡ bởi bài viết này được đề cập ở trên về các khía cạnh tiêu cực của tải lười biếng.


-1

Trong thế giới lý tưởng, DDD đề xuất rằng các Thực thể không nên có tham chiếu đến các lớp dữ liệu. nhưng chúng ta không sống trong thế giới lý tưởng. Các tên miền có thể cần tham chiếu đến các đối tượng miền khác cho logic nghiệp vụ mà chúng có thể không có sự phụ thuộc. Đó là logic cho các thực thể tham chiếu đến lớp kho lưu trữ cho mục đích chỉ đọc, để tìm nạp các giá trị.


Không, điều này giới thiệu sự ghép nối không cần thiết với các thực thể, vi phạm SRP và Tách biệt các mối quan tâm và gây khó khăn cho việc giải trừ thực thể khỏi sự kiên trì (vì quy trình khử lưu huỳnh cũng phải tiêm dịch vụ / kho lưu trữ mà thực thể thường gặp).
thợ rèn
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.