[Spec Memo] API-Based Management of edX Course Blocks, Outlines and Settings (MVP)

Problem statement

We seek to empower course owners to maintain their content on any platform, while programmatically co-hosting that content on different OpenEdx instances. This includes the following:

  1. The ability to replicate existing course content from an external source to an existing open edX CMS course run

  2. The ability to efficiently maintain that replicated content as changes to the source occur naturally over time

2U is driving toward an 80/20 solution that may not be able to replicate every distinct edX course run feature possible, but that with bounded effort allows a great many use cases to be accomplished programmatically within the 2023 calendar year.

MVP Definition

From an MVP perspective, it is essential that co-hosting work with:

  • Hierarchically organized course runs

  • Course runs with

    • HTML components (containing static assets like Images)

    • Video components with subtitles

    • Problem components to be solved by learner (single answer, multiple choice, open response)

    • Graded assessments, with rubrics when appropriate

    • Discussion forums

    • LTI-consumer components

  • Course-wide and unit-specific settings that govern content presentation; e.g.,

    • Course run content may be hidden based on viewer’s role relative to course

    • Course run content may not yet be released (date driven)

This capability must also extend to updates made to course runs, and not be limited to one-time ports. That is, content owners ought to be able to update from the source on-demand, without loss on the Open edX platform of any course run information.

Technical Decisions

At this time, 2U has made three decisions:

  1. Adopt OLX as the basis for how content is communicated to the edX platform.

    1. This is the most complex and consequential of the decisions. Factors that most influenced this decision include:

      1. Desire not to have to support multiple representations

      2. Reduction of work and risk by not having to design an abstraction layer, get buy-in for that layer, and then implement that layer (e.g., a new json-based representation)

      3. Placing the burden of resolving capability gaps or paradigm incompatibilities closest to content owners, as it is content owners who decide what compromises are acceptable

  2. Rely on existing readthedocs documentation to make new studio content management APIs immediately usable without need of new documentation for all remaining XBlock types.

    1. 2U will draft guidance on how to set XBlock settings with OLX for all XBlock types that are in the MVP Definition

  3. Enhance existing import/export API and xblock APIs to enable the maintenance of both course runs and components.

    1. This is to enable efficient implementation of both course-wide and fine-grained use cases

Content data transfers

An API exposing content creation capabilities may be written in terms of edX-native constructs, partner-native-constructs, or in terms of a generic capabilities abstraction. We propose to port content using OLX constructs, the edX-native choice.

Our initial approach will be OLX-centric, without abstracting the content (XBlock) types we offer into some generic API. This will place the paradigm-shift burden on our partners, asking them to either “thin publish” units with LTI, or to “thick publish” units by converting them into an OLX representation.

Working with OLX this way entails using the readthedocs documentation to create sample content on studio, exporting that sample content, and then using the generated OLX as a template for the porting work itself. (More on this in the “Self-documenting API” section below).

Self-documenting API

Course run content and settings will be encoded in this API with Open Learning XML (OLX) encoding. We argue here that this decision affords instant breadth in terms of the types of content and settings that the API offers, but the decision admittedly shifts a complexity burden onto content owners.

edX Studio allows you to export a course to a folder of hierarchically related OLX files, and, likewise, to import a course from such a folder. The structure of those files parallels the course structure. Moreover, contents of the OLX files include the names and values of settings that configure the individual units. This means that the following action sequence reveals what settings need to be set to what values to achieve a desired effect in a course unit:

  • Look up the desired effect in the “Building and Running an edX Course” reference (aka, readthedocs) to learn how to add that to a course in Studio

  • Add the effect in a demo course

  • Export the course

  • Find the OLX file within that course corresponding to the unit with your effect

  • Read the OLX contents to learn the tag that recruits the appropriate XBlock, as well as the name of the settings and the values you applied through the UI

  • Use this knowledge to create OLX for new units you wish to programmatically create.

While the above is undoubtedly a bit cumbersome, it does mean that course team, armed with the ReadTheDocs documentation, can leverage edX’s import/export capability to create and configure courses using whatever mix of XBlock types fits the need. For this procedure to be practical it needs to be speedy, something the current export is not; consequently, a partial export extension to exporting capabilities is included in the import/export extensions section below (in the “Read aka export” row). With the partial export extension, it will be possible to limit exporting to just the portion of a course run under exploration.

Alternatively, a simpler and more direct way to discover available OLX tags for a piece of content may exist and is under study. If available and suitable, it will be offered as well.

All that’s needed for a “self-documenting” solution is an API that allows creation and editing of course run content via OLX files, a-la the import/export capability. It is self-documenting in that the API documentation need not document an XBlock type’s idiosyncrasies for the type to be usable via the API.

Import/export extensions

The existing import/export functionality applies at the course run level only: as and end user, you either export an entire course run, or you import (and replace) an entire course run. The course run you import content into must already exist, and prior content is replaced.

The above is too restrictive for 2U goals. First, it’s slow. Even on an initial port, where the entire course is being worked on, there may be some sections that call for tuning. A slow, whole-course import is not nimble enough for that type of tweaking. Second, whole-course imports for maintenance operations would overwrite settings that may have been tuned after the original import, and this is unacceptable.

