Mô hình thiết kế repository | Hỏi gì?

Mô hình thiết kế repository, được định nghĩa bởi Eric Evens trong cuốn sách Domain Driven Design của mình, là một trong những mô hình thiết kế hữu ích nhất và được áp dụng rộng rãi nhất từng được phát minh. Bất kỳ ứng dụng nào cũng phải hoạt động với sự kiên trì và với một số loại danh sách các mục. Đây có thể là người dùng, sản phẩm, mạng, đĩa hoặc bất kỳ ứng dụng nào của bạn. Nếu bạn có một blog chẳng hạn, bạn phải đối phó với danh sách các bài đăng trên blog và danh sách các bình luận. Vấn đề mà tất cả các logic quản lý danh sách này có điểm chung là làm thế nào để kết nối logic kinh doanh, nhà máy và sự kiên trì.

Mẫu thiết kế nhà máy

Như chúng tôi đã đề cập trong đoạn giới thiệu, Kho lưu trữ sẽ kết nối các nhà máy với Cổng thông tin (sự kiên trì). Đây cũng là những mô hình thiết kế và nếu bạn không quen thuộc với chúng, đoạn này sẽ làm sáng tỏ chủ đề này.

Một nhà máy là một mô hình thiết kế đơn giản xác định một cách thuận tiện để tạo các đối tượng. Nó là một lớp hoặc tập hợp các lớp chịu trách nhiệm tạo các đối tượng mà logic kinh doanh của chúng ta cần. Theo truyền thống, một nhà máy có một phương thức gọi là “make ()” và nó sẽ biết cách lấy tất cả thông tin cần thiết để xây dựng một đối tượng và tự xây dựng đối tượng và trả lại một đối tượng sẵn sàng sử dụng cho logic nghiệp vụ.

Dưới đây là một chút nữa về Mô hình nhà máy trong hướng dẫn cũ hơn của Nettuts +: Hướng dẫn cho người mới bắt đầu về các mô hình thiết kế. Nếu bạn muốn có cái nhìn sâu hơn về Mẫu nhà máy, hãy xem mô hình thiết kế đầu tiên trong khóa học Mẫu thiết kế Agile mà chúng tôi có trên Tuts +.

Mẫu cổng

Còn được gọi là “Cổng dữ liệu bảng” là một mô hình đơn giản cung cấp kết nối giữa logic nghiệp vụ và cơ sở dữ liệu. Trách nhiệm chính của nó là thực hiện các truy vấn trên cơ sở dữ liệu và cung cấp dữ liệu được truy xuất trong cấu trúc dữ liệu điển hình cho ngôn ngữ lập trình (như một mảng trong PHP). Dữ liệu này sau đó thường được lọc và sửa đổi trong mã PHP để chúng tôi có thể có được thông tin và các biến cần thiết để tạo các đối tượng của chúng tôi. Thông tin này sau đó phải được chuyển đến các nhà máy.

Mẫu thiết kế cổng được giải thích và minh họa rất chi tiết trong hướng dẫn của Nettuts + về Phát triển hướng tới một lớp kiên trì. Ngoài ra, trong cùng một mô hình Thiết kế Agile, bài học mô hình thiết kế thứ hai là về chủ đề này.

Những vấn đề chúng ta cần giải quyết

Sao chép bằng cách xử lý dữ liệu

Có thể không rõ ràng ngay từ cái nhìn đầu tiên, nhưng việc kết nối Gateways với Factories có thể dẫn đến nhiều sự trùng lặp. Bất kỳ phần mềm có kích thước đáng kể nào cũng cần tạo cùng một đối tượng từ những nơi khác nhau. Ở mỗi nơi, bạn sẽ cần sử dụng Cổng để truy xuất một tập hợp dữ liệu thô, bộ lọc và làm việc với dữ liệu đó để sẵn sàng gửi đến các nhà máy. Từ tất cả những nơi này, bạn sẽ gọi cùng một nhà máy có cùng cấu trúc dữ liệu nhưng rõ ràng là có dữ liệu khác nhau. Các đối tượng của bạn sẽ được các nhà máy tạo ra và cung cấp cho bạn. Điều này, chắc chắn sẽ dẫn đến rất nhiều sự trùng lặp trong thời gian. Và sự trùng lặp sẽ được lan truyền khắp các lớp hoặc mô-đun xa và sẽ khó nhận thấy và sửa chữa.

Sao chép bằng cách truy xuất logic truy xuất dữ liệu

