Editorial note, April 2021: This document was produced in January/February of 2020. It is an early exploration of frontend plugins, and an attempt at assigning unambiguous terminology to a complex domain. In that sense, it posits plugin types that we may not actually choose to support.
Table of Contents | ||
---|---|---|
|
Overview
Our ability to extend our platform - without modifying it - is a prerequisite for innovation and experimentation, and allows us to maintain a small, stable core where volatile features remain in the periphery.
Frontend pluggability enables loosely coupled, lightweight platform extensions focused on user-facing features. It goes hand-in-hand with our micro-frontend development efforts, endeavoring to bring the speed, ease of development, and modularity of our MFEs to plugin and extension development outside the core.
Furthermore, in the same way that micro-frontends allow us to use client-side integrations to easily fetch data across contexts (LMS, Ecommerce, Discovery, etc.), frontend plugins allow us to securely and maintainably expand that capability to externally hosted and operated systems.
What about LTI?
Whether or not to make use of LTI is an important consideration for frontend pluggability, as application/plugin interactions are right in its wheelhouse. In this document, we take the approach of working up toward full LTI capabilities.
Through some careful consideration of our use cases, we’ve decide that fully supporting the LTI specification is not necessary to enable a broad range of functionality for our plugins, but will ultimately be an important component of our framework. We’ve determined that nothing about our approach is specifically at odds with LTI’s specification, and so it feels like a natural extension of our work when the time is right.
Outcomes
With frontend plugins, we hope to enable the following:
A rich, dynamic ecosystem of innovative teaching and learning extensions, tapping into existing and future LTI tools.
Simple, secure experimentation for course teams and operators.
Clarity and maintainability for platform developers.
Greatly increased velocity and flexibility for plugin developers.
Ultimately, better outcomes for learners.
Examples
Plugins may - almost by definition - take many forms. We have a few specific examples in mind which may help illustrate the scope of our proposed plugin framework, as well as some broader considerations for where we’d like to go with frontend pluggability.
Discussions Plugin
As our most near-term project, a plugin to enable context-specific discussions has greatly informed this document. This would involve a discussions widget which can be loaded on different sequences in a course, providing students with forums to discuss the material.
The discussions plugin would need to be aware of which content it’s attached to in the course. Furthermore, different course teams may want different discussions plugins, so we’ll need to be able to configure the discussions slot on a per-course - or perhaps per-program! - basis.
The plugin will clearly need to store student discussions somewhere, necessitating the existence of some sort of backend for it to talk to. If that location is in a separate data store, it will need a reliable way to associate comments and posts with particular users, necessitating the use of anonymous user identifiers to alleviate PII (personally identifiable information) concerns.
Content Recommendations Plugin
A plugin allowing a recommendation engine to suggest content for a user as they navigate through a course. The plugin may appear in multiple locations on the page (we have multiple sets of navigation buttons). The plugin will need to be able to ask the page to navigate to a different part of the course, and so may need detailed course hierarchy data. It may also want to gather data on a user’s performance in the course to help inform its engine.
Frontend xBlocks
Today, xBlocks are effectively backend plugins. There are dozens (hundreds?) of xBlocks in the world. For certain classes of xBlocks, they may be better and more easily implemented as frontend plugins. Implementing this would require us to be able to serve frontend plugin content at the “component” level of course content, rather than rendering the whole unit server-side.
LTI Tools
In the long run, giving our frontend plugin framework the ability to interoperate with existing and future LTI tools would be a valuable addition. LTI gives us a well-established framework for application/plugin communication and with which to perform authentication, allowing plugins to securely interact with our existing Platform APIs, extending their capabilities. There’s a lot we can do without LTI, but it’s likely the preferred way for us to handle authenticating externally hosted plugins in many cases.
Plugin Definitions
This section defines various concerns in developing our plugin framework, and serves as a broad breakdown of potential features, most of which are necessary to fulfill the above example plugins.
Plugin
Plugins are code that is not bundled with an application, but is built/deployed separately and sandboxed in an iframe at runtime. There are a number of sub-classifications of Plugins along a number of different axes:
Slot - Where is the plugin displayed on the page?
Config - Which plugins should be loaded and when?
Discovery - How does the application learn which plugins should be loaded?
Hosting - Where is the plugin deployed?
Deployment - How is the plugin deployed?
All of these are broken down below into their various sub-classifications, which roughly correspond to increasing levels of effort.
Info |
---|
Supporting DefinitionsA few key terms used in this document. SiteA website like edx.org. A second-level domain (SLD). PlatformThe platform (edx-platform et al) is deployed to a Site. ApplicationAn application is a micro-frontend application deployed on the Site’s domain (or sub-domain). Applications are built by compiling static assets and deploying them to the web along with an index.html file to load them. |
Slots
A Plugin is loaded in a Slot on the page. A slot is a particular mount point in the page (DOM) where the plugin should visually appear. Different slots will have different expectations for their dimensions. Some may grow vertically, horizontally, be statically sized, take over the page, exist in a modal, etc. A given slot may be configured to include multiple plugins. Plugin authors are responsible for understanding the requirements of the Slot in which they expect their plugin to appear, and those requirements will be communicated in documentation.
Examples:
Header
Footer
Sidebars
Below Sequence Nav
Course Tabs
Plugin Configuration
Plugin configuration is done once prior to usage of a plugin. It can be performed by course teams, developers, or operators. The configuration may be changed, of course, which will affect subsequent renderings of the plugin at runtime. Plugin configuration is primarily concerned with helping an Application determine which plugins should be loaded, where, when. It is not concerned with how those plugins choose to load their content.
Configuration as Code
This is effectively a hand-coded configuration document. It's easy to produce but error prone. Because it's code, it will generally need to be updated by a developer.
Configuration via UI
This is a plugin configuration that is created via a UI, serialized down into a configuration document like the above, and is handed to the application so it knows which plugins to load. The distinction here is that this is a level of effort above configuration-as-code. It's harder to produce, but easier and safer to use.
Context Sensitivity
Allows plugins to be loaded under certain circumstances. For example, loading a plugin only on a specific course, or for particular classifications of users. Put another way, it implies letting the Application use its Context data (see below) in order to determine when the plugin should be loaded, not just where.
This is a heavy lift, as we expect that for practical purposes, it will be difficult to use without first implementing Configuration as UI.
Note that “context-sensitive” is different than being “context-aware”, which is described below in the Plugin Context section.
Plugin Discovery
The process an Application follows to determine what plugins it should load.
Build-time
This is taking a static or dynamic plugin configuration and handing it to the Application at build-time so that it "knows" it's plugin configuration immediately upon load. This potentially delivers a modest performance boost by reducing request overhead.
Run-time
This is plugin discovery performed at run-time, as an Application is loading, via a separate call or configuration file download. It may be modestly slower than build-time plugin configuration because of the HTTP overhead of the extra download. It's also more flexible.
Plugin Context
Information handed to the plugin after it has been loaded to help it understand how to display itself. This may include:
Application name
Page name / URL
Slot name
User ID
Layout information (width, height)
Particular applications may have application-specific context they want to add in as well. In the case of courseware Applications, for instance, we’ll want to inform the plugin of:
Course ID
Chapter ID
Sequence ID
Unit ID
To reduce back-and-forth, basic plugin context information can be supplied as query string parameters on the initial load request. This is what LTI does. However, to avoid running into predictable issues with URL length, application-specific context information should be transferred via Events (described below). We can think of this distinction as helping the plugin do progressive rendering; basic context information should be enough to orient the plugin as to where it’s being displayed, whereas application-specific context information is used to fill in content.
As a side note: We’ve been using the term “context aware plugins” - this is an unnecessary qualification, as all plugins are aware of whatever context we choose to share with them. The host page and our framework controls how much context to give plugins. It’s not clear that there are any use cases for providing different amounts of context to different plugins, however.
Plugin Events
Plugins may communicate with the host page (and vice versa) via a pre-defined set of events. Each event has a type and an agreed-upon payload schema. These events will be delivered via the postMessage API. We may add to the set of pre-defined events as we discover new plugin use cases.
Lifecycle Events
There will be a number of lifecycle events specifically to manage a plugin’s loading and rendering process. Because the host Application and the plugin can only communicate through the asynchronous postMessage API, we’ll need a set of events published by both sides to inform and properly sequence overall lifecycle state. As examples, we’ll likely need events related to: plugin loading, transferring context data, ready, content resize, and unloading.
Plugin Hosting
Internal
If a plugin is deployed as part of the Site and is hosted under the Site’s domain, then it will have access to the Platform's authenticated APIs by virtue of having a valid JWT token.
External
If a plugin is hosted on a separate domain, then it will not have access to any Platform APIs. It will identify users and other resources via messages passed into it from the host page. If this is insufficient for it to implement its functionality, then it needs to become an Internal Plugin or an LTI Plugin. We may choose to implement a non-LTI authentication mechanism in the future, but for now our recommended path is LTI.
LTI (Sub-classification of External)
To allow externally hosted plugins to have access to authenticated Platform APIs, the plugin must conform to the LTI specification and be loaded in an iframe. This means the plugin must implement an OAuth/JWT authentication flow according to the LTI spec.
Plugin Deployment
Plugins can be deployed in several different ways that affect their use cases.
Micro-frontend Only
Micro-frontend-based plugins have no backend. In that sense, they have limited ability to persist data. Deployments of the other types may make use of a micro-frontend for their frontend layer - this category is specifically for deployments that have no backend component.
Micro-service
Plugins that are served/associated with a stand-alone backend service, deployed independently from the Platform. If such a plugin has a micro-frontend layer, that is presumably still deployed separately.
Django App Plugin
A plugin that is deployed as part of the Platform via the Django App Plugin mechanism. If such a plugin has a micro-frontend layer, that is presumably still deployed separately.
xBlock
A plugin based on an xBlock. Today, xBlocks were designed to be used in the actual course content, not in secondary/supporting functionality on the page. However, they may just work; some investigation will be required.
LTI xBlock
The LTI xBlock allows LTI tools to be loaded into an xBlock with the caveats described above. i.e., they can only be used on course content, not in supporting UIs.
Proposed Development Milestones
Taken in all their various combinations, the plugin slots, config, discovery, hosting, and deployment sub-types represent a wide variety of plugins with varying degrees of functionality and flexibility. It’s also the case that some of the sub-types may ultimately be subsumed by others, or may not be fully necessary in the long run (build-time plugin discovery, configuration as code)
Now that we have a good understanding of the problem space, we can proceed iteratively to ensure we focus on functionality that we really need.
This is not intended to be a project plan, but merely to help jumpstart those conversations.
A proposal for how we proceed:
Finding a high-value plugin we want to create. (e.g., Discussions)
Determining which functionality it needs to be minimally successful.
Implementing those plugin sub-types in support of the plugin.
Repeating these steps with other plugins that demand further functionality in the framework.
This makes some assumptions about which sub-types will be developed first based on our understanding of the requirements/likely development path for a discussions plugin. The phases laid out here are a draft of how we might get there - they’re expected to be tweaked and changed depending on what we find out about Discussions and other, future plugins.
MVP Milestone
This milestone should be oriented toward an initial MVP of a discussions plugin. It should contain just enough features to make that project feasible. This plan assumes that the plugin will be internally hosted for convenience, and that it will need modest context information in order to function. It does not presume that the MVP will actually go live with this feature set.
Questions
How will the discussions plugin be built? A Django App Plugin? A micro-service? Internal? External? What discussion framework will it use? Will it be home grown?
Functionality Milestone
This milestone is focused on enabling dynamic, context-sensitive self-service of plugins. It will involve an administration interface, perhaps in Studio or Django admin to manage a Site’s plugin configuration. This is not currently aligned with any particular plugin implementation, but could be adjusted to be.
LTI Milestone
Finally, phase three is about enabling LTI and xBlock plugins in support of a rich, externally hosted plugin ecosystem.
Features By Phase
In progress
Done in prior milestone
Milestones:
MVP
Features
LTI
Slots
Right Sidebar
Header
Footer
Course Tabs
Configuration
As code
Via UI
Context-sensitivity
Discovery
Build-time
Run-time
Context
Basic context via query string
User ID
Application Name
Page Name / URL
Slot Name
Layout Information
Learning Context
Application context via event
Course ID
Chapter ID
Sequence ID
Unit ID
Events
Lifecycle
Resize
Further events (Example: external link handling)
Hosting
Internal
External
LTI
Deployment
MFE
Micro-service
Django App Plugin
xBlock
LTI xBlock