OEP | :doc:`OEP-XXXX <oep-XXXX-modular-micro-frontend-domains>` |
Title | Modular Micro-frontend Domains |
Last Modified | 2022-11-09 |
Authors | Adolfo R. Brandes <adolfo@tcril.org> |
Arbiter | Adam Stankiewicz <astankiewicz@2u.com> |
Status | Draft |
Type | Architecture |
Created | 2022-11-09 |
Review Period | TBD |
Resolution | TBD |
References | TBD |
- MFE: a micro-frontend
- SPA: single-page application
- DDD: domain-driven development
The decision to gradually implement a micro-frontend architecture for Open edX was a largely successful attempt at avoiding some limitations of the monolithic edx-platform
codebase. In particular, it allowed the project to integrate more and better features by leveraging newer frontend technology, all much faster than would've been possible otherwise. At the same time, because features encapsulated in MFEs are very loosely coupled to each other, this architecture eliminates most integration bottlenecks, allowing individual teams working on any given feature to be a lot more productive.
However, roughly 3 years into this gradual re-platforming, several drawbacks to the choice of complete MFE independence have been identified. This OEP proposes a middle-ground based on DDD and other industry best-practices, where a minor increase in integration costs is traded for large gains in user experience, customizability, ease of deployment, and development efficiency.
When moving to MFEs, a (now known to be) uncommon choice was made: to have completely independent MFEs, each hosted as a separate SPA, with a separate deployment, URL, and technology stack. While the goal of developing platform features more efficiently was achieved, the price to pay was significant. Here are some critical issues that the new architecture introduced:
- Inconsistency between MFEs and the legacy codebase
- UI is designed differently, resulting in poorer UX across boundaries;
- They're also implemented differently, which makes tooling, development, and branding that much harder than before, because both implementations have to be dealt with.
- Inconsistency between the UIs of MFEs themselves
- Each MFE can use whatever paradigm was in vogue during its development, leading to differences (some minor, some major) in UX;
- This makes it inordinately difficult to theme components that are not common to all MFEs, such as headers and footers.
- Regression in customizability
- To achieve similar customizability to the legacy codebase, MFEs need to be forked, with all the maintenance cost that that entails;
- The fact each MFE is fully independent means that it is harder to implement a consistent plugin interface between all of them.
- Changes that affect multiple MFEs are costly
- Development effort is multiplied by the number of existing MFEs, as each change needs to be duplicated;
- Because no dependencies are shared, Webpack build time of any potentially shared library is multiplied by the number of existing MFEs.
- Inefficient use of network bandwidth
- Inability to route (e.g., with react-router) between MFEs, necessitating a full refresh each time;
- Dependencies have to be downloaded multiple times by the browser.
- Inconsistent documentation
- The fact each MFE is fully independent contributes to inconsistencies in the quality of documentation of the features implemented.
- This leads to confusion at release time: is a given MFE good enough to ship?
- Inconsistency in deprecation efforts
- The gradual replacement of features often goes contrary to the Principle of Least Surprise: features are modified or removed with little to no warning between releases.
Some of these are a natural consequence of abandoning server-side rendering; in particular, the authors do not suggest it will ever be possible for an operator to override anything on a page without forking the codebase, as was possible with Comprehensive Theming. However, it seems all of the listed problems can at least be ameliorated - if not completely resolved - by implementing a combination of industry best-practices that have risen to maturity in the past couple of years, all of which introduce foreseeable costs that are clearly outweighed by their advantages.
It is thus a good time to consider these practices - specified below - weighing both the development cost of implementation and future recurring ones against their benefits.
We propose a compromise between the monolithic codebase and full MFE independence based on Domain Driven Development: the Open edX frontend will be split into formal top-level domains, including but not limited to LMS and CMS, each of which will be implemented as a Container SPA. The container, in turn, will render multiple partly-independent MFEs as sub-components. The latter will be re-engineered to conform to the hosting domain's interfaces.
This is to be a full re-platforming, to be undertaken as a single long-term project prior to release. It is in opposition to the currently in-progress gradual conversion of single pages to fully independent MFEs that can be linked to from the pre-existing monolithic framework; the opposite, injecting legacy pages into a domain SPA as components, would be unjustifiable. The difficulty of working with the legacy codebase is the main reason why it was decided to move to MFEs in the first place!
On the other hand, the current gradual conversion to fully independent MFEs need not be interrupted; the domain-based re-platforming proposed here can be executed in parallel. Why? Converting an already fully independent MFE to a domain-hosted one is much less costly than the initial corresponding extraction from the monolith. And conversely, it is conceivable that a particular monolithic page, after extraction directly into a domain-hosted MFE, could then be cheaply backported the other way, into a fully-independent one for earlier release while the re-platforming project is incomplete.
As this OEP is developed, we will further investigate:
- Integration strategy
- Domain SPAs and their interfaces
- Shared dependencies
- MFEs as components
- Are there any further top-level architectural elements to consider?
- What is the relationship of this project to the Modular Learning effort?
- Both are long-term projects, but beyond that, is there anything else that could be coordinated between them?
- Should this project target the modular codebase exclusively, ignoring
edx-platform
? (Relevant if the APIs are incompatible between them.)
To be defined.
At time of writing these appear to commonly recommended strategies for integration of MFEs into modular container apps:
- Run-time integration via JavaScript
- Run-time integration via Web Components
- Webpack Module Federation
- single-spa
- Piral
With these in mind:
- What are the pros and cons of each integration option, in particular in terms of:
- Cost of conversion: how easy is it to convert existing MFEs to the new paradigm?
- Cost of integration: compare the level of MFE independence.
- Hosting of build artifacts: is it possible to host build artifacts separately on object storage or CDN?
- Lazy loading: is it possible to load MFEs and other components lazily?
- Efficiency of development: consider the level of boiler-plate code.
- User impact: compare the benefits and losses from the point of view of the browser.
- Maturity: how reliable is the codebase? Is it maintained actively? Can it be relied upon to exist in 5 years?
- Project health: is the project active (measure commit activity)? How active is the community?
- Recommend a single choice between the integration options, or suggest another, better alternative.
To be defined.
- What UX elements should go into the domain's containing app? The header? Footer?
- Define further container SPA and interface characteristics.
To be defined.
- What dependencies should be shared between all MFEs? (React, Redux, ...?)
- Should we use lazy loading, and if so, how?
To be defined.
- Define how an MFE will connect to the containing SPA's interfaces.
- Can an MFE be used across domains?
To be defined.
To be defined.
- Document created
- Pull request #XXX