本篇内容是根据2023年1月份#266 Is htmx the way to Go?音频录制内容的整理与翻译

快速浏览一下构建 Web 应用程序的历史,然后讨论htmx以及它与现代和传统构建方式的比较。


过程中为符合中文惯用表达有适当删改, 版权归原作者所有.




Jon Calhoun: 大家好,欢迎来到 Go Time! 今天我们请到了 Carson Gross。Carson,你可以跟大家打个招呼吗?

chg: 大家好,很高兴来到这里!今天很兴奋能聊一聊如何在 Go 中完成更多工作。

Jon Calhoun: Carson 自称是个“灰胡子”程序员(gray beard programmer),他是 htmx 的创作者,我们今天会重点聊这个。他还创建了 Hyperscriptgrugbrain.dev。另外,今天还有 Chris James,他最近是个“失业的流浪汉”,不过如果有人在招人,他在找工作。Chris 是 Learn Go with Tests 的作者。Chris,最近怎么样?

Chris James: 非常不错!很高兴能上节目。我对 htmx 充满好奇,所以很期待今天的讨论,学到更多东西。

Jon Calhoun: 很好!最后还有 Dave Wickes,他是个“想变成灰胡子的程序员”,目前是个“有工作的流浪汉”,他喜欢改 Learn Go with Tests 中的拼写错误---不过我不确定他是不是真的喜欢。他在 Twitter 上不太友好,而且写了很多 JavaScript。Dave,你怎么样?

David Wickes: 嗯,还好。

Jon Calhoun: 如果你觉得这个播客开始走下坡路了,其实我们这里有个写 JavaScript 的人,还有个失业的流浪汉...

chg: [笑] 是啊,我觉得在 Twitter 上过于“好战”是不可能的吧?真的可以过头吗?

Chris James: 在 Twitter 上怎么可能友好?我觉得不可能... 我总是陷入这种讨论里。

Jon Calhoun: 好吧,今天我们主要聊的是 Carson 创建的 htmx。不过在进入主题之前,我想先聊聊我们是如何一步步学习开发 Web 应用的,以及这些技术是如何随着时间演变的。这样大家可以更好地理解今天 Web 应用的现状,以及为什么会有这些不同的技术诞生来帮助我们... 我觉得这会让我们更清楚 htmx 是如何融入整个生态的。

那么,从头开始吧。谁愿意来聊聊你们最开始是怎么开发 Web 应用的?最初的流程是怎样的?

chg: 那我就先说吧。不过我也很好奇 Chris 和 Dave 的经历。我是在 90 年代末开始做 Web 开发的,那时候是用 CGI 技术。所以我们会用 Perl、Bash 或其他任何能用的工具,通过 CGI 网关来创建动态网站,稍微增加一点动态性。跟现在的结构化开发相比,那时的开发非常无序。

后来我对 Java Applets 产生了很大兴趣。它们有点像现代的单页应用(SPA),但它们有个沙盒(sandbox),不能与浏览器进行太多的交互。那段时间我主要研究 Applets,之后 Java 世界接受了 Web,我就开始做一些更贴近今天开发者所熟悉的 Web 编程了。我的入门过程大概就是这样。

Jon Calhoun: 如果我没记错的话,当时 Applets 应该被宣传为 Web 应用的未来,对吧?它们号称可以“运行在任何地方”。

chg: 是的,当时确实是这样。而且还有 Flash,设计领域的人用了很多 Flash。如果今天的开发者想象 Canvas,可以把它想象成一个带有非常复杂 API 的 Canvas。说来也有点有趣,我现在成了超媒体(hypermedia)的代言人之一,不过在 2000 年代中期,我其实放弃了 Web,转而使用 Java Webstart。这是一种通过网络向用户交付 厚客户端(thick client)的方式,用来构建客户端应用和网络应用。后来我又回到了 Web 领域,开始更欣赏它本身的特点... 不过回顾这段经历,我觉得挺有意思的。我很早就接触了 Web,但当时不喜欢,于是转去做 厚客户端,后来又回归并发现“哦,其实 Web 本身还是有它的价值的”。

Jon Calhoun: 那么,当时你用 CGI 技术做页面的时候,是不是跟 PHP 类似?主要是 HTML 页面里嵌入一些代码?

David Wickes: 是的,可以这么说。不过你可以想象 PHP,但比它还“简陋”。

Jon Calhoun: 好吧。

David Wickes: 就像是一个非常“糙”的 PHP。

Jon Calhoun: Chris,你呢?

Chris James: 我也是在 90 年代末开始的。我当时 14 岁,特别酷,我做了一个关于职业摔跤的网站。我是在 Geocities 上做的,这是 Yahoo 提供的一个免费服务,你可以上传自己的 HTML 文件,搭建网站。当时我完全不会编程,只会“查看源代码”,然后去别的网站复制粘贴代码。

现在回想起来,这种方式显得很古老,但其实我现在的博客基本上还是这样的。我有 HTML 文件,唯一的区别是现在我们有 CSS 来做样式,而当时都是内联样式(inline styles)。但核心技术其实并没有太大变化... 过去 24、25 年后,还是差不多的。

Jon Calhoun: 那你当时做网站,是不是用大表格(table)来布局,然后把图片切割开?

Chris James: 是的,当时没有 CSS 布局的概念,也没有“居中 div”这种梗。那时候都用嵌套表格(nested tables)布局... 还有一种技术叫“空白图片”(spacer images),用它来人为地在页面上制造间隔。这种方法虽然很奇怪,但其实挺有创意的... 当然,现在看起来完全无法接受。但当时确实管用。

Jon Calhoun: 我记得不久前我还用过这种方法。我在设计一个东西时,就想着“我知道这个方法以前是可行的”。然后心想“算了,先用这个顶着,以后再改”。

对了,说到 Geocities,你有没有听说过 Neocities?

Chris James: 没有,那是什么?

Jon Calhoun: 我觉得它是模仿 Geocities 的一种“复刻版”,但我没怎么用过,所以也不是很清楚。我只是某次看到过它。

chg: 那里面有火焰动图(fire gifs)吗?

Jon Calhoun: 我希望有,但不确定。

chg: 如果没有火焰动图,那就没意义了。

Chris James: 我当时特别喜欢火焰动图。Flamingtext.com 对我来说简直是神奇的存在。我当时觉得这就是技术的巅峰,太棒了。

Jon Calhoun: 我还记得滚动的文字标语(scrolling marquee text),每个 Geocities 网站上都有。真是一些好功能啊。

Chris James: 本来我还担心这次对话会变成大家在怀念过去的美好时光,结果现在只觉得“天啊,那简直是地狱。” [笑]

Jon Calhoun: 确实有一些糟糕的地方。

chg: 每个时代都有它的好与坏。

Chris James: 继续说我的经历---后来我确实学会了一些编程,我想我参与的第一个比较大的网站是用 PHP 做的。那是一个稍微动态一点的网站,比如从数据库中提取数据之类的功能。但我记得当时 CSS 的支持还不是很好……所以即使我可以写一些编程代码,在服务器上生成 HTML,我当时写的是 PHP,但样式还是直接内嵌(inline styles)在标记里面。

对我来说,Web 开发的一个巨大进步就是 CSS 的支持变得更好了。不仅因为它提供了一种更简单的方式来做样式,还因为它让服务器端的工作变得简单了很多。以前需要生成带有内联样式的 HTML,但现在只需要生成干净的 HTML 就行了。这对我来说是一个转折点,从那时起 Web 开发开始变得比以前高效了。

