6

Tôi đã đấu tranh xung quanh một vấn đề liên quan đến DDD với Thông số kỹ thuật và tôi đã đọc nhiều vào DDD và thông số kỹ thuật và kho.Đặc điểm kỹ thuật Pattern in Domain Driven Design

Tuy nhiên, có sự cố nếu cố gắng kết hợp cả 3 trong số này mà không vi phạm thiết kế được điều khiển theo miền. Nó tóm tắt về cách áp dụng các bộ lọc với hiệu suất trong tâm trí.

Đầu tiên một vài sự kiện rõ ràng:

  1. Repositories để có lớp DataAccess/Cơ sở hạ tầng
  2. miền Models đại diện kinh doanh Logic và đi đến lớp miền
  3. Data Access Models đại diện Persistence lớp và đi đến Lớp Persistance/Infrastructure/DataAccess
  4. Logic nghiệp vụ đi tới Lớp miền
  5. Thông số kỹ thuật là Logic nghiệp vụ, do đó chúng cũng thuộc về lớp Miền.
  6. Trong tất cả các ví dụ này, một khung ORM và SQL Server được sử dụng bên trong Repository
  7. kiên trì mô hình có thể không bị rò rỉ vào miền Lớp

Cho đến nay, dễ dàng như vậy. Vấn đề phát sinh khi/nếu chúng tôi cố gắng áp dụng các thông số kỹ thuật cho kho lưu trữ và không phá vỡ mẫu DDD hoặc gặp sự cố về hiệu suất.

Các cách có thể áp dụng kỹ thuật:

1) cách Classic: Thông số kỹ thuật sử dụng Domain Model ở miền Lớp

Áp dụng các mẫu Đặc điểm kỹ thuật truyền thống, với một phương pháp IsSatisfiedBy, trả lại một bool và composite Thông số kỹ thuật để kết hợp nhiều Thông số kỹ thuật.

này chúng ta hãy giữ thông số kỹ thuật tại miền Layer, nhưng ...

  1. Nó có làm việc với mô hình Domain, trong khi kho sử dụng Persistence Models mà đại diện cho cấu trúc dữ liệu của lớp kiên trì. Điều này dễ sửa với việc sử dụng các người lập bản đồ như AutoMapper.
  2. Tuy nhiên, vấn đề không thể giải quyết được: Tất cả các thông số kỹ thuật sẽ phải được thực hiện trong bộ nhớ. Trong một bảng/cơ sở dữ liệu lớn này có nghĩa là một tác động rất lớn nếu bạn phải lặp qua tất cả các thực chỉ để lọc ra một đáp ứng kỹ thuật của bạn

2) Thông số kỹ thuật sử dụng Persistence Mẫu

này tương tự đến 1), nhưng sử dụng Mô hình Persistence trong đặc điểm kỹ thuật. Điều này cho phép sử dụng trực tiếp Đặc điểm kỹ thuật như một phần của vị từ .Where sẽ được dịch sang truy vấn (nghĩa là TSQL) và việc lọc sẽ được thực hiện trên bộ nhớ Persistence (tức là SQL Server).

  1. Trong khi điều này mang lại hiệu suất tốt, nó rõ ràng vi phạm mẫu DDD. Mô hình Persistence của chúng ta rò rỉ vào lớp Domain, làm cho Lớp Domain phụ thuộc vào Persistence Layer thay vì theo cách khác.

3) Giống như 2), nhưng làm cho thông số kỹ thuật phần của lớp Persistence

  1. này không hoạt động, vì miền lớp cần phải tham khảo các thông số kỹ thuật. Nó vẫn sẽ phụ thuộc vào lớp kiên trì.
  2. Chúng tôi sẽ có logic nghiệp vụ bên trong lớp Persistence. Mà cũng vi phạm DDD mẫu

4) Giống như 3, nhưng sử dụng trừu tượng các thông số kỹ thuật như giao diện

