一种基于自定义runtime object的web业务架构设计方式

代码 · 2022-12-17

我们服务分层时可能会遇到一些问题:

  • 比如在componentA里我们可能会查一下用户信息,同时componentB里可能也需要用到同一个用户信息,这时我们有几种选择:
    • componentA里查一下db或rpc,componentB里再查一下db或rpc。这样的话会带来多次db或rpc查询,浪费了资源,带来性能损失
    • 在componentA和componentB外层查一次db,然后再传给两个component。这样问题是我们每次遇到这种情况都要改代码,把查询不断往上层提,除非提到最上层,否则也不能从根本上解决,而如果全提到最上层的话那分层意义就会弱很多
    • 其中一个component查完自己存起来,然后一层层往下传,哪里用到就通过参数传过去。这样一是会带来参数无限扩张;二是有顺序问题,比如现在是A请求了存起来B用,那就要求A要在B之前调用,如果A前面再来个C也要用,那就需要把A里请求的代码挪过去,很难维护
  • 很多时候,我们的方法常会产生一些逻辑过程的中间值,用于后面其它逻辑使用,这样的话中间值就会在逻辑里各种传递,可能会越传越深,导致如果这个值有什么变动,就需要改很多地方
  • request带进来的参数可能需要一些预计算才可以被正确识别,比如参数uid可能需要解密或需要A、B两个参数一起才能判断是iOS还是Android,我们一般怎么处理?:
    • 解密uid或通过A、B算出iOS,然后结果和request并列一起做为参数一层层传下去,但这样会使参数量膨胀
    • 起一个公共方法或特殊类型的成员函数,每次使用重新计算一遍
    • 手动调整request结构,在request里加个参数,把想要的结果算出来后存进去,和request一起带下去,但这样会修改原始request结构体,可能会给非修改人困惑

比如正常代码可能是下面这样:

public action1(req *request) {
  bIOS = getIsIOS(req);
  userInfo = getBaseUserInfo(req->uid);
  expSwitch = getCurExpSwitch(userInfo);
  ret1 = logic_one(req, bIOS, userInfo, expSwitch);
  ret2 = logic_two(req, userInfo, expSwitch);
  output(ret1, ret2);
}

public logic_one(req, bIOS, userInfo, expSwitch) {
  if bIOS {
    dealUser(userInfo);
  }
  if expSwitch->allow() {
    expCode();
  }
  processLogic(userInfo);   
}

public logic_two(req, userInfo, expSwith) {
	processUserInfo(userInfo);
  if expSwitch->allow() {
    expProcess();
  }
}

那如果解决这样的问题?

这里提出一个runtime object的东西,我理解它要起到一些作用:

  • 阻止参数无限扩展:可以将所有可能参数以接口形式收敛到一个runtime object中
  • 封装与数据打交道的rpc和db查询,所有数据以get形式存在并以类单例形式减少请求量
  • 封装与参数有关的运算逻辑,同样以get形式存在,与用rpc和db一样,让用户只感觉获取数据,不需要关心数据来源是什么,rpc还是逻辑计算是一样的
class ExampleRuntimeObj {
  private req *Req;
  
  private userInfo *UserInfo;
  
  public getUserInfo() {
    if this->userInfo != nil {
      return this->userInfo;
    }
    userInfo = db.FindUserInfo(this->req->uid);
    this->userInfo = userInfo;
    return this->userInfo;
  }

  public isIOS() {
    if this->req->channel == "ios" ||
      this->req->from == "ios" {
      return true;
    }
    return false;
  }

  public isHitSwitch() {
    userInfo = getUserInfo();
    return getRandmonHit(userInfo->uid);
  }
}

这里我们可以把runtime object当做所有获取数据的逻辑全部封装进去,在各logic之前的传递只传它做为参数就可以,其它包括数据方法,数据请求顺序和缓存全不需要计算逻辑关心

public action1(req *request) {
  rtObj = newAction1RuntimeObj(req)
  ret1 = logic_one(rtObj);
  ret2 = logic_two(rtObj);
  output(ret1, ret2);
}

public logic_one(rtObj) {
  if rtObj->isIOS() {
    dealUser(userInfo);
  }
  if rtObj->isHitSwitch() {
    expCode();
  }
  processLogic(rtObj->getUserInfo());   
}

public logic_two(rtObj) {
  processUserInfo(rtObj->getUserInfo());
  if rtObj->isHitSwitch() {
    expProcess();
  }
}

待解决的点:

  • runtime object需要抽象出base类,因为像getUserInfo这类不止是一个接口会用到
  • req如何抽象??runtime object里的方法是需要从req里获取参数的,在req不同的情况下,如何使所有req兼容同一个getUserInfo接口?req是否本身也要接口化??
  • 很多时候我们不只需要get数据,也需要set数据,A里set的数据,是否应该通过runtime object来暂存让B看到?如果这样的话如何解决顺序问题??
Theme Jasmine by Kent Liao Modified by eLangX