Jon Calhoun: 好的,所以我们谈到了静态页面---或者更准确地说,是服务器端渲染的页面,比如用 PHP 或其他技术构建的页面。后来,Web 开始推动更交互式的方向发展。而让我印象最深刻的第一步是 Flash。当然,你提到过 Applets,我相信它们也在做类似的事情。那么,你们有人开发过 Flash 吗?(环顾四周)好吧,我看到大家都摇头,所以我猜没有。但对于年轻一些、可能不记得的人来说,Flash 曾经是互联网上所有游戏的制作方式。你会进入一些页面,上面有非常精致的游戏,这些游戏运行在 Flash 上,你可以完成很多交互操作。我觉得这为我们现在在浏览器中构建应用的概念铺平了道路。现在你会看到像 Google Docs 这样的应用,但回到 Web 刚诞生的时候,能在浏览器中实现一个文本编辑器的想法几乎是不可思议的。你们刚入行时也有这种感觉吗?

chg: 是的,我觉得在早期确实有一个非常大的割裂,割裂在于富内容(rich content)和超媒体内容(hypermedia content)。超媒体内容相对比较简单---其实今天在某些场景下依然很简单,比如一些非常基础的表单和控件。最复杂的情况也无非是一些超媒体文档,用户在其中点击链接跳转。而另一边是富内容的世界,这个世界通常有更丰富的事件处理,通常是基于 Canvas 的,或者有一些在 Canvas 之上的基础设施。这就是 Flash 提供的东西---它让你可以进行更低级别的图形编程。实际上,Flash 是一种非常有趣的技术,回过头来看它有很多很酷的功能……它既提供低级工具,也提供高级工具,可以用来构建这些内容。

Chris James: 我一直觉得 Flash 是早期 Web 世界的一大悲剧。不是指写 Flash 的过程,而是因为有太多令人惊叹的内容是用 Flash 构建的。我记得有成百上千的网站是用 Flash 搭建的,上面有各种游戏。但这些内容现在都无法访问了。没人能再看到它们,没人能再玩这些游戏,也没人能再访问这些网站了,因为它们是基于一个已经消失的平台。这真的很让人难过。

Jon Calhoun: 那些网站都关掉了吗?如果我启动一台非常老的电脑,装上一个老版本的浏览器,还能访问那些网站吗?

Chris James: 我觉得你可能还能找到 Homestar Runner 网站,不知道这个名字你有没有印象……

Jon Calhoun: 有印象……我还能看他收邮件。就这样吧(笑)。好吧,现在我们有了所谓的现代 Web 应用,对很多人来说就是 Web 应用本身,比如 Gmail、Google Docs,或者任何你在线上看到的交互式页面。那么,当你们提到现代 Web 应用时,你们觉得它们通常是怎么构建的?传统的方法是什么?

chg: 我想今天的默认方法应该是用 React 前端连接一个后端,通过 JSON API 通信。这是我看到的大多数人采用的标准方法。我不确定这是否应该被称为“传统”的 Web 应用架构,但我可以肯定地说,这现在是最常见的方式。当然,也有一些网站是用 Vue 或 Svelte 等其他方式构建的……但总体来说,如果你问十个 Web 开发者标准的构建方式是什么,他们大概都会提到这个。Tom MacWright 好像写过一篇博客,说这就是目前大多数人构建网站的标准方法……我不知道 Chris 和 David 是否同意。

Chris James: 这不是我选择构建网站的方式,但这确实是我被支付工资要求我使用的方式。在很多公司里,React 以外的技术选择非常少见。你会被要求“这是技术栈,用 React 吧。请使用自从你上次用 React 以来新增的第七个激动人心的功能”,但这是另一个话题。“然后构建你的后端服务,确保它是‘RESTful’的”(对着摄像头做引号手势),“然后用很多 React 组件构建前端。”这基本上就是现在的主流方式。当然,有人用 Angular 或其他东西,但这些技术的覆盖范围现在已经没以前广了。

Jon Calhoun: 我同意。如果人们想到现代 Web 应用,这种方法是他们脑海中浮现的东西。正如你所说,它也可能是某种不同的 JavaScript 前端,但基本上是类似的架构。我甚至见过有人用 gRPC……有一些服务允许你用各种方式使用 gRPC,比如设置一个前端来将其转换为 JSON,或者直接让某些东西自动处理。但总体来说,还是差不多的思路。我想 GraphQL 也混在其中……虽然它还是返回 JSON,但只是获取数据的一种稍微不同的方法。

chg: 是的,GraphQL 是一种构建在 JSON 之上的技术。David 提到 RESTful……但我认为 GraphQL 是从 REST 迈向通用查询语言的一步……这很合理,因为在我看来,SPA(单页应用)的方式实际上就是在构建一个 厚客户端(thick client)。就像我当年用 Java Swing 尝试做的那样。而当你开始构建厚客户端时,你对网络协议的需求会跟原生 Web 提供的东西不同,你会需要像 SQL 那样的通用查询语言。所以 GraphQL 就显得很有意义。而且,为什么还要用 JSON 呢?可以用 Google 的序列化技术,或者其他技术。

Jon Calhoun: 那么,当有人实际使用这种现代方法来构建应用时,我想你们多少都有一些经验。或者即使没有,你们至少和开发者聊过。那么,实践中这种方式到底是什么样的?感觉如何?因为在理论上听起来很棒:服务器和前端分离,交互流畅。但根据我的经验,这并不总是那么理想。你们的经验如何?这种方式的实际体验如何?

Chris James: 老实说,有时候你会觉得很高效。如果一切设置正确,库和框架的组合也恰到好处,那确实会很高效。但对我来说,每次在这些系统上工作时,我总觉得需要掌握大量关于 React 的知识才能完成工作。而且用这种方式很容易做出不太好的东西。

举个经典例子:很多时候你会访问一个 React 网站,如果写得不好,它可能没有经过优化,比如你可能需要下载 30MB 的 JavaScript,或者当你尝试按返回键时,它却不起作用。人们总会说,“有 React Router,还有其他东西可以解决。”但对我来说,这让我觉得很累,因为又多了一样需要学习的东西。

回到我 14 岁时第一次做网站的时候,我根本不用担心浏览器历史记录或者返回按钮---它们开箱即用,因为浏览器会处理这些功能。但当你进入这个厚客户端的世界时,很多浏览器本来提供的功能被你丢弃了,然后你要么自己重新实现它,要么至少需要知道如何引入某个库的特定版本来让它像浏览器一样工作。对我来说,这就是累人的地方。毫无疑问,SPA 适合很多场景,但它带来了巨大的复杂性。而且,现在 React 已经版本 18 了……有趣的是,Go 和 React 的诞生时间差不多。我知道 React 是一个框架或库,而 Go 是编程语言,但让我感到有趣的是它们同龄。不过 Go 的发展路径非常简单,我从来不觉得需要重新学习 Go。但 React 刚开始是用类组件(class components),突然之间这又变成了错误的方式,你应该用函数组件(functional components)。没人能真正解释为什么,除了大家似乎对面向对象(OO)有很大的厌恶……所以,是的,我觉得这需要学习的东西太多了,尤其是对于一些项目来说,这种复杂性完全没有必要。

