<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>java25 on </title>
    <link>/tags/java25/</link>
    <description>Recent content in java25 on </description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Thu, 30 Apr 2026 10:00:00 +0800</lastBuildDate><atom:link href="/tags/java25/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>全链路 Feature Flag 的升级顺序：先 backend 还是先 frontend？</title>
      <link>/posts/full-stack-flag-upgrade-order/</link>
      <pubDate>Thu, 30 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/full-stack-flag-upgrade-order/</guid>
      <description>本文是 《Spring Boot 3.5 + Java 25 + React：在 K8s 里跑通一套跨链路 OpenFeature flag》 的续篇，聚焦三个场景中的第三个——全链路共享 flag 在升级过程中的顺序选择。
对应的 demo 代码仓库：sb3-k8s-hot-reload（私有）。
从一篇文章引发的真实问题讲起 上一篇写完后，有人在评论区提了一个非常实际的问题：
&amp;ldquo;你说的全链路 flag（full-stack shared flag）确实好理解——后端在 gateway 评估，前端通过 /experience/shared-flags 拿到同样的值。但实际加一个新 flag 的时候，先改哪一边？&amp;rdquo;
这个问题戳中了全链路 flag 最微妙的地方。
假设你的团队要上线一个&amp;quot;会员折扣算法 v2&amp;quot;功能：
后端：pricing-service 要用新算法计算价格 前端：UI 要在会员用户上展示&amp;quot;v2 折扣体验&amp;quot; 目标：u-vip-* 用户看到新体验，其他用户保持原样 先升级前端还是先升级后端？ 这不是一个语义问题，这是一个部署时序问题。部署时序错了，用户就会看到不一致的行为。
三种升级路径的风险拆解 路径一：前后端同时发布（&amp;ldquo;理想情况&amp;rdquo;） 前后端同时发布在理想状态下看起来很干净，但有几个现实坑点：
CI/CD 不同步：后端在 order-service/，前端在 ui/，通常走不同的 PR、不同的 review、不同的 merge。让两个独立流水线在精确的同一分钟上线几乎不可靠。 回滚不对称：如果上线后 10 分钟发现后端有 bug，你 kubectl rollout undo 回滚了后端，前端已经在跑新逻辑——此时前端看到的行为和后端完全脱节。 A/B 验证困难：你想先让 10% 用户尝鲜，但 flag 还没在 flagd 里配好（或者配了但 defaultVariant 不是目标值），新前端就已经在跑了。 所以&amp;quot;同时发布&amp;quot;更多是一种理想态，不是你可以依赖的策略。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 微服务里，Resilience4j 用在 HTTP、Redis、Kafka、DB 上的边界与最佳实践</title>
      <link>/posts/spring-boot-resilience4j-external-calls-best-practices/</link>
      <pubDate>Tue, 28 Apr 2026 10:30:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-resilience4j-external-calls-best-practices/</guid>
      <description>背景 Spring Boot 3.5 配合 Java 25，已经把“同步写法 + virtual threads”这条路线变成了很现实的工程选择。JDK 在 JEP 444 里把 virtual threads 的目标讲得很清楚：让 thread-per-request 风格更容易扩展；Spring Boot 官方文档也提供了 spring.threads.virtual.enabled=true 这条开关入口。
于是一个常见问题会重新冒出来：当一个微服务会同时访问 HTTP 下游、Redis、Kafka、数据库时，Resilience4j 应该怎么用？
这篇文章不是“所有外部调用统一套一层 Resilience4j 模板”的教程，而是一篇事实约束下的探索综述。文中的强结论优先建立在仍在维护的官方资料和工程文档上，例如 Spring / Resilience4j、AWS Builders Library、Azure Architecture Center、Google Cloud retry strategy、Redis 官方文档、Spring for Apache Kafka 文档 和 PostgreSQL 文档。少量技术文章和 case study 只用来补充上下文，不会单独支撑“行业定论”。
如果你使用的是 resilience4j-spring-boot3，或者使用 Spring 生态提供的 Spring Cloud CircuitBreaker + Resilience4j，本文的判断原则都成立；区别更多在接入方式，而不在策略本身。
为什么这篇文章只讨论“按 dependency + operation semantics 设计策略” 我对这个问题最核心的判断是：
Resilience4j 不应该按“所有外部调用统一套模板”来使用，而应该按具体 dependency 与操作语义来设计。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; React：在 K8s 里跑通一套跨链路 OpenFeature flag</title>
      <link>/posts/spring-boot-openfeature-flagd-cross-stack/</link>
      <pubDate>Sun, 26 Apr 2026 15:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-openfeature-flagd-cross-stack/</guid>
      <description>本文对应的 demo 项目：sb3-k8s-hot-reload（私有）。代码组织在 gateway/ order-service/ pricing-service/ ui/ k8s/ scripts/ 下，一条 ./scripts/e2e-demo.sh 从 kind 集群创建到端到端验证全跑完。
