资讯专栏INFORMATION COLUMN

PHP 服务器端内部业务处理失败消息传递方式

MangoGoing / 1182人阅读

摘要:综合以上问题得出以下结论业务处理失败消息要以的方式向上传递给调用者业务处理失败消息以参数的方式传递不是很适合,并且不能以的方式返回再次思考,最终从里面想到了一点思路幸好是出身。

  

我需要拍砖 和 看见你们的意见,为团队少挖坑

场景:创建订单 实际流程:
  

终端调用(PC端、移动端APP、微信端、Web端)-->控制器 或 接口-->实际的业务处理-->控制器 或 接口-->终端做出相应处理(控制器可能是渲染对应页面; 接口返回 JSON数据)

业务处理类动作:

检查用户是否登陆

验证商品 ID、购买数量等参数

检查该商品是否处于上架中

检查该商品是否可以购买

各种检查...

创建订单

记录 Log

返回订单创建结果给调用者

创建失败:...

创建成功:[return true|return Order info]

游戏规则

前后端数据格式约定为 JSON格式如下:

{
    code: "00000",      // 状态码
    msg: "操作成功!",   // 提示信息
    data: {}            // 数据
}
  

注:"00000":业务成功状态码;非"00000"都为业务失败。
为了防止服务器端状态码泛滥成灾,code可以为"",这时 msg 里面则是相应的错误信息,只为给用户提示。

导火线

项目开发完毕,测试人员去测试,提如下Bug:

  

如果用户未登录,进个人中心,提示用户未登录,然后会去登陆view;而在下单页,提示用户未登录,却没有去登陆view。

然后前端童鞋开始去修复该问题,查出如下问题:

个人中心服务器接口返回的数据格式:

{
    code: "00008",
    msg: "用户未登录,请登录",
    data: [ ]
}

下单页服务器接口返回的数据格式:

{
    code: "",
    msg: "用户未登录,请登录!",
    data: [ ]
}

然后前端童鞋对服务器端童鞋讲,这里你应该返回给我code: "00008",我这边一看 code便知是用户未登录,就可以做出相应的操作,这里你只返回提示信息,我这边不好做更加细腻的操作。

然后,后端童鞋开始尝试给该地方添加上 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出去。

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 应该已怎样规范来定义所代表的含义

再来看如下常用的两种方法定义:

public function create($params, & $msg)

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/20921.html

相关文章

  • 腾讯云分布式高可靠消息队列CMQ架构最佳实践

    摘要:是腾讯云内部自研基于的高可靠强一致可扩展分布式消息队列,在腾讯内部包括微信手机业务红包腾讯话费充值广告订单等都有广泛使用。目前已上线腾讯云对外开放,本文对核心技术原理进行分享介绍。 ​ 极牛技术实践分享活动 极牛技术实践分享系列活动是极牛联合顶级VC、技术专家,为企业、技术人提供的一种系统的线上技术分享活动。 每期不同的技术主题,和行业专家深度探讨,专注解决技术实践难点,推动技术创新,...

    Ku_Andrew 评论0 收藏0
  • 马蜂窝火车票系统服务化改造初探

    摘要:为了帮助用户更好地完成消费决策闭环,马蜂窝上线了大交通业务。现在,用户在马蜂窝也可以完成购买机票火车票等操作。第二阶段架构转变及服务化初探从年开始,整个大交通业务开始从架构向服务化演变。 交通方式是用户旅行前要考虑的核心要素之一。为了帮助用户更好地完成消费决策闭环,马蜂窝上线了大交通业务。现在,用户在马蜂窝也可以完成购买机票、火车票等操作。 与大多数业务系统相同,我们一样经历着从无到有...

    Raaabbit 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<