Java Web框架Jakarta Faces无状态版:AliFaces市场应用
完全无状态的 Jakarta Faces 市场应用,采用服务端渲染——无需 HttpSession、无需保存 ViewState、无需粘性会话
(参见 POC:AliFaces POC 运行于 Oracle OCI(Nanos Unikernel))
Jakarta Faces 仍然带有一些根深蒂固的假设:它是有状态的,需要 HttpSession,需要粘性会话才能水平扩展,并且它的服务端渲染模型属于上一代 Web 应用。
AliFaces 用可运行的代码挑战这些假设——这不是一个 Hello World,而是一个市场风格的应用程序,包含商品目录、搜索、筛选、购物车、结账、订单、浏览历史、主题、响应式布局,以及面向移动客户端的 JSON API。
技术栈刻意保持主流:Jakarta Faces 4.1、Mojarra 4.1.14、PrimeFaces 16 和 OmniFaces 5.5,同一个 WAR 可运行在 Tomcat 11 和 Open Liberty 上。
没有 HttpSession。没有保存的 ViewState。没有节点亲和性。
目标很简单:任何 Web 节点都应该能够处理下一个请求,无论上一个请求是由哪个节点处理的。
无状态 Faces:一个旧想法的新审视
无状态 JSF 并不新鲜。早在 2011 年就有人探索它,目的是减少状态和内存开销;到 2013 年,Mojarra 已经开始支持瞬态视图:
AliFaces 提出了一个更广泛的问题:能否设计一个完整的现代 Jakarta Faces 应用程序,使 Web 层不依赖 HttpSession、保存的 ViewState 或节点亲和性?
AliFaces 的每个页面都使用瞬态主视图。生成的 jakarta.faces.ViewState 只是 stateless。应用程序使用 @RequestScoped 和 @ApplicationScoped Bean,没有 @ViewScoped、没有 @SessionScoped,也没有任何对 getSession() 的调用。
NoSessionGuard 将意外创建 HttpSession 视为架构违规。因此,无状态是被强制执行的,而不是被假设的。
这是一个市场应用,不是 Hello World
商品目录包含 24 个类别、64 个品牌下的 194 件商品,以及 582 条评论——每件商品 3 条。每件商品包含约 25 个字段,涵盖定价、库存、尺寸、物流、保修、退货、标签和评论。
所有 194 张缩略图和 194 张全尺寸图片都由浏览器直接从 cdn.dummyjson.com 加载。它们没有打包在 WAR 中,这有助于将完整的 unikernel 镜像保持在 10 MB 左右,尽管目录在视觉上很丰富。
外部 CDN 是 POC 中一个刻意的取舍:如果 CDN 不可用,商品图片会消失,但 AliFaces 仍会继续服务应用。生产部署应使用由应用控制的对象存储和 CDN。
这与公共 SSR 页面的边缘缓存是分开的:在 POC 中,商品资源已经由 CDN 分发;在边缘缓存服务端渲染的公开目录页面是一种架构演进。
状态存在于领域,而非会话中
市场应用仍然需要状态。AliFaces 将状态从 Web 节点上移除,而不是假装它不存在。
浏览器身份通过存储在 HttpOnly Cookie 中的签名 JWT 传递;移动客户端使用 Bearer 令牌;匿名访客会收到一个不透明标识符。ShopperResolver 将这些身份转换为一个稳定的购物者键。
Faces UI 和 JSON API 使用相同的应用服务。购物车和历史记录存放在 SharedStore 之后,而不是 HttpSession。POC 目前使用 InMemorySharedStore;生产适配器可以使用 Redis、数据库或数据网格,而无需改动 Faces 页面或应用服务。
服务端渲染与生俱来地契合
AliFaces 刻意保留 Jakarta Faces 原生的 服务端渲染(SSR) 模型。
这并不是回到过时的架构。大型电商体验,例如 Amazon 和 AliExpress,在更广泛的混合交付策略中使用服务端生成且可缓存的内容;而围绕客户端渲染发展起来的 JavaScript 生态,也通过 Next.js、Nuxt、Angular SSR 和 SvelteKit 引入或扩展了 SSR。
SSR 对于有意义的初始 HTML、公共内容、SEO、减少客户端必须执行的工作以及缓存仍然很有价值。Jakarta Faces 本来就基于服务端渲染;AliFaces 将其与无状态 Web 节点、CDN 分发资源、潜在的边缘缓存和选择性 PrimeFaces Ajax 相结合。
服务端渲染并不是现代前端的对立面。它是现代前端使用的工具之一。
现代 UI 独立于渲染模型
AliFaces 遵循一种现代工作流:
Figma → Design System → Design Tokens → Sass / CSS → PrimeFaces → Jakarta Faces → Responsive UI
CSS Grid、Flexbox、响应式断点和移动优先布局都是浏览器技术。服务端渲染并不妨碍现代市场界面。
现代 UI 是设计系统的属性。
无状态是架构属性。
性能是测量结果。
性能是测量结果
瞬态 Faces 会为每个请求重建组件树。这有成本,但成本不等于基准测试结果。
无状态架构也让应用缓存、公共边缘缓存和水平扩展更容易推理。真正的问题在于,在 SSR、缓存、网络和客户端执行之间,服务用户旅程的总成本是多少。
不要对神话进行基准测试。要对架构进行基准测试。
两个节点,一个购物者
最有力的证据来自一个双节点测试:两个独立的 Open Liberty 实例运行同一个 WAR。一个逻辑购物者旅程被刻意在两个节点之间交替切换。
访客身份可以跨节点移动。在节点 A 上创建的令牌会被节点 B 接受。主题、语言和重定向消息在节点切换后仍然保留。没有创建 JSESSIONID,两个节点都返回无状态的 ViewState。
有一个边界是刻意暴露出来的:使用 InMemorySharedStore 时,购物车在节点 A 上可以有 20 件商品,在节点 B 上为 0 件。这证明了两个 JVM 确实拥有独立的内存,并准确定位了节点本地状态残留在哪里。
将该适配器替换为 Redis、数据库或数据网格后,两个节点将使用共享、持久且原子的状态。Faces 页面和应用服务不需要改变。
总结
AliFaces 并不是要证明一个 JSF 页面可以无状态。它要问的是:一个真实的 Jakarta Faces Web 应用能否让整个 Web 层无状态,同时保留原生 SSR、现代响应式 UI 和水平可扩展性。
没有 HttpSession。没有保存的 ViewState。没有粘性会话。基于令牌的身份。外部化的领域状态。CDN 交付的资源。共享的应用服务。浏览器和移动客户端。任何 Web 节点都可以服务任何请求。
现代 UI 是设计系统的属性。
无状态是架构属性。
性能是测量结果。
服务端渲染是现代前端使用的工具之一。
Jakarta Faces,无状态版。
AliFaces 是一个独立的技术概念验证。它的市场交互模型仅用于演练真实的电商场景。AliFaces 和 AmaFaces 是独立的演示品牌,与 Amazon、AliExpress 或任何其他市场公司无关。
参考链接
- 源码 POC:https://github.com/AngeloRubens/alifaces
- BalusC —— Stateless JSF(2013):https://balusc.omnifaces.org/2013/02/
- AWS Compute Blog —— Server-side rendering micro-frontends -- the architecture:https://aws.amazon.com/blogs/compute/server-side-rendering-micro-frontends-the-architecture/