Rod Johnson 又回到了一线。
他是 Spring 的创造者,曾经几乎重新定义了企业 Java 应用该怎么写。二十多年后,他重新创业,做了一个面向企业 AI Agent 的开源框架 Embabel,试图把 LLM 放进真实的业务系统里,让它不只是会调用工具,而是能在可控、可解释、可审计的流程里工作。
有意思的是,这一次他做的仍然是框架,但他对“框架”的未来并不乐观,至少不再是过去那种乐观。在他看来,模型还会继续变强,工具也会越来越多地替开发者做选择。Embabel 会不会被更强的模型追上?企业还需不需要这样一层 harness?未来的框架到底是由开发者挑选,还是由 AI 工具自动决定?这些问题,都绕不开他在访谈里说出的那句话:这可能是最后一波由人类亲自选择的框架。
这句话放在别人嘴里,可能只是又一次 AI 时代的夸张判断。但从 Spring 创始人口中说出来,意味就不一样了。因为 Rod Johnson 亲手参与过框架时代的兴起,也见过一个框架如何变成企业软件开发的基础设施。现在,他回到战场,却认为选择权正在转移:开发者未必会消失,但开发者亲自挑框架、搭技术栈、决定系统骨架的时代,可能正在进入尾声。
本文基于该播客视频整理,经 InfoQ 编辑。
核心观点如下:
如果我要用 TensorFlow 做模型训练、微调、某些数据处理和摄入,我当然会用 Python。但 GenAI 应用层赋能这件事,更适合在应用原本的语言里完成,如果应用是用 Java 写的,那就在 Java 里完成。
一旦你进入复杂的应用程序,如果你不保持那种架构上的监督,你很快就会陷入一团乱麻。你的 Agent 会愉快地添加新功能,但每添加一个新功能,设计就会退化,代码就会变得非常糟糕。
开发者不应该花大量时间在编写代码上,因为你可以通过专注于你独特增加价值的地方来获得更大的杠杆。
每个开发者原则上都应该每隔一两年学一门新语言,因为它真的会改变你的思维方式。
这可能已经是“最后一代由人类主动选择的框架”了。以后越来越多的技术选型,都会由我们的工具替我们完成。
Spring 创始人回归一线创业
Simon:我翻你的履历时发现,你在创建 Spring 之前,拥有一个关于 19 世纪巴黎钢琴音乐的博士学位。
Rod:是的,我的第一个学位是音乐和计算机科学双专业,当时完全没办法决定往哪个方向走。后来我拿到了澳大利亚的研究奖学金去读音乐博士,还在悉尼音乐学院教过两年音乐史。然而,写代码的冲动一直没停过。90 年代中期我还写过共享软件,真有人寄支票过来。
后来我清醒地认识到,要是继续待在音乐学术界,我大概一辈子都在悉尼买不起房。于是我做了一个决定:这两件事里,一个当爱好,一个当职业——而我当时正好搞反了。不过接下来有十年我几乎没碰过钢琴,因为职业生涯实在太忙了。
Simon:所以这并非什么奇怪的弯路或别的什么,只是碰巧你很有创造力,无论是写代码还是音乐,你都很享受这两者。
Rod:从 80 年代中期第一次写代码到现在,我差不多从未中断过编程,因为我就爱这件事。即便现在绝大部分代码是由 AI Coding Agent 替我写的,我仍然能获得一模一样的兴奋感,因为掌控权还在我手里,我还在创造和塑造东西。哪怕不再亲手敲出每一行,只要结果是相同的,这对我而言同样令人满足。
Simon:在 SpringSource 被收购后,你做了很多年董事会和投资的事情,然后创办了 Embabel,是什么让你觉得“就是现在,这个时机到了”?
Rod:我认为是因为行业正处在一个巨大的转折点上。当 GPT‑3 和后来的 ChatGPT 突然变得真正实用、不再重复自己或陷入奇怪的死胡同时,我立刻意识到,怎么把这项技术真正用来解决企业问题,是很难的问题。其实在这之前两年,出于个人兴趣,我已经写了不少 TensorFlow 代码,和底层 AI 技术打了很多交道这自然而然地演化成了创建一个框架来帮助解决这些问题的想法。
Simon:说到企业,现在很多团队被要求抛下 Java,用 Python 重写一切。你公开讲过这是错误的,甚至说过“这是 Python 在 AI 领域主导的最后一年”,为什么?
Rod:你要解决一个业务问题,就必须考虑这个问题本身的“邻接性(Adjacency)”。你在和什么打交道?你大概率在和数据库、企业服务、现有代码库打交道。同时,你还要处理一个新东西:LLM。可 LLM 并不在你那个 Python 进程里运行——宇宙毁灭之前,Python 都没法自己执行推理。LLM 只是一个非常简单的 HTTP 调用,所以我一直很困惑,为什么人们会认为某门语言在发起一个极其简单的 HTTP 调用时会有天然优势。
事实上,大家已经开始逐渐意识到这一点了。OpenClaw 就不是用 Python 写的,Peter Steinberger 用的是他偏爱的语言。对大量企业应用而言,它们很明显是用 Java 写的,关键的邻接性就在于已有业务逻辑、已有企业服务。那正确的做法很显然,就是从你的 Java 栈里发起一个简单的 HTTP 调用。
我认为人们产生这种混淆的根本原因,是没有把“数据科学”和“企业 AI 应用”区分开,它们是两件完全不同的事。在 Embabel 之前我写了大量 Python,两年前我的 Python 甚至比 Java 流利得多。如果我要用 TensorFlow 做模型训练、微调、某些数据处理和摄入,我当然会用 Python。但 GenAI 应用层赋能这件事,更适合在应用原本的语言里完成,如果应用是用 Java 写的,那就在 Java 里完成。
Simon:Embable 是用 Kotlin 写的,对吧?
Rod:Embabel 核心几乎全部是用 Kotlin 写的。但我们的大多数示例是 Java。我们投入了大量精力来确保对 Java 用户来说,它是完全无缝的 —— 正如你预期的那样,我们绝大多数用户群都在用 Java。你不会看到 companion object、`.kt` 文件,不会看到任何奇怪的东西。它就像非常优雅、流畅的 Java。所以当我在核心框架上工作时,我用 Kotlin;当我在示例应用上工作时,我用 Java。老实说,Java 风格的 API 非常棒,即使在 Kotlin 里使用这个框架时,它和其他语言的体验也差不多。
Simon:而且它们之间的集成本来就很无缝,你可以直接从 Kotlin 跳到 Java。
Rod:大概十三年前,我大量使用过 Scala。我很喜欢 Scala 这门语言,但事实是它与 Java 的集成很痛苦。每次你处理一个集合,那都是折磨。而 Kotlin 的创造者在 Java 互操作性方面做得非常出色,他们没有引入 Scala 曾有的那些问题,破坏性变更、缺乏二进制兼容性等等。所以,是的,我觉得 Kotlin 是一门非常好用的语言。
不过,我也认为指出 Java 已经改进很多这件事很重要,我觉得人们喜欢拿 Java 当稻草人来攻击很烦人。很多人在假装 Java 没有进化,而 Java 实际上已经进化了很多。
Coding Agent 正在毁掉你的代码库
Simon:你之前谈到 AI 基本上是被当作一个断开的层硬塞进去的,而不是更深入地集成到现有系统中,结果这导致了一些失败案例。你认为真正的企业 AI 失败案例是什么样的?
Rod:我认为最大的问题就出在,当高层下达“AI 一切”的指令,团队却在没有真正业务案例、没有确认 AI 是否合适的情况下,盲目开展 AI 项目。一个主要的反模式就是“我们必须用更多 AI”这种念头,却从来不去问一句:为什么要用?用来干什么?虽然我非常热爱并且着迷于 AI,但如果能不用 LLM 就完成一件事,那你当然应该不用 LLM,这样更便宜、更确定、更快。
所以组织首先要做的,是思考“我们怎么从这儿走到那儿去”。举个例子,我们在澳大利亚的一个客户,他们一开始识别出一类小问题:网站上有某张特定表单,客户填写完后需要人工审核才能继续。95% 的情况其实非常简单,虽然用正则表达式处理起来稍微麻烦了点,但本质上很简单。于是他们把这个摩擦点消除掉了,在 95% 的场景里让客户即时推进,不再需要等人工处理。我觉得这是非常棒的起步案例:先在小事上拿到结果,慢慢建立起信任。
另外,对于“异构技术栈(Alien Stack)”问题,它会在两个方向上造成伤害。第一,技术上让一切都变难;第二,它往往还会把战略权交到错误的人手里那些根本不懂核心业务、可能从来没见过任何核心业务应用的人,却在主导战略。当你要赋能这些应用的时候,这条路完全走不通。
去年我和一家澳大利亚大公司的首席 AI 架构师聊过,他是一名 Python 开发者。他礼貌地听完了我的介绍,却没什么兴趣。通话结束时,他试图表现得友善一点,说:“我敢肯定我们公司什么地方有 Java,我回去问问看。”我从来没在那家公司工作过,但作为业内人士我太清楚了,那家公司大约 70% 的代码是 Java 写的,剩下的是 .NET,而且他们正逐步淘汰 .NET。这个人入职将近一年了,却从来没想过要去问一句:“顺便问一下,我们的软件是用什么语言写的?”对他来说,这似乎根本不重要。
Simon:现在有一种越来越普遍的现象,我们作为开发者,与实际编写的代码实现之间出现了巨大的脱节。因为我们对 AI 过度依赖或授权,让 AI 做了太多决策,把太多东西都外包出去了。很多知识实际上在 AI 那边,而我们却被抽象掉了。你认为这个问题有多严重?
Rod:我认为开发者需要掌握的一项核心技能,就是以这种新方式工作,同时保留那些真正重要的控制权。确实可以用 Vibe Coding 来做一些事情,比如某些类型的 UI 应用,它们本来就是一次性的,Agent 在这方面非常非常擅长。但你无法用 Vibe Coding 来编写严肃的软件。
我是一个 Coding Agent 的积极用户,我可能最多只写 5% 的代码,也许更少,但我牢牢掌握着控制权。我发现,从设计的角度来看,Agent 出错的时候比正确的时候多。
我们仍然需要理解架构、清楚正在发生什么、并且不要过于信任。因为一旦你进入复杂的应用程序,如果你不保持那种架构上的监督,你很快就会陷入一团乱麻。你的 Agent 会愉快地添加新功能,但每添加一个新功能,设计就会退化,代码就会变得非常糟糕。
Simon:你提到你只写 5%的代码,其余是 AI 生成的。你这样做是有意为之吗?是因为你不想写更多,因为你想对写出来的东西有更多控制权?
Rod:在开源项目里,我手写的比例更高一些,可能会更保守。但在我们的一些内部应用中,AI 生成的比例接近 95%。要是你读那些代码,你会觉得那是我写的,设计风格非常清晰。我会坐在那里看 diff、看输出,然后经常停下来纠正它:“不对,你把这个硬编码了,这里应该是一个策略,提取出来。”
我相信这种模式有可能产生比纯手工或者纯靠 Coding Agent 更好的结果。我用 Coding Agent 写代码的速度比我手动快得多,而且质量也更好。但如果反过来,我把一切完全丢给 Coding Agent,我深信质量会大幅下降,最终连速度也会降下来。
Embabel 的核心:来自一个游戏 NPC 规划算法
Simon:我们来谈谈 Embabel 里面的核心组件“规划器(planner)”。这是一个叫 GOAP (Goal-Oriented-Action-plann)的寻)寻路算法,最早是为游戏里的 NPC 设计的吧?关键点是,它是确定性的。而其他框架比如 LangChain、Crew.ai,则更多是由 LLM 来决定下一步做什么、怎么规划。为什么你会从游戏 NPC 领域里挑出这个算法?
Rod:我最开始考虑的确实是最显而易见的方法——状态机(state machine)。平心而论,LangGraph 就是确定性的,你提前定义好状态机。所以我也一度用过那种办法。不过我们先退一步,谈谈为什么要做规划。
当然,你可以直接把一堆工具扔给模型,让一个 Agent 循环来全权处理一切,在某些场景下这确实合理。但对于自动化业务流程这类需要一致性和可预测性的场合,这远远不够,因为你压根不知道 LLM 会以什么顺序调用你给它的工具,也不知道它会传什么参数给那些工具。
所以我们的想法是,让用户把流程拆成多个步骤。这些步骤可能是调用一个或多个 LLM,也可能是纯代码步骤。在这一层面上,LangGraph、Crew.ai、Microsoft Semantic Kernel 做的都是类似的事——把大问题切分成小步骤。这也意味着,如果你在某一步中使用 LLM,可以为不同需求使用不同模型。比如你可以在自己的防火墙后面使用本地 LLM 来处理那些足够小、且高度敏感的任务,从而永远不让客户数据流出企业边界,这里面有大量好处。
但问题回到了:怎么规划这些步骤,怎么决定执行顺序?你可以用状态机,但我发现,当我在做类似 LangGraph 的东西时,修改状态机,比如增加更多状态和转换,非常麻烦,你必须重新连线来扩展它。其次,状态转换和每个动作状态所需的类型,通常都是正交的,这会在类型系统上制造麻烦。
GOAP 规划方式有两个很不一样的点。第一,它是动态的,规划发生在运行时。第二,它与类型系统完全集成。我们允许用户创建自定义条件,但动作的排序通常由 Java 方法的参数类型和返回类型决定,这意味着一个动作永远不会在缺少所需参数时被调用。
GOAP 本质上是一个 A* 算法。我们识别出目标,然后查看从当前世界状态出发,哪些步骤可以通向目标。目标有前置条件,世界状态中必须满足某些条件。动作也有前置条件和它承诺的后置条件。前置条件是绝对硬性的,除非条件被满足,否则你不能声明目标已达成,也不能调用某个动作,而后置条件则是动作承诺会产生的副作用。
规划器从当前世界状态出发,找到一条通往目标的路径。它也可能告诉你“不存在可行动路径”,这本身就是一个很有价值的信息。然后它执行第一个动作,再重新规划,在执行每个动作之后,查看世界状态,重新评估怎么到达目标。大多数情况下,快乐路径会照常运作,动作承诺的后置条件得到满足,规划器确认一下,然后迈进下一步。
Simon:这一切都是自动化的?
Rod:完全自动化。通常 Java 开发者不需要了解规划器的内部细节,他们只需要定义“动作方法”来提供输入,用注解标记 Java 方法,方法的参数和返回类型就给规划器提供了链式调用的信息。在某些情况下,你还可以定义自定义条件来进一步控制工作流。这意味着可能存在多条路径能到达同一个目标,而规划器可以选择成本最低的那条路径,因为你可以给每个动作分配成本。成本甚至可以是动态的,如果某个动作需要调用一个负载很高的系统,你就可以将其反映为一个动态成本值,这样一来,规划器也许就会自动选择另一条路线。
Simon:基于当前世界状态在运行时做动态决策,这非常有意思。另一个好处是,因为它是确定性的,当被问到“为什么会做出这个决定”时,你们能提供非确定性规划器无法给到的诊断信息?
Rod:我们可以完整展示规划的整个路径,以及我们观察到的世界状态,是如何导致我们制定出那份计划的。规划器和整个 Embabel 会发出大量事件,你可以编写监听器来持久化这些信息,或者把它们用于审计日志。所以你完全可以解释它为什么做了某件事,并且确保它每次都做同样的事。
当然,一旦进入动作步骤内部,如果你调用了 LLM,那部分就不会是完全确定性的,但这也让你能够“适度”地调用 LLM。如果你像聊天机器人那样,用长达三页纸的提示词和 30 个工具来工作,你永远不可能获得完全的可预测性。而如果你用一段小提示词加上三个工具去完成一件事,可预测性就会非常高。虽然不可能 100% 确定,但你会发现花在提示工程上的时间会大幅减少。所以整体上,系统会变得可解释得多,也确定和可预测得多。
为什么对 MCP 持怀疑态度
Simon:每个人似乎都把 MCP 当作灵丹妙药,好像它能解决 Agent 的所有问题。你认为在这个领域,大多数开发者还没有看到的差距是什么?
Rod:我认为 MCP 在赋能整个生态系统方面发挥了极其重要的作用,它确实是一个催化剂,让更多的人了解到你可以用工具实现什么。然而,我出于几个原因对 MCP 持怀疑态度。
首先,如果你在做“用 AI 赋能企业系统”这件事,为什么要通过 MCP 来运行?你可以用 Embabel、Spring AI 或 LangChain4J,任何框架都可以轻松地将一个 Java 方法暴露为工具。所以你必须问一个问题:如果做起来这么容易,为什么还要多此一举?特别是当你可以在领域对象上暴露工具时,你先通过仓库调用检索到了正确的领域对象,然后暴露这个对象上的方法,这是 MCP 不那么容易做到的事。所以,很多时候,你在暴露任何工具时,第一个想法应该是:“我能用我的技术栈直接暴露它。”“我为什么不直接暴露一个 Java 方法或 Python 函数呢?”完全可以做到。
其次,MCP 的卖点是“一个专门为 Agent 设计的 API 规范”。但如果它是 API 规范,我们已经有了 OpenAPI、Swagger、GraphQL,为什么还需要一个新的?MCP 的论点是"因为它是专门为 Agent 设计的"。但我最近开始得出一个结论:对于一个特定 Agent 来说,唯一完美适合的东西,很可能就是这个 Agent 独有的。比如,你可以有一个 MCP 服务器去暴露服务,但如果已经存在一个 OpenAPI 规范,你也可以连接到那个规范,然后根据你特定 Agent 循环的需求来塑造你自己的工具。
我认为 MCP 对推动行业进步有巨大贡献,但它不是那种一刀切的解决方案。在很多场景下,直接使用现有的 API 规范更有意义,而永远放在首位的方案应该是:从你当前的技术栈里直接暴露逻辑。
Simon:我记得 Karpathy 发过一条推文,大意是模型正在跑在基于模型构建出来的产品前面。现在你在 Embabel 构建的是编排层。这是否意味着,你们其实已经落后于模型了?
Rod:这是一个有意思的问题。当然,如果你看我们现在如何构建软件,coding agent 的规划能力提升得比我原本预期要明显得多。大概在去年 11 月底、12 月那段时间,确实出现过一次非常剧烈的跃迁。我认为模型肯定在变得更好,但我也认为,很多根本问题仍然存在。如果你在自动化业务流程,那么把规划从模型的控制之外移出来,转而使用某种确定性方法,我认为这仍然有价值。可解释性依然很重要。所以我确实认为,即使模型继续进步,模型之上的框架、agent harness、各种框架仍然非常重要。
一个很好的例子是 Claude Code。当然,Sonnet 4.6 比之前的模型强得多。但 Claude Code 本身也强得多。如果你把今天的 Claude Code 和四五个月前的 Claude Code 相比,它的工作方式已经完全不同,而这种不同确实让它更有效。所以我其实并不认为模型变聪明和 harness 变聪明之间存在冲突。我认为我们需要在两个地方同时推进。
我认为你确实会看到一些模型“吃掉 harness”的场景,也就是在那些是否确定并不那么重要的地方。你可以直接给模型一堆工具,即使它有点不可预测也没关系。所以我确实认为,有一块空间里,模型会需要更少的外围基础设施。但我认为,那块空间与企业应用并没有特别大的关系。
AI 擅长批评,不擅长原创
Simon:今年一月,你提到你的 Claude Code 工作流程涉及很多设计对话:和 Claude Code 来回讨论、长时间的规划,然后才开始编码,结果往往比手写更好。但 Claude 并不理解某些事情,比如 companion objects。所以当你用 Claude Code 构建 Embabel 而 Claude 不完全理解你架构的某些部分时,那种感觉如何?
Rod:我认为它工作得很好,因为我完全理解架构,并且我经常纠正它。我确实想知道,如果开发者越来越多地选择“不理解架构”那条简单的路,会发生什么。我处于一个非常有利的位置,我完全理解代码库中的架构,我非常了解 Kotlin 这门语言,我们构建在 Spring 之上,我对 Spring 也很了解。我实际上非常了解技术栈的每一个部分,这使我能够有效地掌控局面。我个人会非常担心那些不理解这些的人,保持控制权非常重要。我认为开发者不应该花大量时间在编写代码上,因为你可以通过专注于你独特增加价值的地方来获得更大的杠杆。
Simon:AI 有没有让你感到惊讶的时候,比如给过一个你没想到的设计或架构建议,让你转向了完全不同的方向?
Rod:不经常。然而,有一件事一直很有用,那就是来回的讨论。确实有很多次它想到了我没想到的问题。比如我们在讨论一个提议的变更时,它指出了我没想到的一点。它更擅长批评,而不是提出原创想法。
总的来说,我非常满意,但有些时候我也会感到沮丧。过去几天,我试图为一个内部产品构建一套相当复杂的测试基础设施,在测试容器里用真实 LLM 进行测试,伪造所有工具等等,而 Claude Code 表现得非常糟糕。我认为原因之一是它以前没见过这种场景,这可能是一种相当新的测试形式。
要让你的 Coding Agent 获得最大的自主性,你需要对测试非常执着。而我这次碰到的,既是它见过的更极端的测试量,而且某些类型的测试可能也是它以前没见过的。尽管有明确的指示,它似乎还是出奇地挣扎。
Simon:你有没有尝试过用技能和上下文来引导它,试图让它提供更接近你想要的建议?
Rod:我试过用 Skills 和 Claude.md 来配置 Coding Agent 的行为,但有点不幸的是,当上下文越变越大,Coding Agent 就会开始忘东西。有个特别奇怪的现象:它老是想在代码体里写全限定名,可我非常讨厌这个,我更喜欢用 import。我已经把这个偏好写进了 claude.md 配置文件里,但大概有 50% 的时间它还是照忘不误。
Simon:就算那是 Agent 必须读取的强制性指令,它也做不到渐进式学习。
Rod:对,注意力就是会衰减。
语言之争
Simon:我记得你曾经说 TypeScript 是当今最重要的语言,因为它能让一个普通 JavaScript 开发者的水平翻倍。既然你认为 TypeScript 比 Java 更重要,那你怎么同时做到既坚持在 JVM 上构建,又相信 TypeScript 是最重要的语言?
Rod:TypeScript 是门非常聪明的语言。有意思的是,你看曾经静态类型与动态类型语言的那条路线,现在某种程度上正在趋同。现代 Python 大量使用类型提示,Python 的类型系统也相当不错了;TypeScript 显然给 JavaScript 加上了一层类型。
TypeScript 是一门优美的语言,它的确非常重要。但我还会不会说它依然是最重要的?这得看应用的类型。如果我从零开始写很多类型的应用,我很可能会用 TypeScript 去写服务端和 React。但你去看企业级应用,没多少是用 Node 写的,也不应该用 Node 去写。在 JVM 上,Java、Kotlin 会给你一堆对这个类别的应用极有价值的东西。TypeScript 再漂亮,在这件事上也帮不上什么忙。
所以我认为,第一,现代 Java 肯定比我当初说那些话的时候好得多;第二,Kotlin 是一门值得认真考虑的出色语言。Kotlin 和 TypeScript 大概旗鼓相当,我仍然会想念联合类型,我可以长篇大论地论证密封层级绝对不如联合类型好,但 Kotlin 同样有很多比 TypeScript 强的地方。TypeScript 的另一个问题是,尽管他们做得非常好,底层仍然有一层疯狂的东西,你偶尔就会撞见,而且没法掩盖。而 Kotlin 是一门现代化语言,它建立在更健壮、更可预测的基础之上。
Simon:你认为 AI 的进步会在五年内改变这些语言格局吗?有没有足够多的变化来颠覆这些既定事实?
Rod:我不知道我的观点是什么,我可以说服自己语言不重要,也可以说服自己语言很重要。
先说很多人想象的那个图景,以为训练语料库会让热门语言拥有天然优势,然后这种优势会被固化下来,这绝对不对。我使用 Coding Agent 的三种语言是 Java、Kotlin 和 Python,而毫无悬念,Coding Agent 做得最好的恰恰是 Kotlin,这完全超乎你的预想。
你想,Java 和 Python 都存在了很久,而且最近几年进化得都很快。你不应该写出 2019 年的 Java,当然也不应该写出 2019 年的 Python。训练数据太多了,你可以说“永远用值类型”“永远用增强 switch 语句”“Python 里永远用类型提示”,但正因为训练数据体量太大,你实际上是在对抗它。我倒不是说他俩不好,Java 和 Python 仍然非常好。
可 Kotlin 更好,这让我非常吃惊,因为 Kotlin 的语料库并不大。Kotlin 本身也没怎么进化,因为它从一开始就是一门现代语言。其次,早期用 Kotlin 的大概率是技术非常娴熟的人,因此没有那么多糟糕的 Kotlin 代码,不像 Java 或 Python 那样。
所以,我不认为热门语言会因为训练数据就被固化。LLM 对任何它们见过足够多的东西都极其擅长,而我们听说过的所有编程语言,它们都见过很多。但这就引出了另一个问题:既然如此,你干嘛不用完美的语言去写每一件事?就像我们之前说的,有些东西我绝对不会用 Python 写,而会用 Java 写。既然 LLM 在每一门语言里都是大师级别,为什么不挑最理想的语言来写每一个服务?用 Rust 写这个服务,用 Go 写那个。(但我不认为现在还有值得用 Go 写的东西了。
问题在于,这就像我们曾经看到过的微服务狂热——还有其他成本。你现在会引入极为庞大的维护成本,除非我们愿意完全信任我们的机器,但我不觉得我们该那样做,我不认为那是个好主意。
Simon:还有技能问题。光是技术栈的多样性,以及人们需要维护这些技术栈,就已经足够头疼了。还有安全,每引入一门新语言或一个新库,威胁面就成倍扩大,而且这种风险会跨语言蔓延。
Rod:至少就目前而言,我们选择语言和技术栈的根本性理由,可能不会改变太多。因为当你把它们投入生产、为它们背书的时候,那些最根本的东西并没有变。
开源生存法则
Simon:你之前提过,开源项目背后的公司需要从第一天起就有商业模式,这对 Embabel 来说是什么样的?
Rod:最可能的模式是开放核心。我认为开源的纯支持模式一直就非常困难,有了 Coding Agent 之后,还会难得多,所以我觉得会存在比框架层面更上一层的产品。我们目前的思路是,把框架作为一种竞争优势,去构建可能甚至不针对软件开发者的产品,而是针对更广泛的知识工作者。框架本身是 Apache 许可证,和 Spring 一样。我们欢迎社区贡献,我们有活跃的 Discord、GitHub 和问题跟踪器,欢迎任何感兴趣的人来贡献和加入社区。
Simon:Spring 前后经历了 VMware、Pivotal 再到如今 Broadcom 的收购。你觉得是什么让它挺过了这些收购?很多开源项目在被收购之后就枯萎甚至死亡了。
Rod:我觉得在 2009 年底那笔首次收购发生时,Spring 已经是一个庞然大物了,它已经是事实上的标准。早期的采用率和围绕它建立的社区,显然有着巨大的惯性。
但有帮助的一点是,有相当多技术娴熟的开发者被全职雇来开发 Spring。这意味着那些不那么令人兴奋的事情也会有人去修,因为这是某个人工作的一部分,不管他们感不感兴趣。我仍然坚信,开源确实能从背后的商业支持中受益。让 Spring 变得可靠的关键,它非常健壮,任何严重问题都会迅速被修复,部分正反映了我们不依赖志愿者。社区贡献当然很棒,但我认为背后的这种专业性至关重要,它给出了那种完整产品式的开发方法。
“这是人们选择的最后一波框架”
Simon:你认为 JVM 最被低估的东西是什么?Python 世界完全不知道自己错过了什么?
Rod:性能当然是其中一个方面。但更严肃的回答是:运行在它上面那些代码,业务逻辑、领域模型,对于理解关键业务,有着难以估量的价值。
Simon:关于 AI Agent,你有什么不受欢迎的观点吗?
Rod:我对 MCP 持有一种相对怀疑的态度。倒不是说我觉得 MCP 不好,而是我觉得现在大家已经把 MCP 当成了“万能锤子”,于是看什么都像钉子。
Simon:如果有 Java 开发者被老板命令“去学 Python,因为 AI 要用 Python”,你会给他什么建议?
Rod:我会让他去读读我的博客。我写了一系列文章,用完全相同的任务来对比 Python 和 Java 的实现——文件处理、API 调用、数据管道。结果非常清楚:Java 代码在性能、可读性和生态成熟度上完全不输 Python。你可以直接把其中一篇文章甩给你的老板,或者更好一点,自己亲手写一段。很多时候你会发现,Java 的代码竞争力其实一点不差。
Simon:如果是已经在线上生产环境跑着的项目,你会选 LangGraph 还是 Embabel?
Rod:Java 项目还是 Python 项目?如果是 Java 项目,那我肯定选 Embabel。否则你就是在往系统里硬塞一个 “Alien Stack”。现在 Embabel 已经到 0.3.5 了,离 API 稳定其实非常近。我会很惊讶如果从现在到 1.0 之间还有什么重大 breaking changes,估计再过四到六周就会到 1.0。
所以说实话,我不觉得现在采用 Embabel 是什么高风险的事。反倒是,如果我有一个 Java 项目,却要引入一整套完全不同的技术栈,再让它去和现有 Java business logic 对接,我会觉得这风险大得多。等你把这一大堆东西折腾完,Embabel 可能都已经 1.0 了,到时候你只会特别懊恼:“我当初到底为什么要这么搞。”
Simon:今天启动一个全新的企业项目,用 Kotlin 还是 Java?
Rod:这取决于团队规模。如果团队少于 20 人,我会认真考虑 Kotlin。如果团队更大,而且大家原本也不熟 Kotlin,那我可能还是会继续用 Java。
Simon:以前 Java 开发者转 Scala 的学习曲线其实挺陡的,现在 Java 转 Kotlin 还有那么难吗?
Rod:老实说,我不太确定。因为我接触 Kotlin 的时候,其实已经写了很多别的语言,尤其是 Python。我虽然也写过几个月 Java,但后来又回到了 Kotlin。所以我并不是一个“纯 Java 背景”转过去的人。不过我可以明确地说:Kotlin 绝对比 Scala 容易学,Kotlin 本身其实是一门相当容易上手的语言。
而且还有两点我一直很认同。第一,我觉得每个开发者原则上都应该每隔一两年学一门新语言,因为它真的会改变你的思维方式。第二,现在学习新语言的门槛已经低到前所未有,LLM 可以极快地帮助你掌握任何语言,所以整体门槛其实已经被大幅降低了,比如我从来没读过 Kotlin 的教程书。
Simon:你刚刚提到让 LLM 去批判代码,有时候相比让它“直接生成”,这种方式反而更值。我也觉得,尤其是在学习新语言的时候,让 LLM 给你解释“这里为什么这么写”“背后的机制是什么”,很多时候比直接说“帮我生成一段代码”更有效。否则你最后只能对着生成结果猜:“它为什么要这么写?”
Rod:尤其是对于有经验的开发者来说,更是这样。我学新语言的时候,经常会问 LLM:“这个语言里有没有某个特性的等价实现?”比如我会问:Python 里有没有类似 TypeScript type guard 的东西?如果你本身已经理解这些概念,那 LLM 特别擅长帮你把这些概念映射到另一门语言上。
Simon:你每天都会用、最喜欢的 AI 工具是什么?除了 Claude Code 之外。
Rod:大概率还是 Claude Desktop。主要是它能让我同时调用一堆工具做 competitive research,我很多事情都会用它来处理。
Simon:关于 Spring,有什么你后来觉得“当初做错了”的地方?如果重做一次,你会在 Embabel 里换一种方式吗?
Rod:有个挺小、但我觉得挺有趣的点。当年我一直想给 Spring retrofit 一套“基于事件的 logging 机制”,但那个时候已经太晚了,架构上不好再改。但在 Embabel 里,我们一开始就这么设计了。这样做的好处是:所有 event 都是完整的。你可以通过 SSH stream 出去,可以监听,可以订阅。而且更有意思的是,我们甚至可以修改你的“logging personality”,比如让你的日志全都用尤达大师的语气输出。
某种程度上,Embabel 的整体风格其实是建立在现代 Spring 的经验之上,只是加了一些在 Kotlin 里更容易实现的东西。比如我们基本不怎么用 builder pattern,因为 Kotlin 可以把这些东西写得更优雅,哪怕最后还是给 Java 用户使用也一样。
Simon:如果 Embabel 五年后不存在了,你觉得是什么杀了它?
Rod:我完全不知道五年后 Embabel 是否还会存在。五年后,人们还会亲手写应用吗?还是他们违背了我的建议,让机器完全接管了一切?我甚至会说:这可能已经是“最后一代由人类主动选择的框架”了。以后越来越多的技术选型,都会由我们的工具替我们完成。但说实话,我们正处在一个几乎独一无二的时代。别说五年,一两年后的预测都可能是错的。
访谈视频原链接:
https://www.youtube.com/watch?v=UcvxYltiS7E