David Wickes: 是的……我进入软件开发行业的时间非常晚,我当时已经35岁了。我参加了一次编程训练营,因为我厌倦了营销工作(这点至今仍然合理)。我在很多方面都很幸运,我赶上了一个转折点。我学会了用服务端渲染来构建Web应用:比如Rails(Ruby on Rails),我学了Sinatra……我是一个Ruby开发者,很多训练营都会教Ruby。基本上,我参与的第二个项目是一个React应用,而第一个项目则是前端HTML、少量JavaScript,以及大量的服务端渲染。从那以后,我的工作一直是断断续续地和React打交道。

每次我再次接触React,每次重新开始时,无论是间隔一个月还是三四个月,我都感觉自己需要从头开始。突然间,你会发现一些新东西,比如“我们现在要用内联样式了,或者,我们要把样式放到这里,现在一切都用hooks了”。在我看来,hooks就是一种偷偷把状态引入应用的方式,而不是明确地用对象表达……不过,这也没关系。

但是,每当我停下来不用React,转而用服务端渲染去构建一些东西时,发现它还是一样的。我依然在做2014年或2015年时我第一次学到的那些事情……而React却一直是一个不断的战斗,不断的更新换代,但最终产出的却是完全相同的网站。就我来说,这真是个灾难(笑)。我用React(当时不知道是哪个版本,也许是8,也许是10)构建的网站,和每一次React的迭代版本构建出来的东西几乎没什么区别。所以,我觉得自己陷入了某种React开发的“轮回”,痛苦永无止境……不过,请雇我来做你的React网站(笑)。

chg: 是啊……早期的时候,我回归到了Web开发领域,2000年代中期我在用Rails工作,因为当时每个创业公司都在用它构建应用。那时,React刚刚问世。我注意到React的一些特点---我当时觉得“哦,这很新颖、有趣,它和我以前做的一些厚客户端编程有些关系。”但我注意到的是,我们开始构建一个庞大的JavaScript前端代码库,就像一个独立的代码库。今天,很多人可能觉得这是理所当然的,但早些时候并不是这样。以前,你主要用后端语言工作。

我并不是JavaScript的超级粉丝。我逐渐接受了它的优点和某些特性。但那时,我对JavaScript的厌恶更加强烈。然而,我们还是开始不断扩展这个JavaScript代码库,而我并不想处理这些。所以,在2013年,我开发Intercooler.js(htmx的初始版本)的动机是这样的:我知道Rails,我喜欢Rails,而且我想继续用Rails,但我也想要更多的交互性。那么,如何在保持我主要在Rails中工作的情况下实现这些交互呢?我可以继续用我熟悉的Rails模板,而不是接管一个庞大的前端代码库。

当时,我注意到一个现象:一旦你开始构建一个庞大的JavaScript前端代码库,它会对你的后端施加压力,要求后端也用JavaScript。毕竟,为什么要使用两种不同的编程语言?为什么不统一编程语言,比如在前后端之间共享领域逻辑、验证逻辑等等?所以,这也是我不想接受庞大JavaScript前端代码库的一个主要原因。当时,我是一个人工作,前后端没有分离,所以我不得不同时处理两者。而我发现,我无法在脑海中同时保留React的知识以及我需要记住的关于Rails的一切。

Jon Calhoun: 所以,我确认一下---你不想写一个庞大的JavaScript应用,所以你写了另一个JavaScript应用?

chg: 哈哈,是的,这确实有点讽刺。为了避免写JavaScript,我写了很多JavaScript。而且后来变得更糟了……也许我们会聊到hyperscript,那可是为了避免写JavaScript而写了许多JavaScript。

Jon Calhoun: Carson,我觉得你提到的一个点非常值得重申,就是有了这个JavaScript前端加上一个独立的后端,小型团队或独立开发者在这种情况下可能会很难构建应用。就像你说的,他们需要知道的东西太多了。我记得我第一次参与一个React前端项目时,最让我绝望的是我们只有一个人懂React,而其他四个开发者都懂后端。即使是一个非常小的改动,在后端需要写一堆代码,而前端只需要改两行,但找到正确的方法并确保运行没问题却很困难。那个懂React的人总是被压得喘不过气来,很少有时间坐下来告诉我们“你需要这样做才能让它本地运行,并且通过测试。”所以整个过程非常难。

有时,人们会看着这些技术觉得“哦,这很棒”,但却忽略了维护成本,以及这种技术是否会改变开发速度。它可能在某些方面加快速度,但你必须有一个围绕这种技术设计的团队结构才行。

chg: 是的……你提到的实际上是全栈开发的概念。早期的Web开发有一个很棒的地方,就是它是全栈的。你不仅负责业务逻辑,通常还需要处理数据库查询等其他部分---虽然你可能会有DBA(数据库管理员)来帮忙---同时你也负责前端。你的工作具有完成感。Mark称其为“非异化的劳动”,不是那种你只拧一个螺丝,汽车就从工厂前门开出去的工作。你构建了整个汽车,整个功能模块。你可以指着它说“看,这是我做的。”还能点击它,和它交互。

写程序时,能和屏幕上的东西交互是一种很满足的体验。但前后端分离后,你的大脑也被分裂了,有时你会进入一种割裂的状态,失去了那种完成感。而如果能做全栈开发,就能有这种满足感。这种分离也会拖慢速度,可能因为沟通不畅,或是对方有其他任务需要优先处理……一旦你把一个完整的功能分割成两部分,就会在多个方面增加摩擦。

这听起来有点玄乎,但我觉得这甚至关乎精神层面。这种完成感和对功能的满足感很难在非全栈开发中实现……至少对我来说,这是我的体会。

Jon Calhoun: 我记得Bill Kennedy不久前在播客中讲过一个故事。他说,他为某个前端功能构建了一个后端,但后来他坐下来研究前端怎么实现时,发现他提供的后端对前端来说简直糟糕透了。他问前端开发者,“为什么你们不告诉我这件事?”前端开发者回答说,“我们只能用你给的东西。”然后,他就和前端开发者一起重新设计了整个后端。

就像你说的,另一个问题是,如果你没有参与整个开发过程,有时候你会设计一个对前端来说完全不友好的后端,只是因为对你来说这样比较简单。你可能意识不到前端需要什么数据,或者他们需要展示什么内容。最终,这种方式可能会让工作量更大,而不是像预期的那样更高效。

chg: 是的。而且,如果你能跨整个栈进行优化,那就不一样了。如果你提供一个通用API---这也是为什么有那么多压力要求采用GraphQL,因为它可以让前端自由决定如何使用数据,而你无需预测前端的需求。但如果你进行全栈开发---这是超媒体的一个很棒的特性。htmx是一个超媒体导向的库。超媒体的工作方式让你可以对后端的改动更具戏剧性,因为你使用的是一个超媒体系统,前后端都在同一个系统中。

这样你可以对系统进行调整和优化,而不需要像前后端分离时那样提交工单去更新API,或者花几周时间修改API。全栈开发的一个优势是,你可以快速完成一个功能,比如在10分钟内搞定,而不需要经历繁琐的流程。刷新浏览器就能看到结果,这种感觉非常神奇。

Jon Calhoun: 你提到GraphQL是为了解决前端自由需求和后端通用API的问题。其实早期的“RESTful API”理念也是类似的。人们会说,“如果按照资源设计API,前端可以调用所需的资源端点。”但这忽略了一个事实:很多页面需要从多个资源中获取数据。

chg: 是的。

Jon Calhoun: 比如一个页面需要从六个不同的资源获取数据。这时,前端开发者唯一的选择就是“让我发六个API请求。”但如果你从前到后设计这个API,你会觉得“这太傻了,为什么不一次性把所有数据发过去呢?”很奇怪,我们竟然走到了这种地步,把应用程序按照这种方式开发,而不是从整体上优化。

