多年來,我看到許多開發人員在Laravel 中使用存儲庫模式,試圖將乾淨的架構概念應用於他們的應用程序,但經常誤解使用像Laravel 這樣的框架的一般概念.
在我開始並必須避開一些我知道你們可能會扔給我的石頭之前,讓我澄清一下:這是我作為一名軟體工程師的觀點,他曾使用過多種語言、框架,從頭開始建立軟體,並且維護舊的遺留程式碼庫。我的話不是最終的,而且一如既往,我相信任何軟體工程問題最可接受的答案是:「這取決於您願意為了解決問題而做出權衡。」
那麼,話不多說,讓我們進入正題吧。
清潔架構是一種軟體設計理念,旨在創建易於維護、測試和理解的系統。它強調關注點的分離以及系統不同部分之間邊界的創建,以確保一個部分的變更不會對其他部分產生不利影響。這種架構由 Robert C. Martin(Bob 叔叔)推廣,通常用於指導程式碼組織,以適應業務規則或技術要求的變化。
簡而言之,Bob 的建議是,您的應用程式應該分為遵循關鍵原則的層,以允許您的業務邏輯與任何外部依賴項解耦,從而為它們提供移動性和單一職責上下文。但這不會是一篇乾淨的架構文章;我只是想讓我們在這件事上達成共識。
說到框架,我們談論的是依賴關係的緊密耦合。您可能沒有選擇 Laravel 來使用 Doctrine;您可能選擇使用 Eloquent。這同樣適用於您可能找到的任何框架功能 - 您選擇框架是為了開發速度和便利性。
所以,我的問題是:為什麼要在你選擇簡單的東西上添加額外的複雜層?
等等,別因為我這麼說而討厭我。您仍然可以在 Laravel 中適應甚至使用乾淨的架構,但任何架構決策中最重要的方面是清楚地了解您想要實現的目標以及這些選擇的優缺點。
這是一個艱難的決定,您可以在實施過程中改變主意。軟體不應該是一成不變的,對吧?我有一篇關於軟體品質的整篇文章,我在其中舉起了這個旗幟。
無論如何,回到 Laravel 和乾淨的架構。以我的拙見,最煩人的方法之一是將儲存庫模式與 Eloquent 結合使用,這就是我寫這篇文章的動機。
儲存庫模式是一種在域和資料映射層之間進行中介的設計模式,可作為域物件的記憶體集合。它的目的是解耦領域和資料映射層。
因此,儲存庫定義是一個域實體。為什麼?因為它與網域交互並定義基礎架構如何與網域交互,充當網域和我們應用程式的基礎架構層之間的契約。
首先,讓我向您展示我在 Laravel 應用程式中經常看到的儲存庫模式的一個糟糕實現,然後為您修復它:
// app/Repositories/UserRepositoryInterface.php <?php namespace App\Repositories; interface UserRepositoryInterface { // ...methods definitions }
// app/Repositories/UserRepository.php <?php namespace App\Repositories; class UserRepository implements UserRepositoryInterface { // ...implementation }
正如您在上面的程式碼中看到的,網域和基礎設施的「契約」不是在網域內部定義的,而是在基礎設施本身內部定義的。是的,App 命名空間是應用程式層的一部分,它包含所有非網域的東西。
現在,讓我們看看如何實現相同的事情:
// domain/Repositories/UserRepositoryInterface.php <?php namespace Domain\Repositories; interface UserRepositoryInterface { // ...methods definitions }
// app/Repositories/UserRepository.php <?php namespace App\Repositories; use Domain\Repositories\UserRepositoryInterface; class UserRepository implements UserRepositoryInterface { // ...implementation }
請注意,現在我們有了適當的領域層和應用程式層。是不是很簡單就可以得到呢?
現在,讓我們想一下。由於儲存庫介面現在是網域的一部分,這意味著我們不能簡單地將其他層的依賴項注入其中。這是一個不好的例子來解釋它:
// app/Repositories/UserRepositoryInterface.php <?php namespace App\Repositories; use App\Models\User; interface UserRepositoryInterface { public function find(int $id): User|null; // ...other methods }
還不清楚嗎?讓我把它移到領域層:
// domain/Repositories/UserRepositoryInterface.php <?php namespace Domain\Repositories; use App\Models\User; interface UserRepositoryInterface { public function find(int $id): User|null; // ...other methods }
看到問題了嗎?我們正在將作為基礎設施層一部分的 Eloquent 模型注入到我們的領域中。這將我們的領域與 Eloquent 緊密耦合,這違背了儲存庫模式的目的。
But how do we solve it? Well, it is not as hard as you might think. You probably already heard about Entities, right? So, let’s fix it:
// domain/Repositories/UserRepositoryInterface.php <?php namespace Domain\Repositories; use Domain\Entities\User; interface UserRepositoryInterface { public function find(int $id): User|null; // ...other methods }
// domain/Entities/User.php <?php namespace Domain\Entities; class User { public function __construct( public int $id; public string $name; public string $email; ) {} // ...other properties and methods }
Our domain layer is now clean from external dependencies, and if we want to move the whole domain to another framework, we can do it.
Now, how do we implement our Repository in our Laravel application? Let me show you:
// app/Repositories/EloquentUserRepository.php <?php namespace App\Repositories; use Domain\Repositories\UserRepositoryInterface; use Domain\Entities\User; use App\Models\User as EloquentUser; class EloquentUserRepository implements UserRepositoryInterface { public function find(int $id): ?User { $eloquentUser = EloquentUser::find($id); return $eloquentUser ? $this->toDomain($eloquentUser): null; } private function toDomain(EloquentUser $eloquentUser): User { return new User( $eloquentUser->id, $eloquentUser->name, $eloquentUser->email ); } }
Now we have a proper implementation of the Repository pattern in Laravel using Eloquent. You can create any other Repository implementation, such as PDOUserRepository.php, DoctrineUserRepository.php, and many others without injecting any dependency into your Domain layer.
Back to what I said in the subject of this article, I’ll complement that, in my humble opinion, using the Repository Pattern with Laravel is just overengineering, and you need a really good reason to do it.
You are adding an extra layer of complexity to your application that you may not need at all, or even worse, it will not guarantee that your business logic is actually isolated into the Domain layer.
What are you trying to achieve here? Think about it. You might end up missing out on many nice functionalities from Laravel or at least making their usage overly complicated.
Are you not sure if Laravel is the best framework for your application and you might move your application to Symfony in the future? Sure, go for the Domain and the Repository. Do you need to replace your SQL database with a NoSQL database later? Maybe it is a good justification. But do you really need it, or is it just charm?
One common argument is that the Repository helps to put some business logic into a separate layer. This normally gives me the creeps because there are better approaches for splitting your logic. Repositories are just for acting as a middle layer to connect the data layer and domain, nothing else. If you want to split your business logic, you should make use of something else — but this is another subject.
In conclusion, while the Repository pattern and clean architecture principles offer significant benefits in terms of maintainability and separation of concerns, their application in a Laravel context often introduces unnecessary complexity. Laravel is designed for simplicity and rapid development, and adding layers of abstraction can complicate this process.
Before implementing the Repository pattern, it is essential to evaluate your project’s specific needs. If decoupling your domain logic from Laravel’s infrastructure is a genuine necessity due to future migrations or a need for flexibility, then the complexity might be warranted. However, for many Laravel applications, leveraging Eloquent directly aligns better with the framework’s strengths and keeps the codebase simpler and more maintainable.
Ultimately, the decision should be driven by a clear understanding of your project’s requirements, balancing the trade-offs involved. Overengineering can lead to more problems than it solves, so aim to keep your solutions as straightforward as possible while still achieving your design goals. The primary objective of any architecture is to solve problems, not to create new ones.
以上是您的 Laravel 應用程式與儲存庫沒有任何意義的詳細內容。更多資訊請關注PHP中文網其他相關文章!