前端输入的数据没有交给后端_如何让前端程序员没有后端也能完成项目?

本文探讨了如何实现Backend as a Database,让前端直接与数据库交互,解决性能、安全和权限问题。介绍了User表的权限管理、业务数据表的类表示、数据迁移、视图表和物化视图的使用,以及存储过程和批量查询的优化策略。通过这种方式,前端可以更高效地访问和处理数据,同时确保数据的安全性和合规性。
摘要由CSDN通过智能技术生成

答:Backend as a Database, Sort Of

直接把 Mysql 暴露在公网给前端使用会有什么问题:

  • I/O 边界 => 性能考虑
    • 批量获取多条数据
    • 对数据进行 count / sum 等聚合
    • 把聚合结果做提前计算,冗余字段
  • 组织边界 => 安全考虑
    • 数据权限校验,防止非法访问
    • 校验业务规则
      • 产生的订单价格应该等于商品单价之和
      • 出库单要创建,需要xxx领导签字单

要做到 Backend as a "Database",就是回答以上问题如何解决。

User 表

Mysql / Postgresql 的权限太粗了。肯定是要在 Mysql / Postgresql 外边套一层去校验权限。这里有如下的挑战要解决

  • B 端权限需要非常细致,不仅仅要到行,甚至到格。权限可能是按职位授予,也可能是因为工单分配临时授予。
  • 权限校验开销不能太大,如果拿来做 C 端业务,单据数量和用户数量都可能会非常大。要减少因为行级别权限引入的开销。所以要能按需打开不同的授权模式。
  • 没有登录的用户需要登录,或者能够匿名浏览。如何方便给没有用户的访问做适当的提权。
  • 非真实人类用户的访问,比如夜间的批处理,需要能够有机器人的账号
  • 账号的初始化是如何描述的,谁来分配最初的那个管理员
  • 如果有多个 Project,每个 Project 有自己的用户体系,不同的 User 表。这两个 Project 要互相访问,用什么 User 身份?

解决了以上问题,我们就获得了一个内建权限的“Database”,可以开放到公网给前端访问。实际拉数据的时候,用的是人类用户自己的身份。只要用户自己对要访问的数据表或者行有权限,就可以访问到,否则就访问不到。

业务数据表

写一个 Typescript 的类,然后把 Mysql 的表建好。和 Java Hibernate 一样

@Biz.profile('买家', 'read')
@Biz.profile('店长', 'read', 'create', 'update', 'delete')
@Biz.profile('管理员', 'read', 'create', 'update', 'delete')
@Biz.authentic({ rowPolicy: 'public' })
@Biz.published
export class RegionFreightTmpl extends Biz.ActiveRecord {
    @Biz.lookup
    public readonly freightTmpl: FreightTmpl;
    // 计费模式:按件(目前只有按件)
    public freightTmplType: string;
    public firstPrice: number;
    public firstAmount: number = 1;
    public additionalPrice: number;
    public additionalAmount: number = 1;
    public regions?: string;
}

查询的时候直接用这个 class 来指代这张数据库表就可以了。

Data Migration

数据库表结构变更了肯定还是要写 SQL 来升级数据库的,标准做法没啥说的。

视图表

能够做好权限校验的前提是只暴露单表的简单查询。那么 count / sum / join 这些怎么处理? 难道是要发明一种 SQL 变种,然后搞 SQL 解析么?

一个简单的做法就是引入“视图表”的概念。把这些聚合查询都建模成一张虚拟的视图表。这样在查询的时候仍然是单表查询。

  • 解决了 rpc 请求的时候如何表达复杂 query 的问题,避免了 sql over http
  • 解决了权限校验难以确定目标是什么的问题

物化视图表

如果所有的聚合查询都要按需计算则会非常慢。经常我们需要一些按日期,按维度提前聚合好的中间结果。这个可以用物化视图表来表达