起点：一个朴素的约束 接到的题目是这样的：
在 Spring Boot 3.5 + Java 25 + Kubernetes（kind 本地）里，不用 Spring Cloud Config Server，不用 Netflix 套件（Eureka / Zuul / Ribbon / Hystrix）的前提下，验证一下&amp;quot;运行时配置变更不重启服务&amp;quot;这件事到底有哪些解、各自取舍是什么。后续要能支持 feature flag。
约束被排除的两块是有理由的：Config Server 在 K8s 原生场景里往往不再是首选（ConfigMap/Secret 已经在那里），Netflix 套件在 Spring Cloud 2023 起官方也已停止维护。但 Spring Cloud 不等于 Spring Cloud Config——spring-cloud-context、spring-cloud-kubernetes 这些仍然在维护，并且在 K8s 场景里依然是常见选择。
把约束精确化之后，问题就清晰了：在允许 spring-cloud-context 的前提下，K8s 上把&amp;quot;配置热加载&amp;quot;和&amp;quot;feature flag&amp;quot;分别做对，应该选什么。
热加载方案的选型矩阵 我把 2026 年还能找到、也比较贴近这个题目的可选项先压成一张表：</description>
    </item>
    
    <item>
      <title>软件供应链最小基线：SBOM &#43; cosign 镜像签名</title>
      <link>/posts/software-supply-chain-sbom-cosign-2026/</link>
      <pubDate>Sun, 26 Apr 2026 00:30:00 +0800</pubDate>
      
      <guid>/posts/software-supply-chain-sbom-cosign-2026/</guid>
      <description>📦 本文基于的完整项目源码：meirongdev/shop
Log4Shell 让全行业意识到：没人能回答&amp;quot;我用了哪些库的哪些版本&amp;quot; 的时候，所谓应急响应就是几千个工程师在同时翻自己的 pom.xml。SLSA、cosign、Sigstore 这套工具链，更多是在试着把这个问题收敛成一次可查询的清单比对。本文聚焦最小基线：每个产物有 SBOM、每个镜像有签名、SBOM 作为 attestation 绑定到镜像。
一、为什么 SBOM 是底线 Q：SBOM 到底是什么，跟以前我们说的&amp;quot;依赖列表&amp;quot;有什么本质区别？
SBOM（Software Bill of Materials）是一份机器可读的、可校验的依赖清单，每条记录包含：
名称、版本、purl（package URL，唯一定位坐标） 哈希（SHA-256，验证产物未被篡改） 许可证 直接 / 间接依赖关系（树状结构） 可选：CVE / VEX 状态 mvn dependency:tree 生成的是给人看的文本；SBOM 是给工具看的、跨语言统一的 JSON 文档。Log4Shell 当时如果每个交付物都已经有 SBOM 在手，问题从&amp;quot;全员盲查&amp;quot;变成 jq &#39;.components[] | select(.purl | contains(&amp;quot;log4j-core&amp;quot;) and contains(&amp;quot;@2.&amp;quot;))&#39;。
Q：CycloneDX 和 SPDX 选哪个？
两个都是 ISO 认可的 SBOM 标准，95% 场景下结果等价。差异在重点：
CycloneDX — OWASP 出品，安全工程师视角，原生支持 VEX（漏洞利用状态）、SaaSBOM、ML-BOM。漏洞工具链（Dependency-Track、Trivy、Grype）的 first-class 输入格式。 SPDX — Linux Foundation 出品，许可证合规视角，法务工具（FOSSA、Black Duck）原生消费。 Java 生态里我会先选 CycloneDX——cyclonedx-maven-plugin 上手成本比较低。如果你的合规需求是法务驱动而非安全驱动，再考虑 SPDX。</description>
    </item>
    
    <item>
      <title>API 与 event contract 兼容性保障：工具机制与正确用法</title>
      <link>/posts/api-event-contract-compatibility-qa-2026/</link>
      <pubDate>Sat, 25 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/api-event-contract-compatibility-qa-2026/</guid>
      <description>📦 本文基于的完整项目源码：meirongdev/shop
上一篇 微服务 contract 兼容性的五层防线：从 ArchUnit 到 japicmp 讨论了如何用 ArchUnit、japicmp、WireMock 和运行时 Deprecation 头构建基础安全带。本文聚焦更具体的问题：BFF 对外的 API、BFF→MS 和 MS→MS 的内部 API、以及 Kafka 事件，各自应该用什么工具保障 contract 兼容性，这些工具的工作机制是什么。
一、两类 contract，两种保障方式 Q：BFF 对外暴露的 API 和 BFF→MS、MS→MS 之间的内部 API，兼容性的保障方式应该一样吗？
在我的实践中不太一样。主要原因在于 source of truth 不同。
BFF 对外的 API（前端、移动端、三方调用方）采用的是 API-first 方式：先有 OpenAPI YAML，生产者的 controller 实现这份 YAML，消费者根据这份 YAML 生成客户端代码或手写调用。在这条链路里，YAML 就是权威来源。保障兼容性就是保障 YAML 文件本身不引入 breaking change。
BFF→MS 和 MS→MS 之间采用的是 code-first 方式：Java record 是 source of truth，没有提前写 spec，序列化层（Jackson）从 record 推导出 JSON 结构。保障兼容性就是保障 Java record 的 JSON 表现形式不引入 breaking change。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 BFF 出站 HTTP 客户端：连接池、超时与 HTTP/2 实战</title>
      <link>/posts/bff-http-client-pool-timeout-and-h2c-2026/</link>
      <pubDate>Sat, 25 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/bff-http-client-pool-timeout-and-h2c-2026/</guid>
      <description>配套阅读：shop-starter-http-client 设计 介绍完整的 starter 设计；本文专注于其中&amp;quot;连接配置&amp;quot;这一块的细节决定。
