***
_我發現,我越是努力,就越發幸運。 -- Thomas Jefferson_
##2.17.1 微服務

Martin Fowler(我喜歡和敬仰的大師)曾發表了上面這一段話。這段話也出現在了2015年QCon分享會上,并加了一張PPT“什么是微服務”加以說明。

里面提到了 **微服務** 這個概念,在PhalApi框架中即對應我們的Api接口服務層,只是我們不是稱之為微服務,而是接口服務。
不管何種說法,我們都應該關注里面提及到的這幾點重要特質:
+ 小,且專注于做一件事情
+ 獨立的進程中
+ 輕量級的通信機制
+ 松耦合、獨立部署
這里不過多地討論微服務相關的分享,而是重溫接口服務層Api與領域驅動和單元測試之間的關系,以及如何開發一個優雅、穩定又簡單的接口。
##2.17.2 層級調用的順序
整體上講根據從Api接口層、Domain領域層再到Model數據源層的順序進行開發。
在開發過程中,需要注意不能 **越層調用** 也不能 **逆向調用** ,即不能Api調用Model。而應該是 **上層調用下層,或者同層級調用** ,也就是說,我們應該:
+ Api層調用Domain層
+ Domain層調用Domain層
+ Domain層調用Model層
+ Model層調用Model層
如果用一張圖來表示,則是:

為了更明確調用的關系,以下調用是 **錯誤** 的:
+ 錯誤的做法1:Api層直接調用Model層
+ 錯誤的做法2: Domain層調用Api層,也不應用將Api層對象傳遞給Domain層
+ 錯誤的做法3: Model層調用Domain層
這樣的約定,便于我們形成統一的開發規范,降低學習維護成本。
比如需要添加緩存,我們知道應該定位到Model層數據源進行擴展;若發現業務規則處理不當,則應該進入Domain層探其究竟;如果需要對接口的參數進行調整,即使是新手也知道應該找到對應的Api文件進行改動。
##2.17.3 接口服務的定義
現實項目開發過程中,絕大部分我們編寫的接口是給別人使用的,或許給Android客戶端同學使用,或者給iOS客戶端同學使用,抑或提供給其他后臺系統的同學使用。
為了提高并行開發的速度,我們不能等待接口完全開發完成后才提供接口文檔,而且他們也不能忍受這么漫長的等待。
所以,客戶端同學時常會問我們:什么時候可以提供接口文檔?
我們提倡“接口先行”,如果有時不能很好地做到這一點(畢竟多變的需求促發多變的情境),我們可以快速提供接口的定義。
這有點像規約層對接口的定義一樣,在PhalApi中定義一個接口,再具體一點即:
+ 1、創建一個接口服務類和聲明函數
+ 2、配置接口參數規則
+ 3、提供接口返回格式的注釋
簡單來說,就是創建一個類,寫個函數,定義參數和返回結果。
下面以 [開發實戰3:一個簡單的小型項目開發(奔跑吧兄弟投票活動)](http://git.oschina.net/dogstar/PhalApi-Demo-Vote) 中的團隊參賽接口為例,說明這三步操作的過程。
###(1)創建一個接口服務類和聲明函數
```javascript
//$ vim ./Vote/Api/Act.php
<?php
class Api_Act extends PhalApi_Api {
public function joinIn() {
}
}
```
###(2)配置接口參數規則
```javascript
<?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() {
}
}
```
###(3)提供接口返回格式的注釋
```javascript
<?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() {
}
}
```
###(4)在線查看一下效果
在完成上面的動作后,我們可以通過在線工具來看下實時的效果,在瀏覽打開后訪問:
```
http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.JoinIn
```
可以看到:

到了這里,即使我們未完成接口的開發,也未提供更完善的接口文檔,但接口客戶端同學也可以根據這個在線的接口參數進行開發了。
##2.17.4 在ATDD下講述故事
我們一直推崇測試驅動開發,但在對于Api接口開發,更準確來說是ATDD,即:驗收測試驅動開發(Acceptance Test Driven Development)。
在前面Domain層文檔中,我們提到了Api層是講述故事的場景。因此,為了驗證我們的業務場景是否正確,我們應該事先編寫好單元測試,以不斷引導我們前往正確的目的地。
我們可以使用腳本來快速生成測試骨架:
```javascript
$ pwd
$ /path/to/api.vote.phalapi.com/Vote/Tests/Api
$ phalapi-buildtest ../../Api/Act.php Api_Act ../test_env.php > Api_Act_Test.php
```
然后,稍微修改完善測試場景:
```javascript
/**
* @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. 執行請求
$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']);
}
```
從上面測試的代碼可以看出,我們先后進行了兩次報名。明顯地,第一次報名應該是成功的,第二次則應該提示不能重復報名。
單元測試的好處,不但在于可以引導我們做正確的事情,還可以提高我們的關注點,不致于在開發過程中被各種事務(如臨時性的會議)打斷后回來卻不知剛才開發到哪了。
然而,更多的是為后期的維護、擴展提供可驗證的業務場景。這點是很重要的。因為每一個測試場景,都保存了對應場景的模擬信息,這樣不僅僅在后面的擴展,還是突如其來的BUGFIXED,我們都可以快速證明我們的修改是正確的,至少不會影響到原來的業務流程。
試想一下,如果原來可以下單支付的接口,突然被影響到而導致支付不成功,這是何等的損失!
我現在慢慢地,每當需要修改別人的代碼時,我都會看下有沒有對應的單元測試。如果沒有,我會先補回,這樣能增強我修改別人代碼的信心。
##2.17.5 精益接口開發
傳統的接口開發,由于沒有很好的分層結構,而且熱衷于在一個文件里面完成絕大部分事情,最終導致了臃腫漫長的代碼,也就是通常所說的意大利面條式的代碼。
在PhalApi中,我們針對接口領域開發,提供了新的分層思想:ADM(Api - Domain - Model)。
即便這樣,如果項目在實際開發中,仍然使用原來的做法,縱使使用再好的接口開發框架,也還是會退化到原來的局面。
為了能讓大家更為明確Api接口層的職責所在,我們建議:
###(1)Api接口層應該做:
+ 應該:對用戶登錄態進行必要的檢測
+ 應該:控制業務場景的主流程,創建領域業務實例,并進行調用
+ 應該:進行必要的日志紀錄
+ 應該:返回接口結果
###(2)Api接口層不應該做:
+ 不應該:進行業務規則的處理或者計算
+ 不應該:關心數據是否使用緩存,或進行緩存相關的直接操作
+ 不應該:直接操作數據庫
+ 不應該:將多個接口合并在一起
在明確了上面應該做的和不應該做的,并且也完成了接口的定義,還有驗收測序驅動開發的場景準備后,相信這時,即使是新手也可以編寫出高質量的接口代碼。
因為他會受到約束,他知道他需要做什么,主要他按照限定的開發流程和約定稍加努力即可。
如果真的這樣,相信我們也就慢慢能體會到 **精益開發** 的樂趣。
最后,讓我們一起來看下上述團隊參賽接口開發的代碼實現:
```javascript
/**
* 團隊參賽接口
*
* @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;
}
```
可以看出,上面的代碼短小達意,簡單清晰。
- 歡迎使用PhalApi!
- 接口,從簡單開始!
- [1.1]-下載與安裝
- [1.2]-創建一個自己的項目
- [1.3]-在線體驗
- [1.4]-文檔、幫助和官網
- [1.10]-對PhalApi框架的抉擇
- [1.11]-快速入門(backup)
- [1.12]-參數規則:接口參數規則配置
- [1.13]-統一的接口請求方式:_sevice=XXX.XXX
- [1.14]-統一的返回格式和結構:ret-data-msg
- [1.15]-數據庫操作:基于NotORM的使用及優化
- [1.16]-配置讀取:內外網環境配置的完美切換
- [1.17]-日記紀錄:簡化版的日記接口
- [1.18]-快速函數:人性化的關懷
- [1.19]-DI服務速查:各資源服務一覽表
- [1.20]-DB操作:數據庫基本操作速查
- [1.21]-類的自動加載:遵循PEAR包的命名規范
- [1.22]-簽名驗證:自定義簽名規則
- [1.23]-請求和響應:GET和POST兩者皆可得及超越JSON格式返回
- [1.24]-緩存策略:更靈活地可配置化的多級緩存
- [1.25]-國際化翻譯:為走向國際化提前做好翻譯準備
- [1.26]-數據安全:數據對稱加密方案
- [1.27]-精益開發:更富表現力的Model層和重量級數據獲取的應對方案
- [1.28]-COOKIE:對COOKIE原生態的支持及記憶加密升級版
- [1.29]-開放與封閉:多入口和統一初始化
- [1.30]-保持的力量:接口開發最佳實踐
- [1.31]-新型計劃任務:以接口形式實現的計劃任務
- [2.11]-核心思想:DI依賴注入-讓資源更可控
- [2.12]-海量數據:可配置的分庫分表
- [2.13]-接口調試:在線SQL語句查看與性能優化
- [2.14]-測試驅動開發:意圖導向編程下的接口開發
- [2.15]-演進:新型計劃任務續篇
- [2.16]-領域驅動設計:應對復雜領域業務的Domain層
- [2.17]-微服務:Api接口服務層
- [2.18]-定制化:資源服務的再實現
- [2.19]-擴展庫:可重用的擴展類庫
- [2.20]-約定編程:架構明顯的編程風格
- [2.21]-服務器統一部署方案簡明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益項目和團隊建設
- [3.1]-擴展類庫:微信開發
- [3.2]-擴展類庫:代理模式下phprpc協議的輕松支持
- [3.3]-擴展類庫:基于PHPMailer的郵件發送
- [3.4]-擴展類庫:優酷開放平臺接口調用
- [3.5]-擴展類庫:七牛云存儲接口調用
- [3.6]-擴展類庫:新型計劃任務
- [3.8]-擴展類庫:用戶、會話和第三方登錄集成
- [3.9]-擴展類庫:swoole支持下的長鏈接和異步任務實現
- [3.11]-擴展類庫:基于FastRoute的快速路由
- [4.2]-開發實戰2:模擬優酷開放平臺接口項目開發
- [4.3]-開發實戰3:一個簡單的小型項目開發(奔跑吧兄弟投票活動)
- [5.1]-架構與思想:PhalApi核心設計和思想解讀
- [5.2]-雜談:扯一些PhalApi的前世和今生
- [5.3]-框架總結:術語表和PHP開發建議
- [5.4]-許可
- [5.5]-聯系和加入我們
- [5.6]-更新日記
- [5.8]-致框架貢獻者:加入PhalApi開源指南
- [6.1]-基于接口查詢語言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi視頻教程
- 附錄1:接口文檔參考模板