chg: 没错。在后端,我们以前是怎么想的呢?我们希望尽量减少数据库查询的次数。数据库有它的表结构,而我们希望一个请求发出时,查询次数最好不要超过五次,理想情况下两三次更好。为了做到这一点,我们需要使用一些非常复杂的SQL,带有大量的联结(join),并收集整理数据结构。

而对于前端开发者来说,随着这些SPA框架越来越多,他们会发现,为了高效工作,他们需要一种像SQL一样的表达能力。他们遇到了同样的问题:他们不希望后端进行大量的查询,他们也不希望前端需要调用一堆端点来拼接UI。这正是GraphQL出现的原因。

中断:

Jon Calhoun: 你创建了现在的htmx。它如何适应这种情况?或者你能为不了解的人描述一下htmx是什么吗?

chg: 就像我刚才提到的,htmx 是一个超媒体导向的库。这是一个 JavaScript 库,但我更愿意说它是一个“补全” HTML 的库。HTML 本身是一种超媒体,我们都很熟悉它。而在 HTML 中,唯一的超媒体控制元素就是锚点标签(也就是我们习惯点击的链接)以及表单。如果你使用的是传统的 Web 应用程序,那么你会习惯于填写下拉框、选择复选框、然后点击“提交”。

而 htmx 所做的事情就是将 HTML 的这两个基本元素进行泛化,并通过在 HTML 中添加属性来增强它们。通过 htmx,你可以让任何 HTML 元素向后端发送 HTTP 请求,比如 GET、PUT、POST 或 DELETE 等。这与原生 HTML 的局限性形成了鲜明对比。

那么是什么让 htmx 与众不同呢?或者说,是什么让它真正提高了 HTML 的能力?那就是你可以将从服务器返回的响应(HTML 内容)放到文档中的任意位置,而不是必须替换整个页面。我认为,这正是为什么人们从最初的 Web 模型转向 SPA(单页面应用)和重度 JavaScript 应用的一个重要原因。传统的 Web 应用程序通常表现得非常笨拙:比如你在表单中点击“提交”,整个页面就会刷新,整个页面闪烁,你的滚动状态丢失了,可能也没有很好的过程指示。这种糟糕的用户体验使得基于旧式 HTML 的应用显得非常笨重。而新的 SPA 可以在内存中更新 DOM,从而让它们感觉更加流畅。

htmx 所做的就是允许你直接在页面中更新 DOM,但它是通过与服务器进行 HTML 数据交换来实现的。从这个角度看,它和旧式应用更相似,但却拥有现代单页面应用的流畅体验。这就是 htmx 的核心理念。在实际使用中,你只需要通过 htmx 的特定属性来注解 HTML。例如,如果你想让一个按钮向 /update 发送 PUT 请求,只需要在按钮的标签上添加属性:hx-put="/update"。这样,当点击这个按钮时,它就会向这个 URL 发送一个 PUT 请求,并携带相关信息。

由于 htmx 使用的是 HTML 属性,而不是直接编写 JavaScript,所以用户在使用 htmx 时只是通过 HTML 属性注解页面。这样一来,传统的模板技术(比如 PHP、Go 的模板,或者 Rails 的 ERB 模板)又重新变得有意义了。你可以在这些模板中直接注解你希望系统实现的交互性,而不用像以前那样依赖后续补充的 JavaScript。

David Wickes: 也许你可以用更通俗的语言解释一下什么是超媒体(hypermedia),同时将其与 REST 联系起来。我们之前提到 REST 时有点讽刺意味,所以我觉得有必要明确一下我们对 REST 的理解,以及它的重要性,并将其与 SPA 式的 Web 应用开发方法进行对比。

chg: 好的,我很乐意深入这个话题。简单来说,超媒体就是一种媒体。在 HTML 的例子中(这是我们最熟悉的一种超媒体),它的特点是包含所谓的超媒体控制元素。经典的超媒体控制元素就是锚点标签(也就是链接)。当 HTML 文档中有一个锚点标签时,它使得文档具有非线性的特性。早期,超媒体最大的特点就是你不仅仅是在阅读一个文档,而是可以与它交互,点击链接跳转到其他文档,这正是 Web 的核心理念。

后来,HTML 2 引入了表单标签(form),它进一步扩展了这种交互能力。表单允许用户向服务器上传大量信息,从而实现了在线更新内容的能力。这使得 Web 从最初的学术文档链接系统变成了一个可以动态更新内容的系统。

因此,HTML 拥有两种核心的超媒体控制方式:链接和表单。这两种方式让用户可以以非线性的方式与文档交互。这也是为什么我们说 HTML 是一种超媒体。

后来,人们认识到这是一个非常有趣的技术方法。尽管这个想法已经存在了一段时间,但 Roy Fielding(他在 Apache 项目的一些早期 Web 技术中做了大量工作)在他的博士论文中正式提出了 REST 这个术语。他试图在论文中解释 Web 与之前采用的其他网络架构有何不同。论文中语言非常学术化,但核心概念是,Web 作为一种网络架构(或者说系统架构)与之前的厚客户端模型有很大的不同。

REST 的核心(我的理解)是所谓的“统一接口”(uniform interface)。简单来说,当你从服务器获取一个 HTML 文档时,你对它的内容一无所知:它可能包含链接,可能包含执行某些操作的表单等。浏览器在请求某个 URL 时,只知道会返回 HTML,但不知道具体内容会是什么。浏览器会渲染 HTML,并根据页面上的超媒体控制元素(比如链接和表单)提供交互选项。

因此,超媒体的一个有趣特性是它不仅提供数据,还同时提供数据操作。这些操作和数据作为一个整体被传输到客户端。用户可以直接选择在页面上要执行的操作,比如删除某个项目或更新某个条目。这种超媒体的特性与 JSON 有很大的不同。

在 JSON 模式中,服务器通常只返回原始数据,比如联系人信息或银行账户信息。客户端需要依赖模板将其转化为 UI,客户端代码中还需要明确指定,比如“如果需要更新客户信息,就要向这个 URL 发起 POST 请求”。这些逻辑都被编码在了客户端应用中。

因此,JSON 风格的数据 API 和超媒体响应之间存在很大的区别。而讽刺的是,现在很多人将 JSON API 描述为“RESTful”,却不认为超媒体 API 是一种 API,甚至会说“那只是一个网页,怎么能叫 API 呢?”这很遗憾,但这就是行业的发展方向。如果你对这段历史感兴趣,可以访问 htmx 的官方网站(htmx.org)的文章页面,那里有一篇文章《How did REST come to mean the opposite of REST》,详细解释了这个技术发展过程中的细节。

所以,超媒体的核心理念是:它是一种包含超媒体控制的媒体,比如链接和表单。而 RESTful 系统是一种具有一系列约束的系统架构,其中之一就是通过超媒体与服务器通信。

David Wickes: 对于一个 Go 开发者来说,这实际上意味着什么呢?这意味着你不用创建一个 JSON API,然后把它交给前端开发者,让他们去解读“如果这个字段有这个标志,就显示这个;如果有另一个标志,就显示那个”。相反,你现在可以完全控制这些交互,通过返回 HTML,让浏览器直接呈现这些控制选项。

chg: 没错。

David Wickes: 我认为 胖客户端(fat client)方法的一个主要缺点是---没错,你需要在客户端上设置这些规则,但通常你也需要在服务器端设置这些规则,以防发生一些恶意操作,对吧?比如说,如果我的银行账户余额为零,我可能不会显示“提现”按钮,对吧?但我需要同时在服务器端和客户端封装好这个逻辑。

