#Mock# Challenge to Mock - Arch Designs

#Mock# Challenge to Mock - Arch Designs

God is a mock. – Just Kidding


目录 Table of Contents


背景

最近接到了新任务——希望 mock 掉项目依赖的底座,主要基于两点考量:

  • 内部联调时可以屏蔽掉底座的影响,不至于阻塞当前开发模块的流程;
  • 之后可集成自动化测试,降低测试成本。

我的第一感觉是这个东西不好搞。一是上下游的依赖关系比较复杂,这意味着:首先让 mock 做到可以替换原来的底座以满足基本功能就需要一些努力,其次项目“看起来”运行正常并不能保证上游亦是如此;二是替换底座包含了 mock 掉数据返回和状态管理两个概念,那么就不得不在简洁和可用之间做取舍了。

本文接下来要讨论的内容,暂不考虑上游影响,并坚持这样的原则:整个模块应当是轻量的,仅对高优先级且不满足的业务场景做出适配。


业务分析

项目架构

project service

  • api:同步的项目入口,直接发请求或开异步任务来调用底座
  • job:异步的后台任务,监控底座资源状态
  • db:存储项目所需的数据

infrastructure base

  • 提供核心功能的底座

external services

  • 出于其它业务需求,会存储底座的某些数据

根据上图的分析,需考虑以下几点:

  1. mock 可以代替 infrastructure base 正常响应;

  2. project service / infrastructure bases / external services 三者之间的数据应当保持一致性;

  3. 异步的后台任务需要监控状态变化,保存状态是必需的。

架构演化

固定数据

问题描述:mock 从无到有

解决方法:选用预研的框架,可以方便地管理路由转发规则和模拟返回数据

架构演化-固定数据

  • mock api proxy:设置路由转发规则。应该采用正则匹配,保证增删查改的通用性,预研框架支持:
    • 请求路径正则匹配
    • 请求参数正则匹配
    • 请求主体正则匹配
  • hardcoded response:提供对应路由的返回数据。应该返回全量的响应字段,其中一些字段可以灵活处理:
    • UUID:创建资源时返回随机 UUID;查询/修改/删除单个资源时从路径读 UUID;查询资源列表时返回固定条目和 UUID
    • 其它字段:必传字段从请求中接收;选传字段硬编码其内容;如果出现不同响应格式,路由转发规则要做区分

引入存根

问题描述:需要对返回字段做逻辑处理,固定数据模式无法直接满足

解决方法:预研框架支持存根注入,保留了扩展业务逻辑的能力

架构演化-引入存根

  • easy stub:支持简易逻辑扩展,主要可以用于:

    • 提供配置
    • 提取、组合和处理请求的数据
    • 返回随机数据或当前时间等

    注:在预研框架中,存根的入口是唯一的,因此如果希望复用多个存根,方式不太优雅。

存储数据

问题描述:目前的架构中没有办法保存数据,无法实现保持数据的一致性和监控状态变化

解决办法:引入 MVC 设计和轻量级的数据库 SQLite

架构演化-存储数据

  • service stub:MVC 中的 Controller
  • dao:MVC 中的 Model
  • sqlite:轻量级关系型数据库,支持单文件和内存两种存储模式

参数匹配

问题描述:预研框架对通过请求参数匹配的支持较弱,infrastructure base A 没有相关需求,infrastructure base B 需解决该问题

解决办法:只能引入轻量的 Web 框架来补充

架构演化-参数匹配

  • query matcher:强化请求参数匹配规则的核心中间件/装饰器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class MatchingMode(object):
    Has = "has" # 模糊匹配
    Is = "is" # 精确匹配


    class QueryMatcher(object):
    def __init__(self, matching_rules, query_arguments, matching_mode):
    self.matching_rules = matching_rules
    self.query_arguments = query_arguments
    self.matching_mode = matching_mode

    def match(self):
    # 检查请求参数的数量
    if self.matching_mode == MatchingMode.Is:
    if len(self.matching_rules) != len(self.query_arguments):
    logger.debug("match failed: length of query arguments is not equal to length of the matching rule")
    return False

    # 检查请求参数的规则
    for key in self.matching_rules:
    if key in self.query_arguments:
    rule = self.matching_rules[key]
    val = str(self.query_arguments[key][0])
    if not re.match(rule, val):
    logger.debug("match failed: query argument not match matching rule. rule: %s, val: %s" % (rule, val))
    return False
    else:
    logger.debug("match failed: query argument not in matching rules")
    return False
    # 均通过则返回真
    return True

统一配置

问题描述:由于同时存在两套框架及一些业务数据,配置比较分散

解决办法:将这些配置统一到一个文件,并在文档上说明约束是有必要的

架构演化-统一配置

  • config:统一的配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "host": "127.0.0.1",
    "mock_port": 1024,
    "web_port": 2333,

    "db_name": "test.db",

    "mock_config_path": "etc/mock_config.yml", // 其中还可以配置日志的路径
    "web_config_path": "etc/web_config.json", // 其中还可以配置日志的路径

    "business_field_1": "",
    "business_field_2": "",
    "business_field_3": "",
    ...
    }

日志记录

问题描述:预研框架自带日志记录,但是新引入的 Web 框架没有

解决办法:需要自行补充日志模块

架构演化-日志记录

  • log:补充的日志模块

    1
    2
    3
    4
    5
    ...
    [formatter_web]
    format=%(asctime)s|%(name)s|%(levelname)s|%(filename)s|%(funcName)s|%(message)s
    datefmt=
    ...

健康检查

问题描述:部署 mock 后不好快速简单地验证是否可用

解决办法:额外增加两个专门用于健康检查的接口

架构演化-健康检查

  • h-c:健康检查接口(Health-Check API),可参考的设计:

    1
    2
    3
    curl --location --request GET 'http://${ip}:{port}/mock/ping' # Expected response body: {"msg": "pong"}

    curl --location --request GET 'http://${ip}:{port}/web/ping' # Expected response body: {"msg": "pong"}

预告

下期将继续讨论以下这些内容:

  • 技术选型
  • 接口文档 / 接口测试
  • 进程部署 / 容器部署
  • 代办事项

# Mock

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×