Chúng tôi sẽ có giao diện Đặc điểm kỹ thuật trong lớp miền của chúng tôi, triển khai cụ thể của chúng ta về kỹ thuật trong lớp Persistence. Bây giờ lớp Domain của chúng ta sẽ chỉ tương tác với các giao diện và không phụ thuộc vào lớp Persistence.

  1. Điều này vẫn vi phạm số 2 từ 3). Chúng tôi sẽ có logic kinh doanh trong lớp kiên trì, đó là xấu.

5) Dịch Tree Biểu hiện từ Domain Model vào Persistence Mẫu

Điều này chắc chắn giải quyết vấn đề, nhưng đó là nhiệm vụ không tầm thường nhưng nó sẽ giữ cho các thông số kỹ thuật bên trong lớp miền của chúng tôi trong khi vẫn được hưởng lợi từ SQL tối ưu, bởi vì các thông số kỹ thuật trở thành một phần của Repositories khoản ở đâu và chuyển thành TSQL

tôi đã cố gắng đi tiếp cận này và có một số vấn đề (mẫu thực hiện bên):

  1. Chúng tôi cần biết Cấu hình từ Trình ánh xạ (nếu chúng tôi sử dụng một) hoặc giữ hệ thống ánh xạ của riêng mình. Điều này có thể được thực hiện một phần (đọc cấu hình Mapper) với AutoMapper, nhưng các vấn đề khác tồn tại
  2. Có thể chấp nhận một trong những thuộc tính của Mô hình A ánh xạ tới một Thuộc tính của Mẫu B. Sẽ khó hơn nếu các loại khác nhau (ví dụ: do các loại kiên trì, ví dụ Enums được lưu dưới dạng chuỗi hoặc cặp khóa/giá trị trong một bảng khác và chúng tôi cần thực hiện chuyển đổi bên trong trình giải quyết. đây không phải là vấn đề đối với Mô hình miền -> Bản đồ mô hình Persistence

** 6) Trình tạo truy vấn như API **

Điều cuối cùng là tạo một loại API truy vấn được chuyển vào đặc tả và từ đó lớp Repository/Persistence sẽ tạo ra một cây biểu thức được chuyển tới mệnh đề .Where và sử dụng Giao diện để khai báo tất cả các trường có thể lọc.

Tôi cũng đã thực hiện một số nỗ lực theo hướng đó nhưng không quá hài lòng về kết quả. Một cái gì đó như

public interface IQuery<T> 
{ 
    IQuery<T> Where(Expression<Func<T, T>> predicate); 
} 
public interface IQueryFilter<TFilter> 
{ 
    TFilter And(TFilter other); 
    TFilter Or(TFilter other); 
    TFilter Not(TFilter other); 
} 

public interface IQueryField<TSource, IQueryFilter> 
{ 
    IQueryFilter Equal(TSource other); 
    IQueryFilter GreaterThan(TSource other); 
    IQueryFilter Greater(TSource other); 
    IQueryFilter LesserThan(TSource other); 
    IQueryFilter Lesser(TSource other); 
} 
public interface IPersonQueryFilter : IQueryFilter<IPersonQueryFilter> 
{ 
    IQueryField<int, IPersonQueryFilter> ID { get; } 
    IQueryField<string, IPersonQueryFilter> Name { get; } 
    IQueryField<int, IPersonQueryFilter> Age { get; } 
} 

và trong đặc tả chúng tôi sẽ vượt qua một IQuery<IPersonQueryFilter> query để các nhà xây dựng chi tiết kỹ thuật và sau đó áp dụng các kỹ thuật để nó khi sử dụng hoặc kết hợp nó.

IQuery<IGridQueryFilter> query = null; 

query.Where(f => f.Name.Equal("Bob")); 

Tôi không thích cách tiếp cận này nhiều, vì nó xử lý các thông số phức tạp một chút (và hoặc nếu chuỗi) và tôi không thích cách và/hoặc/không hoạt động. biểu thị cây từ "API" này.

Tôi đã tìm kiếm tuần trên Internet, đọc hàng tá bài viết về DDD và Đặc điểm kỹ thuật, nhưng chúng luôn xử lý các trường hợp đơn giản và không xem xét hiệu quả hoặc vi phạm mẫu DDD.