chg: 是的,这是个很好的观点。确实需要在通信的两端(客户端和服务器端)都有这样的逻辑,这背后有技术上的原因。浏览器不是一个可信的执行环境,对吧?它是开放的,你对它没有任何保障。所以客户端的任何计算都需要在服务器端重新执行和验证。因此,如果你有一堆前端的 JavaScript 逻辑,就会有这样的压力:“我还需要用 Go 重写所有这些逻辑吗?不,我不想这样做……好吧,那我们只好咬牙用 Node 在后端实现了。”

对于 Go 开发者来说,你们可能不想听到这个;作为一名 Ruby 开发者,我也不想听到这个……所以,基于超媒体(hypermedia)的方法很有帮助,因为你可以把所有的逻辑都放到后端,只需要用超媒体与服务器进行交互。

讽刺的是,我以前也一直在说,如果你仅仅使用 HTML 文档构建一个 Web 应用,那么你构建的系统比绝大多数 JSON API 开发者的系统更符合 RESTful 的规范,因为你真正使用了超媒体,所以实际上满足了 Roy Fielding 在他的论文中提出的约束条件。因此,仅仅使用 HTML 就足够了。一旦你这么做了,你就不必担心 Richardson Maturity(理查森成熟度模型)或其他那些乱七八糟的东西。你只需用 HTML 构建一个应用,它自然就是一个 RESTful 系统。

Jon Calhoun: 我觉得值得提到的是---我不认为现在的 REST 用法是世界上最糟糕的事情。从目前的情况来看,REST 的用法已经在整个 Web 行业中基本被默认了。所以我知道我自己也有这种毛病;我教编程相关的内容,有时候我也会那样用 REST,因为我知道学习者在实际工作中会遇到这种用法,即使它在技术上并不准确。我会比较谨慎,因为没有一个“这是 RESTful 的正确定义”的统一标准---至少根据他们在网上读到的内容来说,他们会看到 100 种不同的答案。

所以我认为很多人习惯于把 REST 看成是资源导向的端点,并使用与之相关的 HTTP 方法……我觉得理解这些是好事。但我也同意你的看法,就是现在这个状态有点令人失望,人们表现得像是 REST 的狂热分子,而实际上 REST 的概念已经演变并偏离得太远了,现在陷入这种争论中有点奇怪。

chg: 是的,我建议听众不要太纠结于这些东西。现在的情况非常有趣,应该以轻松的心态面对它。就像……[笑]。

David Wickes: 如果你在工作时需要找点事情做,花个一小时或者半小时争论 REST 的定义是个不错的选择。

chg: 是的,可以把它留作对付那些烦人的工程师的“杀手锏”。

David Wickes: 没错。“哦,不,这不是 REST。” Carson,你提到的内容真的让我有了些新的想法。当我们谈到超媒体时,指的是同时包含数据和行为的东西。这应该会让很多开发者联想到传统的面向对象编程中的对象。

chg: 没错。

David Wickes: 这两种方法,超媒体和对象概念上来说,其实非常契合。

chg: 我认为它们确实有很多相似之处。但这很复杂,因为当你谈论一个超媒体系统时,客户端是浏览器。现在的 Web 浏览器是非常强大的软件,内置了许多令人惊叹的技术来支持超媒体的工作……如果你退一步,仔细想一想,浏览器这个东西真的很疯狂。你可以用同一款软件与银行、宠物食品商店、汽车经销商、日历、电子邮件客户端等等交互……任何东西!这展示了超媒体的强大之处。

它变得如此强大,以至于人们开始将它作为几乎类似胖客户端风格应用的虚拟机来使用,这其实就是单页应用(SPA)世界的演变。我认为超媒体的优势在某种程度上被削弱了,或者说被超越了,这取决于你如何看待它。但特别是在浏览器刚出现的时候,这种通过一个通用的网络客户端,利用超媒体技术与任何应用交互的想法是非常新颖的。即使现在,它仍然是非常独特的。

我觉得如果你退一步想一想,这真的很特别。如果你告诉 1980 年的人,“你知道吗?你将会用同一个软件访问新闻、银行、日历、电子邮件和其他所有东西。”他们会用一种完全听不懂的眼神看着你,除非他们刚好是某个研究小组的一员,正在研究这个方向。

所以超媒体和面向对象编程在概念上确实有很多重合之处,也具备数据隐藏等特性带来的灵活性。这是一种非常有趣的技术。我必须承认,当我刚开始做 Web 开发时,并没有真正意识到这一点。老实说,直到我开发 Intercooler 之后,我才开始真正理解它。我最初开发 Intercooler 只是因为我不想处理 JavaScript。但是当我将它作为一个开源项目发布后,一些比我更了解超媒体的人开始告诉我,“嘿,这很有趣,因为它仍然是 RESTful 的,但你通过 HTML 获得了更多交互功能,这真的很酷。”当时我对 REST 已经有点放弃了,因为像很多人一样,我对 2000 年代中期网上那些 REST 的纯粹主义争论感到十分厌烦……但当人们开始告诉我“这非常符合 REST 的理念”时,我重新审视了这个概念,并在理解之后更加欣赏 Web 平台的强大。

Jon Calhoun: 我们之前聊过 htmx 如何让我们像使用 React 那样构建交互,但却是通过传统的 HTML 来实现的,而且能完全掌控整个过程。那么这种方法与其他方法相比,有什么缺点吗?或者说,是否有一些普遍的缺点?

chg: 是的,我会建议大家去 htmx 网站上的“文章”页面看看。我写了一篇关于何时选择超媒体方法以及何时不选择的文章。超媒体方法在你想要简化复杂性的时候非常有用。它比管理大量前后端状态并使之相互同步的模型要简单得多。而且你会发现通过这种方法可以实现很多功能。

如果你去 htmx.org/examples 页面,可以看到很多用这种技术构建的用户界面的例子,其中一些可能比你们预期的更复杂。不过也有一些场景超媒体方法并不适用。我常举的一个经典例子是类似 Google Sheets 的情况。当你在 Google Sheet 的界面上修改一个单元格时,这种修改可能会在整个 UI 上产生级联效应---这种场景就不太适合超媒体方法,因为它需要进行一次大的服务器调用,在服务器端重新渲染,然后将更新后的 UI 状态流式传输回前端。

所以如果你的 UI 依赖性很高,而这些依赖关系并不能自然地封装在屏幕上的元素层次结构中,那么 htmx 可能不是一个好的选择。如果你的应用有很多模态状态(modal state)……Web 的特性之一是无状态性(statelessness),这是 Roy Fielding 在他的论文中指出的 RESTful 系统的特性。甚至从纯粹主义的角度来看,连 cookies 都不应该被允许使用(或者更准确地说是 session)。

如果你的前端状态非常复杂,比如你希望用一个模态框来更新数据,然后再叠加一个模态框,甚至是无限模态框嵌套,那种 UI 与超媒体方法可能就不是特别契合。虽然可以实现,但随着你尝试通过超媒体来构建更复杂的 UI,你可能需要大量使用事件(events)来让这些 UI 正常工作,而这会增加复杂性。

所以我认为这些是需要注意的主要限制……从实际角度来看,还有一种情况是当你的公司不允许你使用这种方法时。现实是,React 现在是标准。虽然我很喜欢 htmx,但如果有人问我,“我对编程一无所知,但想找一份前端工作,我该学什么?”我会告诉他们“学 React”,因为如果你去 indeed.com 搜索 React,你会找到 3 万个职位;如果你搜索 htmx,可能一个职位都没有。所以我们作为开发者,还需要考虑职业发展的问题……从这个角度来看,React 当然是一个比 htmx 更安全的选择。