@(Biz.view`SELECT id, gender, age, city, SUM(OrderItem.cost) AS total
FROM ${impactSet}
JOIN ${User} on User.id = impactSet.userId
LEFT JOIN ${Order} on User.id = Order.userId
LEFT JOIN ${OrderItem} on Order.id = OrderItem.orderId`)
@(impactSet`SELECT DISTINCT(Order.userId) AS userId FROM ${ Order }`)
@(impactSet`SELECT DISTINCT(Order.userId) AS userId FROM ${ OrderItem } JOIN ${ Order } on Order.id = OrderItem.orderId`)
@Biz.source(Starriness, { dataSource: 'clickhouse' })
export class UserWithTotal extends Biz.SqlView {
    public readonly id: string;
    public readonly userId: string;
    public readonly created_at: Date;
    public readonly total: number;
}

物化视图的问题在于什么时候刷新。通过用 SQL 定义 impactSet,我们可以由 mysql binlog 触发物化在 clickhouse 中的物化视图表刷新。

"AI" 表

视图表就是用 SQL 这种编程语言写一个函数,把函数映射成表来查询。那么我们有 Python / Julia 等 AI 语言,也可以把 Python / Julia 函数/机器学习模型映射成表来查询。

用户行为表

物化视图的来源是业务数据。用户行为因为数据量比较大,不太适合直接插入到 mysql 中。把用户行为单独提供一张宽表来记录用户做过什么操作。写入可以是内存缓冲,或者经过 kafka 这样的队列缓冲。

宽表的列应该是按业务需求扩展的,如果业务上关心用户操作的订单id,或者商品id,则要加上这些字段。大概的 api 也类似 mixpanel 这些老牌分析厂商的上报接口。

物化视图在计算报表的时候可以 join 用户行为表和业务数据表来得出分析结果。

批量查询

一次 rpc roundtrip 只能发一条查询太慢了。那支持一个数组,一次可以提交多条查询就好了。至于前端代码中怎么把多个组件的查询聚合到一个 rpc 中,这个就看前端的 data query 框架是怎么来弄了。无非就是全局搞个 buffer,在“合适的时候”刷一下这个 buffer,批量查一次。

存储过程

细粒度的用户权限只能解决数据完全被一个用户拥有的问题。很多时候数据是协作数据,有多个 stakeholder,那么就必须经过协商好的规则去修改数据,而不是一个用户说了算。例如你可以决定今天的日记本里随便写啥,但是不能决定把今天晚饭的订单改成0。日记是你拥有的,但是订单是多个相关方都关心的。

解决业务规则校验后写入的问题就是存储过程了。前端同学肯定是希望用 javascript 来写存储过程。实际上就是所谓 FaaS 的云函数。本质上就是后端代码仍然有,只是换了一拨人来写。

如果业务规则比较简单,例如只是一个状态机的转换图,则可以用配置替代code。当然大部分时候,复杂的业务逻辑,上 javascript 是最直观的。

分段执行的存储过程

有的时候一个存储过程执行时间太长了,会导致稳定性问题。例如下单操作,可以以事务完整性为边界,把不影响下单成功与否的业务放到写入订单之后异步完成。本质上就是把一个连续的存储过程分段执行。

这个可以把 kafka 之类的 queue 理解为一张“事件表”。插入订单表,同时也是触发了一个 OrderCreated 事件。通过描述性的订阅规则,把多个存储过程用事件串联起来执行。对应于流行的 FaaS 概念中 Pub/Sub 触发的 Cloud Function。

对于已有数据的修改如果有 Audit Trail 的需求,也可以类似的自动同步一份 Audit Trail 表来跟踪历史上的修改操作。

Backend as a Database

  • 用 RPC 接口提供了一个类似 Database 的东西
  • 把后端业务切分成了 User表/业务数据表/视图表/物化视图表/用户行为表/存储过程/DataMigration 等预设的概念,变得更规整
  • 除了“需要经过业务规则校验之后写入”这个例外,其他的后端接口都不需要上通用编程语言,可以用配置或者SQL定义解决
  • 这个解法既不是 GraphQL,也不是经典的 BFF。排序是 GraphQL - BaaD - BFF,BFF 更后端,GraphQL 更前端,BaaD 介于两者中间。

这不过是历史的又一个轮回。Relational database 刚出来的时候,是以 Rapid application development 平台的方式登场的。在那之前,是每个业务应用都要自己写一个数据库出来。Oracle / Sql 这些免除了每个应用都要发明一套自己的数据库的痛苦,让业务开发可以专注于业务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值