Làm thế nào để bạn giải quyết điều này trong một ứng dụng thế giới thực mà không làm gì trong việc lọc bộ nhớ hoặc rò rỉ Sự kiên trì vào Lớp Miền ??

Có khung công tác nào giải quyết vấn đề ở trên bằng một trong hai cách (Trình tạo truy vấn như cú pháp cây biểu thức hoặc trình dịch cây biểu thức) không?

+0

Tôi rất vui vì tôi đã tìm thấy câu hỏi của bạn: Tôi đã có ** chính xác ** cùng một câu hỏi và cùng một mệnh đề về việc tách mô hình và mẫu đặc điểm kỹ thuật! Tôi tìm thấy [link] này (http://enterprisecraftsmanship.com/2016/04/05/having-the-domain-model-separate-from-the-persistence-model/), nơi anh chàng giải thích rằng có những mô hình riêng biệt mang lại ít tinh khiết hơn cho mô hình của bạn nhưng mang lại rất nhiều chi phí. Đối với anh ta nó không có giá trị nó. Tôi hy vọng bài viết này sẽ hữu ích :-) – Arcord

+0

Bản trình diễn Đặc tả chung với kho lưu trữ EF chung, trong đó thông số kỹ thuật được sử dụng cho cả lọc và tải dữ liệu mong muốn: http://deviq.com/specification-pattern/ – ssmith

Trả lời

2

Tôi nghĩ Đặc điểm kỹ thuật mô hình không được thiết kế cho các tiêu chí truy vấn. Trên thực tế, toàn bộ khái niệm về DDD cũng không. Hãy xem xét CQRS nếu có rất nhiều yêu cầu truy vấn.

Mẫu đặc điểm kỹ thuật giúp phát triển ngôn ngữ phổ biến, tôi nghĩ nó giống như một loại DSL. Nó tuyên bố phải làm gì hơn là làm thế nào để làm điều đó. Ví dụ, trong một bối cảnh đặt hàng, các đơn đặt hàng được coi là quá hạn nếu nó được đặt nhưng không được thanh toán trong vòng 30 phút. Với mẫu Đặc điểm kỹ thuật, nhóm của bạn có thể nói chuyện với một thuật ngữ ngắn nhưng độc đáo: OverdueOrderSpecification.Hãy tưởng tượng cuộc thảo luận dưới đây:

trường hợp -1

Business people: I want to find out all overdue orders and ... 
Developer: I can do that, it is easy to find all satisfying orders with an overdue order specification and.. 

trường hợp -2

Business people: I want to find out all orders which were placed before 30 minutes and still unpaid... 
Developer: I can do that, it is easy to filter order from tbl_order where placed_at is less that 30minutes before sysdate.... 

Mà một trong những bạn thích?

Thông thường, chúng tôi cần một trình xử lý DSL để phân tích cú pháp dsl, trong trường hợp này, nó có thể nằm trong bộ điều hợp liên tục, chuyển đặc tả thành tiêu chí truy vấn. Sự phụ thuộc này (infrastrructure.persistence => domain) không vi phạm hiệu trưởng của kiến ​​trúc.

class OrderMonitorApplication { 
    public void alarm() { 
     // The specification pattern keeps the overdue order ubiquitous language in domain 
     List<Order> overdueOrders = orderRepository.findBy(new OverdueSpecification()); 
     for (Order order: overdueOrders) { 
      //notify admin 
     } 
    } 
} 

class HibernateOrderRepository implements orderRepository { 
    public List<Order> findBy(OrderSpecification spec) { 
     criteria.le("whenPlaced", spec.placedBefore())//returns sysdate - 30 
     criteria.eq("status", spec.status());//returns WAIT_PAYMENT 
     return ... 
    } 
} 
+0

Cảm ơn, tôi sẽ có một cái nhìn sâu hơn về CQRS – Tseng

1

tôi đã được tìm kiếm trong nhiều tuần trên Internet, đọc hàng chục bài viết về DDD và kỹ thuật, nhưng họ luôn chỉ xử lý trường hợp đơn giản và không mất hiệu suất cân nhắc hoặc họ vi phạm DDD mẫu .

Ai đó sẽ sửa tôi nếu tôi sai, nhưng dường như với tôi khái niệm "Mô hình Persistence" không xuất hiện cho đến gần đây trong không gian DDD (bằng cách này, bạn đã đọc ở đâu về nó ?). Tôi không chắc nó được mô tả trong cuốn sách màu xanh gốc.

Cá nhân tôi không thấy nhiều lợi thế. Quan điểm của tôi là bạn có một mô hình quan hệ được giữ nguyên (thường) trong cơ sở dữ liệu của bạn và mô hình miền trong bộ nhớ trong ứng dụng của bạn. Khoảng cách giữa hai được bắc cầu bởi một hành động , không phải là một mô hình. Hành động này có thể được thực hiện bởi ORM. Tôi chưa được bán trên thực tế là một "mô hình đối tượng kiên trì" thực sự có ý nghĩa ngữ nghĩa, hãy để một mình là bắt buộc để tôn trọng các nguyên tắc DDD (*). Bây giờ có cách tiếp cận CQRS nơi bạn có một mô hình đọc riêng biệt, nhưng đây là một động vật hoàn toàn khác và tôi sẽ không thấy Specifications hành động trên các đối tượng Đọc mô hình thay vì thực thể như là một vi phạm DDD trong trường hợp này. Đặc điểm kỹ thuật là sau khi tất cả một mô hình rất chung chung mà không có gì trong DDD về cơ bản hạn chế đối với thực thể.

(*) Edit: Automapper tác giả Jimmy Bogard dường như tìm thấy nó overcomplicated cũng - Xem How do I use automapper to map many-to-many relationships?

+0

Phần này dành cho Ví dụ http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model Có nhiều điều nói chống lại việc sử dụng Mô hình Persistence như Mô hình Miền. Ví dụ: Quan hệ khóa ngoài với khung Entity, yêu cầu chúng tôi có trường StudentID và trường sinh viên chứa thông tin thực, nhưng StudentID (Autoindexed chẳng hạn) là trường lớp duy trì thuần túy và chỉ yêu cầu cho cơ sở dữ liệu quan hệ. Nó sẽ chỉ được yêu cầu cho các phép nối, vì các phép nối trên ints tốt hơn là sử dụng các chuỗi (tức làbằng cách sử dụng một số chuỗi nhận dạng duy nhất) – Tseng

+0

Tôi hoàn toàn không đồng ý với bài viết, vì một loạt các lý do từ "có, Entity Framework * có thể * làm điều đó nếu bạn đào một chút" để "bạn đang thiết lập dogmas trong đá cho chính mình chỉ để nhận được khoảng 1 hoặc 2 lỗ hổng trong ORM "đến" ORM đã là cách bạn lập bản đồ mọi thứ, không thực hiện công việc hai lần ". – guillaume31

+0

@Tseng Giả định chính của nước ngoài mà bạn tạo ra có vẻ không chính xác: http://stackoverflow.com/questions/15595818/mapkey-vs-hasforeignkey-difference-fluent-api http://msdn.microsoft.com/en-us/data/jj591620 # IndependentAssociation – guillaume31

3

Khi tôi thực hiện kỹ thuật nhưng ...

  1. Nó được dựa trên LINQ và IQueryable.
  2. Nó sử dụng Kho lưu trữ thống nhất duy nhất (nhưng đối với tôi nó không phải là xấu và tôi nghĩ rằng đó là lý do chính để sử dụng Đặc điểm kỹ thuật).
  3. Nó sử dụng mô hình duy nhất cho nhu cầu miền và nhu cầu liên tục (mà tôi nghĩ là xấu).

Repository:

public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot 
{ 
    TEntity Get<TKey>(TKey id); 

    TEntity TryGet<TKey>(TKey id); 

    void DeleteByKey<TKey>(TKey id); 

    void Delete(TEntity entity); 

    void Delete(IEnumerable<TEntity> entities); 

    IEnumerable<TEntity> List(FilterSpecification<TEntity> specification); 

    TEntity Single(FilterSpecification<TEntity> specification);   

    TEntity First(FilterSpecification<TEntity> specification); 

    TResult Compute<TResult>(ComputationSpecification<TEntity, TResult> specification); 

    IEnumerable<TEntity> ListAll(); 

    //and some other methods 
} 

đặc điểm kỹ thuật lọc:

public abstract class FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    public abstract IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots); 

    public static FilterSpecification<TAggregateRoot> CreateByPredicate(Expression<Func<TAggregateRoot, bool>> predicate) 
    { 
     return new PredicateFilterSpecification<TAggregateRoot>(predicate); 
    }  

    public static FilterSpecification<TAggregateRoot> operator &(FilterSpecification<TAggregateRoot> op1, FilterSpecification<TAggregateRoot> op2) 
    { 
     return new CompositeFilterSpecification<TAggregateRoot>(op1, op2); 
    }   

    public static FilterSpecification<TAggregateRoot> CreateDummy() 
    { 
     return new DummyFilterSpecification<TAggregateRoot>(); 
    } 

} 