Một vấn đề khác chúng ta có là làm thế nào để diễn đạt các truy vấn chúng ta cần thực hiện với sự trợ giúp của Cổng. Mỗi khi chúng ta cần một số thông tin từ Cổng, chúng ta cần suy nghĩ về những gì chúng ta cần chính xác? Chúng ta có cần tất cả dữ liệu về một chủ đề không? Chúng ta chỉ cần một số thông tin cụ thể? Bạn có muốn truy xuất một nhóm cụ thể từ cơ sở dữ liệu và thực hiện lọc sắp xếp hoặc tinh chỉnh trong ngôn ngữ lập trình của chúng tôi không? Tất cả những câu hỏi này cần được giải quyết mỗi khi chúng tôi lấy thông tin từ lớp lưu trữ thông qua Cổng. Mỗi lần chúng ta làm điều này, chúng ta sẽ phải đưa ra một giải pháp. Theo thời gian, khi ứng dụng của chúng tôi phát triển, chúng tôi sẽ phải đối mặt với những tình huống khó xử tương tự ở những nơi khác nhau trong ứng dụng của chúng tôi. Vô tình chúng tôi sẽ đưa ra các giải pháp hơi khác nhau cho cùng một vấn đề. Điều này không chỉ mất thêm thời gian và công sức mà còn dẫn đến sự trùng lặp, chủ yếu rất khó nhận ra. Đây là loại trùng lặp nguy hiểm nhất.

Sao chép bằng cách thực hiện lại dữ liệu logic

Trong hai đoạn trước chúng ta chỉ nói về việc truy xuất dữ liệu. Nhưng Gateway là hai chiều. Logic kinh doanh của chúng tôi là hai chiều. Chúng ta phải bằng cách nào đó kiên trì đối tượng của chúng tôi. Điều này một lần nữa dẫn đến rất nhiều sự lặp lại nếu chúng ta muốn thực hiện logic này khi cần trong các mô-đun và các lớp khác nhau của ứng dụng.

Các khái niệm chính

Kho lưu trữ dữ liệu

Kho lưu trữ có thể hoạt động theo hai cách: truy xuất dữ liệu và lưu trữ dữ liệu.

UMLRepoQuery

Khi được sử dụng để truy xuất các đối tượng từ tính bền vững, Kho lưu trữ sẽ được gọi với một truy vấn tùy chỉnh. Truy vấn này có thể là một phương thức cụ thể theo tên hoặc một phương thức phổ biến hơn với các tham số. Kho lưu trữ có trách nhiệm cung cấp và thực hiện các phương thức truy vấn này. Khi một phương thức như vậy được gọi, Kho lưu trữ sẽ liên hệ với Cổng để lấy dữ liệu thô từ sự tồn tại. Cổng sẽ cung cấp dữ liệu đối tượng thô (như một mảng có giá trị). Sau đó, Kho sẽ lấy dữ liệu này, thực hiện các phép biến đổi cần thiết và gọi các phương thức Factory phù hợp. Các nhà máy sẽ cung cấp các đối tượng được xây dựng với dữ liệu do Kho lưu trữ cung cấp. Kho lưu trữ sẽ thu thập các đối tượng này và trả về chúng dưới dạng một tập hợp các đối tượng (như một mảng các đối tượng hoặc đối tượng tập hợp như được định nghĩa trong bài học Mẫu tổng hợp trong khóa học Mẫu thiết kế Agile).

Kho lưu trữ dữ liệu bền bỉ

Cách thứ hai mà Kho lưu trữ có thể hoạt động là cung cấp logic cần thực hiện để trích xuất thông tin từ một đối tượng và duy trì nó. Điều này có thể đơn giản như việc tuần tự hóa đối tượng và gửi dữ liệu tuần tự đến
Cổng để duy trì nó hoặc tinh vi như tạo ra các mảng thông tin với tất cả các trường và trạng thái của một đối tượng.

UMLRepoPersist

Khi được sử dụng để duy trì thông tin, lớp khách là lớp giao tiếp trực tiếp với Nhà máy. Hãy tưởng tượng một kịch bản khi một bình luận mới được đăng lên một bài đăng trên blog. Một đối tượng Nhận xét được tạo bởi logic nghiệp vụ của chúng tôi (lớp Máy khách) và sau đó được gửi đến Kho lưu trữ để được duy trì. Kho lưu trữ sẽ duy trì các đối tượng bằng Gateway và tùy chọn lưu trữ chúng trong một danh sách cục bộ trong danh sách bộ nhớ. Dữ liệu cần phải được chuyển đổi vì chỉ có những trường hợp hiếm hoi khi các đối tượng thực sự có thể được lưu trực tiếp vào một hệ thống bền bỉ.

Nối những chấm lại với nhau

Hình ảnh bên dưới là chế độ xem cấp cao hơn về cách tích hợp Kho lưu trữ giữa các nhà máy, Cổng và Máy khách.

UMLRepository

Ở trung tâm của lược đồ là Kho lưu trữ của chúng tôi. Ở bên trái, là một Giao diện cho Cổng, một triển khai và chính sự kiên trì. Ở bên phải, có Giao diện cho các nhà máy và triển khai Nhà máy. Cuối cùng, trên đầu có lớp khách.

Vì nó có thể được quan sát từ hướng mũi tên, các phần phụ thuộc được đảo ngược. Kho lưu trữ chỉ phụ thuộc vào các giao diện trừu tượng cho Factories và Gateways. Gateway phụ thuộc vào giao diện của nó và sự bền bỉ mà nó cung cấp. Nhà máy chỉ phụ thuộc vào Giao diện của nó. Máy khách phụ thuộc vào Kho lưu trữ, có thể chấp nhận được vì Kho lưu trữ có xu hướng ít cụ thể hơn Máy khách.

