PHP 服務器端內部業務處理失敗消息傳遞方式

我須要拍磚 和 看見大家的意見,爲團隊少挖坑javascript

場景:建立訂單

實際流程:

終端調用(PC端、移動端APP、微信端、Web端)-->控制器 或 接口-->實際的業務處理-->控制器 或 接口-->終端作出相應處理(控制器多是渲染對應頁面; 接口返回 JSON數據)php

業務處理類動做:

  1. 檢查用戶是否登錄
  2. 驗證商品 ID、購買數量等參數
  3. 檢查該商品是否處於上架中
  4. 檢查該商品是否能夠購買
  5. 各類檢查...
  6. 建立訂單
  7. 記錄 Log
  8. 返回訂單建立結果給調用者

    1. 建立失敗:...
    2. 建立成功:[return true|return Order info]

遊戲規則

先後端數據格式約定爲 JSON格式以下:html

{
    code: "00000",      // 狀態碼
    msg: "操做成功!",   // 提示信息
    data: {}            // 數據
}

注:"00000":業務成功狀態碼;非"00000"都爲業務失敗。
爲了防止服務器端狀態碼氾濫成災,code能夠爲"",這時 msg 裏面則是相應的錯誤信息,只爲給用戶提示。前端

導火線

項目開發完畢,測試人員去測試,提以下Bug:java

若是用戶未登陸,進我的中心,提示用戶未登陸,而後會去登錄view;而在下單頁,提示用戶未登陸,卻沒有去登錄view。web

而後前端童鞋開始去修復該問題,查出以下問題:面試

  • 我的中心服務器接口返回的數據格式:
{
    code: "00008",
    msg: "用戶未登陸,請登陸",
    data: [ ]
}
  • 下單頁服務器接口返回的數據格式:
{
    code: "",
    msg: "用戶未登陸,請登陸!",
    data: [ ]
}

而後前端童鞋對服務器端童鞋講,這裏你應該返回給我code: "00008",我這邊一看 code便知是用戶未登陸,就能夠作出相應的操做,這裏你只返回提示信息,我這邊很差作更加細膩的操做。ajax

而後,後端童鞋開始嘗試給該地方添加上 code。
開始着手修改代碼:
首先找到接口方法裏面發現以下 demo:後端

php$order = kernel::single('sysapi_ecoupon_order')->create($params, $msg);
if (!$order) {
    return array('code' => '', 'data' => array(), 'msg' => $msg);
}
return array('code' => '00000', 'data' => $order);

改方法返回array(); 在外部統一入口、出口處再返回 JSON出去。api

sysapi_ecoupon_order

phppublic function createNew($params, & $msg)
{
    // 獲取用戶信息
    $member_info = app::get('b2c')->model('members')->get_current_member();

    if (empty($member_info)) {
        $msg = app::get('ecoupon')->_('用戶未登陸,請登陸!');
        return false;
    }
    // 繼續下面的業務處理
}

接口調用的kernel::single('sysapi_ecoupon_order')->create($params, $msg);這裏面作實際的業務處理,錯誤信息是經過 $msg 向上傳遞出去,外部沒辦法經過 $msg 獲知對應的 code。而後給前端童鞋講這種狀況沒辦法返回 code給你。

前端就只能經過判斷 msg的方式來修復該問題
而後寫了以下 demo:

javascriptif("用戶未登陸,請登陸!" == data.msg) {
    // 用戶未登陸,去登陸
    // ...
}

而後提交,測試,經過,上線,N天后

有人跑過來說:下單頁 與 我的中心的提示有點不一樣,貌似多了個 "!"。(舉例而已,更多的多是提示不友好、錯別字等狀況)

而後後端同窗修改成 $msg = app::get('ecoupon')->_('用戶未登陸,請登陸'); 提交,測試不經過,前端同窗再修改成if("用戶未登陸,請登陸" == data.msg),提交,測試經過

// 如此反反覆覆

終究有一天:產品、測試,前端、後端混戰了一場。N人,卒.....

從新正視問題

最終先後端得出結論:要想對用戶實現更加友好的體驗,先後端數據必須有個標識具備惟一性不變性。而如今用的 msg卻不具有,仍是得用 code。而且這裏先後端極度耦合msg。

後端童鞋回來繼續修改代碼,開始着手給這裏添加上相應的 code。
開始思考該怎麼添加 code,如今的問題是 create( ) 方法多是其餘童鞋開發,內部返回的提示信息,我這邊是調用者,不能肯定方法內部到底會返回什麼提示信息,無解。

突然,有一天想到,我在調用該方法以前檢查下用戶有沒有登陸就OK了,而後開始寫以下實現:

public function create($params)
{
    $member = app::get('b2c')->model('members')->get_current_member();

    // 登陸驗證
    if (empty($member)) {
        return array('code' => '00008');
    }

    $msg = '';

    $order = kernel::single('sysapi_ecoupon_order')->create($params, $msg);
    if (!$order) {
        return array('code' => '', 'data' => array(), 'msg' => $msg);
    }
    return array('code' => '00000', 'data' => $order);
}

呵呵,好機智的少年。
而後告訴前端,這裏能夠返回 code了,前端愉快的刪掉原來那坨判斷 msg的代碼,而在 ajax請求的地方統一判斷 code就能預知用戶未登陸,作出相應的操做。

經測試,上線。一切又回到了美好時光。

隨着時光的流逝,業務的增長,後端童靴發現Order類裏面以下 demo:

phppublic function create($params)
{
    $member = app::get('b2c')->model('members')->get_current_member();

    // 登陸驗證
    if (empty($member)) {
        return array('code' => '00008');
    }

    // 實際業務處理....    
}

