5

Những gì tôi có:Repository mẫu - làm cho nó kiểm chứng, DI và IoC thân thiện và IDisposable

public interface IRepository 
{ 
    IDisposable CreateConnection(); 
    User GetUser(); 
    //other methods, doesnt matter 
} 

public class Repository 
{ 
    private SqlConnection _connection; 

    IDisposable CreateConnection() 
    { 
     _connection = new SqlConnection(); 
     _connection.Open(); 
     return _connection; 
    } 

    User GetUser() 
    { 
     //using _connection gets User from Database 
     //assumes _connection is not null and open 
    } 
    //other methods, doesnt matter 
} 

Điều này cho phép các lớp học đang sử dụng IRepository được dễ dàng kiểm chứng và container IoC thân thiện. Tuy nhiên ai đó sử dụng lớp này phải gọi CreateConnection trước khi gọi bất kỳ phương thức nào đang lấy thứ gì đó từ cơ sở dữ liệu, nếu không thì ngoại lệ sẽ bị ném. Bản thân điều này rất tốt - chúng tôi không muốn có kết nối lâu dài trong ứng dụng. Vì vậy, sử dụng lớp này tôi làm điều đó như thế này.

using(_repository.CreateConnection()) 
{ 
    var user = _repository.GetUser(); 
    //do something with user 
} 

Unfortunetelly đây không phải là giải pháp rất tốt bởi vì mọi người sử dụng lớp này (kể cả thậm chí tôi!) Thường quên gọi _repository.CreateConnection() trước khi gọi phương pháp để có được một cái gì đó từ cơ sở dữ liệu.

Để giải quyết vấn đề này, tôi đang xem bài đăng trên blog Mark Seemann SUT Double nơi anh ấy triển khai mẫu Repository theo cách chính xác. Thật không may, ông làm cho Repository thực hiện IDisposable, có nghĩa là tôi không thể đơn giản tiêm nó bởi IoC và DI vào các lớp và sử dụng nó sau đó, bởi vì chỉ sau một lần sử dụng nó sẽ được xử lý. Ông sử dụng nó một lần cho mỗi yêu cầu và ông sử dụng ASP.NET WebApi khả năng để xử lý nó sau khi xử lý yêu cầu được thực hiện. Đây là một cái gì đó tôi không thể làm bởi vì tôi có trường hợp lớp học của tôi sử dụng Repository làm việc tất cả các thời gian.

Giải pháp tốt nhất có thể ở đây là gì? Tôi có nên sử dụng một số loại nhà máy mà sẽ cho tôi IDisposable IRepository? Liệu nó có dễ dàng kiểm tra được không?

Trả lời

8

Có một vài điểm có vấn đề trong thiết kế của bạn. Trước hết, giao diện IRepository của bạn triển khai nhiều cấp độ trừu tượng hóa. Tạo người dùng là khái niệm cấp cao hơn nhiều so với quản lý kết nối. Bằng cách đặt những hành vi này lại với nhau, bạn đang vi phạm Single Responsibility Principle, điều đó quy định rằng một lớp chỉ nên có một trách nhiệm, một lý do để thay đổi. Bạn cũng vi phạm các Interface Segregation Principle đẩy chúng tôi hướng tới các giao diện vai trò hẹp.

Ngày đầu đó, phương pháp CreateConnection() và GetUser được kết hợp theo thời gian. Temporal Coupling là một mùi mã và bạn đã chứng kiến ​​điều này là một vấn đề, bởi vì bạn có thể quên cuộc gọi đến CreateConnection. Bên cạnh đó, việc tạo kết nối là thứ bạn sẽ bắt đầu thấy trên mọi kho lưu trữ trong hệ thống và mọi phần logic nghiệp vụ sẽ cần tạo kết nối hoặc nhận kết nối hiện có từ bên ngoài.Điều này trở nên không thể duy trì được trong thời gian dài. Tuy nhiên, quản lý kết nối là mối quan tâm xuyên suốt; bạn không muốn logic kinh doanh được quan tâm trong mối quan tâm cấp thấp như vậy.

Bạn nên bắt đầu bằng cách phân chia các IRepository thành hai giao diện khác nhau:

public interface IRepository 
{ 
    User GetUser(); 
} 

public interface IConnectionFactory 
{ 
    IDisposable CreateConnection(); 
} 