public class CompositeFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    private readonly FilterSpecification<TAggregateRoot> _firstOperand; 
    private readonly FilterSpecification<TAggregateRoot> _secondOperand; 

    public CompositeFilterSpecification(FilterSpecification<TAggregateRoot> firstOperand, FilterSpecification<TAggregateRoot> secondOperand) 
    { 
     _firstOperand = firstOperand; 
     _secondOperand = secondOperand; 
    } 

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots) 
    { 
     var operand1Results = _firstOperand.Filter(aggregateRoots); 
     return _secondOperand.Filter(operand1Results); 
    } 
} 

public class PredicateFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    private readonly Expression<Func<TAggregateRoot, bool>> _predicate; 

    public PredicateFilterSpecification(Expression<Func<TAggregateRoot, bool>> predicate) 
    { 
     _predicate = predicate; 
    } 

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots) 
    { 
     return aggregateRoots.Where(_predicate); 
    } 
} 

Một loại đặc điểm kỹ thuật:

public abstract class ComputationSpecification<TAggregateRoot, TResult> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    public abstract TResult Compute(IQueryable<TAggregateRoot> aggregateRoots); 

    public static CompositeComputationSpecification<TAggregateRoot, TResult> operator &(FilterSpecification<TAggregateRoot> op1, ComputationSpecification<TAggregateRoot, TResult> op2) 
    { 
     return new CompositeComputationSpecification<TAggregateRoot, TResult>(op1, op2); 
    } 

} 

