V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dusu
V2EX  ›  程序员

「造轮族」一个风火轮:风骚的 PHP 链式 Service 利弊请教

  •  
  •   dusu · 2019-12-10 13:13:46 +08:00 · 2016 次点击
    这是一个创建于 1571 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    介于团队成员开新项目编码风格不统一,

    (主要是因为大家容易混淆 service/model/control 之间的关系,导致代码流程不清晰,可读性略差)

    造了一个「风火轮」,并实现了以下的链式 service 效果

    DEMO1. 这是一个简历置顶的例子

    
    service_manager::instance(resume_service::class)
    ->find($id)// 找到当前简历
    ->permission($uid)//检查权限
    ->get_current('site_id')// 从当前简历中找到 site_id
    ->join(site_service::class) // 切换 site_service
    ->find_by_last_response() // 通过上次找到的 site_id 取到站点配置
    ->get_current('top') // 取到当前站点配置的置顶设置
    ->join(resume_service::class)// 切换 resume_service
    ->top_validate($days) // 验证用户选择的置顶天数是否正确
    ->top_prepare_amount() // 为扣取余额准备参数
    ->join(amount_service::class)// 切换 amount_service
    ->use_money_by_last_response() // 根据 resume_serivce 返回的参数进行支付
    ->join(resume_service::class) // 切换 resume_service 上下文
    ->set_top() // 保存置顶
    ->top_prepare() // 为 top_service 的 create 准备参数
    ->join(top_service::class) // 切换 top_service 
    ->create_by_last_response() // 根据上次准备的参数进行创建
    ->exec(); // 执行
    

    DEMO2. 这是一个发布简历的例子

    service_manager::instance(site_service::class)
    ->find($site_id) // 找到当前站点
    ->get_current('resume', 'freepubs') // 取得免费发布条数
    ->join(resume_service::class) // 切换 resume_service
    ->set_user_id($uid) // 设置 uid
    ->set_site_id($siteid) // 设置 站点 id
    ->top_validate($top_days) // 验证发布时是否选择置顶
    ->create_validate($_POST) // 创建简历前数据验证
    ->create() // 创建简历
    ->create_prepare_amount() // 为创建简历支付余额准备参数
    ->join(amount_service::class) // 切换 amount_service
    ->use_money_by_last_response() // 根据上次准备的参数进行使用余额
    ->join(resume_service::class) // 切换 resume_service
    ->set_top() // 如果用户有置顶,则置顶
    ->top_prepare() // 为 top_service 准备创建置顶数据
    ->join(top_service::class) // 切换 top_service
    ->create_by_last_response() // 根据上次准备的数据进行创建置顶记录
    ->exec(); // 执行
    

    实现:

    service_manager (入口) -> service_proxy ( service 代理类) -> service ( service 实体类)

    关于 service_manager

    由 service_manager::instance 创建 service_proxy 类。

    关于 service_proxy

    service_proxy 的魔术 call 方法代理 service,将所有的调用方法和参数存入栈,

    service_proxy 方法进入 exec 时,

    会回调 service_manager 去遍历所有的调用栈,逐步执行方法。

    链式中断操作均是 throw new Exception

    关于 service:

    实例所有 service 均为单例。

    运行 service 时,会有一个 context(上下文),和 reponse (前一次调用)的默认属性。

    也可以根据方法名(带有 response )的会默认传回上一个非 join 方法的执行结果。

    初衷

    1. 业务分层,至少能让 control 层的逻辑更加清晰
    2. 微服务,后期可直接替换为 RPC 等协议

    最后

    转了 N 圈,没发现业界有这么做的案例,心里有一点虚。

    不知道各位大神们有什么要喷或者讨论的,特来诚心讨教一下利弊。

    已知的好处:

    1. 业务线清晰
    2. service 执行颗粒明确
    3. service 之前可以避免直接调用
    4. 后期 service 可快速更换为微服务

    已知的弊端:

    1. 传参数只能在类内部进行传参
    2. 部分性能损耗,内存消耗可能会双倍( service_proxy 会暂存所有调用栈的方法参数)
    3. service 提供服务,如果要提供不同的 Exception 会比较难受
    4. service 之间事务控制不太友好
    5. 链式的代码提示是个问题
    11 条回复    2019-12-10 14:31:06 +08:00
    haiyang416
        1
    haiyang416  
       2019-12-10 13:26:51 +08:00 via Android
    受不了这么长的链式调用,感觉就跟一段话没有标点一样难受。个人观点,请勿介意。
    jhdxr
        2
    jhdxr  
       2019-12-10 13:48:40 +08:00
    如果我的逻辑里有个 if 呢。。。
    encro
        3
    encro  
       2019-12-10 13:55:56 +08:00
    链式,通常是做一个连续的操作,最后才需要输出结果。
    你这个中间其实有多次需要结果,加入中间变量,至少可以让代码更加易懂和 debug。

    "大家容易混淆 service/model/control "

    是因为没有定义好关系

    绝大多数项目 service 是不必要的,mvc 里面本来就没有 service,这是因为 model 静态方法就是 service

    至于考虑以后的微服务,个人认为微服务不是好东西,管理配置麻烦,测试\维护\DEBUG 成本上升,且 PHP rest 不适合做微服务,试想一个 rest 请求 10ms,一个大页面几十个请求就几百 ms。
    大部分中小公司研究微服务不如研究云函数,既有微服务的便利,又没有微服务得弊端。弊端是可能限制在某单一云平台了。
    能将微服务用起来,且用的好的公司,不知道楼下能举出几家?
    maddot
        4
    maddot  
       2019-12-10 14:02:54 +08:00
    读到这样的代码会想死把
    一点语义都没有
    暴露一大堆数据库操作,你干脆写存储过程算了
    liuxu
        5
    liuxu  
       2019-12-10 14:03:00 +08:00
    看到这个代码我真的好想打人。。。出了问题不好调试
    sunznx
        6
    sunznx  
       2019-12-10 14:03:00 +08:00
    一直不知道 Service 和 Model 的区别,你这些方法放到 Model 里面有什么问题?
    robotdiy
        7
    robotdiy  
       2019-12-10 14:06:23 +08:00
    不想要。
    littleylv
        8
    littleylv  
       2019-12-10 14:11:27 +08:00
    我个人不喜欢这样。再见
    jenschen
        9
    jenschen  
       2019-12-10 14:15:39 +08:00 via iPhone
    这样不就更乱。service 函数 setData,将 data 传入,result 传出。每一次或者其他脚本只要 service.setData(data); ,还有一点就是单一原则好像也违背了吧,代码不好复用
    ben1024
        10
    ben1024  
       2019-12-10 14:20:31 +08:00
    链式挺好看的,过度链式解藕就麻烦了嗯
    dreamerlv3ex
        11
    dreamerlv3ex  
       2019-12-10 14:31:06 +08:00
    你就是想写箭头吗???
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2793 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 11:48 · PVG 19:48 · LAX 04:48 · JFK 07:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.