你有没有过这种时刻:手里攒了一堆自己写的库,每个都能跑,每个都挺满意,但把它们放在一起,却拼不出一个完整的东西。

KiwiEngine 的作者最近就撞上了这个节点。他花了几个月时间做库、设计 API、调配置、改路由、试样式、重想抽象层。然后某一天,他开始把这些东西连起来,突然意识到——自己不再只是在做一堆工具了。

打开网易新闻 查看精彩图片

他在做的是一个引擎。

一堆库加起来,不等于一个引擎

他花了很多时间思考 KiwiEngine 各个组件的职责边界。

  • Juice 负责属性驱动的样式
  • Nectarine 提供配置相关能力
  • Seltzer 处理 HTTP
  • Sig 提供响应式能力
  • 生态里的其他部分则覆盖数据、基础设施和应用层面的问题

每一块都有自己存在的理由。但几个好用的库凑在一起,并不会自动变成一个好用的引擎。

真正的问题在于:这些东西怎么协同工作?它们能不能共享约定?能不能在不紧耦合的前提下通信?能不能被组装成应用,而不逼开发者去理解每一个实现细节?同时,它们还能不能各自独立可用?

到这里,架构才开始变得有意思。

WebEngine 是这些碎片碰头的地方

WebEngine 正在成为这些想法汇合的位置。与其让每个应用自己去琢磨这些库该怎么交互,不如由引擎来建立通用的生命周期和集成模式。

但这不意味着每个库都要丢掉独立性。恰恰相反。

他希望每个组件都保留清晰的职责。引擎应该去协调这些职责,而不是把它们吸收掉。这个区分对他来说越来越重要。

库解决一个具体问题。引擎协调运行一个应用所需的各个系统。应用则给这些系统一个目的。

想想一个应用启动时会发生什么:配置需要被理解,依赖需要被建立,路由需要被注册,服务需要被准备好,应用需要进入可处理工作的状态,最终还需要干净地关闭。

这些不是孤立的功能,它们是一个生命周期的组成部分。一旦开始把它们当成一个协调好的过程来看待,架构的感觉就变了。引擎可以规定每一块在何时、以何种方式参与进来,而不是要求每个库去解决整个问题。这比把所有东西揉成一个巨大框架要可持续得多。

边界让组合成为可能

这个系列里反复出现的一个教训是:抽象需要边界。

Juice 不应该取代 CSS。一个 HTTP 库不应该拥有应用的业务逻辑。配置系统不应该决定应用要达成什么目标。基础设施工具不应该规定用户体验。

每个组件都需要足够的职责来变得有用,但不能变得对一切负责。WebEngine 就是这些边界变得可落地的地方。如果每一块都设计得当,它们可以协同工作而不丢失自己的身份。

这也是他希望 KiwiEngine 达成的事。

CLI 也是体验的一部分

另一块拼图是创建项目时的开发者体验。

他不希望启动一个新的 KiwiEngine 应用意味着手动组装十几个互不相关的组件。从一个想法到一个能跑的项目,应该有一条清晰的路径。Kiwi CLI 可以帮助建立这条路径:创建项目、添加能力、组装应用、应用约定、减少重复的初始化工作。

目标不是隐藏一切是怎么运作的,而是让常见路径变得更容易。开发者应该能理解这个系统,而不必每次都重建这个系统。

当他想到自己想做的那些东西时,实际收益就变得很明显了:一个 Blackwater Sound 应用、一个店面、一个艺术家平台、一个出版系统、一个内部工作坊工具。

这些应用目的各不相同,但它们不需要完全无关的地基。它们可以共享同一套底层架构,同时表达各自的领域。这不是要做出千篇一律的应用,而是让不同的应用建立在可靠的共享基础设施之上。

框架最终应该变得可预测

当架构开始在你提问之前就给出答案时,会有一种满足感。

这个职责应该放在哪里?配置应该怎么被消费?这个组件如何参与生命周期?一个新的应用应该怎么被组装?

答案不需要在每个场景下都一模一样。但系统应该建立足够的一致性,让开发者不必 постоянно 发明新的约定。可预测性带来信心,信心让实验变得更容易。

引擎不是终点。他做 WebEngine 不是因为想余生都用来维护一个 Web 框架,而是因为他想要一个可靠的地基,去支撑他真正想创造的东西:音乐工具、商店、创意平台、出版系统、商业基础设施、支持实体产品的应用。

引擎应该让这些项目更容易推进。它的各个部分越成熟,他就越能把时间花在这些项目上,而不是一遍遍重建它们的地基。

还有很多工作要做,还有很多东西要打磨,改进永远不会停。但工作的性质正在改变。他花在“每个库可能变成什么”上的时间变少了,花在“这些东西如何协同”上的时间变多了。

这件事,开始变得真实了。