Medusa Notification 模块演进解析:从 v2.0 到 v2.20 的核心能力与实现原理
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
Medusa 的 Notification(通知)模块负责将订单、客户、库存等业务事件以邮件、短信等渠道推送给用户,是@medusajs/notification包的核心载体。本文以该包 CHANGELOG.md 的版本演进为主线,结合packages/modules/notification目录下的模型、服务、加载器与内置提供者源码,梳理模块在 Medusa 2.0 大版本下的数据模型变化、提供者(Provider)插件体系、幂等发送流程以及底层基础设施升级,帮助你理解该模块的架构设计,并掌握在 medusa-config 中配置通知提供者的方法。
模块定位与包结构
在 Medusa 2.x 中,Notification 是一个独立模块(Module),通过Module(Modules.NOTIFICATION, ...)在 src/index.ts 中声明,由NotificationModuleService提供服务、loadProviders负责加载配置中的提供者。包结构清晰分层:
src/models/:notification与notification-provider两个 MikroORM 实体;src/services/:模块主服务与提供者内部服务;src/loaders/:提供者注册与数据库同步逻辑;src/providers/:内置的 Medusa Cloud Email 提供者;integration-tests/:模块服务的单元与集成测试。
从 package.json 可见,当前版本为2.20.1,要求 Node.js>=20,并以@medusajs/framework(同为2.20.1)作为 peer dependency,这也解释了 CHANGELOG 中大量Updated dependencies条目——模块的功能始终紧随框架同步演进。
数据模型演进:notification 实体的字段补全
CHANGELOG 中最具业务价值的变化集中在 v2.12.0(对应 src/models/notification.ts):
- PR #14104:为 notification 模型新增
provider_data字段; - PR #14102:为 notification 模型新增
from字段。
这两个字段的加入补齐了通知场景的关键诉求:
from:model.text().searchable().nullable(),标识发送方(可以是邮箱、电话号码或用户名,取决于渠道),解决"以谁的名义发送"的问题;provider_data:model.json().nullable(),存放渠道或提供者特有的附加数据,例如邮件中的cc/bcc列表。
结合 medusa-cloud-email.ts 的send实现可见,from、provider_data与to、template、data、attachments、content一起被完整透传给提供者端点,是发送请求体的一部分。
完整的 notification 实体还包含以下关键字段(均在 src/models/notification.ts 中定义):
| 字段 | 类型 | 说明 |
|---|---|---|
id | model.id({ prefix: "noti" }) | 主键,前缀noti |
to | text,searchable | 接收方,依渠道而定 |
from | text,searchable,nullable | 发送方(v2.12.0 新增) |
channel | text | 渠道,如email |
template | text,nullable | 提供者系统中的模板名,v2.11.2 起允许为空 |
data | json,nullable | 传给提供者渲染通知的数据 |
provider_data | json,nullable | 渠道/提供者附加数据(v2.12.0 新增) |
trigger_type | text,nullable | 触发来源(事件名、工作流等) |
resource_id | text,searchable,nullable | 关联资源 ID,便于 UI 展示 |
resource_type | text,nullable | 关联资源类型,如order |
receiver_id | text,index,nullable | 接收者 ID(客户、用户等) |
original_notification_id | text,nullable | 重试时指向原始通知 |
idempotency_key | text,unique,nullable | 幂等键,防重发 |
external_id | text,nullable | 外部系统中的通知 ID |
status | enum,默认pending | 发送状态 |
其中status枚举定义在 packages/core/utils/src/notification/common.ts:pending、success、failure三态,是幂等重试逻辑的判断基础。实体级注释也点明设计意图:每个条目应当有 TTL,以避免数据库膨胀并满足 GDPR 的留存要求。
此外,CHANGELOG v2.11.2 中"Make template nullable on emails"(PR #13889)将template字段改为可空,允许不依赖外部模板系统、直接以content发送纯内容型通知,与 Cloud Email 提供者的content参数设计相互印证。
提供者(Provider)插件体系与 Medusa Cloud Email
CHANGELOG v2.11.2 中 PR #13781 新增了 Medusa Cloud Email 提供者,这是模块内置的唯一默认提供者,实现在 src/providers/medusa-cloud-email.ts。它继承AbstractNotificationProviderService,在send中向{endpoint}/send发起fetchPOST 请求:
- 请求头携带
Authorization: Basic <api_key>; - 配置了
sandbox_handle时附加x-medusa-sandbox-handle头(对应 v2.11.2 中"Inject sandbox handle in cloud config"的变更); - 配置了
environment_handle时附加x-medusa-environment-handle头; - 请求体透传
to、from、attachments、template、data、provider_data、content,返回外部通知 ID。
自动注册逻辑
src/loaders/providers.ts 展示了提供者的加载流程:
- 检查配置中是否已有覆盖
email渠道的提供者; - 若没有,且
cloud配置通过validateCloudOptions校验(要求提供api_key与endpoint,且environment_handle或sandbox_handle至少其一),则自动注册cloud提供者并追加到 providers 列表; - 调用
moduleProviderLoader注册所有自定义提供者; - 通过
syncDatabaseProviders将 providers 同步进notification_provider表。
提供者配置与校验
模块选项类型NotificationModuleOptions定义在 src/types/index.ts,支持:
providers[]:每项包含resolve(模块提供者导出或路径)、id(唯一标识)、options(传给提供者构造器的键值对,channels数组声明支持的渠道);cloud:Medusa Cloud Email 选项(api_key、endpoint、environment_handle?、sandbox_handle?)。
syncDatabaseProviders的要点:
- 每个 provider 必须提供
id,否则抛错; - 通过
validateProviders校验同一渠道不能配置多个提供者(重复配置同一channel会直接抛错); - 数据库中已存在但配置中不再出现的提供者会被自动禁用(
is_enabled = false),而非删除。
对应的notification_provider实体(src/models/notification-provider.ts)字段包括handle、name、is_enabled(默认 true)、channels(数组)以及与通知的一对多关系。
提供者内存缓存与渠道路由
src/services/notification-provider.ts 是提供者的内部服务:
getProviderForChannels首次调用时按is_enabled: true查询数据库,构建"渠道 → 提供者"的内存 Map(因为提供者只在启动时注册、运行期不变,可安全缓存);send通过retrieveProviderRegistration从 Awilix 容器中按np_<providerId>前缀(NotificationProviderRegistrationPrefix)解析提供者实例,调用其send,解析失败时会给出"请检查项目配置文件"的明确错误提示。
核心发送流程:幂等、去重与状态回写
模块主服务 src/services/notification-module-service.ts 的createNotifications是发送入口,其内部createNotifications_展示了完整的工程化设计:
- 幂等键查重:收集所有
idempotency_key,在同一事务内查询已存在记录,构建existsMap; - 过滤待处理项:只有"无幂等键"或"已存在但状态为
failure"的通知才会进入发送队列——失败重试不会产生重复记录; - 渠道路由:按
channel调用getProviderForChannels匹配提供者,生成noti前缀的 ID 并写入provider_id; - 事务内预创建:先行
create通知记录,源码注释明确说明这是"为防止并发操作列出同一批通知"; - 并发发送:
promiseAll并行发送,发送成功则回写external_id与status = success;提供者缺失、未启用或发送异常时置status = failure并抛出对应MedusaError(NOT_FOUND或UNEXPECTED_STATE); - finally 统一回写:无论成败都
update状态,并按原顺序重组结果返回。
发送失败的典型错误信息(源码中可复现):
Could not find a notification provider for channel: <channel> for notification id <id>——未配置对应渠道的提供者;Notification provider <id> is not enabled. To enable it, configure it as a provider in the notification module options.——提供者被禁用。
这一流程与 v2.10.2 中"Module Internal Events"(PR #13296)带来的@EmitEvents()装饰器配合,可在发送前后向事件总线广播模块内部事件,供订阅者扩展。
版本演进时间线与工程化治理
除上述业务功能外,CHANGELOG 还记录了模块在工程化层面的演进,可按主题归纳如下:
| 版本 | 变更类型 | 核心内容 |
|---|---|---|
| 2.0.0 | Major | 随 Medusa 2.0 发布,模块化架构落地 |
| 2.4.0 | Minor/Patch | 升级至 MikroORM 6;修复唯一约束需考虑软删除记录的问题 |
| 2.5.0 | Patch | AbstractModuleService的 create 方法类型安全化 |
| 2.6.1 | Patch | 移除 Medusa 各包上的版本范围限制 |
| 2.10.2 | Patch | 支持模块内部事件 |
| 2.11.2 | Patch | 新增 Medusa Cloud Email 提供者;邮件模板可空;云配置注入 sandbox handle |
| 2.11.3 | Patch | 依赖清理与改进 |
| 2.12.0 | Patch | 模型新增provider_data与from字段 |
| 2.12.5 | Patch | medusa 配置支持模块选项自动补全 |
| 2.13.0 | Minor | 常规 minor 版本升级 |
| 2.17.2 | Patch | 补充包 bugs 元数据 |
| 2.20.x | Patch | 跟随@medusajs/framework同步更新 |
几个值得注意的底层变化:
- MikroORM 6 升级(v2.4.0,PR #10292):模块的实体定义、迁移文件与
mikro-orm.config.dev.ts均基于新版本构建,migrations/目录下的多个迁移文件(Migration20240509083918_InitialSetupMigration等)记录了表结构随版本演进的轨迹; - 唯一约束与软删除(v2.4.0,PR #11048):修复唯一约束需将软删除记录纳入考虑,保证
idempotency_key等唯一字段在软删除场景下不冲突; - 模块配置自动补全(v2.12.5,PR #14465):
NotificationModuleOptions通过模块声明合并进入@medusajs/types的ModuleOptions接口(见 src/types/index.ts),使 medusa-config 中编写@medusajs/notification选项时获得 IDE 类型提示; - 依赖治理(v2.11.3、v2.6.1):peer 依赖收敛到
@medusajs/framework单一包并重新导出(v2.11.0,PR #13439),同时移除包间版本范围,降低依赖解析复杂度。
典型配置示例与验证路径
在 medusa-config 中配置 Notification 模块的方式如下(字段均对应NotificationModuleOptions类型):
module.exports = defineConfig({ modules: [ { resolve: "@medusajs/notification", options: { providers: [ { resolve: "@medusajs/notification-sendgrid", id: "sendgrid", options: { channels: ["email"], // 提供者所需的其他选项 api_key: process.env.SENDGRID_API_KEY, from: process.env.SENDFROM_EMAIL, }, }, ], // 或使用内置的 Medusa Cloud Email: // cloud: { // api_key: process.env.MEDUSA_CLOUD_API_KEY, // endpoint: "https://notification.medusajs.com", // environment_handle: process.env.MEDUSA_ENV_HANDLE, // sandbox_handle: process.env.MEDUSA_SANDBOX_HANDLE, // }, }, }, ], })需要注意的约束:
- 同一
channel只能配置一个提供者,重复配置会在启动时抛错; - 若配置了覆盖
email渠道的自定义提供者,Medusa Cloud Email 不会自动注册; - 未配置任何提供者却发起发送,通知会以
failure状态落库并抛出NOT_FOUND错误。
模块的集成测试集中在 integration-tests/tests/notification-module-service/(含默认提供者与 Medusa Cloud Email 两条测试线),通过yarn test:integration运行(见 package.json 的 scripts),可用于验证上述发送流程与幂等行为。
结语
从 v2.0 到 v2.20,@medusajs/notification的 CHANGELOG 记录了一条清晰的演进路径:数据模型层面补全了from与provider_data,提供者层面引入了内置的 Medusa Cloud Email 并建立"渠道唯一"的注册与校验机制,底层则随框架完成 MikroORM 6 升级、内部事件与类型安全化改造。理解这些版本变更背后的源码实现,可以帮助你在自建提供者、配置多渠道通知或排查发送失败时快速定位问题所在。
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考