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

相关文章
相关标签/搜索