上篇簡要介紹了Clean Architecture和union architecture,并給出clean architecture的一些共同點:框架無關,可測性,UI無關,數據庫無關,無外部依賴,本篇會具體介紹里面的一些點。
本文為系列文章的第八篇,完成的目錄請查看Clean Architecture
框架無關(Framework Independence)
首先我們必須說:框架是好的!大大的提高了我們的開發速度,像市面上流行的框架如:laravel,symfony,zend framework提供了一些通用問題的解決方案,如認證,數據庫交互,MVC,路由等,最重要的是這些方案一般都是一些久經考驗的方案。正是由于這些方案,我們能更關注我們的業務邏輯,不必陷入一些重復的、細節的問題中。
使用框架的另一個好處是:快速的進步,因此快去使用、學習框架吧。框架定義好了設計模式,你如果不按照框架定義好的模式去做,你就run不起來,于是你就必須去用正確的,好的模式,這樣你就可以不斷進步。
但是,我們不得不承認,使用框架都是有成本的,在正式開始項目之前,你必須要去學習它,但是一旦學習過后,你就不用再去做那些惱人的重復工作了,辛苦一次,快樂一生_。
框架的一些不足
講了這么多框架的好處,但是必須不幸的告訴你,所有的框架都有一個共同的問題:耦合。你越是使用這個框架,你越是離不開他,你跟他的耦合也越深,一旦這個框架某一天“消失”了,你就game over
了!此處的消失,可能是框架升級了,不兼容了,或者是作者不維護了,等等。
框架無關指的是什么
框架無關到底指的是什么?
我們能夠快速的切換框架,可能今天laravel挺火,我們用這個,明天突然symfony挺好,換換換的!
當我們在寫中大型應用的時候,我們可能會有些處理表單的代碼,有些和數據庫交互的代碼,有些輔助函數,但是這些是我們的業務邏輯嗎?NO!
那什么才是我們的業務邏輯呢,或者說是我們的應用。答案是:domain model和domain services。
領域模型和領域服務包括了:services,repositories,factories和entities,這些才是我們真正的應用。至于其他的,都是在領域模型和領域服務基礎上構建的UI。
為了達到框架無關,下面是一些建議。
對于框架的使用進行抽象
我們沒多寫一行使用框架的代碼,我們都在增加一分對于框架的依賴。那怎么做才能減少對于框架的依賴呢?
-
盡可能使用接口
盡可能依賴于接口,然后通過依賴注入實現依賴反轉
-
使用適配器模式
通過適配器模式來使用第三方庫,實現定義好的接口
-
堅持SOLID原則和clean code
堅持SOLID和clean code原則,使得我們代碼能組織的很好,并且減少依賴
說完這么多,可能大家還是不是很懂,還是讓我們上代碼的。
talk is cheap, show me the code
路由和控制器
路由是控制器是我們應用程序的入口,我們真的很難想象不依賴框架提供的路由和框架,怎么寫我們的代碼,下面是我們開發中最常見的一段代碼:
class CustomersController extends BaseController { }
寫下這行代碼的同時,意味著我們接下去控制器中的每一行都依賴于BaseController
,怎么辦?
使用適配器模式來適配控制器
namespace MyApp\Controller;
class Customers {
public function index() {
return [
'users' => $this->customerRepository->getAll() ];
}
}
然后適配器如下:
class CustomersController extends AbstractActionController {
protected $controller;
public function __construct( Customers $controller )
{
$this->controller = $controller;
}
public function indexAction()
{
return $this->controller->index();
}
}
適配器做的事情就是包裹著我們自己的控制器Customers
,然后進行調用。到這里,我們不禁會問自己,這么做是否值得?
我們做的這一切工作都是為了讓我們的代碼不耦合于框架
另一個解決方案是:盡可能保持控制器簡單。
就像SRP(單一職責原則)倡導的,我們要使得我們的控制器盡可能的功能單一。如果我們將控制器比喻為一個產生response的工廠,那控制器的職責只負責將輸入轉換為輸出,至于具體的業務邏輯,都應該封裝在領域模型和領域服務中。
我們堅持的一個原則是:胖model,瘦controller。基于這個原則,我們的控制器應該是下面這樣的:
class CustomersController extends AbstractActionController {
public function indexAction()
{
return [
'users' => $this->customerRepository->getAll() ];
}
}
上面的控制器很好的說明了我們原則:控制器盡可能簡單,將所有邏輯放入領域層。
視圖層
視圖層中都是一些展示邏輯,但是我們需要注意的是:每個框架都提供了一些輔助函數來生成一些html代碼,如果換框架,這會是很頭痛的一部分。
因此我們在寫下每一行代碼的同時,需要時刻提醒自己:盡量減少對于框架的依賴。
表單
表單是我們項目中最難處理一部分,同樣的,我們也很難做到和框架解耦。
在使用表單的過程中,我們應該牢記:表達只包含驗證和過濾規則,和業務邏輯相關的都應該放入領域層中。
框架服務
大多數框架都提供一些封裝好的服務,如laravel中的發送email,我們只需簡單的調用:
Mail::send( 'emails.hello', $data, function ( $message ) {
$message->to( 'you@yoursite.com', 'You' )->subject( 'Hello, You!' );
} );
但是一旦我們換框架,我們就只能痛苦的重構了,一個解決方案是使用適配器:
interface MailerInterface {
public function send( $template, array $data, callable $callback );
}
class LaravelMailerAdapter implements MailerInterface {
protected $mailer;
public function __construct( Mailer $mailer )
{
$this->mailer = $mailer;
}
public function send( $template, array $data, callable $callback )
{
$this->mailer->send( $template, $data, $callback );
}
}
class MailController extends BaseController {
protected $mailer;
public function __construct( MailerInterface $mailer )
{
$this->mailer = $mailer;
}
public function sendMail()
{
$this->mailer->send( 'emails.hello', $data, function ( $message ) {
$message->to( 'you@yoursite.com', 'You' )->subject( 'Hello, You!' );
} );
}
}
App::bind('MailerInterface', function($app) {
return new LaravelMailerAdapter($foo['mailer']);
});
上面的一段代碼給我們很好的示范了怎么使用適配器模式來減少對于框架的依賴。
總結
以上介紹的一些方法具體在實際使用時候,還需要細細斟酌,特別是要視你項目規模來酌情使用。
如果你項目非常小,那就放開手腳,想怎么弄就怎么弄,但是如果你是做ERP這種應用,那就請好好設計的,前期良好的設計會讓你后期的維護成本大大降低。
數據庫無關(Independent of Database)
我們大多數的應用后端存儲都是使用數據庫,自然而然應用也是維護數據庫的表結構設計的,我們的應用所有邏輯都是圍繞著數據庫展開,前期這沒什么問題,但是隨著應用繼續開發,帶來的問題有:
- 代碼中到處都是和數據庫的交互,我們看業務邏輯的時候,完全沒辦法關注于業務,只能看到數據庫交互,更糟糕的是:一旦我們需要換數據庫抽象層,那將是一場噩夢
- 由于我們使用數據庫,我們基本上不可能測試我們代碼,每次測試一個功能,我們都必須要保證數據庫可用,然后數據庫中的數據符合我們的預期,這種痛苦只有做過的才知道
那如果數據庫不是中心,那什么是我們應用的中心呢?
前面我們講過clean architecture,最核心的就是領域模層,我們應用的中心也應該是領域層,領域層有可以分為領域模型和領域服務。
領域模型
領域模型在php中就是最簡單的php對象,可能是下面這個樣子的:
class Customer {
protected $id;
protected $name;
protected $creditLimit;
protected $status;
public function getId()
{
return $this->id;
}
public function setId( $id )
{
$this->id = $id;
return $this;
}
public function getName()
{
return $this->name;
}
public function setName( $name )
{
$this->name = $name;
return $this;
}
// ...
}
由于是純的php類,所以不會有什么依賴了,因此是完全解耦的,是能夠方便測試的。
但是如果只有領域模型,意義不大,要配合上領域服務,才能真正的發揮作用。
領域服務
領域服務內部可以細分為3層:
-
Repositories
服務領域對象的存取,如果后端是數據庫,就是負責將數據從數據庫中取出,將對象存入數據庫。
-
Factories
負責對象的創建。
-
Services
具體的業務邏輯,通過調用多個對象和其他服務來完成一個業務目標。
具體可以參考之前的文章:The Clean Architecture in PHP 讀書筆記(六)之你不知道的MVC。
講到這,介紹clean architecture的內容就都結束了,下一篇將會以一個實際的例子來加深對clean architecture的理解,盡情期待。
這是The Clean Architecture in PHP的第八篇,你的鼓勵是我繼續寫下去的動力,期待我們共同進步。