微服務:Api接口服務層

2018-11-21 21:20 更新

我發(fā)現(xiàn),我越是努力,就越發(fā)幸運。 -- Thomas Jefferson

2.17.1 微服務

a pic

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

a pic

里面提到了 微服務 這個概念,在PhalApi框架中即對應我們的Api接口服務層,只是我們不是稱之為微服務,而是接口服務。
不管何種說法,我們都應該關注里面提及到的這幾點重要特質:

  • 小,且專注于做一件事情
  • 獨立的進程中
  • 輕量級的通信機制
  • 松耦合、獨立部署

這里不過多地討論微服務相關的分享,而是重溫接口服務層Api與領域驅動和單元測試之間的關系,以及如何開發(fā)一個優(yōu)雅、穩(wěn)定又簡單的接口。

2.17.2 層級調用的順序

整體上講根據從Api接口層、Domain領域層再到Model數據源層的順序進行開發(fā)。

在開發(fā)過程中,需要注意不能 越層調用 也不能 逆向調用 ,即不能Api調用Model。而應該是 上層調用下層,或者同層級調用 ,也就是說,我們應該:

  • Api層調用Domain層
  • Domain層調用Domain層
  • Domain層調用Model層
  • Model層調用Model層

如果用一張圖來表示,則是:
a pic

為了更明確調用的關系,以下調用是 錯誤 的:

  • 錯誤的做法1:Api層直接調用Model層
  • 錯誤的做法2: Domain層調用Api層,也不應用將Api層對象傳遞給Domain層
  • 錯誤的做法3: Model層調用Domain層

這樣的約定,便于我們形成統(tǒng)一的開發(fā)規(guī)范,降低學習維護成本。
比如需要添加緩存,我們知道應該定位到Model層數據源進行擴展;若發(fā)現(xiàn)業(yè)務規(guī)則處理不當,則應該進入Domain層探其究竟;如果需要對接口的參數進行調整,即使是新手也知道應該找到對應的Api文件進行改動。

2.17.3 接口服務的定義

現(xiàn)實項目開發(fā)過程中,絕大部分我們編寫的接口是給別人使用的,或許給Android客戶端同學使用,或者給iOS客戶端同學使用,抑或提供給其他后臺系統(tǒng)的同學使用。
為了提高并行開發(fā)的速度,我們不能等待接口完全開發(fā)完成后才提供接口文檔,而且他們也不能忍受這么漫長的等待。

所以,客戶端同學時常會問我們:什么時候可以提供接口文檔?

我們提倡“接口先行”,如果有時不能很好地做到這一點(畢竟多變的需求促發(fā)多變的情境),我們可以快速提供接口的定義。
這有點像規(guī)約層對接口的定義一樣,在PhalApi中定義一個接口,再具體一點即:

  • 1、創(chuàng)建一個接口服務類和聲明函數
  • 2、配置接口參數規(guī)則
  • 3、提供接口返回格式的注釋

簡單來說,就是創(chuàng)建一個類,寫個函數,定義參數和返回結果。

下面以 開發(fā)實戰(zhàn)3:一個簡單的小型項目開發(fā)(奔跑吧兄弟投票活動) 中的團隊參賽接口為例,說明這三步操作的過程。

(1)創(chuàng)建一個接口服務類和聲明函數

//$ vim ./Vote/Api/Act.php 

<?php
class Api_Act extends PhalApi_Api {

    public function joinIn() {
    }
}

(2)配置接口參數規(guī)則

<?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)提供接口返回格式的注釋

<?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

可以看到:
a pic

到了這里,即使我們未完成接口的開發(fā),也未提供更完善的接口文檔,但接口客戶端同學也可以根據這個在線的接口參數進行開發(fā)了。

2.17.4 在ATDD下講述故事

我們一直推崇測試驅動開發(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)在慢慢地,每當需要修改別人的代碼時,我都會看下有沒有對應的單元測試。如果沒有,我會先補回,這樣能增強我修改別人代碼的信心。

2.17.5 精益接口開發(fā)

傳統(tǒng)的接口開發(fā),由于沒有很好的分層結構,而且熱衷于在一個文件里面完成絕大部分事情,最終導致了臃腫漫長的代碼,也就是通常所說的意大利面條式的代碼。

在PhalApi中,我們針對接口領域開發(fā),提供了新的分層思想:ADM(Api - Domain - Model)。
即便這樣,如果項目在實際開發(fā)中,仍然使用原來的做法,縱使使用再好的接口開發(fā)框架,也還是會退化到原來的局面。

為了能讓大家更為明確Api接口層的職責所在,我們建議:

(1)Api接口層應該做:

  • 應該:對用戶登錄態(tài)進行必要的檢測
  • 應該:控制業(yè)務場景的主流程,創(chuàng)建領域業(yè)務實例,并進行調用
  • 應該:進行必要的日志紀錄
  • 應該:返回接口結果

(2)Api接口層不應該做:

  • 不應該:進行業(yè)務規(guī)則的處理或者計算
  • 不應該:關心數據是否使用緩存,或進行緩存相關的直接操作
  • 不應該:直接操作數據庫
  • 不應該:將多個接口合并在一起

在明確了上面應該做的和不應該做的,并且也完成了接口的定義,還有驗收測序驅動開發(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;
    }

可以看出,上面的代碼短小達意,簡單清晰。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號