We propose to augment the whole-course import mechanism to allow CRUD operations acting at a sub-tree scope within a course run.

Create

  • An OLX folder structure can be injected as the root of a course run (no change)

  • All create operations return version hashes to uniquely identify the course run version, post-create

Read (aka, export)

  • Either a whole course run or a subtree defined by a root unit within that course run can be exported to an OLX folder structure

  • Returns version hash associated with read version to detect whether course run has been modified in out-of-band operations between API-based accesses

Update

  • An OLX folder structure defining a course subtree can be injected to replace an existing subtree in a course run, provided that its compatible with that placement within the course run

    • Ditto an OLX structure specifying a single node; if that single-node OLX structure keeps children ID values from before, the subtree under that node is preserved

  • An OLX folder structure can be added as a new child under a specified unit, provided that its compatible with that placement within the course run

  • Unit settings (and course run settings) can be edited

  • Units may be moved to other locations or ordering, but must remain at the same level of content hierarchy

  • All update operations return version hashes to uniquely identify the course run version, post-update

  • Update requests will require a flag that governs how to respond to requests that would leave orphan XBlocks (e.g., by separating a child node from its parent without deleting the child node). The options will be to decline such requests with a suitable error message that clearly identifies the to-be-orphaned XBlocks, or to force automatic deletion any XBlock left as an orphan as a side effect of the update.

Delete

  • A course run’s unit and all its descendants can be deleted; the unit at the root of the subtree to be deleted is identified by the url_name in its OLX representation

  • All delete operations return version hashes to uniquely identify the course run version, post-delete

edX courses runs are composed of XBlocks. An XBlock is a package that defines how a component is presented to the student, how it behaves, and how it is stored. The above discussion based on OLX representations frames porting work in term of existing XBlock types. While that’s a valid assumption in most cases, new XBlock types may be added as a measure of last resort to resolve capability gaps or paradigm incompatibilities.

Operational “-ilities”

Concurrency

API-driven changes to a course run may need to occur in the context of a locking mechanism: programmatic access to a course run will be limited to one client at a time. Locks will time out after some period of inactivity. 2U acknowledges that lock maintenance is expensive and error prone, other options are also being explored.

Error Handling

  • OLX with improper syntax will be reported as such, with the offending unit identified. The end user always has the option of experimenting with the OLX content, manually importing iterations into a test course, until satisfied.

  • Configuration errors reported by XBlock logic and course run validation will be passed on verbatim to the API client.

  • Timeout conditions on bulk processing will be fatal and will be reported. The end user has the choice of subdividing the bulk operations into finer-sized units.

  • Bulk operations may take minutes or tens of minutes. The API will provide a mechanism to periodically reveal progress during this wait.

  • 2U-internal logging tools will capture all reported errors.

Observability

Analytics will be collected by 2U-internal Observability Tools to identify usage levels by application ID, by API, by XBlock type, and by course run. The analytics is to be rich enough to measure historical API performance and to allow detection of congestion problems.

Visibility into ported XBlock types and the settings that apply to them will exist. This is intended to assist with bug reports; also, it will be useful to know whether a bug is a first instance or not, and to have breadcrumbs as to earlier usage.

Resiliency

  • The API will not be chatty (i.e., it will offer bulk operations).

  • Calls into the API from a client will be throttled.

  • The burden of handling duplicate requests will fall to the client.

Scalability

The design is to be scalable for performance/throughput, but need not be dynamically scalable. That is, static reservation of more resources for programmatic content creation and editing is acceptable.

Security

  • Applications accessing the Studio API will need to register with CMS.

  • Authentication and authorization will be OAuth2-based. Authorization will be for the same course creation/modification privileges the end user would have if on studio directly.

  • We have not yet identified the need for any new scopes.

Testability

  • Automated tests should allow us to publish at any time a set of XBlock types and settings that are known to pass on a syntax basis.

  • Automated tests should cause errors generated by the API to fire, to ensure that they, too, are working.

  • Automated testing of features controlled by XBlock-specific settings is out of scope.

Risks

Capability gaps

A randomly selected course from another CMS may call for capabilities not currently supported in any edX platform XBlock. Porting such a course would either be a lossy process or would require the development of new XBlocks. Our project plan does not currently provide for creation of new XBlocks. How often this will prevent a course from being ported is unknown.

Paradigm-shift incompatibilities

Per the “Solution Alternatives” section below, mapping on-edX course paradigms onto edX course paradigms may sometimes be lossy.

In extreme cases, a capability required to port a given course may be offered by an edX platform XBlock, but its paradigm may differ so much from the source platform’s paradigm as to be effectively unusable. This results in the same risk as with a capability gap.

Inversion of Control

This API will provide more scriptable control to content creators and with it comes more opportunity to do the wrong thing. There is a risk that operators will “shoot themselves in the foot” in a very bad way. The Operational “-ilities” section attempts to mitigate as many of those risks as possible within 2U’s scope of effort.