Clojure Web 应用栈的构建:第一冥想 (Meditation One)
Aditya Athalye 的写作与工作空间
目前正在进行的项目: "为极客写作 (Writing for nerds) ".
Clojure Web 应用栈的构建:第一冥想 (Meditation One)
↓ toc 发布时间: 2024-08-24 更新时间: 2025-02-18 作者: Aditya Athalye
在一个缺乏标准 "杀手级应用" Web 框架的领域,人们必须思考所有移动部件的 "什么"、"为什么"、"如何"、"在哪里"。 在这里,除了 Web 应用程序架构之外,人们还必须成为 Web 框架架构的学生。 因为在这里,在 Clojure 的世界里,两者合二为一。 ☯
✉ 通过电子邮件发送评论 » 分享 → [bsky] [in] [HN] [lobste.rs] [reddit] feed Email 标签: #clojure#web_development#websites#functional_programming#software_design#architecture#howto#whyto
目录
关于 (这篇文章)
变得像寓言一样 (Getting Parable-ic)
掌握全局 (Getting the Big Picture)
变得哲学 (Getting Philosophical)
杀手级应用杀了什么? (What does a Killer App kill?)
有什么陷阱? (What's the catch?)
在 Clojure 的世界里,你堆叠库,并自己承担风险 (In Clojureland you stack libraries and the odds yourself)
Ring 世界 (The Ring world)
类框架的 Web 栈项目 (Framework-like web stack projects)
为知情人士提供的依赖注入 (Dependency injection for those in the know)
那不是全部 (That's not all folks)
"没有 ~~勺子~~ 架构" ("There is no ~~spoon~~ architecture")
为什么要成为 Web 栈的学生? (Why become a student of the web stack?)
获得第一性原理 (Getting First-Principled)
Web 应用只是一个多态分发器 (A web app is just a polymorphic dispatcher)
Ring 与 Jetty 是经典组合 (Ring with Jetty is the classic combo)
最简 ring.adapter.jetty
Web 应用 (Bare minimum ring.adapter.jetty web app)
最简目录结构 (Bare minimum directory structure)
最简库依赖 (Bare minimum library dependencies)
最简代码 (Bare minimum code)
最简实时应用 (Bare minimum live application)
最简 HTTP 请求 (Bare minimum HTTP requests)
从第一性原理派生的最简 Ring 项目 (Bare minimum Ring project derived from first principles)
与外部世界交互 (Interface with the outside world)
与我们交互 (Interface with us)
提供 HTTP 的舒适性 (Provide HTTP creature comforts)
使用中间件协调和控制处理程序执行 (Orchestrate and control handler execution using middleware)
最简路由器 - 多态分发器出现 (Bare minimum router - the polymorphic dispatcher appears)
入门和辅导 (Getting Started and Tutored)
通过小型演示快速入门 (Speed run through small demos)
进行更多动手实践 (Do more hands-on practice)
审查当前的技术水平 (Review the current state of the art)
将自己的脖子放在线上 (Putting one's neck on the line)
脚注 (Footnotes)
关于 (这篇文章)
- 这篇文章很长! 跳过任何让你感到无聊的部分... 遵循 精彩的目录!
- 这是一篇 "我到我" 的解释,我希望我很久以前就能得到。 我参考了 入门材料 和 "自带电池" 的 Clojure Web 栈,它们可能这篇文章中最实用的部分。
- 更新 (2025-02-18): 链接到我在 Functional Conf 2025 上的演讲: 使用函数式第一性原理组合 (Clojure) Web 栈 (视频, 幻灯片).
- 更新 (2024-09-04): 我刚刚发布了一个完整的项目,它遵循了这篇文章中设置的概念。 adityaathalye/usermanager-first-principles 是 seancorfield/usermanager-example 的一个简化版本。 该项目 README 解释了所有内容。
- 错误和不准确之处都是我个人的 1。 如果您发现任何错误,请写信至 complaints @ 我的域名。 (如果您想说一些好听的话,请写信至 compliments @ 我的域名 :)。 请在 HackerNews (这里) 和 r/Clojure (这里) 上进行讨论。
- 假设您对 Clojure 编程语言有基本的了解 2。 熟悉 Web 开发会有所帮助。
- 我将坚持讨论与经典 Web 框架相关的 Clojure Web 应用程序栈。 主要是 3。
- 是的,正在起草 第二冥想。 它是关于 深入森林。 如果它面世,也将是一篇巨大的文章。 Lambda 帮助我们。
变得像寓言一样 (Getting Parable-ic)
无数宣誓的 "Rails 开发者"、"Laravel 开发者"、"Django 开发者"、"Next.js 开发者" 等等遍布宇宙... 为什么?
新手不知道他们不知道。
The Framework
将他们从自己手中拯救出来。 所以他们生存下来。中级开发者知道足够的东西,但宁愿不 需要 做。 但是 这是一种生活。 人必须吃饭。 所以就是这样。 RTFM。
专家知道足够的知识来深入关心 4。
The Framework
是肌肉记忆; 所有的黑客、技巧、深层的秘密。 它会屈服于意志。 大部分情况下。大师知道足够的知识来不自己动手。 然而... 有时他们会这样做。 一个新的循环开始了。
很久以前,有一个。 WebObjects。 现在他们数不清。
掌握全局 (Getting the Big Picture)
以这种方式想象 Clojure Web 栈... 首先做一个很好的近似:
- 我们的业务逻辑 (用 Clojure 编写),
- 依赖于一堆 Clojure 库 (通常是 ring-clojure),
- 知道如何使用应用程序服务器 (Jetty)。
- 依赖于一堆 Clojure 库 (通常是 ring-clojure),
- 在最简单的部署模型中,此图适合单个计算实例 (例如 PC、云 VM 或 "无服务器" 容器)。
| |
| The bulk of our | Our Business Domain's
| application logic, | data representations
| written in Clojure. | {} [] #{} '() 'x 42
| |
+- - - - - - - - - - - + -- RING SPEC -- -- -- -- -- --
| | ^ |
| The subset of Ring | | |
| libraries we use | { } REQUEST hash-maps
| as-provided, and | | |
| as utilities to | | |
| make custom handlers| | |
| and middleware in | | { } RESPONSE hash-maps
| Clojure. | | |
| | | v
+----------------------+ -- RING SPEC -- -- -- -- -- --
| CLOJURE MAPS | Clojure facing interfaces
| | (functions, hash-maps)
+- ring.adapter.jetty -+- - - - - - - - - - - - - - - -
| | Java facing interfaces
| JAVA OBJECTS | (Servlet API, Jetty config API)
+----------------------+ ------------------------------
| Jetty | (deserialize ^)
| (Application server, | HttpServlet Objects
| "embedded" mode.) | (serialize v)
+----------------------+ ------------------------------
(Plaintext HTTP Responses v)
NETWORK BOUNDARY facing the WWW side
(Plaintext HTTP Requests ^)
+----------------------+ ------------------------------
| Public Web Server | (deserialize ^)
| (for SSL termination | HTTP Objects
| static assets etc.) | (encrypt, serialize v)
+----------------------+ ------------------------------
(HTTPS Responses v)
NETWORK BOUNDARY with the Public WWW
(HTTPS Requests ^)
但在变得太实用之前,先做一个放纵的哲学插曲。
变得哲学 (Getting Philosophical)
我认为框架是一种工业自动化形式 (选择、行为、工作流程、细节等)。 从这个角度来看,它们体现了工业自动化的权衡 5。
Tim Ewald 在他的演讲 "Clojure: 使用手动工具编程" 中对这种现象进行了精明的观察。 正如他所说,自动化的普遍使用具有改变我们看待世界和我们感知问题的方式的阴险特性。 对于一个投资于 一个 框架的思想来说,所有 Web 软件看起来都具有不可抗拒的框架形状。 眯起眼睛,答案就会显现出来。 而且他们很可能是对的。 直到他们不是。
Clojure 世界以艰难的方式做到这一点; 即,非框架方式。
杀手级应用杀了什么? (What does a Killer App kill?)
构建 Web 应用程序可以说是进入软件行业最常见的途径。 自然。 地球上许多最有价值的公司都是 Web 应用程序。 富裕的 Web 表单 统治 着世界。
随着 Web 的发展,Web 应用程序框架 获得了任何受人尊敬的编程语言生态系统的 “杀手级应用” 的地位。 框架制造商努力满足 Web 应用程序的多方面、不断发展的需求。 他们的产品包含经过时间考验的想法; 许多思想积累的知识,与狂野的 Web 进行全面接触 Kumite 带来的战斗伤痕。 在礼貌的社会中,我们将这些伤痕称为 “设计模式”。
很好地了解框架可以将一个人从构建软件以解决诸如以下问题之类的兔子洞中解放出来 6:
- 应用架构 (MVC) 和代码布局 (项目模板)
- HTTP 请求/响应解析和处理
- 路由和分发
- HTML 模板和/或渲染
- API 设计和使用 (HTML / 文本 / JSON 等)
- 表单处理
- 数据序列化/反序列化
- 会话
- 持久连接 (Websocket、长轮询)
- 数据库连接器/驱动程序 (例如 JDBC)
- 数据库 ORM
- 发送电子邮件
- 管理作业队列
- 配置 (通过环境变量、文件、远程源)
- 应用运行时生命周期 (依赖注入、启动/停止等)
- 安全性 (加密、数据清理等)
- 身份验证和/或授权
- 应用日志记录
- 监控 (使用指标和/或探针来监控实时运行时)
- 构建和打包
- 部署 (新时代框架)
- 使所有这些协同工作所需的样板代码和粘合代码。
- 开发者体验 (框架感知工具和 IDE 是救命稻草)。
- 更多...
也就是说,与所有事情一样,TANSTAAFL。
有什么陷阱? (What's the catch?)
使用框架的权衡源于放弃给它及其生态系统 (想法、插件、包、工具等) 的控制程度。 人们接受某种形式的供应商锁定,以代替预期的好处。 一些权衡是:
- 固定的核心架构。 任何框架的架构都是固定的; 从外部无法更改。 例如,如果您不喜欢内置在您选择的框架中的路由器或 ORM 或模板引擎,您可以直接将它们删除并放入其他选择 (出于 API 或性能或安全原因) 吗? 不,您将不得不迁移到整个替代框架,并打赌 这个 框架将满足您当前 和 (未知的) 未来需求。
- 泄漏的抽象。 框架和/或插件作者的设计选择和心智模型不可避免地会流入应用程序。 它开始依赖于他们如何编码显式和隐式行为、软件设计模式、关于部署和操作的观点等。 使用越多,绑定越强。
- 维护。 您拥有整个复合的设计和维护,特别是您自己策划的和/或定制的部分,这些部分修补、调整或解决那些泄漏的抽象。 不可避免的框架更新是理所当然的 (例如安全补丁和/或访问新功能)。 即使没有自定义部件,应用程序制造商也必须仔细更新所有现成的插件和工具,以保持 API - 兼容性。 然后还要更新他们自己的应用程序代码,以与任何更新的第三方 API 兼容。
- 生产专业知识。 调试生产问题可以很快变成理解框架的内部运作。 也就是说,迟早人们必须成为该特定 Web 框架的学生。
- 选择是专家的事情。 虽然流行的语言生态系统有一个或两个标准的 Web 框架,但所有语言都有大量的替代方案。 在框架之间进行选择是专家的事情。 新手被引导到最标准的框架是有充分理由的。 每种替代方案都体现了一组具体的权衡、社区支持、知识等,所有这些对于新手来说都是不透明的,即使对于外部专家来说也很难解析。 这可能就是为什么团队围绕一个框架 以及 一两个框架专家而建立。
在 Clojure 的世界里,你堆叠库,并自己承担风险 (In Clojureland you stack libraries and the odds yourself)
这里的文化强烈偏爱库而不是框架。 这是一个快速概述我们在 Web 生态系统中拥有的内容,以及由此产生的影响。
Ring 世界 (The Ring world)
Ring 项目,由 James Reeves (又名 weavejester) 创建,是 Clojure 生态系统中标准的 HTTP 库集合。 它的设计选择对整个 Clojure Web 生态系统产生了深远的影响。 因此,值得熟悉 Ring。
James 还创建了 hiccup (HTML 渲染) 和 compojure (路由),它们与 Ring 和 Clojure 的标准库一起使用,足以创建一个功能齐全的传统多页面 Web 应用程序,由文件系统支持。 要使用数据库,我们只需要像 next-jdbc 这样的库即可。 使用 HTMX 制作 "现代感觉" 的 Web UI 变得容易,HTMX 与 hiccup "开箱即用"。
我认为,大多数 Web 应用程序都可以从这种方式开始 (并且可能可以保持这种方式)。
类框架的 Web 栈项目 (Framework-like web stack projects)
几个类框架的 Web 栈也为 Clojure 的世界增光添彩,即 Fulcro、Biffweb、Kit ( Luminus 的后继者)、Duct、Pedestal 等。 然而,与完全集成的单体对象导向框架不同,这些是开放式的库集合,代表了项目开发者关于如何构建 Web 应用程序的观点。 像 sitefox 和 donut 这样的新项目旨在成为更 "完全集成" 的框架。 单页面应用程序爱好者可能会觉得 hoplon 很酷。 如果你想要真正新颖的系统,请查看 Red Planet Labs 的 hyperfiddle/electric 和 Rama。
为知情人士提供的依赖注入 (Dependency injection for those in the know)
另一种方法是使用像依赖注入框架这样的东西,通过一些通用系统连接和协调我们应用程序的所有移动部件。 像 component、integrant、mount、donut-system 这样的库可以实现此目的。 这些受到已经对他们需要的 (以及为什么) 的库和基础设施特定集合有特定看法的人的青睐。
那不是全部 (That's not all folks)
我们甚至还没有开始列举数据库、缓存、安全性、日志记录、监控、队列、作业等所需的其他库。
即使是经验丰富的程序员,如果刚接触 Clojure,也会难以在这一系列令人眼花缭乱的选择中找到方向。
"没有 ~~勺子~~ 架构" ("There is no ~~spoon~~ architecture")
唉,不仅没有明显的 一个真正框架 ,也没有明显的 一个真正框架架构 。 这增加了每个 Clojure 新手的挣扎,即使是饱经风霜的 Web 老手。 作为一个思想实验,我觉得 Rails 开发者可以很容易地理解 Django 或 Laravel 项目,而不是用我们在 Clojure 生态系统中的工具构建的任何应用程序。
流行的 Web 框架,一直可以追溯到 WebObjects (1996),都是面向对象的 GUI 软件; 沿常见行业范围内的 OOP 模式 趋同进化 的产品。 它们被设计为通过公共 API 使用。 核心部件焊接在一起。 因此,一个有能力的 Rails 开发者跳伞到一个 Django 项目中,可以合理地期望沿着熟悉的类层次结构和方法链,跨越熟悉的模型、视图、控制器结构来遵循他们的嗅觉。
为什么要成为 Web 栈的学生? (Why become a student of the web stack?)
Clojure 的世界,虽然是用 Java 为 JVM、.Net 为 .Net CLR 和 Javascript 为 Node 和浏览器引擎构建的,但却与那些底层面向对象的基础截然不同。
这一事实深刻地影响了一切,包括制作 Web 应用程序。 因此,虽然新铸造的勇敢的 Clojurians 会选择 Ring 栈或流行的 "入门工具包",但我们也必须有意识地成为 Web 框架架构的学生。
因为在这里,制作 Web 应用程序的问题也是组合定制 Web 栈的元问题。
获得第一性原理 (Getting First-Principled)
许多精彩的资源教授 Clojure/ClojureScript Web 开发。 然而,在我制定一个第一性原理模型来构建我的理解之前,我一直难以构建一个连贯的图像。 因此,这里有一些基本要素,以激发进一步的学习,使用我 稍后参考 的材料。
Web 应用只是一个多态分发器 (A web app is just a polymorphic dispatcher)
想想... Web 应用程序真正做什么?
HTTP request ->
/pattern-1/ method-1
/pattern-2/ method-2
/pattern-3/ method-3
-> HTTP response
Shell 脚本爱好者会立即想到 AWK 程序,并且他们的设计感不会是错误的。 但是 当然还有更多故事。 因为 "模式" 是一组或多条从 HTTP 请求中提取的信息,最关键的是 URI 和 HTTP 动词 7。
HTTP request ->
GET /uri-1/ getter-method
PUT /uri-1/ putter-method
POST /uri-1/ poster-method
PATCH /uri-1/ patcher-method
DELETE /uri-1/ deleter-method
-> HTTP response
这种模式诱使我们构建一个 HttpObject
,并且可以说这就是为什么现代 OOP 风格 看起来 是一个自然的选择。 是的,最小的一块看起来像一个对象。 是的,整个 Web 应用程序作为一个系统 是 非常面向对象的。 然而 ,恕我直言,框架的单体设计根植于不得不使用最小的数据作为一些具体的 HttpObject,而不是通用数据。
Clojure 开发者喜欢通用数据而不是具体的对象,喜欢组合而不是继承,因为使用可组合的部件构建使我们几乎可以无限地控制应用程序的形状、大小和复杂性。 随着时间的推移,最初的学习曲线会得到回报,因为我们可以保持简单的应用程序保持绝对简单,并确保不太简单的应用程序仅在需要时才变得复杂。
我们使用函数式编程 部件 构建我们的多态 系统 。
考虑到这一点,我们构建了 Clojure Web 应用程序解剖结构的核心直觉,它存在于 ring-clojure 的核心...
Ring 与 Jetty 是经典组合 (Ring with Jetty is the classic combo)
参考 全局。
Ring 项目 是生产 Clojure 8 Web 应用程序中最受欢迎的项目。 它建立了 [Ring 规范](https://www.evalapply.org/posts/clojure-web-app-from-scratch/<https:/