中断:

Jon Calhoun: 那接下来我想谈谈,如果有人想尝试使用 htmx,同时假设我们的听众是 Go 开发者……对于如何开始尝试,你有什么建议吗?

chg: 嗯,htmx 和 Go 搭配得非常好,特别是 Go 自带的模板功能。有一个人开发了一个 Go 的 Web 框架---我现在一时想不起来名字了---它已经把 htmx 集成进去了。我之后可以挖出来再发给你们。

但 htmx 的好处在于,因为它使用了超媒体(hypermedia),所以你的主要工作可以集中在用 Go 完成。根据你的熟练程度,可能需要写一些前端的脚本,但大部分逻辑都可以主要在后端用 Go 实现。我觉得如果你想玩玩 htmx,最好的办法是访问 htmx.org/example,看看那里的例子,然后用 Go 把它们实现出来,把 Go 作为服务器端。那些例子都很简单明了,我觉得你可以在一个下午把它们实现出来,并很快熟悉 htmx。

再次强调,htmx 的核心思想是你只需要对 HTML 进行标注。例如,如果你想给一个文本框增加自动补全功能,你只需要在这个文本框上添加两三个属性,比如“当发生按键事件时,我希望发起一个 GET 请求到某个 URL,然后将结果插入到下方的某个 div 中。”这些操作可以通过 ID 来完成,用一个 CSS 选择器指向那个 div。

所以你可能只需要在输入框上加三四个属性,就可以实现 HTTP 请求。这个请求会被 Go 捕获,然后返回一个匹配结果的表格,整个流程就会“魔法般地”运行起来。

所以你确实需要对 HTML 有一定的了解。这是我建议 Go 开发者应该掌握的一点基础知识。但一旦你掌握了这些,你就可以很快完成工作了。有谁尝试过上手 htmx 并玩一玩吗?我很好奇你们的感受。

Chris James: 有的。在圣诞节前的一个周六早上,我决定试试看,做了一个待办事项应用---虽然这不是世界上最革命性的东西,但……我不确定,最终我做出了一个看起来像单页应用(SPA)的东西,比如没有页面刷新,可以拖拽、添加、移除、删除项目,还加了搜索功能……老实说,我只花了几个小时就完成了。

我确实觉得 htmx 网站上的例子非常棒。它们就像一个菜单,列出了网站上常见的一些功能,比如“原地编辑”(Edit in Place)、重新排序等……真的非常优秀。对我来说,还有一点需要注意的是,虽然这种方法在许多情况下确实降低了整体复杂性,但你的服务器从“瘦服务器”变成了稍微“胖一点”的服务器。所以你的 Go Web 服务器需要承担更多工作,你需要思考如何组织代码。你需要确保你的控制器(controllers)保持简洁,不要让太多的业务逻辑渗透进去……

我确实花了一些时间阅读了 Go 标准库里 HTML 模板包的文档,因为那里面有很简单的例子,教你如何在服务器端生成 HTML。一旦熟悉了这些,老实说,你可以很快制作出一些非常丰富的应用程序。这种感觉真的很棒,就像突然之间所有的阻力都消失了,我可以快速完成任务。

David Wickes: 是的,我完全同意。我之前把一个传统的服务器端渲染应用拿来改造,因为我喜欢用不同的语言构建待办事项应用……然后我尝试给它加上 htmx,也就是“htmx 化”它。这个过程非常简单。模板已经在那里了,所有内容都用 Go 实现了,但整个体验非常流畅。我发现这个过程有趣的一点是,它进一步优化了应用程序的结构。它促使我改进了应用的结构,因为就像 Bill Kennedy 在构建他的 CLI 应用时,通过前端的行为找出了后端的一些问题一样,当你更多地关注前端时,你的后端也会变得更好。结构会变得更清晰、更显而易见、更易用,因为前端在使用它们。

所以是的,我只能说这非常令人愉快。我实际上会一边微笑一边觉得很开心,因为你觉得某个功能应该能这么实现---然后它真的可以!它真的能运行!这种感觉从来没有在我用 React 时出现过。 [笑声]

Chris James: 是的,我本来还想说,这一整期听起来都比较理论化,但我觉得必须强调的是,使用 htmx 的过程真的很有趣。老实说,我觉得用 htmx 工作非常有趣。这就像卸下了一副重担,我觉得我能更高效地完成任务。真的,太有趣了。我强烈建议大家试试看。

chg: 是的,这是一种完全不同的思维方式。超媒体是一种不同的心态,用它开发时会有一种类似搭乐高积木的满足感,而用其他方法开发 Web 应用时却不会有这种感觉。我也许有点偏爱它,但我同意这个说法。

还有一点我想说,Dave,你提到的前端可以改进后端代码的观点,其实是超媒体的一个优势。因为它会和数据一起流下来的还有操作这些数据的行为,所以你可以非常彻底地重构你的应用程序。如果你构建的是一个基于超媒体的应用,那么你可以非常大幅度地改变应用程序的 URL 布局,而这在基于 JSON 的应用中是做不到的。对于 JSON API,你必须进行版本控制,保持非常稳定等等。而超媒体的一个优势就在于它非常灵活,因为操作会和它们所作用的数据一起流下来。如果你决定某个操作不再存在,或者有其他条件变化,或者无论是什么原因,你可以完全更改后端生成 HTML 的方式,而前端客户端、浏览器完全不在意,它只负责渲染 HTML。所以这种灵活性是 JSON API 没有的,除非这是一个私有的 JSON API。因此,超媒体方法确实非常灵活,这是 Fielding 在他论文中指出的一个重要优势。

Jon Calhoun: 好的,我想我们关于 htmx 的讨论差不多到此为止了。不过我们还有时间来聊聊“非主流观点”(unpopular opinions)……你们有兴趣吗?

David Wickes: 哦,当然。我有一个很有趣的观点。

Chris James: 当然了。

Jon Calhoun: 那就开始吧。

音乐插曲:

Jon Calhoun: 好吧,有谁想来分享一个不那么受欢迎的观点?

David Wickes: 那我来吧。

Jon Calhoun: 刚才大家都很踊跃,直到我放了主题曲,大家就沉默了。

David Wickes: 不不……我想让 Chris 先来,因为我觉得他的观点会---呃,不,我还是先说我的吧。我更喜欢我的这个观点。好吧,其实这不算是个观点,更像是一个阴谋论,我想把它灌输给大家,因为几年前它就开始折磨我的大脑了……我称之为 SPA 阴谋论。这个想法是这样的---整个前端开发之所以转向厚客户端 JavaScript 应用,就是为了确保我们必须在浏览器中启用 JavaScript,这样大公司---顺便说一下,他们也是单页应用程序的开发者---就可以追踪我们。

所以基本上,Google 开发了 Angular,就是为了确保我们打开 JavaScript,从而让 Google 的追踪 cookie 能正常运行。同样的道理也适用于 Facebook。你可能注意到,很多其他的框架---比如 Svelte,它的最初设计也是为了视觉广告,确保我们也需要用到 JavaScript。所以,这就是我的不受欢迎的观点:过去十年的前端框架发展,完全是为了确保 Google 能在网络上追踪你。呃,我其实并不完全相信这个观点,但是……

Jon Calhoun: 但你又有点相信,对吧?

