ARTICLE DETAIL

资讯详情

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

Open edX Learning Sequences:与 ModuleStore 解耦的课程大纲数据服务解析

Open edX Learning Sequences:与 ModuleStore 解耦的课程大纲数据服务解析 Open edX Learning Sequences与 ModuleStore 解耦的课程大纲数据服务解析【免费下载链接】openedx-platformThe Open edX LMS Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform导读learning_sequences是 Open edX 平台中一个旨在构建ModuleStore 无关ModuleStore-independent学习序列Learning Sequence即 Studio 中的 subsection数据层的 Django 应用其首个落地的 API 就是计算Course Outline课程大纲。本文以 learning_sequences/README.rst 为骨架结合仓库内源码、模型、管理命令与测试系统讲解该应用的设计动机、数据流入链路、公共 API、个性化大纲机制以及三类典型扩展方式帮助你理解 Open edX 如何逐步把课程结构与导航的职责从 XBlock 运行时中剥离出来。一、Learning Sequences 是什么Learning Sequences 包的核心使命是创建一种不依赖 ModuleStore 的学习序列表示并描述这些序列如何组装成课程。在 Studio 中学习序列对应的概念就是 subsection小节在课程层面若干小节组成 Section章节若干章节组成一门课的大纲。该应用的主要服务对象是 LMS 的最终用户——它负责向浏览器端交付课程大纲元数据同时它也向 Studio 开放写入能力用于在发布课程时把大纲数据推进系统。其实现的第一个 API 就是 Course Outline 的计算。一条硬性约束不直接依赖 ModuleStore原文档用important提示块强调了一个架构红线This package shouldnotdepend on the modulestore directly.这条约束在 models.py 的模块级 docstring 中体现得更加具体公共 API 承诺的内容应能通过这些模型高效查询偶尔可以触达其他为快速课程级查询而构建的系统如 grading、scheduling但绝不能触碰 ModuleStore 或 Block Transformers模型层允许做基础校验如唯一性约束但真正的业务逻辑必须放在api包内尽量少用 JSON 字段这类blob 实体把数据推进规范化的关系表模型保持薄而笨的持久化层定位缓存策略由api包决定。也就是说这是一个发布时一次性写入、运行时快速读取的架构昂贵的课程结构遍历只发生在发布那一刻运行时通过细粒度的数据库行完成毫秒级查询。二、动机为什么在 ModuleStore 与 Block Transformers 之外再造一层README 开篇就替读者提出了质疑我们已经有了 ModuleStore 和 Block Transformers为什么还要再做一个访问课程结构数据的途径这不是会像移动端 API 那样因为访问规则需要重新实现而引入 bug 吗原文档给出了三条理由支撑更动态的课程体验平台正把课程结构与导航的职责从 ModuleStore/XBlock 中迁移出来。继续保持 OLX 兼容性但未来 LMS 的 XBlock 运行时只会在 Unit 层级及以下被调用——这意味着课程骨架章节/小节的呈现不再依赖 XBlock 渲染链路。Block Transformers 缺乏细粒度模型Block Transformers 一次性处理整门课程缺少为单个序列做快速元数据查询所需的粒度化数据库模型。复杂性与性能权衡Block Transformers 功能强大但复杂且慢。为了本用例去优化它们成本高昂且会引入更多复杂度而序列与大纲相关的元数据量小得多可以做出简化假设——把大纲视为树而非 DAG即每个序列只属于一个章节从而大幅降低建模与查询难度。这一动机还可以从数据结构的实现中得到印证data.py 中CourseOutlineData.__attrs_post_init__会在初始化时校验同一个序列出现在多个章节的情况并直接抛出ValueError这正是大纲是树而非 DAG这一假设在代码层面的落地。三、数据如何流入发布信号与管理命令双通道README 明确指出把课程大纲数据喂入 Learning Sequence 模型的主路径是Studio 课程发布时触发的信号处理器signal handler此外也可以通过update_course_outline管理命令手动播种数据。3.1 主路径Studio 发布Studio 侧的接入点在 cms/djangoapps/contentstore/outlines.py其中update_outline_from_modulestore(course_key)负责把 ModuleStore 中最近发布的课程内容转换为CourseOutlineData核心流程为get_outline_from_modulestore(course_key)遍历课程块的 children逐章节构建CourseSectionData与CourseLearningSequenceData并收集内容错误构造CourseOutlineData其中published_at取course.subtree_edited_on统一为 UTCpublished_version取str(course.course_version)BSON 对象转字符串避免 MongoDB 专用对象进入公共数据结构调用replace_course_outline(course_outline_data, content_errorscontent_errors)将数据落库。异步批量场景则由 cms/djangoapps/contentstore/tasks.py 中的update_all_outlines_from_modulestore_task/update_outline_from_modulestore_taskCelery 任务承载可对一批课程逐个刷新大纲。3.2 手动路径update_course_outline 管理命令该命令位于 cms/djangoapps/contentstore/management/commands/update_course_outline.py用于调试、错误恢复或回填backfillingpython manage.py cms update_course_outline course_key其handle方法把字符串 course key 解析为CourseKey后直接调用update_outline_from_modulestore(course_key)。注意命令说明中强调应在 Studio 进程cms中调用因为正常发布流程由 Studio 完成LMS 进程不会主动写这份数据。四、公共 API 与数据契约4.1 导入纪律只从顶层 api 包导入README 对使用方式给出了三条严格的契约允许在自己的应用里对 learning_sequence 模型建立外键但参见下文模型章节的限制允许引用openedx.djangoapps.content.learning_sequences.api.data中定义的数据结构除此之外只能从顶层包openedx.djangoapps.content.learning_sequences.api导入并使用函数不得从包内其他位置包括api的子模块导入。顶层 API 实际导出的函数见 api/init.py函数用途key_supports_outlines(opaque_key)判断某 course key 类型是否支持大纲见 4.2get_course_keys_with_outlines()返回所有已具备大纲的 LearningContext key 的惰性 QuerySetget_course_outline(course_key)获取某课程 run 的大纲不含任何用户个性化数据与权限过滤get_user_course_outline(course_key, user, at_time)针对某用户在某个时刻定制的大纲get_user_course_outline_details(course_key, user, at_time)用户大纲 补充信息日程 schedule、特殊考试记录等get_content_errors(course_key)获取最近一次发布产生的内容错误列表replace_course_outline(course_outline, content_errors)用新的CourseOutlineData替换落库的大纲模型数据业务逻辑全部集中在 api/outlines.py其 docstring 同样告诫不要直接从此模块导入请经由顶层 api 包。4.2 key_supports_outlines 支持范围key_supports_outlines的实现明确划分了支持边界先排除LibraryLocatorv1 图书馆虽然继承自 CourseKey但不应支持其余CourseKey只要deprecated为 False 即支持包括普通的 SplitMongo 课程course-v1:与 CCX 课程ccx-v1:旧式斜杠分隔课程 IDOrg/Course/Run与 Old Mongo 课程不支持。相应地_get_course_context_for_outline对course_key.deprecated会直接抛出ValueError若LearningContext尚不存在例如课程还没发布过则抛出CourseOutlineData.DoesNotExist。4.3 公共数据结构api/data.pyapi/data.py 定义了整个应用的公共数据契约全部基于attr的frozenTrue不可变类便于调试与安全共享。其编写准则包括数据结构尽量不可变本模块不得反向导入应用其他部分数据类保持笨业务逻辑归api包数据类只允许做完全自包含的校验绝不能发起数据库调用、网络请求或触发昂贵计算。核心类型CourseVisibility枚举private/public_outline/public控制匿名访问模式。其中public_outline模式下匿名用户能看到大纲但无法访问内部内容在UserCourseOutlineData.accessible_sequences的注释中有说明。CourseOutlineData课程级大纲字段包括course_key、title、published_at、published_version、days_early_for_beta、sections、self_paced、course_visibility、entrance_exam_id。约束course_key不得为 deprecateddays_early_for_beta不允许为负序列总数上限MAX_SEQUENCE_COUNT 1000sequences字段由sections派生initFalse并在后置钩子中校验一个序列不得出现在多个章节。它还提供remove(usage_keys)方法返回一个移除了指定序列/章节后的新大纲副本移除章节会连带移除其全部序列若某章节因此变空章节本身也会被移除。CourseSectionData/CourseLearningSequenceData章节与小节数据各自携带visibilityhide_from_toc、visible_to_staff_only、examis_practice_exam、is_proctored_enabled、is_time_limited与user_partition_groupsUserPartition ID 到 Group ID 集合的映射。user_partition_groups_not_empty校验器保证内容若与某个 User Partition 关联则必须至少关联一个 Group。UserCourseOutlineData继承CourseOutlineData追加base_outline未被裁剪的完整大纲便于回溯 staff-only 内容、user、at_time、accessible_sequences用户可访问的序列集合用户可能知道其存在但无法交互例如已关闭的考试。UserCourseOutlineDetailsDataoutlineschedulespecial_exam_attempts未来还会扩展到 Completion 等其他系统。ScheduleItemData/ScheduleData/SpecialExamAttemptData日程start / effective_start / due与特殊考试尝试数据。4.4 数据库模型models.pymodels.py 中的模型刻意保持薄而笨并遵循若干约定模型与data.py结构不必 1:1但数据类统一加...Data后缀如LearningContext↔LearningContextData强烈区分序列本身固有属性与序列在课程语境下的属性。主要表结构LearningContext序列的聚合容器context_key唯一索引。之所以不用外键指向 CourseOverview是因为该表未来要容纳非课程实体如 Content Libraries、Pathways。允许外部应用对它建外键。CourseContext与LearningContext一对一保存课程特有信息course_visibility、days_early_for_beta、self_paced、entrance_exam_id。LearningSequence序列本体usage_keytitle标题最长 1000 字符。允许外部应用对它建外键。它刻意不直接外键到CourseSection为未来课程之外的序列形态留出空间。CourseSection映射 chapter 块与CourseSectionSequencejoin排序表ordering全课程连续编号 0..N-1含inaccessible_after_due与继承自CourseContentVisibilityMixin的hide_from_toc、visible_to_staff_onlyCourseSectionSequence会在每次课程发布时被清空重建README 与 docstring 都明确警告不要对这张表建外键。UserPartitionGrouppartition_id group_id 唯一及两张 through 表SectionPartitionGroup/SectionSequencePartitionGroup内容可以关联多个 Group如同时关联 Verified 与 Masters而单个用户在每个 Partition 中只属于一个 Group。CourseSequenceExam特殊考试标记练习考、监考启用、限时。PublishReport与ContentError记录每次发布时的错误数、章节数、序列数以及面向课程团队和支持人员的人类可读错误消息例如 OLX 导入产生的畸形结构。五、用户个性化大纲OutlineProcessor 机制get_course_outline返回的是不包含任何用户信息的全量大纲而 LMS 最终看到的大纲需要根据用户身份与时间裁剪。get_user_course_outline/get_user_course_outline_details会先取全量大纲再串行执行一组处理器。5.1 基类契约api/processors/base.py 定义了OutlineProcessor基类README 提示请阅读api/processors/base.py的 docstring 了解如何编写。处理器在请求大纲期间被同步调用生命周期固定为三步__init__(course_key, user, at_time)——只做初始化不做任何实际工作load_data(full_course_outline)——抓取所需的课程与用户数据禁止在此触碰 ModuleStore 或 Block Structuresdocstring 要求该方法在数百个序列的课程上也应控制在几十毫秒内inaccessible_sequences(...)与usage_keys_to_remove(...)——返回被标记为不可访问、或被整体移除的 UsageKey 集合二者之间没有顺序保证也不应假设与其他处理器的执行顺序未来可能并行执行。基类注释还说明这两个过滤方法不会对 staff 用户运行staff 可以访问一切无需在此检查 staff 权限。5.2 内置处理器清单_get_user_course_outline_and_processors中按序注册了 9 个处理器处理器职责ContentGatingOutlineProcessor内容门控如按前置条件放行内容MilestonesOutlineProcessor里程碑 / 前置条件序列过滤ScheduleOutlineProcessor按发布时间线start/due过滤并额外提供schedule_data供 details API 使用SpecialExamsOutlineProcessor特殊考试相关可见性额外提供exam_dataVisibilityOutlineProcessor基于hide_from_toc/visible_to_staff_only等可见性标记过滤EnrollmentOutlineProcessor选课状态相关过滤EnrollmentTrackPartitionGroupsOutlineProcessor按学习轨道如 Verified/Masters分区过滤CohortPartitionsOutlineProcessor按分组cohort分区过滤TeamPartitionsOutlineProcessor按团队分区过滤其中ContentGatingOutlineProcessor、MilestonesOutlineProcessor、ScheduleOutlineProcessor等位于 api/processors/ 目录下各自文件都带有说明其用途的顶层 docstring。5.3 权限捷径与缓存api/permissions.py 的can_see_all_content(user, course_key)借助lms.djangoapps.courseware.access.has_access判断用户是否为 global staff、课程 staff 或 instructor若是则跳过全部处理器的过滤逻辑代码注释明确无需为这些用户运行处理器以裁剪结果同时兼容 masquerade 伪装身份的场景。性能方面get_course_outline使用edx_django_utils.cache.TieredCache分层缓存缓存 key 由context_key published_version构成learning_sequences.api.get_course_outline.v2.{context_key}.{version}TTL 为 300 秒查询时对CourseSectionSequence做了select_related(sequence, exam)与prefetch_related(new_user_partition_groups)并显式说明空章节也要保留因此单独查询CourseSection而不能只依赖 join 表。函数级function_trace与set_custom_attribute则把调用频率、耗时、用户维度等指标暴露给监控系统。六、消费端REST 视图与运行时服务6.1 REST APIviews.py 被刻意设计为薄层只负责用户输入/输出的翻译业务逻辑全部在api包。路由注册于 urls.pyv1/course_outline/path:course_key_str对应CourseOutlineView认证方式为JwtAuthentication与SessionAuthenticationAllowInactiveUser未来还打算放开匿名访问。其内联的UserCourseOutlineDataSerializer故意写在视图内部以避免共享序列化器带来的意外回归序列化时会把内部的 UsageKey 翻译为对外暴露的 ids并在accessible_sequences之外补充schedule、exam_information等合并后的扁平字段。6.2 运行时服务注入apps.py 的ready()在settings.ENABLE_SPECIAL_EXAMS开启时会把 services.py 中的LearningSequencesRuntimeService暴露get_user_course_outline与get_user_course_outline_details通过edx_proctoring.runtime.set_runtime_service(learning_sequences, ...)注入监考运行时供考试子系统查询用户可见性与考试信息。七、如何扩展三类典型场景README 的 How to Extend? 一节提醒本应用正在尝试一些新约定请先阅读决策文档docs/decisions——对应仓库内路径为 learning_sequences/docs/decisions/其中 0001-extensions-to-inter-app-apis.rst 记录了跨应用 API 扩展的决策同时许多模块的顶层 docstring 都在说明它应该被用于什么场景务必通读。这些约定旨在保证行为可预期即使是小规模的破坏也会显著削弱其效果。7.1 想给公共 API 增加更多数据公共数据类型放在 api/data.py数据库持久化照常放在models.py但模型保持极薄且笨所有真正的业务逻辑放在api包内的某个模块中。目前既有逻辑都在 api/outlines.py 并通过 api/init.py 顶层再导出。如果你的新功能与大纲相关请遵循此约定否则可在api/下新建模块例如api/sequences.py承载逻辑并在顶层api/__init__.py中再导出。7.2 想新增一条影响序列在大纲中展示的规则应当创建或修改一个OutlineProcessor位于 api/processors/。该接口目前尚不可插拔但设计上已为未来可插拔做好准备outlines.py 的注释指出处理器注册列表就是未来加入 pluggability 的位置。编写细节以 api/processors/base.py 的 docstring 为准新增后还需在_get_user_course_outline_and_processors的processor_classes列表中登记名称与类。7.3 想从 ModuleStore 或 Block Structures 拉取数据禁止。对这些系统的任何同步调用都会破坏本应用的性能目标。如果你确实需要这些系统的数据请在课程发布时把数据推入更小的模型这正是发布信号处理器 replace_course_outline的设计初衷在发布时刻完成昂贵的遍历与转换运行时刻只做廉价查询。这也解释了 models.py 中对CourseSectionSequence的警告——它是发布时重建的 join 表任何依赖它的同步查询都可能随时面对数据被整体删除的情况。八、小结与最佳实践清单Learning Sequences 代表了 Open edX 在课程结构数据访问上的一次架构转向以发布时推入、运行时读取的细粒度模型替代运行时全量遍历的 Block Transformer 方案并严格限定公共 API 边界。落地使用时值得记住的实践导入纪律只从顶层openedx.core.djangoapps.content.learning_sequences.api导入函数只引用api.data的数据结构外键纪律可以外键到LearningContext与LearningSequence但不要对CourseSectionSequence等发布时重建的表建外键性能纪律任何新逻辑都不得在运行期同步访问 ModuleStore / Block Structures数据必须随发布推入小模型扩展纪律新增展示规则优先以OutlineProcessor形式实现业务逻辑留在api包模型保持薄持久层定位运维手段需要手动回填或修复某门课程的大纲时在 Studio 进程执行python manage.py cms update_course_outline course_key或调用cms/djangoapps/contentstore/tasks.py中的批量 outline 任务。通过阅读 learning_sequences 应用目录、其 迁移历史从初始建表到UserPartitionGroup去重与唯一约束再到PublishReport索引优化可以进一步追踪该架构的演进脉络决策文档 0001-extensions-to-inter-app-apis.rst 则记录了这一设计的关键取舍。【免费下载链接】openedx-platformThe Open edX LMS Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表