và tập quán:

triển khai

Tuỳ chỉnh có thể trông giống như

public class CheckUnitsOfGivenProductExistOnPlaceComputationSpecification : ComputationSpecification<Unit, bool> 
{ 
    private readonly Product _product; 
    private readonly Place _place; 

    public CheckUnitsOfGivenProductExistOnPlaceComputationSpecification(
     Place place, 
     Product product) 
    { 
     _place = place; 
     _product = product; 
    } 

    public override bool Compute(IQueryable<Unit> aggregateRoots) 
    { 
     return aggregateRoots.Any(unit => unit.Product == _product && unit.Place == _place); 
    } 
} 

Cuối cùng, tôi buộc phải nói rằng đơn giản Specficiation thực hiện phù hợp xấu theo DDD. Bạn đã thực hiện nghiên cứu tuyệt vời trong lĩnh vực này và không chắc ai đó đề xuất điều gì mới :). Ngoài ra, hãy xem http://www.sapiensworks.com/blog/ blog.

+0

Tôi sẽ cung cấp cho CQRS một cái nhìn gần hơn. Blog bạn đã liên kết cho biết, bạn có thể sử dụng CRUD cho các cấu trúc dữ liệu và CQRS cho các đối tượng kinh doanh đa dạng trong cùng một dự án. Kinh nghiệm của bạn là gì? – Tseng

+0

Tôi chưa có đủ kinh nghiệm sử dụng CQRS. –

Các vấn đề liên quan