David Wickes: 也许吧……

chg: 我是有点信。毕竟,这种说法有一定的合理性,不是吗?

Chris James: Dave,你得站住脚啊!

chg: 你打开了 JavaScript,就是你同意了。他们说:“行吧,你可以追踪我们了,因为我们想要更多的 JavaScript。”

Chris James: 是的,现在网络基本上没办法用了,除非你打开 JavaScript。所以说,这一招成功了。

chg: 确实如此。

Jon Calhoun: 可能唯一能反驳这种说法的是,我相信 Remix 是设计成大部分功能在没有 JavaScript 的情况下也能运行的……我不知道你有没有关注过它。Remix.run 应该是它的网站……它是一个 JavaScript 前端框架,但他们很多目标是让普通的 HTML 功能正常工作。而且这家公司最初是由一小群开发者创立的,但后来被收购了。我记得是 Shopify 收购的?所以我很好奇,这是不是又会变成一个偏离初衷的例子。

Chris James: Remix 确实很有趣,是个挺有意思的想法。

chg: 不过说实话,HTML 在过去二十年里几乎没有什么进步,这一点还是挺让人困惑的。为什么 HTML 作为一种超媒体几乎没有变得更好?它确实有了新的组件、画布,以及 JavaScript 的新 API。但作为一种超媒体,它从 HTML 2.0 开始就几乎没什么变化。这种情况下很容易产生一些阴谋论式的想法。

Jon Calhoun: 曾经有一段时间,我记得是 Apple 想要推动一种理念……HTML 将是应用程序的未来。那时他们就是这么宣传的。我觉得如果那个时候 HTML 真成了未来,可能它会有更多的发展。但实际上,不知道出于什么原因,应用程序最终还是选择了私有的编程语言。而现在我们意识到:“哦,其实 HTML 已经足够强大,可以用来构建很多应用了。”所以我们有点绕了一圈又回来了。也许从现在开始它会再次发展,但谁知道呢……

David Wickes: 我想说的是,HTML 甚至都没有完全支持 HTTP,对吧?你甚至无法在 HTML 中使用绝大多数的 HTTP 动词,对吧?所以它甚至没有完全契合超文本传输协议(HTTP)。它缺少的只是组件,以及支持那些方法的能力,还有一些零零碎碎的东西,而这些 htmx 已经实现了。我觉得让 HTML 停滞在那个阶段真的很奇怪。这实在太奇怪了。

chg: 是啊,我真的希望 htmx 的功能,或者至少是它的理念,能被折叠进浏览器,纳入 HTML 规范……在我看来,这样才合理。你只是想要一个更强大的超媒体;你想要的不仅仅是链接和表单的功能,而是更多的触发事件的方式。就像你说的,你应该能够通过 HTML 发送 put、delete 和 patch 请求,为什么不能做到呢?最后一点是,把响应插入到页面的其他部分,而不是整个页面刷新。我觉得他们完全可以在一个版本里实现这些功能,这样 HTML 会变成一个更强大的开发工具,而 htmx 就可以退居二线,而我也可以稍微轻松一点。

Jon Calhoun: 但让我觉得很奇怪的是---尤其是作为一个教授别人编程的人,你教他们所有这些 HTTP 方法,然后当你向他们展示 HTML 的时候,你又得说:“顺便说一下,这些方法都没法用,所以你只能用 post,然后为它们创建不同的端点。”

chg: 这不合理啊……

Jon Calhoun: 真的挺让人沮丧的。就像,“为什么你要教我这些?”“哦,你需要知道它们,但现在用不上。”

chg: “我只是在向你介绍 Web 开发过程中所伴随的失望。这就是你接下来 20 年心理打击的开端。”

Jon Calhoun: 你对未来充满期待,结果有人把希望从你手里夺走了……

David Wickes: 我至今还记得关于表单提交的那种感觉,这是八年前的事情了。看到它的时候,我就想:“什么?!”直到现在它还是让我抓狂……

Jon Calhoun: 最糟糕的是,你会看到表单的 method 是 post,然后你会想:“哦,我可以改这个。”[笑声] 但其实不能……

chg: 你可以改成 get……

David Wickes: [笑] 对!然后它就……[01:03:19.22 听不清] 哦,天啊……

Jon Calhoun: Chris,你的“不受欢迎的观点”是什么?

Chris James: 好的,我相信地球是平的---不,不要再讲阴谋论了。想象一下,你开始了一个有六个开发者的新项目,第一件事就是写了 10 个 Go 接口,描述了解决这个问题可能需要的一些代码模块。然后你把这些接口分配给每个人,说:“去实现这些接口吧,几周后我们再把所有东西拼接起来。”你可能会质疑我这种方法的判断力,因为这显然是“过早抽象”。我怎么知道这个设计是正确的?我们怎么知道这些接口是我们需要的?我们一开始独立地工作,不是很冒险吗?我们会不会掉进整合地狱?然而,很多团队在开始一个项目时都会用微服务架构,而这其实跟我刚刚描述的非常相似,甚至更糟,因为在所有这些假设之上,还加上了一个分布式系统。

所以,不管你在设计中犯了什么错误,修复起来都会比在单一代码库中难得多。而我不受欢迎的观点---也许并不不受欢迎,我不知道---是,我相信在绝大多数情况下,与其在白板上画出 100 个微服务,不如从一个单体应用(monolith)开始。用一个代码库开始项目。如果你的假设有问题,在一个单一代码库中修复要容易得多,而不是把它们分散在一大堆服务中。而且,如果你能写出一个好的单体应用,当你真正需要分布式工作时,你就可以把它拆分成独立的分布式服务。

顺便说一句,我的另一个不受欢迎的观点(其实跟前一个类似)是,如果你不能写出一个好的单体应用,你肯定也写不出一个好的微服务架构。对我来说,构建两者的技能是一样的。所以,如果你做不好单体应用,那你也做不好微服务架构。

Jon Calhoun: 那么对此的反驳会是什么?是因为有人认为微服务很小,所以它可以设计得很糟糕吗?他们觉得代码不需要设计得很好?我是想问,你是如何跟别人探讨这个问题的?

chg: 我在网上听到的反驳是,微服务并不是为了解决技术问题,而是为了解决组织问题。它让企业中的某个部门能够独立地部署。我不太相信这个观点,但这是人们为微服务架构辩护时常用的说法。就像 Chris 和这里的其他人一样,我也不是微服务的超级粉丝。所以在这个话题上,我站在你这一边。