Thay vì để cho logic kinh doanh quản lý các kết nối riêng của mình, bạn có thể quản lý các giao dịch ở mức cao hơn. Đây có thể là yêu cầu, nhưng điều này có thể quá hạn chế. Những gì bạn cần là bắt đầu giao dịch ở đâu đó giữa mã lớp trình bày và mã lớp kinh doanh, nhưng không cần phải sao chép chính mình. Nói cách khác, bạn muốn có thể áp dụng một cách minh bạch mối quan tâm xuyên suốt này, mà không cần phải viết lại nhiều lần. Đây là một trong nhiều lý do mà tôi bắt đầu sử dụng thiết kế ứng dụng như được mô tả here một vài năm trước đây, nơi hoạt động kinh doanh được xác định bằng cách sử dụng đối tượng tin nhắn và logic kinh doanh tương ứng của họ được ẩn đằng sau một giao diện chung. Sau khi áp dụng các mẫu này, bạn sẽ có một điểm chặn rất rõ ràng nơi bạn có thể bắt đầu giao dịch với các kết nối tương ứng của chúng và cho phép toàn bộ hoạt động kinh doanh chạy trong cùng một giao dịch đó. Ví dụ, bạn có thể sử dụng mã chung sau đây có thể áp dụng xung quanh tất cả các mảnh logic kinh doanh trong ứng dụng của bạn:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> 
{ 
    private readonly ICommandHandler<TCommand> decorated;  
    public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> decorated) { 
     this.decorated = decorated; 
    } 

    public void Handle(TCommand command) { 
     using (var scope = new TransactionScope()) { 
      this.decorated.Handle(command); 
      scope.Complete(); 
     } 
    } 
} 

Mã này kết thúc tốt đẹp tất cả mọi thứ xung quanh một TransactionScope. Điều này cho phép kho lưu trữ của bạn chỉ cần mở và đóng một kết nối; wrapper này sẽ đảm bảo rằng cùng một kết nối được sử dụng dù sao. Bằng cách này, bạn có thể tiêm một IConnectionFactory trừu tượng vào kho lưu trữ của bạn và để kho lưu trữ đóng trực tiếp kết nối vào cuối cuộc gọi phương thức của nó, trong khi dưới nắp .NET sẽ giữ kết nối thực được mở.

2

Tạo nhà máy lưu trữ tạo IDisposable kho lưu trữ.

public interface IRepository : IDisposable { 
    User GetUser(); 
    //other methods, doesn't matter 
} 

public interface IRepositoryFactory { 
    IRepository Create(); 
} 

Bạn tạo chúng trong quá trình sử dụng và chúng được xử lý khi hoàn tất.

using(var repository = factory.Create()) { 
    var user = repository.GetUser(); 
    //do something with user 
} 

Bạn có thể tiêm nhà máy và tạo kho khi cần.

+0

Xin vui lòng không, [không có nhà máy] (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=100). – Steven

-1

tôi sẽ tạo ra một kết nối máy ...

public class ConnectionFactory 
{ 
    public IDbConnection Create() 
    { 
     // your logic here 
    } 
} 