1. 问题场景 典型的 BFF 模式：
graph LR BFF[buyer-bff] --&gt; M[marketplace-service] BFF --&gt; O[order-service] BFF --&gt; L[loyalty-service] BFF --&gt; P[promotion-service] BFF --&gt; S[search-service] BFF -. 偶尔 .-&gt; EXT[第三方支付 API] 每个下游的特征可能完全不同：marketplace 响应 50ms，搜索可能要 5s；payment 是外部 HTTPS API，order 是内部 cleartext；某个服务上了 HTTP/2，其他还是 HTTP/1.1。
三个绕不开的问题：
不同下游应该共用一个连接池，还是每个独立？ connectTimeout / readTimeout 怎么按服务定制？ 内部服务用 HTTP/2（h2c）值不值得开？怎么避坑？ 下面逐个讲清楚。
2. 连接池：共享还是隔离？ 我通常会先共享一个 java.net.http.HttpClient 实例。
2.1 为什么很多时候先不需要隔离 HttpClient 的连接复用天然按 origin 收敛 Oracle 的 HttpClient API 文档明确写了“一个 HttpClient 实例通常管理自己的连接池”；连接对同一 origin 复用、不同 origin 分开这一点，可以再配合 OpenJDK 源码 一起看。对 marketplace-service:80 和 order-service:80 的连接不会混在一个全局 bucket 里，所以“共享一个 HttpClient 实例”并不等于“所有下游抢同一组 socket”：</description>
    </item>
    
    <item>
      <title>shop-starter-http-client 集成指南：从零到第一个 @HttpExchange 客户端</title>
      <link>/posts/shop-starter-http-client-integration-2026/</link>
      <pubDate>Sat, 25 Apr 2026 05:00:00 +0800</pubDate>
      
      <guid>/posts/shop-starter-http-client-integration-2026/</guid>
      <description>这是 shop-starter-http-client 的集成指南，按这套步骤通常可以跑起第一个出站客户端。
想了解 starter 内部如何设计：shop-starter-http-client 设计 想深入连接池/超时/HTTP2 的取舍：BFF 连接池与 HTTP/2 实战 前置条件 项 要求 JDK Java 21+（建议 Java 25） Spring Boot 3.5.x 依赖 已通过 shop-common-bom / shop-contracts-bom 管理共享版本 可观测性（推荐） micrometer-tracing-bridge-otel、micrometer-registry-prometheus 5 步集成 下面以 buyer-bff 调用 marketplace-service 列商品接口为例，从 0 走完。
graph LR A[Step 1加依赖] --&gt; B[Step 2配 application.yml] B --&gt; C[Step 3声明 @HttpExchange 接口] C --&gt; D[Step 4注入 Support 建代理 Bean] D --&gt; E[Step 5业务代码调用] Step 1：加依赖 services/buyer-bff/pom.xml：
&amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;dev.</description>
    </item>
    
    <item>
      <title>shop-starter-http-client：Spring Boot 3.5 微服务 HTTP 客户端基础设施设计</title>
      <link>/posts/shop-starter-http-client/</link>
      <pubDate>Fri, 24 Apr 2026 20:00:00 +0800</pubDate>
      
      <guid>/posts/shop-starter-http-client/</guid>
      <description>代码背景来自开源项目 meirongdev/shop，Java 25 + Spring Boot 3.5 + Spring Cloud 微服务电商平台。
本文讲设计——starter 内部为什么这么写。如果你的目标是集成 starter，跟着步骤跑通第一个客户端，请直接看 shop-starter-http-client 集成指南。
配套阅读：Spring Boot 3.5 微服务 tracing 为什么会断链。
1. 背景 项目是典型的 BFF（Backend For Frontend）模式：
graph LR C[Client] --&gt; G[api-gateway] G --&gt; BB[buyer-bff] G --&gt; SB[seller-bff] BB --&gt; M[marketplace] BB --&gt; O[order] BB --&gt; L[loyalty] BB --&gt; P[promotion] SB --&gt; M SB --&gt; O 每个 BFF 都要向多个 domain service 发出站请求。shop-starter-http-client 把以下四件事提取到共享 starter，避免每个 BFF 重复实现：
关注点 由谁负责 身份传播 TracingHeaderInterceptor：Micrometer Baggage → 直接 HTTP header 错误语义保留 SharedDownstreamErrorHandler：4xx/5xx → DownstreamException（带原始状态码） 可观测性 DownstreamServiceObservationConvention：metric / span 加 downstream.</description>
    </item>
    
    <item>
      <title>Java 项目怎么做 contract testing：一次 Spring Cloud Contract 实践</title>
      <link>/posts/java-contract-scc-poc-recap/</link>
      <pubDate>Tue, 14 Apr 2026 10:10:00 +0800</pubDate>
      
      <guid>/posts/java-contract-scc-poc-recap/</guid>
      <description>这篇文章可以看作 《Contract First 工作流：让 AI 帮你写 OpenAPI YAML》 的实现篇。上一篇讨论的是“spec 如何成为协作中心”；本文只讨论当接口边界已经确定之后，JVM 团队如何把contract变成验证、stubs 和 CI Quality Gates。
微服务接口兼容性的问题，通常不是在设计阶段暴露，而是在某个 PR 合并之后才通过联调、回归测试，甚至线上流量被发现。这个 java-contract 仓库的目的，就是把这类问题尽量前移到提交阶段，用 contract testing 把 producer 和 consumer 之间的接口约束固定下来。
这篇文章不打算再展开 AI 生成 OpenAPI、style guide 或 breaking change 治理这些协作层话题。它更像是一次基于当前仓库完成态的工程复盘：为什么这个 Java 25 + Spring Boot 3.5 + Maven 多模块项目最终选择了 Spring Cloud Contract，contract如何在仓库里流动，以及这套做法如何进入 CI，成为一条可执行的兼容性 Quality Gates。
项目代码：github.com/meirongdev/java-contract
有了 OpenAPI 和集成测试，还需要 SCC 吗？ 采用 Contract First 工作流的团队常会遇到这个问题。我的理解是：三者关注的粒度不同，不互相替代。
OpenAPI spec 描述的是接口形状：字段、类型、必填约束、状态码有哪些。集成测试验证的是系统整体行为：多个服务部署在一起能否跑通。SCC 填的是中间那层——接口行为：在某个具体场景下，一次请求会得到什么确定性响应，且这个断言在提交阶段就能被执行。
以 /users/{id} 为例。OpenAPI 会告诉你：</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 下数据库实践记录：HikariCP 调优、N&#43;1 防护、事务管理与 Testcontainers 集成</title>
      <link>/posts/spring-boot-database-best-practices/</link>
      <pubDate>Sun, 12 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-database-best-practices/</guid>
      <description>📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