Chris James: 说实话,我并不是反对微服务,我只是反对在项目开始时就使用微服务。而且我确实有点相信微服务在组织层面上的好处。我只是觉得,在我工作过的很多组织里,我们的组织方式非常糟糕,Conway 定律(Conway's Law)对我们的伤害特别大,因为我们的组织结构决定了我们的架构,而我们的架构一塌糊涂。

对我来说,当你一开始就使用微服务时,你实际上是在说:“在我们写任何代码之前,我们已经知道如何组织自己。”在我看来,这是一种非常“瀑布式”的思维方式,认为“我们可以完美设计一切,然后去执行就能万无一失。”但实际上,大多数情况下,当你开始构建某样东西时,你并不了解它。你需要先生活在这个领域里,写一些代码,对它进行迭代,感受它的运行方式,然后才能开始更好地理解问题。到那时,你才能开始做一些有趣的设计工作。

David Wickes: 如果有人对微服务作为组织工具的想法感兴趣,我目前所在公司 SaltPay 的同事 Adam 在今年早些时候的 GopherCon London 上做了一场非常精彩的演讲,讲述了微服务如何在组织中运作。我会试着找一下那个链接。他的观点非常有说服力,虽然我有些不同意,但他的论点是,通过使用微服务,你实际上迫使整个组织进行良好的沟通。因为微服务之间的集成失败,或者跨网络边界的通信问题,只有通过团队和组织之间的良好社交沟通才能真正解决。所以,这让你不得不面对那些社交问题,而不是把它们隐藏在单体应用中。

Jon Calhoun: 我觉得很难就这个问题给出一个很好的反馈,因为---说实话,我过去十年大部分时间都在自己创业,或者和少于五个人的团队一起工作。所以我并没有在一个使用微服务的大型团队中工作过。我很幸运能在这些小团队里工作,我们有一个单体应用……

David Wickes: 多棒啊!

Jon Calhoun: ……而且它运行得非常快……

Chris James: 听起来很美好。

Jon Calhoun: ……我们可以快速迭代,大家也都觉得很好,这很棒。所以当我听到微服务架构时,我的反应是:“是的,我不需要这个,我也真的不想在不需要的时候去折腾它。”

chg: 确实如此。

Jon Calhoun: 但这也是因为我知道,每当我尝试新东西时,我一定会犯错。所以我知道,如果我现在去设计一个微服务系统,它不会很好。我可能能跳进一个现有的代码库并做些事情,但我认为我从一开始就不会把它设计得很好。Carson,你有没有想分享的不受欢迎的观点?

chg: 嗯,没有。我对所有事情都很有把握,所以……我的不受欢迎的观点---

Jon Calhoun: 我们可以为此做个投票……

chg: [笑] 是啊,在 Twitter 上发个投票,看看结果会怎么样。结果可能对我不利。不过没关系,我是个反对派。

Jon Calhoun: “你结婚了吗?如果是的话---

David Wickes: 我本来想说,htmx 可能就是一个非常长、不受欢迎的观点。

chg: 是啊,确实如此。

David Wickes: 整个库都是。[笑]

chg: 哇,这真是一个“目标丰富”的话题(意指有很多可以讨论的内容),但我想要提到的是,我有一个叫 grugbrain.dev 的网站,这是我开设的一个玩笑性质的网站,记录了我过去26到27年的编程经验……呃,其实比这还久。但不管怎么说,我在网站里提到的一个观点是关于“害怕看起来很蠢”的问题。我的一个不太受欢迎的观点是,在技术行业中,有大量的技术决策要么是因为害怕看起来很蠢而被接受了,要么是因为害怕看起来很蠢而没有被反对。

比如,有人提出了一个架构上的决策,或者一些代码决策,或者其他的东西,这东西看起来很疯狂。而现场的工程师们可能会想,“天哪,这看起来真疯狂。但如果我说出来,我会显得很蠢。我会看起来不够聪明,无法理解他们的想法。所以我还是保持沉默吧。” 我认为这是技术行业中的一个普遍问题。这种现象可以理解,因为这是一个相对残酷的行业。我们的智力被高度依赖,同时行业里也存在年龄歧视。如果你表现得不够聪明,这可能会对你的职业生涯造成不小的影响……但不幸的是,这种心态经常导致糟糕的架构结果和糟糕的代码结果。因为人们不愿站出来说,“这太疯狂了。如果有更简单的方式,我们就按更简单的方式来做吧。” 所以这是我的不受欢迎的观点:害怕看起来很蠢驱动了许多技术决策。

David Wickes: 我也觉得,我通过经常说一些蠢话来以身作则……不过说真的,我不认为这应该是不受欢迎的观点。我觉得你说得非常对。让大家感到处于一个安全的环境中并能大胆表达自己的想法是非常重要的,即使他们觉得自己的想法看起来很蠢……因为很多时候,事实并非如此。根据我的经验,尤其是那些刚入行的初级工程师,他们可能经验很少,但经常会提出一些问题,让你从不同的角度去看待事情。这时候你会想,“天哪,幸好这个人问了这个问题……要不然我们可能会走上一条糟糕的道路。”

chg: 没错。

Jon Calhoun: 你觉得员工流动性会加剧这种情况吗?比如说,如果你在一家公司工作了三年,你和同事们相处了很久,我觉得这种情况下更容易大胆发声。但如果你刚加入一个团队,你肯定不想站出来说话,显得自己是那个刚加入、不知道自己在干什么的“怪人”。

David Wickes: 我认为这也是为什么团队稳定性这么重要的原因之一。我们之前谈到过这些大型组织……我完全不认同那种“把开发人员视为可以随意更换的齿轮”的观点。你不能随意调整团队配置,而忽视软件工程中的社交属性。拥有健康且开放的同事关系是非常有价值的,但这种关系不是免费的。你不可能在认识一个新同事一天之后就完全信任他。这需要时间。我对这个问题其实有很深的感触。

chg: 最近科技行业有很多裁员,我可以保证,这只会增加那些糟糕技术决策被悄悄放行的概率。大家都在低头做人……于是一些人可能会带着最疯狂的想法进来……也许也不是最疯狂,但却是非常复杂的解决方案,却没有人会站出来反对。他们只是想保住自己的工作,这可以理解。

所以,如果你处在一个高级工程师的位置,而且你知道自己很安全,并且在公司里有很高的信誉,我认为你可以为公司创造价值的方法之一就是在大庭广众之下说,“天哪,这方案好复杂啊。” 或者,“哇,我没听懂。” 尤其是在那些年轻开发者面前,或者在那些不太自在的开发者面前。如果你能利用你的社会地位,帮助大家消除这种恐惧感,我觉得最终会让整个组织的技术决策更好。

Chris James: 完全同意。我在这方面也试图以身作则,但我也会告诉每一个我共事过的初级开发人员,他们的工作就是提问。他们的工作不是写代码;一开始他们不可能写得很出色。他们的工作是提升自己。所以他们的工作就是在每一次会议中提出所有的问题,包括他们不理解的地方,或者他们觉得哪里有问题,或者他们觉得哪里很蠢……因为这能让所有人变得更好。这些问题是他们的价值所在。我喜欢被问一些非常难回答的问题,或者一些看似很蠢的问题,因为这有时候会让我自己看起来很蠢……因为我确实很蠢。(笑)

Jon Calhoun: 有时候重新回到原点,思考“我为什么会做出这样的决定?” 也是很有意义的。这种反思让我们确保在做出一个决定之后,事情没有发生变化。Chris,我完全同意你刚才说的“不可互换性”的观点。我觉得,把开发人员视为可以随意更换的做法简直是疯了。我们在体育领域本能地知道这一点。你不能把一个运动员换下来,随便换另一个进去,然后指望团队表现不受影响。但在软件领域,我们却期待这样的事情能行得通。而且随着每次决策的叠加,会产生更多的技术债务,以及其他问题……而能和那些你熟悉、了解他们决策方式的人共事,是非常有价值的……不过我猜,这可能也是为什么我很久没有在大团队里工作了。我在这方面被宠坏了。

Jon Calhoun: 好吧,我想这就是本期节目的全部内容了……Carson,谢谢你加入我们。Chris 和 Dave --

chg: Jon,你有什么不太受欢迎的观点吗?

Jon Calhoun: 今天没有。我甚至没时间去想不太受欢迎的观点。这次的“舞台中心”就交给你们吧。

chg: 好的,没问题。

Jon Calhoun: 那我来结束本期节目吧……


好文收藏
38 声望6 粉丝

好文收集