HighLevelDesign

Đặt trong quan điểm, đoạn văn trên tôn trọng kiến trúc cấp cao của chúng tôi và hướng phụ thuộc mà chúng tôi muốn đạt được.

Quản lý bình luận cho bài viết trên blog với một kho lưu trữ

Bây giờ chúng ta đã thấy lý thuyết, đã đến lúc cho một ví dụ thực tế. Hãy tưởng tượng chúng ta có một blog nơi chúng ta có các đối tượng Post và các đối tượng Comment. Nhận xét thuộc về Bài đăng và chúng tôi phải tìm cách duy trì chúng và truy xuất chúng.

Lời bình luận

Chúng tôi sẽ bắt đầu với một bài kiểm tra sẽ buộc chúng tôi phải suy nghĩ về những gì đối tượng Nhận xét của chúng tôi nên chứa.

class RepositoryTest extends PHPUnit_Framework_TestCase { function testACommentHasAllItsComposingParts() { $postId = 1; $commentAuthor = “Joe”; $commentAuthorEmail = “[email protected]”; $commentSubject = “Joe Has an Opinion about the Repository Pattern”; $commentBody = “I think it is a good idea to use the Repository Pattern to persist and retrieve objects.”; $comment = new Comment($postId, $commentAuthor, $commentAuthorEmail, $commentSubject, $commentBody); } }

Thoạt nhìn, một Nhận xét sẽ chỉ là một đối tượng dữ liệu. Nó có thể không có bất kỳ chức năng nào, nhưng đó là tùy thuộc vào bối cảnh ứng dụng của chúng tôi quyết định. Đối với ví dụ này chỉ cần giả sử nó là một đối tượng dữ liệu đơn giản. Xây dựng với một tập hợp các biến.

class Comment { }

Chỉ bằng cách tạo một lớp trống và yêu cầu nó trong bài kiểm tra làm cho nó vượt qua.

require_once ‘../Comment.php’; class RepositoryTest extends PHPUnit_Framework_TestCase { [ … ] }

Nhưng đó là xa hoàn hảo. Thử nghiệm của chúng tôi chưa kiểm tra bất cứ điều gì. Chúng ta hãy ép mình viết tất cả các getters trên lớp Comment.

function testACommentsHasAllItsComposingParts() { $postId = 1; $commentAuthor = “Joe”; $commentAuthorEmail = “[email protected]”; $commentSubject = “Joe Has an Opinion about the Repository Pattern”; $commentBody = “I think it is a good idea to use the Repository Pattern to persist and retrieve objects.”; $comment = new Comment($postId, $commentAuthor, $commentAuthorEmail, $commentSubject, $commentBody); $this->assertEquals($postId, $comment->getPostId()); $this->assertEquals($commentAuthor, $comment->getAuthor()); $this->assertEquals($commentAuthorEmail, $comment->getAuthorEmail()); $this->assertEquals($commentSubject, $comment->getSubject()); $this->assertEquals($commentBody, $comment->getBody()); }

Để kiểm soát độ dài của hướng dẫn, tôi đã viết tất cả các xác nhận cùng một lúc và chúng tôi cũng sẽ thực hiện chúng cùng một lúc. Trong cuộc sống thực, đưa họ từng người một.

class Comment { private $postId; private $author; private $authorEmail; private $subject; private $body; function __construct($postId, $author, $authorEmail, $subject, $body) { $this->postId = $postId; $this->author = $author; $this->authorEmail = $authorEmail; $this->subject = $subject; $this->body = $body; } public function getPostId() { return $this->postId; } public function getAuthor() { return $this->author; } public function getAuthorEmail() { return $this->authorEmail; } public function getSubject() { return $this->subject; } public function getBody() { return $this->body; } }

Ngoại trừ danh sách các biến riêng tư, phần còn lại của mã được tạo bởi IDE của tôi, NetBeans, do đó, việc kiểm tra mã được tạo tự động có thể hơi tốn một chút thời gian. Nếu bạn không tự viết những dòng này, hãy thoải mái thực hiện chúng trực tiếp và đừng bận tâm với các bài kiểm tra cho setters và constructor. Tuy nhiên, bài kiểm tra đã giúp chúng tôi thể hiện tốt hơn các ý tưởng của mình và tài liệu tốt hơn về những gì lớp Nhận xét của chúng tôi sẽ chứa.

Chúng ta cũng có thể coi các phương thức kiểm tra và các lớp kiểm tra này là các lớp “Máy khách” từ các lược đồ.

Cổng vào sự bền bỉ của chúng tôi

Để giữ cho ví dụ này đơn giản nhất có thể, chúng tôi sẽ chỉ triển khai InMemoryPersistence để chúng tôi không làm phức tạp sự tồn tại của chúng tôi với các hệ thống tệp hoặc cơ sở dữ liệu.

