Biff.core: 用于Clojure Web应用的系统组成
摘要
Biff.core是一个新库,为Biff项目提供系统组成和接口,允许将样板代码干净地提取到带有init函数的模块中。
暂无内容
查看缓存全文
缓存时间: 2026/06/10 00:22
# 新库:biff.core
来源:https://biffweb.com/p/core
正如我之前[所写](https://biffweb.com/p/biff2/)的那样,我一直在将 Biff 拆分成多个独立的库,并在此过程中对各方面进行调整。我已经完成了全部[十二个库](https://github.com/jacobobryant/biff/tree/v2.x#libraries)的初稿,现在正在逐一打磨并发布它们。第一个库已经准备就绪。
**biff.core** (https://github.com/jacobobryant/biff/tree/v2.x/libs/core):Biff 项目的系统组合与其他接口。这是将所有其他库粘合在一起的胶水,因此我先发布它。
长期以来,Biff 一直采用“模块和组件”结构:项目中每个应用命名空间暴露一个“模块”映射,然后你需要一堆样板代码来将这些模块中的内容组合成一个“系统”映射,最后在启动时将该系统映射传递给你的“组件”函数。Biff 2 保留了这种结构,并新增了一些处理样板代码的功能。
举个例子,请看[这段代码](https://github.com/jacobobryant/biff/blob/e164e341fd51ca361f065a5ecd59873c39f247b9/starter/src/com/example.clj#L25-L31),它从你的模块中获取 `:routes`(以及 `:api-routes`)键,并将它们转换为系统映射中的 `:biff/handler` 值。我希望有一种一等公民的方式,能够将这类逻辑干净地提取到库中,这样库的说明只需“将这个模块添加到你的项目”,而无需附带“然后把所有这些代码粘贴到你的主命名空间”。
因此,这个新的 biff.core 库包含了一个“初始化函数”的概念。这些函数接收一个模块集合,并返回一个可合并到系统映射中的单一映射。[看这里有个例子](https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/libs/ring/src/com/biffweb/ring.clj#L407)。初始化函数存储在模块映射的 `:biff.core/init` 键中,这样我们就得到了那种“你只需要模块(嗯,还有组件)”的良好效果。
这里的主要复杂之处在于,在你的应用代码中定义一个 `(def handler ...)` 变量实际上有一个很好的副作用:后期绑定。如果你修改了任何模块,handler 变量会更新,而如果你将系统映射中的 `:biff/handler` 设置为该变量而非其值(即 `#’handler`),那么传入的 Ring 请求无需重启 Web 服务器就能获取最新的 handler。如果我们把这段样板代码提取到库代码中,我们就没了这个变量。
我最终采用了以下解决方案:
- 初始化函数接收的是你模块向量的*变量*,而非向量本身。
- 系统映射中任何希望在不重启情况下更新的内容都需要是一个函数。在某些情况下,这意味着你需要在系统映射上设置一个 `:com.example/get-my-thing` 函数(返回 `my-thing`),而不是直接设置 `:com.example/my-thing` 键。
- 系统映射上的该函数应解引用模块变量,并将其传递给一个已记忆的函数,该函数构建你需要的任何东西(比如 Ring handler)。
还是[这个例子](https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/libs/ring/src/com/biffweb/ring.clj#L407)。结果在美学上相当令人满意:你得到一个简洁的[主命名空间](https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo.clj),不需要怎么改动,你只需添加[模块](https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo/modules.clj)和[组件](https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo/components.clj)即可。
总有一种冲动想要进一步整合。为什么还要一个单独的组件向量?为什么不直接让模块支持 `:biff.core/on-start` 和 `:biff.core/on-stop` 键,然后通过某种方式表达这些生命周期函数之间的依赖关系,以便我们可以按正确顺序调用它们?
答案就是:这样我们就不需要去实现某种方式来表达这些生命周期函数之间的依赖关系了。自己把组件按正确顺序排列并不难(特别是 Biff 启动项目已经为你做了),而且这样更容易理解组件的工作原理。它只不过是一个你通过映射传递的函数序列。如果你的项目中有大量的有状态资源,难以全部追踪,你总可以在 biff.core 之上再加一层东西,计算出你的组件向量应该是什么。
---
*广告:[我的团队正在招聘](https://jobs.ashbyhq.com/tyba/efd553f6-0e29-4827-af95-0fc41f063042?utm_source=5P6E9lPL4v)一名高级软件工程师,主要使用 ClojureScript 和 Python。我们为可再生能源项目制作建模软件。*
相似文章
cl-bbs: 用Common Lisp重写的类schemeBBS文本公告板
cl-bbs 是一个用 Common Lisp 编写的高性能匿名文本公告板引擎,忠实复刻了原始 SchemeBBS 的风格。它提供格式化支持、图片预览以及零 JavaScript 渲染等功能。
@so_ainsight: 这太方便了。这是一个在海外引起轰动的方法:只需让Claude,“将整个应用打包成一个HTML文件和一个JSON文件…”
一种使用Claude将整个应用打包成一个HTML文件和一个JSON文件的方法正在海外流行,它使复杂系统变得可见且自文档化。
Gobee:用Go编写eBPF程序,通过clang转译
Gobee是一个将Go的子集转译为BPF C的工具,允许开发者用Go而非C编写eBPF程序。它为用户空间生成类型化的Go绑定,并利用clang的后端进行编译。
介绍模块化扩散器 - 用于扩散管道的可组合构建块
Hugging Face 推出了模块化扩散器(Modular Diffusers),这是一个使用可组合、可重用的构建块而非单体管道实现的新框架,用于构建扩散管道。该系统允许灵活地混合匹配图像生成工作流的组件,并支持与 Mellon 等可视化工作流工具的集成。
@corelumen: https://x.com/corelumen/status/2057880730827661359
llmff v0.1.2 已发布,提供受FFmpeg启发的命令行界面,用于构建可组合的LLM推理流水线。此版本侧重于在Linux、macOS和Windows上的分发和可安装性。