2026-04 实践更新 当前主线仓库里，MySQL 领域服务的集成测试基线已经基本统一为 @ServiceConnection；少数仍保留 @DynamicPropertySource 的测试，主要是因为除了数据库容器之外还需要额外挂接自定义属性，而不是数据库接线本身没有迁移完成。
在 Shop Platform 的 15 个服务中，11 个领域服务各自拥有独立的 MySQL 8.4 数据库。Spring Boot 3.5 结合 Spring Data JPA 3.5 提供了比较完善的数据库集成能力，但默认配置未必直接适合当前生产环境。需要额外说明的是：当前仓库已经稳定落地的是 ddl-auto: validate、Flyway 迁移、@ServiceConnection 集成测试，以及部分服务显式关闭 OSIV；像 Hikari 数值和 @EntityGraph 更适合作为“可参考实践”，不应直接写成“仓库现状”。
下面从六个维度整理一下 Spring Boot 3.5 下数据库实践里更常遇到的点。
HikariCP 连接池调优 为什么需要调优 Spring Boot 默认的 HikariCP 配置是 maximum-pool-size: 10。这个值适合当起点，但对容器化环境来说，既可能偏小，也可能偏大。
一个更稳妥的压测起点 spring: datasource: hikari: maximum-pool-size: 10 # 先作为压测起点 minimum-idle: 2 # 最小空闲连接 connection-timeout: 5000 # 获取连接超时（ms） idle-timeout: 300000 # 空闲连接超时（5 分钟） max-lifetime: 1200000 # 连接最大生命周期（20 分钟） 怎么确定 maximum-pool-size HikariCP 作者 Brett Wooldridge 的公式：</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 下 Redis 实战记录：从连接池调优到 Bloom Filter 自动配置</title>
      <link>/posts/spring-boot-redis-best-practices/</link>
      <pubDate>Sat, 11 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-redis-best-practices/</guid>
      <description>📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
2026-04 实践更新 当前主线代码已经把 buyer-bff / auth-server / activity-service / api-gateway 的 Redis 访问统一切到 RedissonClient；其中网关限流仍然保留 Lua 原子脚本，但执行器也已经从 StringRedisTemplate 切到 RScript。本文下面的示例已按仓库当前实现同步。
Redis 在 Shop Platform 中承担的角色远不止&amp;quot;缓存&amp;quot;——它是限流的令牌桶状态（Gateway Lua + RScript）、抢红包的原子存储（activity-service Redis List）、反作弊的状态机（Anti-CheatGuard）、幂等检查的 Bloom Filter（shop-starter-idempotency），以及游客购物车的存储（buyer-bff）。
本文从五个维度整理 Spring Boot 3.5 下 Redis 的一些实践记录。
Lettuce 连接池调优 Spring Boot 3.5 默认使用 Lettuce 作为 Redis 客户端。Lettuce 基于 Netty，连接本身是线程安全的；对大多数普通 GET / SET / HSET / EVAL 场景来说，共享连接就已经够用。连接池更适合拿来解决阻塞命令、事务、Pub/Sub 或需要 dedicated connection 的场景，而不是“高并发就一定要开池”。
什么时候才需要连接池 如果你确实要跑阻塞命令、事务，或者要为特定操作准备专用连接，可以再启用连接池：
spring: data: redis: host: ${REDIS_HOST:redis} port: ${REDIS_PORT:6379} lettuce: pool: max-active: 20 # 最大连接数 max-idle: 10 # 最大空闲连接 min-idle: 5 # 最小空闲连接 max-wait: 2000ms # 获取连接最大等待时间 参数 说明 常见起点 max-active 连接池最大连接数 CPU 核心数 × 2 ~ × 4 max-idle 最大空闲连接数 max-active 的 50%-80% min-idle 最小空闲连接数 max-active 的 20%-30% max-wait 获取连接超时 1-3 秒（过长说明连接池不够） 连接池依赖 Spring Boot 不会自动引入连接池，需要手动添加 commons-pool2：</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 下 Kafka 实战记录：从消费者并发模型到序列化选型</title>
      <link>/posts/spring-boot-kafka-best-practices/</link>
      <pubDate>Fri, 10 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-kafka-best-practices/</guid>
      <description>📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