require_once ‘../InMemoryPersistence.php’; class InMemoryPersistenceTest extends PHPUnit_Framework_TestCase { function testItCanPerisistAndRetrieveASingleDataArray() { $data = array(‘data’); $persistence = new InMemoryPersistence(); $persistence->persist($data); $this->assertEquals($data, $persistence->retrieve(0)); } }

Như thường lệ, chúng tôi bắt đầu với thử nghiệm đơn giản nhất có thể thất bại và cũng buộc chúng tôi phải viết một số mã. Thử nghiệm này tạo ra một đối tượng InMemoryPersistence mới và cố gắng duy trì và lấy một mảng gọi là dữ liệu.

require_once __DIR__ . ‘/Persistence.php’; class InMemoryPersistence implements Persistence { private $data = array(); function persist($data) { $this->data = $data; } function retrieve($id) { return $this->data; } }

Mã đơn giản nhất để làm cho nó vượt qua chỉ là giữ dữ liệu $ đến trong một biến riêng tư và trả về trong phương thức truy xuất. Mã như hiện tại không qua
n tâm đến biến được gửi trong $ id. Đó là điều đơn giản nhất có thể làm cho bài kiểm tra vượt qua. Chúng tôi cũng đã tự do giới thiệu và thực hiện một giao diện gọi là Sự kiên trì.

interface Persistence { function persist($data); function retrieve($ids); }

Giao diện này xác định hai phương thức mà bất kỳ Gateway nào cần thực hiện. Kiên trì và lấy. Như bạn có thể đã đoán, Gateway của chúng tôi là lớp InMemoryPersistence và sự bền bỉ vật lý của chúng tôi là biến riêng giữ dữ liệu của chúng tôi trong bộ nhớ. Nhưng chúng ta hãy quay trở lại việc thực hiện điều này trong sự kiên trì của bộ nhớ.

function testItCanPerisistSeveralElementsAndRetrieveAnyOfThem() { $data1 = array(‘data1’); $data2 = array(‘data2’); $persistence = new InMemoryPersistence(); $persistence->persist($data1); $persistence->persist($data2); $this->assertEquals($data1, $persistence->retrieve(0)); $this->assertEquals($data2, $persistence->retrieve(1)); }

Chúng tôi đã thêm một bài kiểm tra. Trong phần này, chúng tôi tồn tại hai mảng dữ liệu khác nhau. Chúng tôi hy vọng có thể lấy từng cái một.

require_once __DIR__ . ‘/Persistence.php’; class InMemoryPersistence implements Persistence { private $data = array(); function persist($data) { $this->data[] = $data; } function retrieve($id) { return $this->data[$id]; } }

Các thử nghiệm buộc chúng tôi phải thay đổi một chút mã của chúng tôi. Bây giờ chúng ta cần thêm dữ liệu vào mảng của chúng ta, không chỉ thay thế nó bằng dữ liệu được gửi để duy trì (). Chúng ta cũng cần xem xét tham số $ id và trả về phần tử tại chỉ mục đó.

Điều này là đủ cho InMemoryPersistence của chúng tôi. Nếu cần, chúng ta có thể sửa đổi nó sau.

Nhà máy của chúng tôi

Chúng tôi có một Khách hàng (các thử nghiệm của chúng tôi), sự kiên trì với Cổng và các đối tượng Nhận xét vẫn tồn tại. Điều còn thiếu tiếp theo là Nhà máy của chúng tôi.

Chúng tôi đã bắt đầu mã hóa với một tệp Rep repositoryTest. Thử nghiệm này, tuy nhiên, thực sự đã tạo ra một đối tượng Nhận xét. Bây giờ chúng tôi cần tạo các thử nghiệm để xác minh xem Nhà máy của chúng tôi có thể tạo các đối tượng Nhận xét hay không. Có vẻ như chúng tôi đã có lỗi trong đánh giá và thử nghiệm của chúng tôi có nhiều khả năng là thử nghiệm cho Nhà máy sắp tới của chúng tôi hơn là cho Kho lưu trữ của chúng tôi. Chúng ta có thể di chuyển nó vào một tệp thử nghiệm khác, CommentFactoryTest.

require_once ‘../Comment.php’; class CommentFactoryTest extends PHPUnit_Framework_TestCase { function testACommentsHasAllItsComposingParts() { $postId = 1; $commentAuthor = “Joe”; $commentAuthorEmail = “[email protected]”; $commentSubject = “Joe Has an Opinion about the Repository Pattern”; $commentBody = “I think it is a good idea to use the Repository Pattern to persist and retrieve objects.”; $comment = new Comment($postId, $commentAuthor, $commentAuthorEmail, $commentSubject, $commentBody); $this->assertEquals($postId, $comment->getPostId()); $this->assertEquals($commentAuthor, $comment->getAuthor()); $this->assertEquals($commentAuthorEmail, $comment->getAuthorEmail()); $this->assertEquals($commentSubject, $comment->getSubject()); $this->assertEquals($commentBody, $comment->getBody()); } }

