W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
我發(fā)現(xiàn),我越是努力,就越發(fā)幸運。 -- Thomas Jefferson
Martin Fowler(我喜歡和敬仰的大師)曾發(fā)表了上面這一段話。這段話也出現(xiàn)在了2015年QCon分享會上,并加了一張PPT“什么是微服務”加以說明。
里面提到了 微服務 這個概念,在PhalApi框架中即對應我們的Api接口服務層,只是我們不是稱之為微服務,而是接口服務。
不管何種說法,我們都應該關注里面提及到的這幾點重要特質:
這里不過多地討論微服務相關的分享,而是重溫接口服務層Api與領域驅動和單元測試之間的關系,以及如何開發(fā)一個優(yōu)雅、穩(wěn)定又簡單的接口。
整體上講根據從Api接口層、Domain領域層再到Model數據源層的順序進行開發(fā)。
在開發(fā)過程中,需要注意不能 越層調用 也不能 逆向調用 ,即不能Api調用Model。而應該是 上層調用下層,或者同層級調用 ,也就是說,我們應該:
如果用一張圖來表示,則是:
為了更明確調用的關系,以下調用是 錯誤 的:
這樣的約定,便于我們形成統(tǒng)一的開發(fā)規(guī)范,降低學習維護成本。
比如需要添加緩存,我們知道應該定位到Model層數據源進行擴展;若發(fā)現(xiàn)業(yè)務規(guī)則處理不當,則應該進入Domain層探其究竟;如果需要對接口的參數進行調整,即使是新手也知道應該找到對應的Api文件進行改動。
現(xiàn)實項目開發(fā)過程中,絕大部分我們編寫的接口是給別人使用的,或許給Android客戶端同學使用,或者給iOS客戶端同學使用,抑或提供給其他后臺系統(tǒng)的同學使用。
為了提高并行開發(fā)的速度,我們不能等待接口完全開發(fā)完成后才提供接口文檔,而且他們也不能忍受這么漫長的等待。
所以,客戶端同學時常會問我們:什么時候可以提供接口文檔?
我們提倡“接口先行”,如果有時不能很好地做到這一點(畢竟多變的需求促發(fā)多變的情境),我們可以快速提供接口的定義。
這有點像規(guī)約層對接口的定義一樣,在PhalApi中定義一個接口,再具體一點即:
簡單來說,就是創(chuàng)建一個類,寫個函數,定義參數和返回結果。
下面以 開發(fā)實戰(zhàn)3:一個簡單的小型項目開發(fā)(奔跑吧兄弟投票活動) 中的團隊參賽接口為例,說明這三步操作的過程。
//$ vim ./Vote/Api/Act.php
<?php
class Api_Act extends PhalApi_Api {
public function joinIn() {
}
}
<?php
class Api_Act extends PhalApi_Api {
public function getRules() {
return array(
'joinIn' => array(
'teamName' => array('name' => 'team_name', 'require' => true, 'min' => 1, 'max' => 100),
),
);
}
public function joinIn() {
}
}
<?php
class Api_Act extends PhalApi_Api {
public function getRules() {
return array(
'joinIn' => array(
'teamName' => array('name' => 'team_name', 'require' => true, 'min' => 1, 'max' => 100),
),
);
}
/**
* 團隊參賽接口
*
* @return int code 0,參賽成功;1,隊名已存在
* @return int team_id 新建的團隊ID
*/
public function joinIn() {
}
}
在完成上面的動作后,我們可以通過在線工具來看下實時的效果,在瀏覽打開后訪問:
http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.JoinIn
可以看到:
到了這里,即使我們未完成接口的開發(fā),也未提供更完善的接口文檔,但接口客戶端同學也可以根據這個在線的接口參數進行開發(fā)了。
我們一直推崇測試驅動開發(fā),但在對于Api接口開發(fā),更準確來說是ATDD,即:驗收測試驅動開發(fā)(Acceptance Test Driven Development)。
在前面Domain層文檔中,我們提到了Api層是講述故事的場景。因此,為了驗證我們的業(yè)務場景是否正確,我們應該事先編寫好單元測試,以不斷引導我們前往正確的目的地。
我們可以使用腳本來快速生成測試骨架:
$ pwd
$ /path/to/api.vote.phalapi.com/Vote/Tests/Api
$ phalapi-buildtest ../../Api/Act.php Api_Act ../test_env.php > Api_Act_Test.php
然后,稍微修改完善測試場景:
/**
* @group testJoinIn
*/
public function testJoinIn()
{
//Step 1. 構建請求URL
$url = 'service=Act.JoinIn';
$params = array(
'sign' => 'phalapi',
'team_name' => 'test team name',
'user_id' => '1',
'token' => '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731',
);
DI()->notorm->team->where('team_name', $params['team_name'])->delete();
//Step 2. 執(zhí)行請求
$rs = PhalApiTestRunner::go($url, $params);
//var_dump($rs);
//Step 3. 驗證
$this->assertNotEmpty($rs);
$this->assertArrayHasKey('code', $rs);
$this->assertArrayHasKey('team_id', $rs);
$this->assertEquals(0, $rs['code']);
$this->assertGreaterThan(0, $rs['team_id']);
//create again
$rs = PhalApiTestRunner::go($url, $params);
$this->assertEquals(1, $rs['code']);
}
從上面測試的代碼可以看出,我們先后進行了兩次報名。明顯地,第一次報名應該是成功的,第二次則應該提示不能重復報名。
單元測試的好處,不但在于可以引導我們做正確的事情,還可以提高我們的關注點,不致于在開發(fā)過程中被各種事務(如臨時性的會議)打斷后回來卻不知剛才開發(fā)到哪了。
然而,更多的是為后期的維護、擴展提供可驗證的業(yè)務場景。這點是很重要的。因為每一個測試場景,都保存了對應場景的模擬信息,這樣不僅僅在后面的擴展,還是突如其來的BUGFIXED,我們都可以快速證明我們的修改是正確的,至少不會影響到原來的業(yè)務流程。
試想一下,如果原來可以下單支付的接口,突然被影響到而導致支付不成功,這是何等的損失!
我現(xiàn)在慢慢地,每當需要修改別人的代碼時,我都會看下有沒有對應的單元測試。如果沒有,我會先補回,這樣能增強我修改別人代碼的信心。
傳統(tǒng)的接口開發(fā),由于沒有很好的分層結構,而且熱衷于在一個文件里面完成絕大部分事情,最終導致了臃腫漫長的代碼,也就是通常所說的意大利面條式的代碼。
在PhalApi中,我們針對接口領域開發(fā),提供了新的分層思想:ADM(Api - Domain - Model)。
即便這樣,如果項目在實際開發(fā)中,仍然使用原來的做法,縱使使用再好的接口開發(fā)框架,也還是會退化到原來的局面。
為了能讓大家更為明確Api接口層的職責所在,我們建議:
在明確了上面應該做的和不應該做的,并且也完成了接口的定義,還有驗收測序驅動開發(fā)的場景準備后,相信這時,即使是新手也可以編寫出高質量的接口代碼。
因為他會受到約束,他知道他需要做什么,主要他按照限定的開發(fā)流程和約定稍加努力即可。
如果真的這樣,相信我們也就慢慢能體會到 精益開發(fā) 的樂趣。
最后,讓我們一起來看下上述團隊參賽接口開發(fā)的代碼實現(xiàn):
/**
* 團隊參賽接口
*
* @return int code 0,參賽成功;1,隊名已存在
* @return int team_id 新建的團隊ID
*/
public function joinIn() {
$rs = array('code' => 0, 'team_id' => 0);
DI()->userLite->check(true);
$domain = new Domain_Team();
if ($domain->isExists($this->teamName)) {
$rs['code'] = 1;
return $rs;
}
$teamId = $domain->joinIn($this->teamName);
$rs['team_id'] = $teamId;
return $rs;
}
可以看出,上面的代碼短小達意,簡單清晰。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: