trong DDD, các kho lưu trữ nên phơi bày một thực thể hoặc các đối tượng miền?


11

Theo tôi hiểu, trong DDD, việc sử dụng một mẫu kho lưu trữ với một gốc tổng hợp là phù hợp. Câu hỏi của tôi là, tôi có nên trả lại dữ liệu dưới dạng thực thể hoặc đối tượng miền / DTO không?

Có thể một số mã sẽ giải thích thêm câu hỏi của tôi:

Thực thể

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

Tôi có nên làm một cái gì đó như thế này?

public Customer GetCustomerByName(string name) { /*some code*/ }

Hoặc thứ gì đó giống thế này?

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Câu hỏi bổ sung:

  1. Trong kho lưu trữ, tôi nên trả lại IQueryable hoặc IEnumerable?
  2. Trong một dịch vụ hoặc kho lưu trữ, tôi nên làm một cái gì đó giống như .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? hoặc chỉ làm một phương pháp giống như thế GetCustomerBy(Func<string, bool> predicate)nào?

Điều gì sẽ GetCustomerByName('John Smith')trở lại nếu bạn có hai mươi John Smith trong cơ sở dữ liệu của bạn? Có vẻ như bạn cho rằng không có hai người có cùng tên.
bdsl

Câu trả lời:


8

Tôi nên trả lại dữ liệu dưới dạng thực thể hoặc đối tượng miền / DTO

Vâng, điều đó hoàn toàn phụ thuộc vào trường hợp sử dụng của bạn. Lý do duy nhất tôi có thể nghĩ đến việc trả lại một DTO thay vì một thực thể đầy đủ là nếu thực thể của bạn rất lớn và bạn chỉ cần làm việc trên một tập hợp con của nó.

Nếu đây là trường hợp, thì có lẽ bạn nên xem xét lại mô hình miền của mình và chia thực thể lớn của bạn thành các thực thể nhỏ hơn có liên quan.

  1. Trong kho lưu trữ, tôi nên trả lại IQueryable hoặc IEnumerable?

Một nguyên tắc nhỏ là luôn trả về loại đơn giản nhất (cao nhất trong hệ thống phân cấp kế thừa) có thể. Vì vậy, trả lại IEnumerabletrừ khi bạn muốn cho phép người tiêu dùng kho lưu trữ làm việc với một IQueryable.

Cá nhân, tôi nghĩ rằng trả lại một IQueryablelà một sự trừu tượng bị rò rỉ nhưng tôi đã gặp một số nhà phát triển tranh luận một cách say mê rằng nó không phải là. Trong tâm trí của tôi, tất cả logic truy vấn nên được lưu trữ và che giấu bởi Kho lưu trữ. Nếu bạn cho phép gọi mã để tùy chỉnh truy vấn của họ thì điểm của kho lưu trữ là gì?

  1. Trong một dịch vụ hoặc kho lưu trữ, tôi nên làm gì đó như .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? hoặc chỉ tạo một phương thức giống như GetCustomerBy (vị ngữ Func)?

Vì lý do tương tự như tôi đã đề cập ở điểm 1, chắc chắn không sử dụng GetCustomerBy(Func<string, bool> predicate). Thoạt nghe có vẻ hấp dẫn, nhưng đây chính xác là lý do tại sao mọi người đã học cách ghét các kho lưu trữ chung chung. Nó bị rò rỉ.

Những thứ như GetByPredicate(Func<T, bool> predicate)chỉ hữu ích khi chúng ẩn đằng sau các lớp cụ thể. Vì vậy, nếu bạn có một lớp cơ sở trừu tượng được gọi là lớp RepositoryBase<T>tiếp xúc protected T GetByPredicate(Func<T, bool> predicate)chỉ được sử dụng bởi các kho lưu trữ cụ thể (ví dụ, public class CustomerRepository : RepositoryBase<Customer>) thì sẽ ổn thôi.


Vì vậy, câu nói của bạn, có ổn khi có các lớp DTO trong lớp miền không?
tiền mã hóa

Đó không phải là những gì tôi đã cố gắng nói. Tôi không phải là một chuyên gia DDD vì vậy tôi không thể nói nếu điều đó được chấp nhận. Vì tò mò, tại sao bạn không trả lại thực thể đầy đủ? Có quá lớn không?
MetaFight

Ồ không thật. Tôi chỉ muốn biết điều gì là đúng và chấp nhận được để làm. Là nó để trả về một thực thể đầy đủ hay chỉ là tập hợp con của nó. Tôi đoán nó phụ thuộc vào câu trả lời của bạn.
tiền mã hóa

6

Có một cộng đồng khá lớn những người sử dụng CQRS để triển khai các miền của họ. Cảm giác của tôi là, nếu giao diện của kho lưu trữ của bạn tương tự như các thực tiễn tốt nhất được sử dụng bởi chúng, thì bạn sẽ không đi quá xa.

Dựa trên những gì tôi đã thấy ...

1) Trình xử lý lệnh thường sử dụng kho lưu trữ để tải tổng hợp thông qua kho lưu trữ. Các lệnh nhắm đến một thể hiện cụ thể duy nhất của tổng hợp; kho lưu trữ tải gốc bằng ID. Không có gì, tôi có thể thấy, một trường hợp các lệnh được chạy với một tập hợp tổng hợp (thay vào đó, trước tiên bạn sẽ chạy truy vấn để lấy tập hợp tổng hợp, sau đó liệt kê tập hợp và đưa ra lệnh cho từng tập hợp.

Do đó, trong bối cảnh bạn sẽ sửa đổi tổng hợp, tôi mong muốn kho lưu trữ trả về thực thể (còn gọi là gốc tổng hợp).

2) Trình xử lý truy vấn hoàn toàn không chạm vào tập hợp; thay vào đó, chúng làm việc với các phép chiếu của tập hợp - các đối tượng giá trị mô tả trạng thái của tập hợp / tập hợp tại một thời điểm nào đó. Vì vậy, hãy nghĩ ProjectionDTO, thay vì AggregateDTO, và bạn có ý tưởng đúng.