Bây giờ, thử nghiệm này rõ ràng vượt qua. Và trong khi đó là một thử nghiệm chính xác, chúng ta nên xem xét sửa đổi nó. Chúng tôi muốn tạo một đối tượng Factory, chuyển vào một mảng và yêu cầu nó tạo một Comment cho chúng tôi.

require_once ‘../CommentFactory.php’; class CommentFactoryTest extends PHPUnit_Framework_TestCase { function testACommentsHasAllItsComposingParts() { $postId = 1; $commentAuthor = “Joe”; $commentAuthorEmail = “[email protected]”; $commentSubject = “Joe Has an Opinion about the Repository Pattern”; $commentBody = “I think it is a good idea to use the Repository Pattern to persist and retrieve objects.”; $commentData = array($postId, $commentAuthor, $commentAuthorEmail, $commentSubject, $commentBody); $comment = (new CommentFactory())->make($commentData); $this->assertEquals($postId, $comment->getPostId()); $this->assertEquals($commentAuthor, $comment->getAuthor()); $this->assertEquals($commentAuthorEmail, $comment->getAuthorEmail()); $this->assertEquals($commentSubject, $comment->getSubject()); $this->assertEquals($commentBody, $comment->getBody()); } }

Chúng ta không bao giờ nên đặt tên cho các lớp của chúng tôi dựa trên mô hình thiết kế mà chúng triển khai, nhưng Factory và Rep Kho không chỉ đại diện cho mô hình thiết kế. Cá nhân tôi không có gì chống lại việc bao gồm hai từ này trong tên của lớp chúng tôi. Tuy nhiên, tôi vẫn khuyến khích và tôn trọng khái niệm không đặt tên các lớp của chúng ta sau các mô hình thiết kế mà chúng ta sử dụng cho các mô hình còn lại.

Thử nghiệm này chỉ khác một chút so với thử nghiệm trước, nhưng không thành công. Nó cố gắng tạo một đối tượng CommentFactory. Lớp học đó chưa tồn tại. Chúng tôi cũng cố gắng gọi một phương thức make () trên đó với một mảng chứa tất cả thông tin của một nhận xét dưới dạng một mảng. Phương pháp này được xác định trong giao diện Factory.

interface Factory { function make($data); }

Đây là một giao diện Factory rất phổ biến. Nó định nghĩa phương thức cần thiết duy nhất cho một nhà máy, phương thức thực sự tạo ra các đối tượng chúng ta muốn.

require_once __DIR__ . ‘/Factory.php’; require_once __DIR__ . ‘/Comment.php’; class CommentFactory implements Factory { function make($components) { return new Comment($components[0], $components[1], $components[2], $components[3], $components[4]); } }

Và CommentFactory thực hiện giao diện Factory thành công bằng cách lấy tham số $ thành phần trong phương thức make () của nó, tạo và trả về một đối tượng Comment mới với thông tin từ đó.

Chúng tôi sẽ giữ cho sự kiên trì và logic tạo đối tượng của chúng tôi đơn giản nhất có thể. Chúng tôi có thể, đối với hướng dẫn này, bỏ qua một cách an toàn mọi xử lý lỗi, xác nhận và ném ngoại lệ. Chúng tôi sẽ dừng lại ở đây với sự kiên trì và thực hiện tạo đối tượng.

Sử dụng một kho lưu trữ để nhận xét liên tục

Như chúng ta đã thấy ở trên, chúng ta có thể sử dụng Kho lưu trữ theo hai cách. Để lấy thông tin từ sự kiên trì và cũng để duy trì thông tin trên lớp kiên trì. Sử dụng TDD, phần lớn thời gian, dễ dàng hơn để bắt đầu với phần lưu (duy trì) của logic và sau đó sử dụng triển khai hiện có đó để kiểm tra truy xuất dữ liệu.

require_once ‘../../../vendor/autoload.php’; require_once ‘../CommentRepository.php’; require_once ‘../CommentFactory.php’; class RepositoryTest extends PHPUnit_Framework_TestCase { protected function tearDown() { Mockery::close(); } function testItCallsThePersistenceWhenAddingAComment() { $persistanceGateway = Mockery::mock(‘Persistence’); $commentRepository = new CommentRepository($persistanceGateway); $commentData = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment = (new CommentFactory())->make($commentData); $persistanceGateway->shouldReceive(‘persist’)->once()
->with($commentData); $commentRepository->add($comment); } }

Chúng tôi sử dụng Mockery để chế nhạo sự kiên trì của chúng tôi và đưa đối tượng bị chế giễu đó vào Kho lưu trữ. Sau đó, chúng tôi gọi add () trên kho lưu trữ. Phương pháp này có một tham số loại Nhận xét. Chúng tôi hy vọng sự kiên trì sẽ được gọi với một mảng dữ liệu tương tự như $ commentData.

require_once __DIR__ . ‘/InMemoryPersistence.php’; class CommentRepository { private $persistence; function __construct(Persistence $persistence = null) { $this->persistence = $persistence ? : new InMemoryPersistence(); } function add(Comment $comment) { $this->persistence->persist(array( $comment->getPostId(), $comment->getAuthor(), $comment->getAuthorEmail(), $comment->getSubject(), $comment->getBody() )); } }

