Aditya Athalye 的写作与工作空间

read see mail self now feed

目前正在进行的项目: "为极客写作 (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)

关于 (这篇文章)

变得像寓言一样 (Getting Parable-ic)

无数宣誓的 "Rails 开发者"、"Laravel 开发者"、"Django 开发者"、"Next.js 开发者" 等等遍布宇宙... 为什么?

新手不知道他们不知道。 The Framework 将他们从自己手中拯救出来。 所以他们生存下来。

中级开发者知道足够的东西,但宁愿不 需要 做。 但是 这是一种生活。 人必须吃饭。 所以就是这样。 RTFM。

专家知道足够的知识来深入关心 4 The Framework 是肌肉记忆; 所有的黑客、技巧、深层的秘密。 它会屈服于意志。 大部分情况下。

大师知道足够的知识来不自己动手。 然而... 有时他们会这样做。 一个新的循环开始了。

很久以前,有一个。 WebObjects。 现在他们数不清。

掌握全局 (Getting the Big Picture)

以这种方式想象 Clojure Web 栈... 首先做一个很好的近似:

|           |
| 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:

也就是说,与所有事情一样,TANSTAAFL

有什么陷阱? (What's the catch?)

使用框架的权衡源于放弃给它及其生态系统 (想法、插件、包、工具等) 的控制程度。 人们接受某种形式的供应商锁定,以代替预期的好处。 一些权衡是:

在 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 的世界增光添彩,即 FulcroBiffwebKit ( Luminus 的后继者)、DuctPedestal 等。 然而,与完全集成的单体对象导向框架不同,这些是开放式的库集合,代表了项目开发者关于如何构建 Web 应用程序的观点。 像 sitefoxdonut 这样的新项目旨在成为更 "完全集成" 的框架。 单页面应用程序爱好者可能会觉得 hoplon 很酷。 如果你想要真正新颖的系统,请查看 Red Planet Labs 的 hyperfiddle/electricRama

为知情人士提供的依赖注入 (Dependency injection for those in the know)

另一种方法是使用像依赖注入框架这样的东西,通过一些通用系统连接和协调我们应用程序的所有移动部件。 像 componentintegrantmountdonut-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:/