Trong bối cảnh nơi bạn sẽ chạy các truy vấn dựa trên tổng hợp, chuẩn bị hiển thị, v.v., tôi mong đợi sẽ thấy một DTO hoặc bộ sưu tập DTO, được trả về, thay vì một thực thể.

Tất cả các getCustomerByPropertycuộc gọi của bạn trông giống như các truy vấn đối với tôi, vì vậy chúng sẽ rơi vào loại sau. Có lẽ tôi muốn sử dụng một điểm vào duy nhất để tạo bộ sưu tập, vì vậy tôi sẽ tìm kiếm xem

getCustomersThatSatisfy(Specification spec)

là một lựa chọn hợp lý; các trình xử lý truy vấn sau đó sẽ xây dựng các đặc tả thích hợp từ các tham số đã cho và chuyển đặc tả đó vào kho lưu trữ. Nhược điểm là chữ ký thực sự gợi ý rằng kho lưu trữ là một bộ sưu tập trong bộ nhớ; Tôi không rõ ràng rằng vị ngữ mua cho bạn nhiều nếu kho lưu trữ chỉ là một bản tóm tắt của việc chạy một câu lệnh SQL đối với cơ sở dữ liệu quan hệ.

Có một số mô hình có thể giúp đỡ, mặc dù. Ví dụ, thay vì xây dựng đặc tả bằng tay, hãy chuyển đến kho lưu trữ một mô tả về các ràng buộc và cho phép thực hiện kho lưu trữ để quyết định những việc cần làm.

Cảnh báo: java giống như gõ được phát hiện

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

Tóm lại: sự lựa chọn giữa việc cung cấp tổng hợp và cung cấp DTO phụ thuộc vào những gì bạn đang mong đợi người tiêu dùng sẽ làm với nó. Tôi đoán sẽ là một triển khai cụ thể hỗ trợ một giao diện cho từng bối cảnh.


Đó là tất cả tốt đẹp và tốt, nhưng người hỏi không đề cập đến việc sử dụng CQRS. Mặc dù bạn đúng, vấn đề của anh ta sẽ không tồn tại nếu anh ta làm vậy.
MetaFight

Tôi không có kiến ​​thức về CQRS tho Tôi đã nghe về nó. Theo tôi hiểu, khi suy nghĩ về những gì sẽ trả về nếu AggregateDTO hoặc ProjectionDTO, tôi sẽ trả về ProjectionDTO. Sau đó, getCustomersThatSatisfy(Specification spec)tôi sẽ chỉ liệt kê các thuộc tính tôi cần cho các tùy chọn tìm kiếm. Tôi hiểu đúng chưa?
tiền mã hóa

Không rõ. Tôi không tin AggregateDTO nên tồn tại. Quan điểm của tổng hợp là đảm bảo rằng tất cả các thay đổi đều thỏa mãn tính bất biến của doanh nghiệp. Đó là sự gói gọn của các quy tắc miền cần được thỏa mãn - tức là, đó là hành vi. Mặt khác, các hình chiếu là đại diện cho một số ảnh chụp nhanh về trạng thái được doanh nghiệp chấp nhận. Xem chỉnh sửa để cố gắng làm rõ các đặc điểm kỹ thuật.
VoiceOfUnreason

Tôi đồng ý với VoiceOfUnreason rằng AggregateDTO không nên tồn tại - Tôi nghĩ rằng ProjectionDTO chỉ là một chế độ xem cho dữ liệu. Nó có thể điều chỉnh hình dạng của nó với những gì người gọi yêu cầu, lấy dữ liệu từ nhiều nguồn khác nhau khi cần thiết. Root tổng hợp phải là một đại diện hoàn chỉnh của tất cả các dữ liệu liên quan để tất cả các quy tắc tham chiếu có thể được kiểm tra trước khi lưu. Nó chỉ thay đổi hình dạng trong các trường hợp quyết liệt hơn, chẳng hạn như thay đổi bảng DB hoặc các mối quan hệ dữ liệu được sửa đổi.
Brad Irby

1

Dựa trên kiến ​​thức của tôi về DDD, bạn nên có cái này,

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

Trong kho lưu trữ, tôi nên trả lại IQueryable hoặc IEnumerable?

Nó phụ thuộc, bạn sẽ tìm thấy các quan điểm hỗn hợp từ các dân tộc khác nhau cho câu hỏi này. Nhưng cá nhân tôi tin rằng nếu kho lưu trữ của bạn sẽ được sử dụng bởi một dịch vụ như CustomerService thì bạn có thể sử dụng IQueryable nếu không thì gắn bó với IEnumerable.

Các kho lưu trữ có nên trả về IQueryable không?

Trong một dịch vụ hoặc kho lưu trữ, tôi nên làm gì đó như .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? hoặc chỉ tạo một phương thức giống như GetCustomerBy (vị ngữ Func)?

Trong kho lưu trữ của bạn, bạn nên có một hàm chung như bạn đã nói GetCustomer(Func predicate)nhưng trong lớp dịch vụ của bạn thêm ba phương thức khác nhau, điều này là do có khả năng dịch vụ của bạn được gọi từ các máy khách khác nhau và chúng cần các DTO khác nhau.

Bạn cũng có thể sử dụng Mẫu lưu trữ chung cho CRUD chung, nếu bạn chưa biết.

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.