Như bạn có thể thấy, phương thức add () khá thông minh. Nó gói gọn kiến thức về cách chuyển đổi một đối tượng PHP thành một mảng đơn giản có thể sử dụng được bằng sự kiên trì. Hãy nhớ rằng, cổng kiên trì của chúng tôi thường là một đối tượng chung cho tất cả dữ liệu của chúng tôi. Nó có thể và sẽ duy trì tất cả dữ liệu của ứng dụng của chúng tôi, vì vậy việc gửi tới các đối tượng sẽ khiến nó làm quá nhiều: cả chuyển đổi và tồn tại hiệu quả.

Khi bạn có một lớp InMemoryPersistence như chúng tôi, nó sẽ rất nhanh. Chúng ta có thể sử dụng nó như là một thay thế để chế nhạo cổng.

function testAPersistedCommentCanBeRetrievedFromTheGateway() { $persistanceGateway = new InMemoryPersistence(); $commentRepository = new CommentRepository($persistanceGateway); $commentData = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment = (new CommentFactory())->make($commentData); $commentRepository->add($comment); $this->assertEquals($commentData, $persistanceGateway->retrieve(0)); }

Tất nhiên, nếu bạn không có sự thực thi trong bộ nhớ về sự kiên trì của mình, thì chế giễu là cách hợp lý duy nhất để đi. Nếu không, bài kiểm tra của bạn sẽ quá chậm để trở nên thực tế.

function testItCanAddMultipleCommentsAtOnce() { $persistanceGateway = Mockery::mock(‘Persistence’); $commentRepository = new CommentRepository($persistanceGateway); $commentData1 = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment1 = (new CommentFactory())->make($commentData1); $commentData2 = array(2, ‘y’, ‘y’, ‘y’, ‘y’); $comment2 = (new CommentFactory())->make($commentData2); $persistanceGateway->shouldReceive(‘persist’)->once()->with($commentData1); $persistanceGateway->shouldReceive(‘persist’)->once()->with($commentData2); $commentRepository->add(array($comment1, $comment2)); }

Bước hợp lý tiếp theo của chúng tôi là thực hiện một cách để thêm một số bình luận cùng một lúc. Dự án của bạn có thể không yêu cầu chức năng này và nó không phải là thứ được yêu cầu bởi mô hình. Trong thực tế, Mẫu lưu trữ chỉ nói rằng nó sẽ cung cấp một truy vấn tùy chỉnh và ngôn ngữ bền vững cho logic kinh doanh của chúng tôi. Vì vậy, nếu logic bụi rậm của chúng tôi cảm thấy cần phải thêm một số nhận xét cùng một lúc, thì Kho lưu trữ là nơi logic sẽ cư trú.

function add($commentData) { if (is_array($commentData)) foreach ($commentData as $comment) $this->persistence->persist(array( $comment->getPostId(), $comment->getAuthor(), $comment->getAuthorEmail(), $comment->getSubject(), $comment->getBody() )); else $this->persistence->persist(array( $commentData->getPostId(), $commentData->getAuthor(), $commentData->getAuthorEmail(), $commentData->getSubject(), $commentData->getBody() )); }

Và cách đơn giản nhất để vượt qua bài kiểm tra là chỉ cần xác minh xem tham số chúng ta đang nhận có phải là một mảng hay không. Nếu nó là một mảng, chúng ta sẽ quay vòng qua từng phần tử và gọi sự tồn tại với mảng chúng ta tạo từ một đối tượng Nhận xét duy nhất. Và trong khi mã này đúng về mặt cú pháp và làm cho bài kiểm tra vượt qua, nó đưa ra một sự trùng lặp nhỏ mà chúng ta có thể thoát khỏi khá dễ dàng.

function add($commentData) { if (is_array($commentData)) foreach ($commentData as $comment) $this->addOne($comment); else $this->addOne($commentData); } private function addOne(Comment $comment) { $this->persistence->persist(array( $comment->getPostId(), $comment->getAuthor(), $comment->getAuthorEmail(), $comment->getSubject(), $comment->getBody() )); }

Khi tất cả các thử nghiệm đều có màu xanh, đó là thời gian để tái cấu trúc trước khi chúng tôi tiếp tục với thử nghiệm thất bại tiếp theo. Và chúng tôi đã làm điều đó với phương thức add (). Chúng tôi trích xuất việc thêm một nhận xét vào một phương thức riêng tư và gọi nó từ hai nơi khác nhau trong phương thức add () công khai của chúng tôi. Điều này không chỉ làm giảm sự trùng lặp mà còn mở ra khả năng làm cho phương thức addOne () công khai và để logic nghiệp vụ quyết định nếu nó muốn thêm một hoặc một vài bình luận tại một thời điểm. Điều này sẽ dẫn đến việc triển khai Kho lưu trữ khác của chúng tôi, với các phương thức addOne () và addMany () khác. Nó sẽ là một triển khai hoàn toàn hợp pháp của Mẫu lưu trữ.

