ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Open edX 公共课程创作 API 的混合编辑决策:ADR 0003 的否决、权衡与最终责任边界

Open edX 公共课程创作 API 的混合编辑决策:ADR 0003 的否决、权衡与最终责任边界 Open edX 公共课程创作 API 的混合编辑决策ADR 0003 的否决、权衡与最终责任边界【免费下载链接】openedx-platformThe Open edX LMS Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform本文以 cms/djangoapps/contentstore/docs/decisions/0003-hybrid-approach-for-public-apis.rst 为核心梳理 Open edX 在规划通过 OAuth 对外提供课程创作 API时围绕API 与 Studio 人工编辑并存的混合方案所做出的架构决策它如何定义两大并发冲突、否决了哪些备选方案、最终把责任边界划在哪里并结合当前仓库中的 API 实现与 modulestore 版本机制验证该决策的落地形态。读完本文你将理解该 ADR 的核心权衡逻辑并能在设计对外可写 API 时复用它明确责任、规避交通警察式复杂机制的思路。一、文档定位一份被否决的架构决策记录ADR 0003Hybrid approach for public course authoring APIs位于 cms/djangoapps/contentstore/docs/decisions/属于 contentstore 模块的架构决策记录系列。这份文档的状态栏明确标注为Rejected已否决其否决理由写道公共创作 API 的目标自本决策做出之时起已经改变我们现在只提供一组实验性 API用来探索一套受支持的 API 最终可能长什么样。因此我们现在实现的创作 API 只是现有功能的一层公开包装并不适合生产环境的课程创作。避免冲突以及在冲突发生后解决它们的责任由用户承担。这段话本身就揭示了该 ADR 在仓库中的真实价值它并非描述一个已经实现的方案而是完整记录了一次关于如何在可写 API 上做并发控制的设计推演——即使最终方向调整为实验性 API文档中对冲突类型、备选方案、责任边界的分析仍然是理解后续 API 设计基调尤其是不做复杂并发控制的第一手依据。二、上下文为什么需要混合编辑方案该决策的背景是团队计划通过 OAuth 提供公共 API让课程作者能够借助外部应用创建和更新课程内容。一个自然的诉求是不把课程限制为要么只能通过 API 编辑、要么只能手动编辑而是提供一种两者皆可的混合方案——作者既能在 Studio 界面里人工编辑也能通过外部程序批量操作课程内容。混合方案立刻面临两个典型的并发一致性问题文档对此做了精确命名问题描述典型危害过期写冲突out-of-date write conflictsAPI 用户基于一份过期的课程快照作为起点尝试更新课程可能覆盖另一位作者刚刚做出的修改脏写dirty writes存在多个并发的、异步的导入或更新操作彼此可能相互冲突相互覆盖、数据状态不一致文档同时点出了两个让并发控制变得困难的现实约束系统可靠地扮演交通警察并不简单主要原因是系统大量依赖慢速运行的异步任务async tasks——在异步执行的写入路径上做精确的冲突仲裁复杂度会显著上升。即使忽略交通警察问题接受混合编辑意味着客户端必须有一种方式获知其他用户是否已经做了修改——也就是说仅靠锁定或版本号机制还不够还需要向用户暴露变更感知能力。三、被否决的备选方案为什么不做复杂的并发控制团队分析了一系列可行的方案排除了风险过高或复杂度过高的选项最终剩下三个候选其中两个被否决原文表述为we rejected the following two3.1 方案一课程级编辑模式切换开关被否决增加一个开关让用户把课程在仅手动编辑和仅 API 编辑两种模式之间切换。这个开关需要允许正在运行的导入操作完成后再切换因此实现复杂。该方案的否决理由是模式切换需要处理运行中的导入操作尚未结束的边界情况逻辑复杂且本质上仍然要求系统承担协调职责。3.2 方案二显式版本号的乐观并发控制被否决使用一种乐观并发控制形式预期会有不同用户对每次变更显式打版本号然后禁止任何 API 操作除非 API 调用方提供的版本标识没有过期。该方案要求在服务端维护严格版本序列并对每个写请求校验版本新鲜度实现与运维成本较高。文档总结否决二者的共同原因它们实现起来复杂并且让我们去扮演交通警察play Traffic cop。值得注意的是文档为未来留下了余地然而未来对 API 的迭代可能会要求我们更严格并采取进一步的保障措施而且始终存在一种可能即回头在我们现在最初做的基础上叠加这些选项之一。也就是说这份 ADR 不是永远不做并发控制的终局承诺而是现阶段不为复杂度买单、把严格保障推迟到确有需求时的阶段决策。四、决策内容把责任边界划给用户在否决了两个复杂方案后ADR 0003 做出了最终决策。其核心思路是主动降低系统复杂度明确用户责任具体包含五条用户对冲突的避免、解决与修复负全责系统不检测、不调解、不仲裁写冲突。混合编辑课程的设计前提是同一时间只由一位用户编辑即课程既通过 Studio 编辑、也通过 API 编辑时被预期为一次只允许一位用户操作API 文档中会要求用户遵循这一原则。今天不强制、未来保留强制权当前不打算强制执行禁止并发用户这一约束把它作为用户自我约束的责任留给用户但保留未来强制执行的可能。提供历史变更日志向用户提供关于过去变更的日志以提醒他们其他用户已做出的修改。为 XBlock 及其树结构提供足够的回滚工具只要 modulestore 的版本机制使 XBlock 的旧版本可用就提供一种程序化方式按版本 ID 将 XBlock 的当前版本替换为旧版本当前目标是以撤销任务undo task操作的形式打包提供。非 XBlock 内容不提供版本化、回滚与日志静态资源、课程级策略与设置等不属于 XBlock 的内容不做版本化与回滚建议用户在自己侧使用版本控制管理。4.1 回滚能力的边界XBlock 可以静态资源不行文档对能回滚什么、不能回滚什么划得很清楚这是整份 ADR 中最具操作指导意义的部分可以回滚XBlock 内部是带版本号的internally versioned可以借助这一内部版本机制提供回滚工具并且可以扩展到对 XBlock 树结构的回滚——例如添加子树、重排子 XBlock 顺序等结构性变更。不能回滚被导入的其他文件如静态资源和部分课程级配置不具备这种版本能力。按该 API 的规划架构系统侧不支持对它们的版本化与回滚期望用户通过自行备份和版本控制来独立处理这些问题。无草稿机制API 不提供任何草稿能力变更会立即发布immediately published。五、后果分析有意识的取舍ADR 的 Consequences 章节毫不回避该决策的代价并发冲突风险自担不强制执行一次一个客户端意味着如果用户不遵循串行协作的指示就可能遇到冲突或问题。资产丢失风险转移给用户由于 API 对课程资源不提供回滚能力维护资源在外部源码控制之下的责任落在用户身上用户如果未能做到可能导致不可挽回的内容丢失或课程损坏。系统侧收益决策明确了处理问题的责任在哪一方团队无需构建复杂机制去避免或解决冲突扮演交通警察也无需在该机制不完美时去处理由此产生的任何错误。未来升级路径文档明确指出将来很可能需要增加保障措施最直接简单的动作就是真正执行单并发用户策略不允许超过一个用户同时编辑。六、仓库实证该决策在代码中的实际落地形态虽然 ADR 0003 本身被否决但其不扮演交通警察、责任交还用户、以现有功能包装成实验性 API的基调在当前仓库中可以找到清晰的对应实现。以下从三个层面验证。6.1 实验性 API 的实际载体cms/djangoapps/api/v1当前仓库中确实存在一个独立于 contentstore 内部视图的对外 API 应用 cms/djangoapps/api/v1/其 urls.py 使用 DRF 的DefaultRouter注册了course_runs路由from rest_framework.routers import DefaultRouter from .views.course_runs import CourseRunViewSet app_name cms.djangoapps.api.v1 router DefaultRouter() router.register(rcourse_runs, CourseRunViewSet, basenamecourse_run) urlpatterns router.urls其视图 cms/djangoapps/api/v1/views/course_runs.py 展示了与 ADR 基调一致的设计特征权限层面使用permissions.IsAdminUser属于管理员级别的受控访问而非面向海量外部开发者的开放写接口操作覆盖list/retrieve/update/partial_update/create以及images上传课程图片、rerun重新运行课程、clone克隆课程等动作从实现细节看update直接读取请求数据、校验序列化器并serializer.save()没有携带任何版本号校验或冲突检测逻辑——这正对应 ADR 中不强制版本标识、不处理过期写的决策方向clone接口的 docstring 明确给出请求/响应语义成功返回 HTTP 201参数非法返回 400无权限返回 401体现出对外文档化 API的形态但其能力本质上是现有内容存储功能contentstore的get_course_and_check_access、_accessible_courses_iter等的包装——与否决理由中只是现有功能的一层公开包装的描述完全吻合。6.2 modulestore 的版本机制回滚工具的前提确实存在ADR 决策第 5 条的前提是modulestore 版本机制使 XBlock 的旧版本可用。这一点在 split_mongo 存储层有直接的实现证据。在 xmodule/modulestore/split_mongo/split.py 中结构structure和定义definition都维护显式的版本溯源字段文件头注释对此有系统说明** previous_version: the structure from which this one was derived. For published courses, this ... ** original_version: the original structure id in the previous_version relation. ... ***** previous_version: the guid for the structure which previously changed this xblock *** previous_version: the definition_id of the previous version of this definition每次对结构的拷贝都会更新历史信息new_structure[previous_version] structure[_id]而在 xmodule/modulestore/init.py 中EditInfo类同样携带previous_version字段第 389、404 行并在__repr__中将其输出为EditInfo(previous_version...)。这套previous_version 链 结构 ID 版本的机制正是 ADR 设想中按版本 ID 将 XBlock 替换为旧版本得以程序化实现的基础。同时cms/djangoapps/contentstore/views/block.py 中关于块操作命令的文档字符串也提到discard_changes - reverts to the last published version说明 Studio 侧已经具备放弃修改、回退到最近发布版本的能力与 ADR 中提供足够回滚工具修复 XBlock 问题的决策目标相互印证。6.3 更多版本contentstore REST API 的多版本演进在 contentstore 内部还存在一套分版本演进的 REST API 目录 cms/djangoapps/contentstore/rest_api/包含v0、v1、v2、v3、v4多个版本如 v4 下已有home相关的序列化器、视图与测试。这从侧面印证了 ADR 否决理由中先以实验性 API 探索、未来再逐步收紧的策略——API 能力在持续迭代演进而每一次演进都无需背负早期 ADR 中设想的复杂并发机制。七、实践启示从这份被否决的 ADR 中可以学到什么否决不等于失败ADR 的价值在于沉淀决策过程。即使 0003 被否决它对冲突类型的分类过期写 vs 脏写、对交通警察复杂度的警惕、对回滚能力边界XBlock 可回滚、静态资源不可回滚的辨析都成为后续 API 设计可直接复用的分析框架。把责任边界写清楚是一种有效的架构决策在异步任务密集、写入路径慢速的系统中与其构建高成本的冲突仲裁机制不如明确单用户串行编辑 用户自备版本控制 系统提供日志与 XBlock 回滚的责任划分先保证系统可维护再考虑未来是否强制执行单并发策略。关注文档中的未来可能性表述ADR 明确保留了未来强制执行单并发用户和叠加乐观并发控制的选项。阅读此类文档时应将其视为演进路线图的早期注记而非终局设计。【免费下载链接】openedx-platformThe Open edX LMS Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表