在 Shop Platform 的事件驱动架构中，Apache Kafka 3.9（KRaft 模式）是服务间异步通信的核心基础设施。Spring Boot 3.5 结合 Spring Kafka 3.3 提供了开箱即用的 Kafka 集成，但要把 Kafka 用得稳、用得好，还需要理解底层的并发模型、序列化策略、offset 语义以及错误处理机制。
本文从六个维度整理 Spring Boot 3.5 下 Kafka 的一些实践记录。文中的“现状判断”都以 shop 项目的实际实现为准，额外的代码片段会明确视为建议写法，而不是仓库已经落地的事实。
生产者调优：acks、retries 和 linger.ms acks 配置 acks 决定了生产者需要等待多少个 broker 副本确认后才认为发送成功：
acks 值 含义 可靠性 延迟 acks=0 不等待任何确认 最低（可能丢消息） 最低 acks=1 Leader 确认即可 中（Leader 宕机丢消息） 低 acks=all 所有 ISR 副本确认 最高 高 Kafka 生产者文档里的默认值仍是 acks=1。对于事件驱动架构中承载业务事实的事件（如订单事件、钱包事件），更稳妥的做法仍然是显式写成 acks=all：
spring: kafka: producer: acks: all 参考：Apache Kafka Producer Configs - acks、Confluent: Understanding Kafka acks</description>
    </item>
    
    <item>
      <title>Spring Boot 微服务中的 Feature Toggle 实战：OpenFeature Property Provider &#43; K8s ConfigMap 热更新</title>
      <link>/posts/spring-boot-feature-toggle-openfeature/</link>
      <pubDate>Tue, 07 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-feature-toggle-openfeature/</guid>
      <description>在生产环境中推过新功能的人，几乎都遇到过同一个问题：&amp;ldquo;这个功能怎么在不开完整服务的前提下，先让一小部分人用起来？&amp;rdquo;