Lấy bình luận với kho lưu trữ của chúng tôi

Kho lưu trữ cung cấp ngôn ngữ truy vấn tùy chỉnh cho logic nghiệp vụ. Vì vậy, tên và chức năng của các phương thức truy vấn của Kho lưu trữ phụ thuộc rất nhiều vào yêu cầu của logic nghiệp vụ. Bạn xây dựng kho lưu trữ của mình khi bạn xây dựng logic kinh doanh của mình, vì bạn cần một phương thức truy vấn tùy chỉnh khác. Tuy nhiên, có ít nhất một hoặc hai phương thức mà bạn sẽ tìm thấy trên hầu hết mọi Kho lưu trữ.

function testItCanFindAllComments() { $repository = new CommentRepository(); $commentData1 = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment1 = (new CommentFactory())->make($commentData1); $commentData2 = array(2, ‘y’, ‘y’, ‘y’, ‘y’); $comment2 = (new CommentFactory())->make($commentData2); $repository->add($comment1); $repository->add($comment2); $this->assertEquals(array($comment1, $comment2), $repository->findAll()); }

Phương thức đầu tiên như vậy được gọi là findAll (). Điều này sẽ trả về tất cả các đối tượng mà kho lưu trữ chịu trách nhiệm, trong trường hợp của chúng tôi Nhận xét. Bài kiểm tra rất đơn giản, chúng tôi thêm một bình luận, sau đó là một bình luận khác và cuối cùng chúng tôi muốn gọi findAll () và nhận một danh sách chứa cả hai bình luận. Tuy nhiên, điều này không thể thực hiện được với InMemoryPersistence của chúng tôi vì nó là vào thời điểm này. Một bản cập nhật nhỏ là bắt buộc.

function retrieveAll() { return $this->data; }

Đó là nó. Chúng tôi đã thêm một phương thức lấy lại () chỉ trả về toàn bộ mảng dữ liệu $ từ lớp. Đơn giản và hiệu quả. Bây giờ là lúc triển khai findAll () trên CommentRep repository.

function findAll() { $allCommentsData = $this->persistence->retrieveAll(); $comments = array(); foreach ($allCommentsData as $commentData) $comments[] = $this->commentFactory->make($commentData); return $comments; }

findAll ()
sẽ gọi phương thức lấy lại tất cả () trên sự kiên trì của chúng tôi. Phương pháp đó cung cấp một mảng dữ liệu thô. findAll () sẽ quay vòng qua từng phần tử và sử dụng dữ liệu cần thiết để được chuyển đến Nhà máy. Nhà máy sẽ cung cấp một Nhận xét một lần. Một mảng với các nhận xét này sẽ được xây dựng và trả về vào cuối findAll (). Đơn giản và hiệu quả.

Một phương pháp phổ biến khác mà bạn sẽ tìm thấy trên các kho lưu trữ là tìm kiếm một đối tượng hoặc nhóm đối tượng cụ thể dựa trên khóa đặc trưng của chúng. Ví dụ: tất cả các bình luận của chúng tôi được kết nối với một bài đăng trên blog bằng một biến nội bộ $ postId. Tôi có thể tưởng tượng rằng trong logic kinh doanh của blog chúng tôi hầu như chúng tôi luôn muốn tìm tất cả các bình luận liên quan đến một bài đăng blog khi bài đăng trên blog đó được hiển thị. Vì vậy, một phương thức gọi là findByPostId ($ id) nghe có vẻ hợp lý với tôi.

function testItCanFindCommentsByBlogPostId() { $repository = new CommentRepository(); $commentData1 = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment1 = (new CommentFactory())->make($commentData1); $commentData2 = array(1, ‘y’, ‘y’, ‘y’, ‘y’); $comment2 = (new CommentFactory())->make($commentData2); $commentData3 = array(3, ‘y’, ‘y’, ‘y’, ‘y’); $comment3 = (new CommentFactory())->make($commentData3); $repository->add(array($comment1, $comment2)); $repository->add($comment3); $this->assertEquals(array($comment1, $comment2), $repository->findByPostId(1)); }

Chúng tôi chỉ tạo ra ba bình luận đơn giản. Hai cái đầu tiên có cùng $ postId = 1, cái thứ ba có $ postID = 3. Chúng tôi thêm tất cả chúng vào kho lưu trữ và sau đó chúng tôi mong đợi một mảng có hai mảng đầu tiên khi chúng tôi thực hiện findByPostId () cho $ postId = 1.

function findByPostId($postId) { return array_filter($this->findAll(), function ($comment) use ($postId){ return $comment->getPostId() == $postId; }); }

