Replies: 6 comments 1 reply
-
民工叔叔能不能给一些概念如(模型驱动视图,为什么要这么做,是为了解决什么问题,和状态管理有什么关系)提供一些解释或者链接,小白对那些概念比较生疏,看得有点晕 |
Beta Was this translation helpful? Give feedback.
-
深有体会 做低代码平台最重要的就是DDD和DSL 把业务抽离出来 把领域模型定义好 结合上下文信息实现业务需求 |
Beta Was this translation helpful? Give feedback.
-
这是来自QQ邮箱的自动回复邮件。您好,我是董永*,我已经收到你的邮件,我会尽快给您回复。
|
Beta Was this translation helpful? Give feedback.
-
你已经成功召唤了小龙虾
|
Beta Was this translation helpful? Give feedback.
-
你已经成功召唤了小龙虾
|
Beta Was this translation helpful? Give feedback.
-
这是来自QQ邮箱的自动回复邮件。您好,我是董永*,我已经收到你的邮件,我会尽快给您回复。
|
Beta Was this translation helpful? Give feedback.
-
在广义的前端领域,模型驱动视图已经不是什么新鲜话题了,“低代码”和“搭建”也炙手可热,而这些概念都是以增强应用系统的可配置性为前提的。在这个大前提下,建立元数据驱动的前端架构就变得很重要了。
本次分享的目标是希望从零开始,初步建立一个小小的元数据驱动的原型系统(暂时只包括前端部分),并以此介绍这套系统与业务领域的可能结合方式。
模型驱动的视图
从最简单的结构来看,一个模型驱动的视图体系包含以下要素:
这是很简单的一种渲染模式,可以适用于所有的场景(暂且忽略性能之类的情况)。
举例来说,我们尝试把状态与渲染分离:
在这个例子中,Boolean 组件持有状态,而下层的 Checkbox 只负责消费这个状态,或者触发上层传入的修改状态的动作。
进而,可以造出更加泛化的数据表达形态:
到这里,我们可以注意到,在同一个数据上下文之下,可以拥有若干个共享该数据的纯渲染组件,也有机会在不影响整体结构的情况下,把 Checkbox 换成与之等价的其他交互,比如 Switch,并不会影响业务的表达。甚至我们在 Data 下面添加任意的布局组件,也不会产生额外的改动。
之前的结构中,我们对于状态的操作方式还是非常简单的,只有读写两种操作,还可以使用 useReducer 进一步拓展,支持添加更多的自定义动作响应:
在这个时候,下层渲染组件的能力包括:
更极端一点,这里的各种动作都可以是在外部注册的,这样,可以把动作的实现外置,放在某些类似 serverless 的体系中去支撑。
并且,我们发现,渲染部分仍然是很轻量的,而且可以很容易有跨平台实现。
对元数据的初步认知
以上的例子仍然太过简单了,我们逐步去看一些更加复杂的,比如表格和表单的状态结构:
表格:
表单:
如果是按照之前的理念来实现,我们当然也可以把这些信息全部糅合到状态里,类似这样:
表单也是类似这样的:
这里的 fields 就是一种没有经过抽象的元数据,我们可以考虑对这些代码进行一种初步抽象,把字段信息隔离出去:
经过这样的抽象过程,我们把一些独立于数据状态的描述信息抽取出去,单独处理了。最下层的组件仍然职责很单一,只是与之前相比,多了使用一些配置信息的权利。
类似这种字段配置,就是一种元数据。它实际上是另外一个层面的类型信息,可以携带对业务模型的定义。
使用 Schema 描述数据结构
刚才的示例促使我们进行思考:在很多时候,我们需要运行时获取模型结构定义的详细信息。如果我们始终拥有这种信息,会导致编程过程变得不一样吗?
比如说,当我们试图表达一个任务实体的时候:
它可以分解为最原子的数据类型的组合,而每种类型又可以使用一个描述数据来约束,据此,我们尝试描述各种常见数据类型的结构:
上面的这些类型定义很简陋,但是可以初步描述数据的基本形态。在此之上,可以更进一步,直接把业务的领域模型表达出来,比如,把前面示例中的 Task,可以换成这样的方式来描述:
这样,我们可以重构刚才的代码结构,变成下面这种形状:
在 SchemaProvider 中,我们可以从定义中取出当前类型的初始值,甚至可以自动生成一个校验函数,以验证给定数据是否符合自身描述的规则。
从 Schema 到 TypeScript 类型
至此,我们已经可以给一个承载状态的组件添加相应的 schema,但是,需要注意到,它对 TypeScript 的支持很不友好,schema 跟 value 没有建立比较好的关联。
设想有如下代码:
在这个地方,当我们填写了 schema,然后为 value 传入数据的时候,它们并未产生关联,简单来说,在 DataProps 定义的时候,如果不建立 schema 与 value 之间的关联,至少需要两个泛型参数:
在 T1 和 T2 之间,很明显 T1 的结构更可靠,那么,我们就考虑把类型定义变成下面这样,让 value 变成 schema 的一种类型运算:
这样,我们就得实现 ValueOf 这么一个类型操作了,不难得出类似以下的代码:
这时候,再看看刚才的数据类型:
就能够实时校验出 value 结构的错误了。
语义化的数据展开
建立了完整的 schema 结构之后,我们再回头去看表格和表单,就会发现比较简单了。
我们会发现,它们其实是两种迭代模式,一种是对象迭代为字段,一种是列表迭代为列表项。如果在迭代过程中拥有字段这类信息,那么,整个迭代过程都是可以抽象的。
比如这里是简单的字段迭代的过程:
在使用的时候,可以:
类似,ListIterator 也可以很容易表达出来。这样,我们之前碰到的表格表单,或者类似的形态,就有了比较统一的抽象方式了。
更夸张一些,我们还可以对常见的数据结构都实现一遍这样的组件,而且内部可以做很多优化,比如虚拟滚动之类的,这样,就减轻了渲染组件的负担。
基于类型的等价交互
在业务中,我们常常看到若干种交互形态,其内在的数据结构完全一致。在之前的示例中,已经简单看到一些了。
在软件架构中,一个很重要的过程是在抽象的基础上合并同类项。回到刚才的场景,我们会发现,对字段的描述,实际上是很通用的,这部分信息很大程度上并非来自前端,而是业务建模的一个体现。
这就是说,只要存在能够表达这种业务模型的最低交互,它在业务上就是可用的,只是不一定友好。然后,在不修改其他代码的情况下,替换为表达能力等价,但是交互更友好的渲染器,就可以提升这部分的体验。
举例来说,假设我们有一个下象棋的游戏,已知规则,但是暂时还没时间写棋盘和棋子,能不能在表单和表格里面下棋呢?
从这里我们就可以认识到,棋盘和表单,尽管形态差异非常大,实际上是等价的。推而广之,我们甚至可以用表单表达一切业务。
小结
理想状态下,应用架构可以划分以下两个部分:
在这种状态下,我们期望:
业务专家尽可能不需要去关注具体实现,而通过某种方式描述和表达业务细节,这就是业务建模。
比如说,当我们做业务建模的时候,并不需要去额外关心:
而是侧重于描述:
然后,尽可能把技术设施变成一个底层实现多样化的业务解释引擎,再去具体组合业务。
在以上的探讨中,我们已经努力去做了以下事项:
在此基础上,前端部分成为了对领域模型的解释引擎,视图的组合与布局都不再影响业务正确性。沿着这个角度思考,我们可以看到更多的可能性,比如:
更语义化地表达:数据源、查询、请求、异常 等概念,并且定义它们的组合方式。
而更大的体系,则是前后端一体化,整个都是业务领域的解释引擎,元数据从存储、到传输、再到呈现,一直伴随整个应用的生命周期。
这个时候,我们发现,一个完整的“配置化”的业务软件系统,就拥有了完整的表达链路了。
注:本文主要是为了说明基于元数据思考的方式,本身的实现很简陋,也并不代表需要这样完全从底层建立应用架构,在一些环节,社区早已存在很多相关库可以使用了。
本文是在厦门稿定的现场分享稿,感谢雪碧 @doodlewind 邀请。
Beta Was this translation helpful? Give feedback.
All reactions