为什么要做这个项目#

在实际工作中,每当要推一套新的技术方向时,团队最常遇到的问题不在"知不知道这个技术",而在下面这几个:“这东西跑起来是什么样子?出了问题怎么排查?和现有的模块能不能配合?”

光读官方文档和博客往往很难回答这些问题。于是我决定做一个尽量完整跑通的 POC 项目:一个电商平台原型,把当时想亲手验证的技术选型尽量放进同一个可运行工程里,看看它们在微服务场景中能不能协同工作。

这个项目就是 Shop Platform,也是本系列文章的主角。

2026-04 实践更新 目前主线仓库已经完成一批最初文章发布时仍在演进中的平台项:auth-serverRS256 + JWKSapi-gatewayJWKS 校验 + 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["客户端层
Buyer Portal / Buyer App / Seller Portal"] Gateway["边缘层: API Gateway
JWT 校验 · Trusted Headers · Redis Lua 限流 · Canary 灰度"] BFF["聚合层: Buyer BFF ↔ Seller BFF
Virtual Threads + Resilience4j"] Domain["领域服务层
profile · marketplace · order · wallet
promotion · loyalty · activity · search
notification · webhook · subscription"] Infra["基础设施层
MySQL 8.4 · Kafka 3.9 · Redis 7.4 · Meilisearch 1.12 · Garage S3"] Obs["可观测层
Micrometer · OTel Collector · Tempo · Loki
Grafana · Prometheus · Pyroscope"] Client -->|"HTTPS"| Gateway Gateway -->|"Trusted Headers"| BFF BFF --> Domain Domain --> Infra Domain -.->|"metrics · traces · logs"| Obs BFF -.->|"metrics · traces · logs"| Obs Gateway -.->|"metrics · traces · logs"| Obs

流量路由#

路径前缀 目标
/buyer/** buyer-portal(Kotlin SSR,SEO/游客模式)
/buyer-app/** buyer-app(KMP WASM SPA,交互式购物体验)
/seller/** seller-portal(部署单元名;内容来自 frontend/kmp/seller-app
/api/buyer/** buyer-bff(聚合领域服务)
/api/seller/** seller-bff(聚合领域服务)
/api/loyalty/** loyalty-service
/api/activity/** activity-service
/api/webhook/** webhook-service
/api/subscription/** subscription-service
/auth/** auth-server
/public/** 白名单,无需鉴权

上面的架构图只画了 Client → Gateway → BFF → 领域服务这条主链路。从路由表可以看到,/api/loyalty/**/api/activity/**/api/webhook/**/api/subscription/** 这几类流量由 Gateway 直接转发到对应领域服务,不经过 BFF。

Gateway 完成 JWT 校验后,把身份信息(Buyer-Id、Username、Roles、Portal)以 Trusted Headers 形式注入请求,下游服务直接信任这些 header,不需要重复解析 token。

前端架构演进#

项目采用渐进式前端策略,根据不同场景选择最适合的技术栈:

flowchart TD BP["Buyer Portal
Kotlin + Thymeleaf SSR"] BA["Buyer App
KMP WASM SPA"] SP["Seller Portal
KMP WASM via seller-portal"] BP --> L["/buyer/login 登录页"] BP --> R["/buyer/register 注册页"] BP --> C["/buyer/catalog 商品浏览 SEO"] BA --> D["/buyer-app/ 购物车 · 结账 · 订单追踪"] SP --> E["/seller/ 商品 · 订单 · 促销管理"]

KMP(Kotlin Multiplatform)模块采用功能模块化设计:

KMP 模块 职责
core 网络层、状态管理、路由、主题系统
feature-auth 认证流程(JWT、Google/Apple/OTP 登录)
feature-cart 购物车管理(游客/登录态统一)
feature-marketplace 商品浏览、搜索、筛选
feature-order 订单创建、追踪、历史
feature-profile 用户档案、地址簿
feature-promotion 优惠券、活动展示
feature-wallet 钱包余额、充值、Stripe 支付
ui-shared 通用 UI 组件库
buyer-app 买家应用组装(WASM)
seller-app 卖家应用组装(WASM)

这种拆分让同一套 feature-* 模块可以复用到 WASM Web、Android 和 iOS 客户端;对一个技术验证型 POC 来说,这种组织方式比为每个端单独复制业务逻辑更容易观察演进成本。

服务清单#

服务 语言 职责
api-gateway Java 25 统一入口、JWT 校验、限流、灰度
auth-server Java 25 JWT 签发、Google/Apple/OTP 登录、游客 token
buyer-bff Java 25 买家聚合、游客购物流、结账编排
seller-bff Java 25 卖家工作台聚合
buyer-portal Kotlin 买家 SSR 门户(Thymeleaf)
buyer-app Kotlin Multiplatform 买家 WASM 应用(frontend/kmp/buyer-app
seller-portal Kotlin Multiplatform 卖家门户静态发布单元(构建自 frontend/kmp/seller-app
profile-service Java 25 用户档案、地址簿
marketplace-service Java 25 商品目录、SKU、库存、评价
order-service Java 25 订单状态机、购物车(登录态)
wallet-service Java 25 钱包余额、充值/提现、Stripe 接入
promotion-service Java 25 促销引擎、优惠券(策略模式可扩展)
loyalty-service Java 25 积分账户、签到、积分兑换
activity-service Java 25 互动活动中心(插件化游戏引擎)
search-service Java 25 Meilisearch 全文搜索、Feature Toggle 试点
notification-service Java 25 邮件通知、Channel SPI 可扩展
webhook-service Java 25 Webhook 订阅、HMAC 签名、失败重试
subscription-service Java 25 订阅计划、自动续费

技术栈#

技术栈按三层列:跑业务代码的语言与框架、把它托起来的基础设施、在旁边看着它的可观测组件。

语言与框架基线#

维度 版本
JDK Java 25
Spring Boot 3.5.11
Spring Cloud 2025.0.1
Kotlin 2.3.20
构建工具 Maven + Maven Wrapper

父 POM 统一管理所有版本,通过 Maven Enforcer 强制最低 JDK 版本,各子模块继承父 POM 而不自行指定 Spring 版本。这让版本组合更容易复现和比较;至于是否直接作为生产基线,仍要结合团队支持策略与依赖兼容性评估。

基础设施#

维度 技术选择
数据库 MySQL 8.4(每服务独立 schema + Flyway 迁移)
消息队列 Apache Kafka 3.9(KRaft 模式,无需 ZooKeeper)
缓存 Redis 7.4 + Redisson(分布式锁、Lua 脚本)
搜索 Meilisearch 1.12
对象存储 Garage S3(兼容接口,存 Trace/Log 数据和商品图片)
支付 Stripe SDK(通过 Gateway 接口抽象,可插拔替换)

可观测栈#

flowchart TD App["应用层
Micrometer + OTLP Logback + shop.profiling"] OTel["采集层: OpenTelemetry Collector
Trace + Log + K8s 元数据富化"] Tempo["Tempo
Trace"] Loki["Loki
Log (OTLP)"] Grafana["展示层: Grafana"] Prometheus["指标: Prometheus
Exemplar (Trace ↔ Metric 双向关联)"] Pyroscope["持续分析: Pyroscope
CPU/内存火焰图"] Alert["告警: Prometheus Alert Rules
+ SLO Burn-rate Rules"] App -->|"OTLP"| OTel OTel --> Tempo OTel --> Loki Tempo --> Grafana Loki --> Grafana Prometheus --> Grafana App -->|"scrape"| Prometheus Prometheus --> Exemplar["Exemplar 关联 Trace"] Tempo -.->|"关联"| Pyroscope Loki -.->|"关联"| Pyroscope Prometheus --> Alert

所有 Java 服务通过 shop-commonObservabilityAutoConfiguration 自动装配,当前基线里就具备 metrics、traces、structured logs 和 profiling 接入点,不需要每个服务单独重复配置。

几个值得关注的设计决策#

这五个决策各自独立,可以挑着看。前两个是拿掉了一个本来要引进来的东西(WebFlux、分布式事务),后三个是怎么在多个服务之间共享扩展点和代码。

Virtual Threads 替代 WebFlux#

Gateway 和所有 BFF/Domain 服务都启用了 Virtual Threads,而没有选 WebFlux。

原因:Virtual Threads 让 I/O 密集型微服务获得可接受的并发能力,同时保留了同步代码的可读性和可调试性。对于聚合多个下游调用的 BFF 来说,实际写法是把每个下游调用显式提交到 virtual thread executor(buyer-bff BuyerAggregationService#loadDashboard 节选,省略异常处理):

ContextSnapshot snapshot = ContextSnapshot.captureAll();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    var profileFuture = executor.submit(snapshot.wrap(() -> getProfile(buyerId)));
    var walletFuture = executor.submit(snapshot.wrap(() -> getWallet(buyerId)));
    var promotionsFuture = executor.submit(snapshot.wrap(() -> listPromotions()));
    var marketplaceFuture = executor.submit(snapshot.wrap(() -> listMarketplace()));
    var loyaltyFuture = executor.submit(snapshot.wrap(() -> getLoyaltyAccount(buyerId)));
    return new BuyerApi.DashboardResponse(
            profileFuture.get(),
            walletFuture.get(),
            promotionsFuture.get(),
            marketplaceFuture.get(),
            loyaltyFuture.get());
}

五路下游并发,代码保持阻塞式写法;ContextSnapshot 负责把 trace 上下文带进子线程。如果用 WebFlux,同样的逻辑会变成 Mono.zip(...) 链条,调试和错误追踪的成本都会上升。

Outbox Pattern 替代分布式事务#

跨服务传播的业务事实(如下单扣库存、充值通知积分)全部走:

sequenceDiagram participant Biz as 业务代码 participant DB as 数据库 participant Outbox as outbox 表 participant Pub as Publisher participant Kafka as Kafka participant Consumer as 消费端 Biz->>DB: 1. 写入业务数据 Biz->>Outbox: 2. 同事务写入 outbox 表 Note over Biz,Outbox: 同一本地事务,原子提交 Pub->>Outbox: 3. 定时轮询 PENDING 记录 Pub->>Kafka: 4. 发送到 Kafka 话题 Pub->>Outbox: 5. 标记 PUBLISHED Kafka->>Consumer: 6. 消费端幂等落地

这套结构保证的是「业务数据和 outbox 记录在同一个本地事务里原子落库」,并且不依赖分布式事务协调器。至于事件从 outbox 表到 Kafka 这一段,可靠性取决于 publisher 对发送结果的处理:当前 order / wallet 的实现是异步 send() 后立即标记 PUBLISHED,发送失败时这条事件会带着已发布标记丢掉。各服务变体的差异和取舍在事件驱动一篇里单独展开。

Strategy + Plugin 保持扩展性#

两个场景用了类似的思路:

促销引擎BenefitCalculatorConditionEvaluator 都是 SPI 接口,新增一种折扣规则只需实现接口,PromotionEngine 通过 Spring 自动发现。

活动游戏引擎GamePlugin 接口 + GamePluginRegistry,目前已有砸金蛋/抽奖、抢红包、集卡、虚拟养成四种插件。新增游戏类型至少不需要改引擎核心代码;是否需要重启或额外部署,仍取决于最终交付方式。每个插件有自己的扩展表前缀,Redis key 命名空间隔离。

shop-contracts 集中管理#

API path 常量、跨服务共享的 DTO、事件 envelope 全部放在 shop-contracts 模块。BFF、Portal、Worker 都从同一个地方引用,消除字符串拼接和 DTO 复制。

shop-common 的共享能力#

  • ApiResponse<T>:统一 northbound 响应模型
  • BusinessException + CommonErrorCode:统一错误语义(SC_ 前缀错误码)
  • HeaderPropagationInterceptor:在 RestClient 链路中继续透传 Gateway 注入的可信 Header
  • OTLP logback appender + 结构化日志自动配置

云原生工程实践#

这四项没有先后,分别管部署形态、发布节奏、架构约束和新服务的起点。

Kubernetes 优先#

platform/k8s/ 目录包含完整的 K8s manifest:应用部署、基础设施(MySQL/Redis/Kafka)、可观测组件(Prometheus/Tempo/Loki/Grafana/Pyroscope)。本地用 Kind 集群运行,流程尽量贴近集群环境。

每个服务遵循统一端口规范(应用 8080,Actuator/Management 8081),K8s 探针、Prometheus ServiceMonitor、资源请求/限制在当前仓库里也都已经给出了对应配置。

Feature Toggle#

search-service 是试点场景:

OpenFeature SDK (供应商无关 API)
    → 仓库内 property provider
    → ConfigMap 目录挂载
    → Configuration Watcher + /actuator/refresh(热更新)

不依赖第三方控制面,用 K8s 原生能力就能完成动态配置下发。

ArchUnit 架构测试#

architecture-tests 模块通过 ArchUnit 验证代码约定:

  • Controller 不能直接调用 Repository
  • BFF 不能持有 JPA Entity,需要通过 HTTP 聚合领域服务
  • 不允许使用 RestTemplate 或手动创建传统线程池
  • Kafka 消费者需要实现幂等处理

架构约束变成可自动执行的测试用例,而不是靠 Code Review 口头约定。

Maven Archetype 脚手架#

shop-archetypes 提供 6 个模板:

Archetype 适用场景
gateway-service-archetype Spring Cloud Gateway 服务
auth-service-archetype OAuth2 资源服务器 + JWT
bff-service-archetype BFF 聚合层
domain-service-archetype JPA + Flyway + MySQL 领域服务
event-worker-archetype Kafka 消费者 + Outbox worker
portal-service-archetype Kotlin + Thymeleaf SSR 门户

新建服务时从 archetype 出发,开箱就有统一的目录结构、依赖、可观测配置和测试基类,不需要从旧模块复制再手工删改。

参考与实现位置#

小结#

这个项目的初衷是为真实项目的技术选型提供一个可运行的参照系。整理下来,我更愿意拿这几点当讨论起点:

  • 版本组合:Java 25 + Spring Boot 3.5 + Spring Cloud 2025.0.1 在这套 POC 里已经跑通;是否直接进生产,还要结合团队对支持周期、依赖兼容性和运维基线的判断
  • Virtual Threads 而非 WebFlux:I/O 密集型微服务用同步代码模型拿到了可接受的并发;没有明确 Reactive/Streaming 诉求的新项目可以先从这里起步,再按压测结果决定要不要换
  • Outbox 而非分布式事务:order、wallet、marketplace、activity 里都有具体实现,适合拿来讨论一致性的边界和取舍
  • SPI 驱动扩展:促销引擎和活动游戏引擎的 Strategy/Plugin 设计,是业务规则多变场景下控制主线代码膨胀的一个样例
  • 可观测栈:OTLP + Prometheus + Tempo + Loki + Pyroscope 验证过一轮基础接线和排障路径,实际项目可按需裁剪

下一篇会深入 API Gateway 模块:Spring Cloud Gateway Server MVC 的路由配置、JWT 校验过滤器、Redis Lua 限流、Trusted Headers 注入,以及从 WebFlux 迁移到 MVC + Virtual Threads 的背景和得失。

项目仓库:github.com/meirongdev/shop