Medusa Fulfillment 模块深度解析:从 CHANGELOG 看版本演进与核心架构
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
Medusa 2.0 将履约能力抽离为独立的@medusajs/fulfillment模块,负责配送地址、配送选项、服务区域、地理区域、履约单与第三方履约 Provider 的全生命周期管理。本文以该模块的 CHANGELOG.md 为时间主线,结合 packages/modules/fulfillment 下的源码实现,梳理从 0.1.x 到 2.20.x 的关键演进脉络,并深入讲解模块的领域模型、服务层、规则引擎与 Provider 加载机制,帮助你在阅读源码、二次开发或升级时快速定位核心概念与关键代码路径。
一、模块定位:Medusa 2.0 的履约中枢
在 Medusa 2.0 的模块化架构中,@medusajs/fulfillment承担了所有与"发货"相关的职责:
- 管理FulfillmentSet / ServiceZone / GeoZone三层地域模型,描述"在哪里能发货";
- 管理ShippingOption / ShippingOptionRule / ShippingOptionType / ShippingProfile,描述"提供哪些配送选项、受什么规则约束、属于什么配置档案";
- 管理Fulfillment / FulfillmentItem / FulfillmentLabel,描述"实际发了什么、面单与追踪号是什么";
- 通过FulfillmentProvider抽象层对接真实物流服务商,支持按
fp_<identifier>_<optionName>约定注册任意自定义 Provider。
从 CHANGELOG 可以看到,该模块自0.1.1(“Version all modules to allow for initial testing”)随 Medusa 2.0 一起被独立发布,经历了 DML(Data Model Language)重构、Mikro-ORM 6 升级、Shipping Option Type 体系从无到有,再到2.19.0支持自定义配送地址与透传附加数据,至今已演进到2.20.1。
二、版本演进主线:0.1 到 2.20 的关键节点
CHANGELOG 记录了 40 余个版本,大部分为依赖同步(Updated dependencies指向@medusajs/framework的同版本号),但其中穿插着若干具有实质业务含义的变更,它们共同勾勒出模块的成长轨迹。
2.1 0.1.x:模块独立化起点
0.1.1(PR #6700):统一对所有模块进行版本化,为后续发布到 npm 做准备。0.1.2(PR #7175):支持从配送选项(shipping option)中更新其规则(rules)。这是规则引擎能力第一次进入该模块,对应的实现在 src/utils/utils.ts 中validateAndNormalizeRules与isContextValid等函数中延续至今。
2.2 2.0.0:伴随 Medusa 2.0 的整体重构
2.0.0(PR #7341,chore: Medusa 2.0)是一个 Major Changes,标志着模块正式随 Medusa 2.0 发布,依赖同步到@medusajs/framework@2.0.0。从源码看,模块采用了标准化的Module(Modules.FULFILLMENT, ...)声明方式(见 src/index.ts),并与 framework 包强耦合(peerDependencies 为@medusajs/framework@2.20.1,见 package.json)。
2.3 2.1.x:DML 重写与"取消后可删除"
2.1.3(PR #10617):fulfillment module DML。模块数据模型改用 Medusa 的 DML(model.define(...))声明,例如 src/models/shipping-option.ts 中model.define("shipping_option", {...}),并配合.cascades({ delete: ["rules"] })声明级联删除行为。2.1.2(PR #10602):支持删除已取消的履约单(canceled fulfillment)。对应服务方法deleteFulfillment的实现会先校验canceled_at是否非空,否则抛出INVALID_DATA错误(见 src/services/fulfillment-module-service.ts)。2.0.5(PR #10138):优化了 Provider 检索失败时的错误提示信息,提示开发者检查 Provider 是否在容器中注册、是否在项目配置中正确配置——这与 src/services/fulfillment-provider.ts 中retrieveProviderRegistration对AwilixResolutionError的专门处理一致。
2.4 2.4.0:Mikro-ORM 6 升级与软删除唯一约束
2.4.0的 Minor Changes(PR #10292):升级到 Mikro-ORM 6,这是整个 Medusa 数据层的底层升级。- 同一版本的两个 Patch:修复唯一约束应计入软删除记录的问题(PR #11048);修复shipping option rules 迁移到 Mikro-ORM v6的兼容问题(PR #11109)。这说明模块的数据层迁移是随 ORM 升级逐步演进的,仓库中 src/migrations 下的 9 个迁移文件(从
Migration20240311145700_InitialSetupMigration到Migration20251114133146)正是这一过程的存档。
2.5 2.10.0 → 2.12.0:Shipping Option Type 体系成型
这是一段密集的功能演进,围绕"配送选项类型"(Shipping Option Type)展开:
2.10.0:- 将 shipping option 关联到 type(PR #13226);
- 删除 shipping option 时不再级联删除其 type(PR #13280,
don't cascade delete shipping option type); - 清理旧的自动生成 shipping type(PR #13298);
- 支持 shipping option type 的 API 端点(PR #13191);
- Dashboard 增加 shipping option type 管理界面(PR #13208)。
2.12.0(Minor,PR #14061):将 ShippingOption 与 ShippingOptionType 的关系修正为 M:1(make relationship between SO and SO type M:1)。
从当前源码可以印证这一演进的结果:ShippingOption模型通过model.belongsTo(() => ShippingOptionType, { foreignKey: true, foreignKeyName: "shipping_option_type_id", ... })声明 M:1 关系(见 src/models/shipping-option.ts),ShippingOptionType实体(label/description/code)及其到 shipping_options 的 1:N 反向关系定义在 src/schema/index.ts。
2.6 2.11.x / 2.12.x:依赖治理与工程化
2.11.0(PR #13439):将 peer dependencies 合并进单一包并从 framework 统一再导出,同时升级 Mikro-ORM 到 6.5.4(PR #13450),并修复 "Fulfillment custom schema error on provider"。2.11.3(PR #13910):依赖清理与改进。2.12.3(PR #14315):修复迁移生成器生成的 import。2.14.0(PR #14801):为 cart、order、product、inventory、fulfillment、stock-location 等模块补充缺失字段以支持类型自动生成。2.17.2(PR #15683):为包补充bugs元数据(体现在 package.json 的bugs字段)。
2.7 2.12.6:动态翻译设置管理
2.12.6(PR #14536):在 fulfillment 等多个模块中实现动态翻译设置管理。源码侧的对应物是ShippingOption.name字段声明为model.text().searchable().translatable()(见 src/models/shipping-option.ts),使配送选项名称可搜索且可被翻译系统动态管理。
2.8 2.19.0:自定义配送地址与附加数据透传
2.19.0(PR #16139)是最近一次实质性业务增强:支持自定义配送地址,并向createFulfillment传递附加数据(additional data)。源码实现清晰可见:
createFulfillment方法签名解构出order与additional_data,其余字段用于创建履约记录,随后将additional_data透传给 Provider 的createFulfillment(见 src/services/fulfillment-module-service.ts);- Schema 中
Fulfillment实体包含必填的delivery_address: FulfillmentAddress!,FulfillmentAddress定义了从company到phone的完整地址字段(见 src/schema/index.ts)。
2.9 2.13.0 至今:稳定期与依赖同步
2.13.0(Minor bump)、2.6.1(移除 Medusa 包上的版本范围,PR #11738)、2.7.0(批量事件发射,PR #12097)、2.8.7(order constraint and receive return)、2.10.2(模块内部事件,PR #13296)等版本以工程化改进和@medusajs/framework依赖同步为主。当前最新版本2.20.1仅包含框架依赖更新,模块 API 已趋于稳定。
三、源码架构纵深
3.1 模块入口与依赖注入
模块入口 src/index.ts 使用 Medusa 的标准模块声明:
import { FulfillmentModuleService } from "@services" import loadProviders from "./loaders/providers" import { Module, Modules } from "@medusajs/framework/utils" export default Module(Modules.FULFILLMENT, { service: FulfillmentModuleService, loaders: [loadProviders], })FulfillmentModuleService继承ModulesSdkUtils.MedusaService并实现IFulfillmentModuleService(见 src/services/fulfillment-module-service.ts),通过generateMethodForModels为 8 个模型(FulfillmentSet、ServiceZone、ShippingOption、GeoZone、ShippingProfile、ShippingOptionRule、ShippingOptionType、FulfillmentProvider)自动生成标准的 CRUD 方法;Fulfillment被刻意排除,只暴露模块自定义的方法(源码注释明确说明了这一点)。
构造器注入了 10 个内部服务(fulfillmentSetService_、serviceZoneService_、geoZoneService_、shippingProfileService_、shippingOptionService_、shippingOptionRuleService_、shippingOptionTypeService_、fulfillmentProviderService_、fulfillmentService_与baseRepository_),每个服务都对应一个领域模型。
3.2 领域模型全景
src/models 下共 12 个 DML 模型,src/schema/index.ts 给出了对应的 GraphQL 风格实体定义,二者共同构成模块的数据契约:
| 实体 | 职责 | 关键字段/枚举 |
|---|---|---|
FulfillmentSet | 履约集(如"国内发货") | name,type,service_zones |
ServiceZone | 服务区域 | geo_zones,shipping_options |
GeoZone | 地理区域 | type(country/province/city/zip),country_code,postal_expression |
ShippingOption | 配送选项 | price_type(calculated/flat),service_zone_id,shipping_profile_id,provider_id,shipping_option_type_id |
ShippingOptionRule | 配送规则 | attribute,operator,value |
ShippingOptionType | 配送选项类型 | label,description,code |
ShippingProfile | 配送档案 | name,type |
Fulfillment | 履约单 | location_id,provider_id,packed_at,shipped_at,delivered_at,canceled_at,delivery_address,items,labels |
FulfillmentItem | 履约条目 | title,quantity,sku,barcode,line_item_id,inventory_item_id |
FulfillmentLabel | 面单/追踪号 | tracking_number,tracking_url,label_url |
FulfillmentAddress | 配送地址 | 完整地址字段集 |
FulfillmentProvider | 履约 Provider 记录 | is_enabled |
ShippingOption的 DML 声明值得留意:price_type默认FLAT,rules与fulfillments为 1:N,删除时级联删除rules(cascades({ delete: ["rules"] })),而type是 M:1 的belongsTo(见 src/models/shipping-option.ts)。
3.3 服务层:FulfillmentModuleService
服务类约 2300 行(见 src/services/fulfillment-module-service.ts),核心方法包括:
createFulfillmentSets/updateFulfillmentSets:履约集及其服务区域、地理区域的创建与更新(更新时通过getSetDifference校验存在性,并计算待删除的 service zone / geo zone);createShippingOptions/updateShippingOptions:配送选项的创建与更新;createFulfillment/createReturnFulfillment:正向与退货履约的创建;cancelFulfillment/deleteFulfillment:履约取消与删除;validateFulfillmentData/calculateShippingOptionsPrices:履约数据校验与配送价格计算(委托给 Provider);listShippingOptions/listShippingOptionsForContext:配送选项查询与上下文过滤。
方法大量使用@InjectManager()、@InjectTransactionManager()与@MedusaContext()装饰器管理事务边界,并用@EmitEvents()在写操作后发出领域事件。
3.4 规则引擎:上下文驱动的配送过滤
src/utils/utils.ts 内置了一套规则引擎(源码注释说明它未来可能迁移到 utils 包供更多模块复用):
- 支持的运算符:
in、nin、eq、ne、gt、gte、lt、lte,其中比较类运算符对日期字符串(Date.parse可解析)做日期比较,否则做数值比较; validateRule:校验规则必须包含attribute、operator、value三个字段,校验运算符合法性,并约束in/nin的 value 必须是数组、其余运算符的 value 不能是数组或对象;normalizeRulesValue:将布尔型 value 归一化为字符串"true"/"false";isContextValid:根据上下文对象与规则集合判定是否命中,默认要求全部规则满足(every),可通过someAreValid: true切换为任一命中(some)。
listShippingOptionsForContext正是用这套引擎对候选配送选项做过滤:无规则的选项直接通过,有规则的选项需要isContextValid(context, rules)为真(见 src/services/fulfillment-module-service.ts 附近)。
3.5 Provider 加载与数据库同步
src/loaders/providers.ts 是模块的 loader,负责:
- 通过
moduleProviderLoader加载配置文件中声明的providers,每个 Provider 以fp_<identifier>_<optionName>为 key 注册进 awilix 容器(lifetime 取自klass.LIFE_TIME,默认SINGLETON); - 调用
syncDatabaseProviders将已注册的 Provider 标识符与数据库中的FulfillmentProvider记录对齐:新增的入库、已存在的启用(is_enabled: true)、未再注册的禁用(is_enabled: false)。
FulfillmentProviderService.getRegistrationIdentifier要求 Provider 类必须声明静态identifier,否则抛出INVALID_ARGUMENT错误(见 src/services/fulfillment-provider.ts)。集成测试的夹具 integration-tests/fixtures/providers/default-provider.ts 展示了 Provider 接口的实现形态。
3.6 Joiner 配置与远程查询
src/joiner-config.ts 通过defineJoinerConfig(Modules.FULFILLMENT, {...})声明模块的链接键(linkable keys):fulfillment_id、fulfillment_set_id、shipping_option_id、shipping_option_rule_id、fulfillment_provider_id,并结合 src/schema/index.ts 的远程查询 schema,使其他模块(如 order、cart)能够跨模块关联查询履约数据。
四、核心流程与关键方法
4.1 createFulfillment:创建履约
createFulfillment的流程(见 src/services/fulfillment-module-service.ts):
- 解构入参,分离出
order与additional_data(2.19.0新增能力); - 先用
fulfillmentService_.create落库履约记录; - 调用
fulfillmentProviderService_.createFulfillment(provider_id, data, items, order, rest, additional_data)让真实物流 Provider 处理发货; - 用 Provider 返回的
data与labels更新履约记录; - 若 Provider 调用抛错,则删除已创建的履约记录并重新抛出,保证数据一致性(补偿回滚)。
createReturnFulfillment走类似流程,但额外检索shipping_option并传给 Provider 的createReturn。
4.2 deleteFulfillment:取消后才能删除
deleteFulfillment(见 src/services/fulfillment-module-service.ts)首先检索履约记录,若canceled_at为空则抛出:
Fulfillment with id ${id} needs to be canceled first before deleting这正是2.1.2版本引入的语义:只有已取消的履约单才允许物理删除,避免误删进行中的发货记录。
4.3 上下文感知的配送选项过滤
listShippingOptions在收到context或address过滤条件时会转入listShippingOptionsForContext:先按普通过滤条件查询候选选项,再对带规则的选项逐条执行isContextValid上下文判定(规则判定逻辑位于 src/utils/utils.ts),最终返回命中的配送选项。价格计算则通过calculateShippingOptionsPrices委托给对应 Provider 完成。
五、测试、迁移与本地开发
- 集成测试:integration-tests/tests/fulfillment-module-service 下共 7 个 spec,覆盖
fulfillment-set、fulfillment、geo-zone、index、service-zone、shipping-option、shipping-profile的服务行为;测试夹具位于 integration-tests/fixtures,其中providers/default-provider.ts是自定义 Provider 的实现范例。 - 迁移管理:模块自带 mikro-orm.config.dev.ts,并通过 package.json 提供
migration:initial、migration:create、migration:up脚本,底层使用medusa-mikro-ormCLI 管理 src/migrations 下的迁移文件。 - 运行环境:模块要求 Node.js
>=20(见 package.json 的engines字段),并以@medusajs/framework@2.20.1为 peer 依赖。
六、总结
通过 CHANGELOG 与源码的对照阅读可以看到,Medusa Fulfillment 模块从 0.1.x 的"独立模块化尝试",经历了 DML 化、Mikro-ORM 6 升级、Shipping Option Type 体系构建、动态翻译与自定义配送地址等能力增强,最终形成一套以 12 个 DML 模型为数据底座、以FulfillmentModuleService为门面、以内置规则引擎与 Provider 抽象层为核心能力的成熟履约子系统。对于开发者而言:
- 想扩展物流能力,从实现
IFulfillmentProvider并声明静态identifier入手,参考 integration-tests/fixtures/providers/default-provider.ts; - 想调整配送选项的可用性逻辑,关注 src/utils/utils.ts 的规则引擎与
listShippingOptionsForContext; - 想理解模块间如何联动查询,阅读 src/joiner-config.ts 与 src/schema/index.ts。
后续升级版本时,建议同步关注 CHANGELOG.md 中带有 PR 描述的条目——它们几乎总是对应着源码中真实可查的实现变更。
【免费下载链接】medusaThe world's most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考