Feature Toggle（特性开关）是回答这个问题的一种常见思路。但在 Spring Boot 微服务体系里，怎么实现一套不绑定具体供应商、能随代码一起版本管理、还能在生产环境动态切换的特性开关，仍然是很多团队需要摸索的事情。
本文结合 shop 项目中的 shop-starter-feature-toggle 模块，完整讲一遍从实现到部署的 Feature Toggle 落地路径。
为什么不用 LaunchDarkly / Unleash 直接上手 在开始之前，先明确一个关键问题：为什么不用现成的 SaaS Feature Flag 平台？
LaunchDarkly、Unleash、Split 等确实是成熟的产品，但在实际落地时往往面临几个门槛：
引入外部依赖：需要在生产环境部署或购买 SaaS，增加运维和成本复杂度 供应商锁定：一旦业务代码深度耦合某个平台的 SDK，切换供应商的成本很高 对于 POC 和中小项目来说过度工程化：很多团队只需要&amp;quot;开/关&amp;quot;级别的开关，不需要复杂的受众定位、A/B 测试等功能 OpenFeature 提供了第三条路。
OpenFeature：供应商无关的特性开关标准 OpenFeature 定义了一套供应商无关的特性 flag API。它的架构很简单：
flowchart LR App[&#34;Spring Boot 应用&#34;] SDK[&#34;OpenFeature SDK(供应商无关 API)&#34;] Provider[&#34;Property Provider(从 Spring Config 读取)&#34;] Config[&#34;K8s ConfigMap或 YAML 文件&#34;] App --&gt; SDK SDK --&gt; Provider Provider --&gt; Config 核心原则：
业务代码只依赖 OpenFeature SDK，不依赖任何具体 Provider Provider 可替换：当前用 Spring Property Provider，未来可以换成 Unleash、LaunchDarkly、flagd 等，业务代码改动通常可以控制在较小范围 标准化评估结果：ProviderEvaluation 统一返回 value、variant、reason、errorCode，便于监控和调试 参考：OpenFeature 规范、OpenFeature Concepts、OpenFeature Java SDK</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（七）：架构质量Quality Gates</title>
      <link>/posts/shop-platform-architecture-quality-gates/</link>
      <pubDate>Mon, 06 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-architecture-quality-gates/</guid>
      <description>在这个系列的前几篇文章中，我们看了 Shop Platform 的各个技术层面：API Gateway、BFF 聚合、领域服务、事件驱动、活动引擎。但有一个问题始终没有回答——如何保证 15+ 个服务、多个开发者在长时间演进中不把这些设计搞乱？
靠 Code Review 口头约定往往不够。人总是会犯错，尤其是当 deadline 临近的时候。按我目前的理解，一个更稳妥的办法是：把架构约束尽量变成可自动执行的测试用例。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（六）插件化活动引擎
2026-04 实践更新 当前主线仓库里，ArchUnit、WireMock consumer contract tests、@HttpExchange 客户端基线、以及 JWKS 认证链都已经进入可验证状态；本文里的质量Quality Gates思路仍然适用，但哪些Quality Gates“只是方向”、哪些已经落地，请以 main 分支和 docs/ROADMAP-2026.md 为准。
质量Quality Gates全景 Shop Platform 的架构质量保障由三个支柱组成：
┌─────────────────────────────────────────────────────────┐ │ 架构质量Quality Gates │ │ │ │ ① ArchUnit 规则 → 验证已有代码是否符合约定 │ │ (19 条规则) │ │ │ │ ② Maven Archetype → 新服务从标准化骨架出发 │ │ (6 个模板) │ │ │ │ ③ WireMock contract → BFF 与领域服务接口兼容 │ │ (13 个 contract tests) │ └─────────────────────────────────────────────────────────┘ ArchUnit：用代码验证架构 ArchUnit 是一个 Java 库，可以在单元测试中验证类之间的依赖关系、命名约定、注解使用等架构约束。它的核心价值在于：把架构规则变成编译和 CI 中自动执行的测试，而不是文档里容易漂移的约定。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（六）：插件化活动引擎</title>
      <link>/posts/shop-platform-activity-engine/</link>
      <pubDate>Sun, 05 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-activity-engine/</guid>
      <description>在之前的文章中，我们看了 API Gateway、BFF 聚合、领域服务和事件驱动架构。本文继续整理 Shop Platform 里我觉得比较有意思的一块——activity-service 的插件化游戏引擎。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（五）事件驱动架构
2026-04 实践更新 当前主线代码里，RedEnvelopePlugin 和 AntiCheatGuard 都已经从 StringRedisTemplate 统一迁到 RedissonClient；本文讨论的 Lua 原子性和反作弊思路不变，但 Redis 客户端层已经与早期版本不同。
为什么需要活动引擎 电商平台经常需要举办营销活动来吸引用户：
砸金蛋/抽奖：用户消耗次数参与，随机获得奖品 抢红包：限时限量，用户拼手速抢金额 集卡：集齐一套卡片兑换大奖 虚拟养成：每天浇水/喂养，成熟后收获奖品 如果每个活动都独立开发一个新服务：
代码大量重复（参与记录、奖品发放、反作弊逻辑） 每次上线新活动都要走完整的发版流程 活动下线后代码变成死代码 这次项目里我更倾向用插件化引擎：一个服务、一套运行框架，新增活动时先实现一个插件接口，再让 Spring 负责发现和注册。
GamePlugin SPI 接口设计 核心接口 public interface GamePlugin { /** 声明支持的游戏类型 */ GameType supportedType(); /** 游戏激活时的初始化（如生成红包金额） */ default void initialize(ActivityGame game) {} /** 核心玩法：参与一次 */ ParticipateResult participate(ParticipateContext ctx); /** 游戏结束时的清理（如 Redis 数据回收） */ default void settle(ActivityGame game) {} /** 扩展表前缀（插件自带数据表时使用） */ default Optional&amp;lt;String&amp;gt; extensionTablePrefix() { return Optional.</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（五）：事件驱动架构</title>
      <link>/posts/shop-platform-event-driven/</link>
      <pubDate>Sat, 04 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-event-driven/</guid>
      <description>在之前的文章中，我们看了同步请求链路上的 Gateway → BFF → 领域服务。但微服务架构中并不是所有交互都适合同步发生。事件驱动架构让服务之间通过异步消息协作，在不少场景下用最终一致性换取更低耦合和更好的链路弹性。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（四）领域服务设计
2026-04 实践更新 当前主线仓库已经把 IdempotencyGuard + Redis Bloom Filter、补偿任务重试、以及关键 Kafka consumer 的幂等保护补齐到 Phase 1 基线；本文整体设计仍然准确，但“待补齐”的幂等链路请以当前 main 分支实现为准。
为什么用事件驱动 在 Shop Platform 中，异步事件主要用来承接那些不必阻塞用户请求、但又需要可靠传播的业务事实。例如：
flowchart TD Order[&#34;用户下单order-service&#34;] Stock[&#34;扣库存marketplace-service&#34;] Points[&#34;发积分loyalty-service&#34;] Email[&#34;发邮件通知notification-service&#34;] Webhook[&#34;推送外部订阅webhook-service&#34;] Order --&gt;|&#34;同步 需要立即确认&#34;| Stock Order -.-&gt;|&#34;异步 可以延迟&#34;| Points Order -.-&gt;|&#34;异步&#34;| Email Order -.-&gt;|&#34;异步&#34;| Webhook 如果全部走同步 HTTP 调用：
下单接口响应时间 = 最慢下游服务的响应时间 任意下游服务不可用 → 下单失败 服务间耦合紧，新增消费者需要修改生产者 事件驱动的方式：
flowchart LR OS[&#34;order-service写入订单 + outbox同一事务&#34;] Pub[&#34;Outbox Publisher&#34;] Kafka[&#34;Kafkaorder.events.v1&#34;] L[&#34;</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（四）：领域服务设计</title>
      <link>/posts/shop-platform-domain-services/</link>
      <pubDate>Fri, 03 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-domain-services/</guid>
      <description>在前两篇文章中，我们看了 API Gateway 的路由分发和 BFF 的聚合编排。这一篇继续往里走，看看业务承载最集中的一层——领域服务层。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（三）BFF 聚合层
2026-04 实践更新 领域服务侧当前已经和早期文章发布时相比前进了一步：Problem Details、@ServiceConnection、补偿任务持久化、Kafka 幂等守卫都已落地；本文中的领域边界划分仍然成立，但涉及“后续会做”的部分请以主线仓库现状为准。
领域服务清单 Shop Platform 核心业务按边界拆成 11 个领域服务，每个服务独立开发、独立部署、拥有自己的数据库 schema；此外 auth-server 自己维护 shop_auth，但它更偏认证边界，本文重点放在业务域服务。
服务 数据库 核心业务 profile-service shop_profile 用户档案、地址簿、卖家档案 marketplace-service shop_marketplace 商品目录、SKU、库存、评价 order-service shop_order 订单状态机、购物车、退款 wallet-service shop_wallet 钱包余额、充值、Stripe 支付 promotion-service shop_promotion 促销引擎、优惠券 loyalty-service shop_loyalty 积分账户、签到、兑换 activity-service shop_activity 插件化游戏引擎 search-service — Meilisearch 集成、Feature Toggle notification-service shop_notification 邮件通知、Channel SPI webhook-service shop_webhook 开放平台 Webhook、HMAC 签名 subscription-service shop_subscription 订阅计划、自动续费 其中 search-service 没有关系型数据库——它的数据存储在 Meilisearch 搜索引擎中。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（三）：BFF 聚合层</title>
      <link>/posts/shop-platform-bff-aggregation/</link>
      <pubDate>Thu, 02 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-bff-aggregation/</guid>
      <description>在上一篇中，我们看了 API Gateway 如何作为统一入口处理 JWT 校验和路由分发。请求经过 Gateway 后，到达的就是本文的主角——BFF（Backend for Frontend）聚合层。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（二）API Gateway 架构
2026-04 实践更新 当前主线代码里，buyer-bff / seller-bff 都已经完成了 typed @HttpExchange 客户端改造；BuyerAggregationService / SellerAggregationService 不再直接持有一堆 raw RestClient 调用链，而是通过 HttpServiceProxyFactory 注入声明式客户端。本文下面与客户端装配相关的表述已按当前实现校正。
BFF 模式：为什么需要聚合层 在微服务架构中，前端页面往往需要从多个领域服务获取数据。如果没有 BFF，前端直接调用每个领域服务会面临：
前端 ←→ profile-service (获取用户档案) ←→ wallet-service (获取钱包余额) ←→ promotion-service (获取可用优惠) ←→ marketplace-service (获取推荐商品) ←→ loyalty-service (获取积分账户) 问题显而易见：前端需要知道每个服务的地址、认证方式、数据格式；任何服务变动都会影响前端代码；更重要的是，前端暴露了内部服务拓扑，安全和治理成本都会明显上升。
graph LR FE1[&#34;前端&#34;] P[&#34;profile-service&#34;] W[&#34;wallet-service&#34;] PM[&#34;promotion-service&#34;] M[&#34;marketplace-service&#34;] L[&#34;loyalty-service&#34;] FE1 -.-&gt; P FE1 -.-&gt; W FE1 -.-&gt; PM FE1 -.</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; OpenTelemetry 实践笔记（2026）</title>
      <link>/posts/spring-boot-35-otel-best-practices/</link>
      <pubDate>Thu, 02 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-35-otel-best-practices/</guid>
      <description>背景 在 2026 年，虽然 Spring Boot 4 已经发布，但大多数企业项目（包括我当前的工作项目）仍基于 Spring Boot 3.5，且没有立即升级到 Spring Boot 4 的计划。就我这次接入来说，Spring Boot 3.5 + Java 25 提供的 OpenTelemetry 自动配置已经覆盖了很多常见场景，剩下的主要是按项目需要做取舍。
本文以一个三服务微服务项目（hello-service → user-service + greeting-service）为例，记录我在 Spring Boot 3.5 下接入 OTel 的过程。项目代码完全开源，更适合作为一个可对照的 demo，而不是放之四海而皆准的模板。
实践思路：这个示例先不使用 Javaagent。 主要考虑是想把接入路径尽量放在 Spring Boot 自身的自动配置和 Micrometer Observation 上，这样更容易观察 AOT / Native Image 场景里的行为，也少一层运行时黑盒。对这个 demo 来说，这样的取舍已经够用。
先从 Agentless 方案试起，利用 Micrometer 实现原生可观测性（参见 Micrometer Observation Documentation） 尽量少写接入代码，优先让 Spring Boot 自动配置接管 启用 Java 25 Virtual Threads，观察它在 I/O 场景下的表现（参见 Project Loom） 使用 JFR（Java Flight Recorder）持续性能分析，补全可观测性的第四个信号 用 ArchUnit harness 固化关键约束 项目代码：github.</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（二）：API Gateway 架构</title>
      <link>/posts/shop-platform-api-gateway/</link>
      <pubDate>Wed, 01 Apr 2026 10:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-api-gateway/</guid>
      <description>在上一篇总览文章中，我们看到了 Shop Platform 的整体架构分层。API Gateway 作为整个系统当前统一的外部入口，承载了身份验证、流量控制、路由分发、文档聚合等多项职责。
📦 本文基于的完整项目源码：https://github.com/meirongdev/shop
上一篇：（一）Shop Platform 总览
2026-04 实践更新 当前主线代码已经从 HS256 共享密钥切到 RS256 + JWKS，并把网关限流的 Lua 执行器统一到 RedissonClient + RScript。下面相关代码片段已按仓库当前实现同步。
为什么选 Spring Cloud Gateway Server MVC Spring Cloud Gateway 最初基于 WebFlux（Reactor）构建，是一个响应式网关。但在 2024–2025 年间，Spring Cloud 团队推出了 Spring Cloud Gateway Server MVC——一个基于 Servlet/MVC 的网关实现。
我们选择 MVC 而非 WebFlux，主要考虑了三点：
1. 统一编程模型
项目里 15+ 个后端服务全部是 Spring Boot MVC + Virtual Threads。如果网关用 WebFlux，就意味着团队需要同时维护两套编程模型：一套响应式（网关），一套同步阻塞（所有下游服务）。调试、日志追踪、异常处理的风格都不一样，增加了认知负担。
2. Virtual Threads 目前能满足需求
Virtual Threads 让每个 I/O 阻塞操作都运行在轻量级虚拟线程上，网关的并发能力不再完全依赖非阻塞 I/O 模型。对于 API Gateway 这种典型的 I/O 密集型应用（读 JWT、查 Redis、转发请求），它在当前项目的流量模型里已经提供了可接受的吞吐量，同时保留了同步代码的可读性。</description>
    </item>
    
    <item>
      <title>Liquibase Split POC：把单体数据库迁移拆成三阶段的实战记录</title>
      <link>/posts/liquibase-split-progressive-migration/</link>
      <pubDate>Thu, 26 Mar 2026 23:20:00 +0800</pubDate>
      
      <guid>/posts/liquibase-split-progressive-migration/</guid>
      <description>很多团队在做单体拆微服务时，第一反应都是先拆代码、拆接口、拆部署。但数据库层真正难的地方，往往不是“怎么建新表”，而是：
旧系统已经在线跑着，不能随便停 Liquibase changelog 早就耦合成一个大文件 业务表和外键关系跨领域交织 拆服务后，数据库迁移责任边界也要跟着重画 这次我做了一个 Liquibase Split POC，目标不是做一个“看起来很对”的设计稿，而是把一条在本地跑通并验证过的渐进式迁移路径做出来：
完整 POC 代码及运行脚本见 GitHub: meirongdev/liquibase-split
Phase 1：单体 + 单库 + 单 changelog Phase 2：还是单库，但 changelog 按服务拆开 Phase 3：每个服务拥有自己的数据库 而且这次不是只写代码，我尽量把整个实验跑完整，并记录了每一步的迁移结果。
这次 POC 要解决什么问题 如果一开始就要求把单体数据库“一刀切”拆成多个库，风险通常很高：
Liquibase 历史已经存在，不能随便改 checksum 线上数据库里已经有数据，不能直接重建 跨服务 FK 会让拆库变得很痛苦 微服务代码和数据库边界很容易出现错位 所以这个 POC 的核心想法是 progressive split：
先把迁移职责拆开 再把数据库边界拆开 最后才进入真正的独立库阶段 和“先想理想架构，再一把切过去”相比，这条路径在现有项目里通常更容易落地。
三阶段目标 Phase 数据库形态 Liquibase 形态 关键目标 Phase 1 单个 shopdb 单体主 changelog 先把完整单体 schema 跑起来 Phase 2 仍然是 shopdb 每个服务各自维护 changelog，DATABASECHANGELOG 隔离 零停机拆迁移边界 Phase 3 userdb / productdb / orderdb 每个服务各自迁移到自己的库 完成数据库层拆分 这个模型里，Phase 2 是真正的关键。</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 开启 h2c 后，真的比 HTTP/1.1 更快吗？一次完整压测实验复盘</title>
      <link>/posts/spring-boot-35-h2c-benchmark/</link>
      <pubDate>Wed, 25 Mar 2026 16:30:00 +0800</pubDate>
      
      <guid>/posts/spring-boot-35-h2c-benchmark/</guid>
      <description>如果你还没理清 h2 和 h2c 的区别，可以先看我的基础概念篇。
这一篇不再讲概念，而是回答一个更实际的问题：
Spring Boot 3.5 把 h2c 打开以后，真的会比 HTTP/1.1 更快吗？
我在自己的 Shop Platform 概念验证（POC）项目里，围绕 buyer-bff -&amp;gt; marketplace-service 做了一整轮压测。这次实验全程基于 Java 25 (LTS) 并在 Spring Boot 中开启了 虚拟线程 (Virtual Threads)。
2026-04 实践更新 当前主线仓库里，BFF 的下游调用已经进一步演进成 @HttpExchange typed clients；因此本文关于 h2c / HTTP/1.1 的性能讨论依然成立，但调用栈样例请理解为“传输层对比实验”，不再代表今天 main 分支的全部客户端装配细节。
先说这次实验里的几个观察：
不会天然更快 但在特定 workload 下，的确可能更快 关键不在“有没有开 h2c”，而在“你的请求模型有没有真的吃到多路复用”，以及虚拟线程在高并发 I/O 场景下会不会放大这件事 这篇文章就是这次实验的完整复盘。
实验背景 这个项目本身是一套基于 Spring Boot 3.5 + Java 25 的微服务电商平台 POC。BFF 层会聚合多个下游领域服务，非常适合拿来验证协议层优化。
在 Java 25 这一最新的 LTS 版本中，虚拟线程的调度已经非常成熟。我在所有服务中都开启了： spring.</description>
    </item>
    
    <item>
      <title>Spring Boot 3.5 &#43; Java 25 &#43; Cloud Native 系列（一）：Shop Platform 总览</title>
      <link>/posts/shop-platform-cloud-native-overview/</link>
      <pubDate>Sat, 21 Mar 2026 15:00:00 +0800</pubDate>
      
      <guid>/posts/shop-platform-cloud-native-overview/</guid>
      <description>为什么要做这个项目 在实际工作中，每当要推一套新的技术方向时，团队最常遇到的问题不是&amp;quot;知不知道这个技术&amp;quot;，而是——&amp;ldquo;这东西跑起来是什么样子？出了问题怎么排查？和现有的模块能不能配合？&amp;rdquo;
光读官方文档和博客往往很难回答这些问题。于是我决定做一个尽量完整跑通的 POC 项目——一个电商平台原型，把当时想亲手验证的技术选型尽量放进同一个可运行工程里，看看它们在微服务场景中能不能协同工作。
这个项目就是 Shop Platform，也是本系列文章的主角。
2026-04 实践更新 目前主线仓库已经完成一批最初文章发布时仍在演进中的平台项：auth-server → RS256 + JWKS、api-gateway → JWKS 校验 + Redisson Lua 限流、buyer-bff / seller-bff → 全量 @HttpExchange typed clients、镜像构建 → AppCDS 三阶段构建。还刻意保持延期的，主要是 DistributedLockExecutor 抽象、搜索增强、AI 推荐等下一阶段能力。
系列会围绕几个方向展开：
总览（本文）：项目背景、整体架构、技术栈速览 各层专题：API 网关、认证信任链、BFF 聚合、领域服务、事件总线、可观测、活动引擎…… 工程实践：ArchUnit 架构测试、Maven Archetype 脚手架、Feature Toggle、KMP 跨端…… 项目是什么 Shop Platform 是一个云原生微服务电商平台 POC，支持：
买家/游客浏览商品、加购物车、结账下单 卖家管理商品、订单、促销 钱包真实货币支付（Stripe 接入） 积分、优惠券、活动（签到/抽奖/集卡/虚拟养成） 搜索、Webhook 开放平台、订阅续费 项目目的是技术验证，不是生产级业务实现。功能覆盖面是为了让各个技术模块都能找到真实的应用场景，而不是为了做完整的业务产品。
整体架构 整体分为五个层次：
flowchart TD Client[&#34;客户端层Buyer Portal / Buyer App / Seller Portal&#34;</description>
    </item>
    
  </channel>
</rss>