Bây giờ làm cho nó một sự phụ thuộc vào kho của bạn, và sử dụng nó bên trong bạn vị trí lưu trữ cũng ... Bạn don `t cần một kho IDisposable, bạn cần phải vứt bỏ kết nối. Tôi đang ở trên điện thoại di động, vì vậy thật khó để cung cấp cho bạn một ví dụ chi tiết hơn. Nếu bạn cần, tôi có thể chỉnh sửa nó sau này với một ví dụ chi tiết hơn.

+0

Đây là một lớp cụ thể đề cập đến các chi tiết triển khai.Giải pháp nằm ở mức trừu tượng của giao diện. – JuanR

+0

Với nhà máy kết nối, anh ta sẽ chỉ có thể vứt bỏ kết nối chứ không phải kho lưu trữ. Đó là ok -1, đó không phải là một câu trả lời hoàn chỉnh. Tôi sẽ sửa nó sau, nhưng tôi quên mất. –

2

Vì vậy, bạn đã đề cập rằng

chúng tôi không muốn có các kết nối lâu dài trong ứng dụng

đó là hoàn toàn đúng!

Bạn cần mở kết nối trong mỗi triển khai phương thức lưu trữ, thực thi truy vấn hoặc lệnh đối với cơ sở dữ liệu, sau đó đóng kết nối. Tôi không thấy lý do tại sao bạn sẽ phơi bày bất cứ điều gì giống như kết nối với lớp miền. Nói cách khác, loại bỏ các phương thức CreateConnection() khỏi kho. Họ không cần thiết. Mỗi phương pháp sẽ mở/đóng nó bên trong, khi được triển khai.

Có những lúc bạn sẽ muốn quấn một vài phương pháp kho gọi vào một cái gì đó, nhưng đó là chỉ liên quan đến giao dịch, không kết nối. Trong trường hợp đó có 2 câu trả lời:

  1. Kiểm tra tính chính xác của việc thực hiện Repository pattern của bạn. Bạn chỉ nên có kho lưu trữ cho Aggregate Roots. Không phải mọi thực thể đều đủ điều kiện là gốc tổng hợp. Tổng hợp gốc là ranh giới giao dịch được bảo đảm, vì vậy bạn không nên lo lắng về giao dịch ra khỏi kho - mỗi cuộc gọi phương thức lưu trữ sẽ tự động theo ranh giới, vì nó chỉ xử lý một gốc tổng hợp tại một thời điểm.
  2. Nếu bạn vẫn cần phải thực hiện các thao tác đối với một số nguồn tổng hợp trong một lần, thì bạn sẽ phải triển khai một mẫu có tên là Unit of Work. Đây thực chất là một triển khai giao dịch tầng kinh doanh.Tôi không khuyên bạn nên dựa vào các tính năng giao dịch tích hợp vào các công nghệ lưu trữ cho trường hợp cụ thể này (một số tập hợp trong một lần), bởi vì chúng khác nhau giữa nhà cung cấp với nhà cung cấp (trong khi các DB quan hệ có thể đảm bảo một số nguồn gốc tổng hợp chỉ một lần, NoSQL DBs) đảm bảo tổng hợp duy nhất tại một thời điểm).

Từ kinh nghiệm của tôi, bạn chỉ cần sửa đổi tổng hợp một lần tại một thời điểm. Đơn vị công việc là một trường hợp rất hiếm. Vì vậy, chỉ cần suy nghĩ lại kho của bạn và rễ tổng hợp, mà nên làm các trick cho bạn.

Chỉ để có đầy đủ câu trả lời - bạn cần có giao diện kho lưu trữ mà bạn đã có. Do đó, cách tiếp cận của bạn đã được kiểm thử đơn vị.

1

Bạn đang trộn táo với cam và đào.

Có ba khái niệm tại chơi ở đây:

  • Hợp đồng kho
  • Các chi tiết thực hiện
  • đời Repository quản lý

kho của bạn khái niệm giữ người dùng, nhưng nó có một CreateConnection() phương pháp chỉ ra chi tiết của việc thực hiện (một kết nối là cần thiết). Không tốt.

Những gì bạn cần làm là xóa phương thức CreateConnection() khỏi giao diện. Bây giờ bạn có một định nghĩa thực sự về kho lưu trữ của người dùng (bằng cách này, bạn nên gọi nó là IUserRepository).

Bật để các chi tiết thực hiện:

Bạn có một kho lưu trữ người dùng nói chuyện với một cơ sở dữ liệu, vì vậy bạn nên thực hiện một lớp DatabaseUserRepository. Đây là nơi các chi tiết tạo kết nối và xử lý nó được lưu trữ. Bạn có thể quyết định giữ kết nối mở suốt đời của đối tượng hoặc bạn có thể quyết định tốt nhất là mở và đóng kết nối cho mọi thao tác.

Bật để tuổi thọ của đối tượng:

Bạn có một container phụ thuộc. Bạn có thể đã quyết định bạn muốn kho lưu trữ của bạn được sử dụng như một singleton vì lớp DatabaseUserRepository của bạn thực hiện các hoạt động nguyên tử, an toàn luồng, hoặc bạn có thể muốn kho lưu trữ của bạn được thoáng qua để một cá thể mới được tạo ra bởi vì nó thực hiện một đơn vị mẫu làm việc có nghĩa là tất cả các thay đổi được lưu cùng nhau (ví dụ: EF.SaveChanges()).

Xem sự khác biệt ngay bây giờ?

Giao diện cho phép kiểm tra đơn vị. Bất kỳ thành phần nào cần dữ liệu từ cơ sở dữ liệu đều có thể sử dụng kho lưu trữ giả để tải rác từ bộ nhớ (ví dụ: MemoryUserRepository).

Triển khai cung cấp một kho lưu trữ người dùng trong cơ sở dữ liệu. Bạn thậm chí có thể quyết định có hai phiên bản của lớp này thực hiện giao diện cùng với các chiến lược hoặc mẫu khác nhau.

Thời gian tồn tại của kho lưu trữ sẽ được thiết lập theo chi tiết triển khai trong vùng chứa phụ thuộc.

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