public function getOrderList($params)
{
    $member = app::get('b2c')->model('members')->get_current_member();

    // 登陸驗證
    if (empty($member)) {
        return array('code' => '00008');
    }

    // 實際業務處理....    
}

public function getOrderDetail($params)
{
    $member = app::get('b2c')->model('members')->get_current_member();

    // 登陸驗證
    if (empty($member)) {
        return array('code' => '00008');
    }

    // 實際業務處理....    
}

// ...

這都是什麼玩意............ 而後開始封裝,稍微好了點

又過了一段時間,有人過來講建立訂單還須要優化體驗,
點擊建立訂單提示以下:

  • 超過最大購買量——給出提示,繼續留在建立訂單頁
  • 該商品已賣光或已下架——引導用戶去商品列表頁

這時,前端童鞋告訴後端童鞋,商品下架的時候,你也應該返回一個狀態碼。

後端童鞋開始打算添加 code,發現以下 demo

phpkernel::single('sysapi_ecoupon_order')->create($params, $msg);

這裏的提示信息是 $msg 返回的,用戶登陸外部能夠提早檢測,這裏的商品可否購買要實現添加 code也須要提早檢測,未來要是須要添加類是功能豈不是...... 每須要一個精確的 code返回出去,這裏就須要添加檢測,這裏代碼將會變得沒法直視。
何況這裏本該在業務裏面檢測,一切不那麼友好起來了。

再次思考,代碼寫的不爽了,必定是哪裏不對

問題所在

開始懷疑 public function create($params, & $msg) { } 這裏不該該是經過 & $msg 來做爲 調用者與 被調用者之間的 錯誤信息通訊約定,一切的問題都出在了這裏。錯誤消息向上傳播的約定不合適

若是這裏約定的是 code做爲錯誤向上傳播一切的問題即將不復存在。在調用業務方法以前的檢測代碼就均可以去掉了,代碼簡約,一切又美好起來。

接下來繼續思考,使用 code做爲業務處理失敗消息傳遞問題又來了

  • 如今已有的業務代碼都是如此定義public function create($params, & $msg),怎樣更加友好的替換成 code
  • 若是使用 code,code只能服務器端 與 前端約定的一個具備惟一性的標識(code 比 msg 對國際化的實現更加容易)可是並不能直接展現給用戶,那麼就須要定義每一個 code 的表明的意義 與 對應的提示信息。那麼問題來了,code 應該已怎樣規範來定義所表明的含義

再來看以下經常使用的兩種方法定義:

  1. public function create($params, & $msg)
  2. public function create($goodsId, $num, & $msg)

第一種方式,參數經過一個 $params數組傳遞過來,方法內部在把錯誤提示放到 $msg中。

  • 好處:$params是個數組,裏面參數能夠任意添加
  • 缺點:該方法調用者在外部不能知道該方法須要什麼參數,必須來看該方法內部實現,作出對應的數組 key的轉換(如: user_id 轉 userId)。方法調用者 與 方法實現 極度耦合。維護成本大、出Bug係數高

第二種方式,按基本類型分別傳遞單個參數

  • 好處:方法調用者根據方法定義就可以知道方法具體須要的參數,調用方法時不須要做 key轉換,只須要傳遞對應的參數便可
  • 缺點:參數數目過多時,慘不忍睹

這裏有以下問題:

  • 這裏如何已一種更加容易維護,擴展的方式來處理(Java裏面方法參數已對象的方式傳遞能夠借鑑
  • 這裏的& $msg 真的合適嗎,若是是第二種方式定義的方法,之後擴展個 $phone 該如何處理?public function create($goodsId, $num, & $msg, $phone='')這樣麼?怎麼看怎麼蛋疼
  • 再來看不經過 & $msg傳遞錯誤信息以後的代碼
phppublic function create($goodsId, $num)
{
    if ( ? ) {
        // 返回狀態碼
        return '0001';
    }
    if ( ? ) {
        // 返回狀態碼
        return '0002';
    }
    // 建立訂單
    // ...
    // 返回訂單信息
    return $order;
}

看似實現了,可是方法調用者,怎麼調用怎麼蛋疼,一會返回狀態碼,一會返回訂單信息,徹底兩種類型。

綜合以上問題:

得出如下結論:

  • 業務處理失敗消息要以 code 的方式向上傳遞給調用者
  • 業務處理失敗消息以參數的方式傳遞不是很適合,而且不能以 return的方式返回

再次思考,最終從 Java裏面想到了一點思路(幸虧是 Java出身。疑問:爲什麼面試的時候 Java的工做經驗都不算在 PHP工做經驗裏呢,並無所以而加分)

解決方案:

  • 自定義一個異常類,包括 codo屬性 和 msg 屬性
  • 凡是遇到業務不能正常處理的時候就建立一個異常對象,設置對應的 code 或者 msg屬性(爲了減小 code氾濫,這裏的 code 與 msg 能夠2選一,若是前端須要作精準的處理,就設置 code,若是隻是爲了給用戶提示,就只返回 msg,則能夠減小一個 code),而後拋出異常
  • 方法調用者在外部統一捕捉該異常,如 接口的統一入口出口的方法內部處理

因我的工做時間、項目經歷很少、歸根結底經驗不足。如今將該方案寫下來,還望有經驗的大神拍磚,以避免給團隊挖坑,以上 $msg 就是 N久之前埋下的坑。

該文章發佈在本身站點地址:http://www.webdevs.cn/article/91.html

相關文章
相關標籤/搜索