Việc thực hiện không thể đơn giản hơn. Chúng tôi tìm thấy tất cả các ý kiến bằng cách sử dụng phương thức findAll () đã triển khai của chúng tôi và chúng tôi lọc mảng. Chúng tôi không có cách nào để yêu cầu sự kiên trì thực hiện việc lọc cho chúng tôi, vì vậy chúng tôi sẽ thực hiện ở đây. Mã sẽ truy vấn từng đối tượng Nhận xét và so sánh $ postId của nó với đối tượng chúng tôi đã gửi dưới dạng tham số. Tuyệt quá. Bài kiểm tra đã qua. Nhưng tôi cảm thấy chúng ta đã bỏ lỡ một cái gì đó.

function testItCanFindCommentsByBlogPostId() { $repository = new CommentRepository(); $commentData1 = array(1, ‘x’, ‘x’, ‘x’, ‘x’); $comment1 = (new CommentFactory())->make($commentData1); $commentData2 = array(1, ‘y’, ‘y’, ‘y’, ‘y’); $comment2 = (new CommentFactory())->make($commentData2); $commentData3 = array(3, ‘y’, ‘y’, ‘y’, ‘y’); $comment3 = (new CommentFactory())->make($commentData3); $repository->add(array($comment1, $comment2)); $repository->add($comment3); $this->assertEquals(array($comment1, $comment2), $repository->findByPostId(1)); $this->assertEquals(array($comment3), $repository->findByPostId(3)); }

Thêm một xác nhận thứ hai để có được nhận xét thứ ba với phương thức findByPostID () cho thấy lỗi của chúng tôi. Bất cứ khi nào bạn có thể dễ dàng kiểm tra các đường dẫn hoặc trường hợp bổ sung, như trong trường hợp của chúng tôi với một xác nhận thêm đơn giản, bạn nên. Những xác nhận bổ sung đơn giản hoặc phương pháp thử nghiệm có thể tiết lộ các vấn đề tiềm ẩn. Giống như trong trường hợp của chúng tôi, Array_filter () không giới thiệu lại mảng kết quả. Và trong khi chúng ta có một mảng với các phần tử chính xác, các chỉ mục bị rối tung.

1) RepositoryTest::testItCanFindCommentsByBlogPostId Failed asserting that two arrays are equal. – Expected +++ Actual @@ @@ Array ( – 0 => Comment Object (…) + 2 => Comment Object (…) )

Bây giờ, bạn có thể coi đây là một thiếu sót của PHPUnit hoặc thiếu sót về logic kinh doanh của bạn. Tôi có xu hướng nghiêm ngặt với các chỉ số mảng vì tôi đã đốt tay với chúng một vài lần. Vì vậy, chúng ta nên coi lỗi là một vấn đề với logic của chúng tôi trong CommentRep repository.

function findByPostId($postId) { return array_values( array_filter($this->findAll(), function ($comment) use ($postId) { return $comment->getPostId() == $postId; }) ); }

Vâng. Thật đơn giản. Chúng tôi chỉ chạy kết quả thông qua mảng_values () trước khi trả lại. Nó sẽ độc đáo reindex mảng của chúng tôi. Nhiệm vụ đã hoàn thành.

Tổng kết

Và đó cũng là nhiệm vụ hoàn thành cho Kho lưu trữ của chúng tôi. Chúng tôi có một lớp có thể sử dụng bởi bất kỳ lớp logic kinh doanh nào khác, cung cấp một cách dễ dàng để duy trì và truy xuất các đối tượng. Nó cũng tách rời logic kinh doanh từ các nhà máy và cổng dữ liệu bền vững. Nó giảm sự trùng lặp logic và đơn giản hóa đáng kể các hoạt động kiên trì và truy xuất cho các bình luận của chúng tôi.

Hãy nhớ rằng, mô hình thiết kế này có thể được sử dụng cho tất cả các loại danh sách và khi bạn bắt đầu sử dụng nó, bạn sẽ thấy tính hữu dụng của nó. Về cơ bản, bất cứ khi nào bạn phải làm việc với một số đối tượng cùng loại, bạn nên xem xét giới thiệu một Kho lưu trữ cho chúng. Các kho lưu trữ được chuyên biệt theo loại đối tượng và không chung chung. Vì vậy, đối với một ứng dụng blog, bạn có thể có kho lưu trữ riêng biệt cho các bài đăng trên blog, cho nhận xét, cho người dùng, cho cấu hình người dùng, cho chủ đề, cho thiết kế, cho bất kỳ thứ gì bạn có thể có nhiều phiên bản.

Và trước khi kết luận điều này, một Kho lưu trữ có thể có danh sách các đối tượng riêng và nó có thể thực hiện lưu trữ cục bộ các đối tượng. Nếu không thể tìm thấy một đối tượng trong danh sách cục bộ, chúng tôi sẽ truy xuất nó từ sự kiên trì, nếu không chúng tôi sẽ phục vụ nó từ danh sách của chúng tôi. Nếu được sử dụng với bộ nhớ đệm, Kho lưu trữ có thể được kết hợp thành công với Mẫu thiết kế Singleton.

Như thường lệ, cảm ơn bạn đã dành thời gian và tôi chân thành hy vọng tôi đã dạy cho bạn một cái gì đó mới ngày hôm nay.