-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[FC-0118] docs: ADR for documenting and consolidating internal MFE APIs #38309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: docs/ADRs-axim_api_improvements
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| ADR-012: Document & Consolidate Internal APIs Used by MFEs | ||
| ========================================================== | ||
|
|
||
| :Status: Proposed | ||
| :Date: 2026-04-09 | ||
| :Deciders: API Working Group | ||
| :Technical Story: Open edX REST API Standards - MFE API documentation and consolidation | ||
|
|
||
| Context | ||
| ------- | ||
|
|
||
| Multiple Open edX MFEs depend on undocumented internal LMS APIs. This causes two concrete problems: | ||
|
|
||
| 1. **Runtime breakages**: Backend refactors silently break MFE data fetches because there | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have an example (or two, or three) of when such a breakage happened? |
||
| is no contract guaranteeing response shape or field presence. MFE teams discover | ||
| breakages only after deployment, since no schema validation exists to catch | ||
| incompatible changes earlier in the development cycle. | ||
| 2. **Blocked integrators**: External developers and AI-driven tooling cannot discover or | ||
| rely on MFE-facing endpoints because they lack OpenAPI schemas, versioning, and | ||
| deprecation guarantees. Without formal contracts, third-party systems have no stable | ||
| surface to build against. | ||
|
|
||
| Decision | ||
| -------- | ||
|
|
||
| We will document and consolidate all internal APIs used by MFEs into stable, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here's my main concern with this ADR: Why does it have to be about MFEs? Certainly it makes sense for edx-platform to have stable, documented REST APIs that are usable by any clients, not just our own MFEs. This is, of course, a massive undertaking, but it has already started via #38137. Is this a part of that?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, this was a framing mistake. The underlying problem, undocumented endpoints with no contract guarantees, isn't MFE-specific; MFEs are just the most visible current consumer. I'll rewrite the framing to be about the platform's REST surface generally (under #38137), with MFEs as one motivating example rather than the scope. Confirming for the thread above: yes, this is part of #38137. |
||
| OpenAPI-described contracts. | ||
|
|
||
| Implementation requirements: | ||
|
|
||
| * All backend APIs consumed by MFEs MUST be documented with OpenAPI specifications, | ||
| including field descriptions, types, and example responses. | ||
| * Consolidate MFE configuration into a single, documented endpoint per MFE context | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like an independent decision. There already is an ADR for it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed the existing ADR 0001 for |
||
| (target pattern: ``/api/mfe_config/v1/``). | ||
| * The consolidated endpoint MUST accept ``mfe=<name>`` for app-specific overrides and | ||
| MAY accept ``course_id`` for course-contextual data (see `Authentication & Authorization`_ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this is yet another decision. Is this ADR supposed to be about API documentation, or the creation of a new configuration endpoint? (By the way, there already is a new configuration endpoint: /api/frontend_site_config/v1. I'm not sure we want to create yet another one, so soon.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both which is the scoping problem you flagged up top, so I'll split. And thanks for pointing to |
||
| below for scope rules). | ||
| * Deprecate undocumented internal endpoints once a supported, documented replacement | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A minor quibble with the naming choice: if you mean REST endpoints, they're external by definition, even if not documented properly. And, again, I'd be curious to learn of cases where REST endpoints were broken from one release to the next. |
||
| exists. Deprecations MUST follow the `Open edX DEPR process (OEP-21) | ||
| <https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0021-proc-deprecation.html>`_. | ||
| * Maintain backward compatibility during migration. If a breaking change is unavoidable, | ||
| it MUST be handled by creating a new API version and transitioning consumers using the DEPR process. | ||
| * Use URL-path versioning (``/api/mfe_config/v1/``, ``/v2/``, etc.) consistent with | ||
| existing Open edX API conventions. | ||
|
|
||
| Authentication & Authorization | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| The existing ``/api/mfe_config/v1`` endpoint does not require authentication. Adding | ||
| user-role context changes this boundary. The consolidated endpoint serves two categories | ||
| of data with different security profiles: | ||
|
|
||
| * **Public configuration** (``BASE_URL``, ``LMS_BASE_URL``, feature flags): No | ||
| authentication required. Highly cacheable. | ||
| * **User-contextual data** (user roles, course-specific permissions): Requires a valid | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I'm looking at a separate ADR, here. What you seem to be suggesting is a single new multi-purpose endpoint that would be useful exclusively for MFEs. I'm sorry to say that I'm not in favor of this, as it goes against resource-oriented API design. The key concept is that each API resource can be reused for different purposes, and a monolithic endpoint would be the antithesis of this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed and addressed in more detail in my top-level reply. The multi-purpose endpoint piece is coming out of the proposal entirely. User roles and course permissions belong as their own resources, usable by any client, not as fields on a config blob. |
||
| session or JWT. Responses MUST NOT be cached in shared caches. | ||
|
|
||
| When ``course_id`` is provided and the request is authenticated, the response MAY include | ||
| a ``user_context`` key containing role information. Unauthenticated requests with | ||
| ``course_id`` return only public course metadata. | ||
|
|
||
| Relevance in edx-platform | ||
| -------------------------- | ||
|
|
||
| Current patterns that should be migrated: | ||
|
|
||
| * **Existing MFE config API**: ``lms/djangoapps/mfe_config_api/views.py`` implements | ||
| ``MFEConfigView`` at ``/api/mfe_config/v1`` (see ``lms/urls.py``). It returns merged | ||
| config from legacy settings, ``MFE_CONFIG``, and ``MFE_CONFIG_OVERRIDES``, with optional | ||
| ``?mfe=<name>`` for app-specific overrides. | ||
| * **Response shape**: JSON with keys such as ``BASE_URL``, ``LMS_BASE_URL``, | ||
| ``STUDIO_BASE_URL``, ``ENABLE_COURSE_SORTING_BY_START_DATE``, etc. | ||
| * **Enrollment API**: Several MFEs call enrollment endpoints that return hand-built JSON | ||
| without serializer validation. | ||
| * **Course metadata endpoints**: MFEs fetch course details from internal views that lack | ||
| versioning or schema documentation. | ||
|
|
||
| Code examples | ||
| ------------- | ||
|
|
||
| **Current pattern (edx-platform) — manual config merging, no schema:** | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # lms/djangoapps/mfe_config_api/views.py (simplified) | ||
| class MFEConfigView(APIView): | ||
| def get(self, request): | ||
| legacy_config = self._get_legacy_config() | ||
| mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) | ||
|
|
||
| mfe_config_overrides = {} | ||
| if request.query_params.get("mfe"): | ||
| app_config = configuration_helpers.get_value( | ||
| "MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES | ||
| ) | ||
| mfe_config_overrides = app_config.get(request.query_params["mfe"], {}) | ||
|
|
||
| merged_config = legacy_config | mfe_config | mfe_config_overrides | ||
| return JsonResponse(merged_config, status=200) | ||
|
|
||
| **Target pattern — serializer-backed, schema-documented, with optional user context:** | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # serializers.py | ||
| from rest_framework import serializers | ||
|
|
||
| class MFEConfigSerializer(serializers.Serializer): | ||
| BASE_URL = serializers.URLField(help_text="Root URL of the MFE deployment") | ||
| LMS_BASE_URL = serializers.URLField(help_text="LMS base URL") | ||
| STUDIO_BASE_URL = serializers.URLField(help_text="Studio base URL") | ||
| # ... additional config fields documented explicitly | ||
|
|
||
| class UserContextSerializer(serializers.Serializer): | ||
| course_id = serializers.CharField(help_text="Course identifier") | ||
| user_roles = serializers.ListField( | ||
| child=serializers.CharField(), | ||
| help_text="Roles the requesting user holds in this course context", | ||
| ) | ||
|
|
||
| class MFEConfigWithContextSerializer(serializers.Serializer): | ||
| config = MFEConfigSerializer(help_text="MFE configuration values") | ||
| user_context = UserContextSerializer( | ||
| required=False, | ||
| help_text="Present only for authenticated requests with course_id", | ||
| ) | ||
|
|
||
| # views.py | ||
| from rest_framework.views import APIView | ||
| from rest_framework.response import Response | ||
| from rest_framework import status | ||
|
|
||
| class MFEConfigView(APIView): | ||
| """ | ||
| GET /api/mfe_config/v1/?mfe=learning | ||
| GET /api/mfe_config/v1/?mfe=learning&course_id=course-v1:edX+DemoX+1T2024 | ||
| """ | ||
| def get(self, request): | ||
| merged_config = self._build_merged_config(request.query_params.get("mfe")) | ||
|
|
||
| payload = {"config": merged_config} | ||
|
|
||
| course_id = request.query_params.get("course_id") | ||
| if course_id and request.user.is_authenticated: | ||
| payload["user_context"] = { | ||
| "course_id": course_id, | ||
| "user_roles": get_user_roles(request.user, course_id), | ||
| } | ||
|
|
||
| serializer = MFEConfigWithContextSerializer(payload) | ||
| return Response(serializer.data, status=status.HTTP_200_OK) | ||
|
|
||
| Consequences | ||
| ------------ | ||
|
|
||
| Positive | ||
| ~~~~~~~~ | ||
|
|
||
| * Stabilizes MFE-backend contracts; backend refactors can be validated against the | ||
| OpenAPI schema before deployment. | ||
| * Improves discoverability for external integrators and AI tooling through schema-based | ||
| documentation. | ||
| * Enables automatic documentation generation and client SDK creation from OpenAPI specs. | ||
|
|
||
| Negative / Trade-offs | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| * Requires cataloging API calls across multiple MFE repositories and their backend | ||
| dependencies — a non-trivial audit effort. | ||
| * MFE teams will need to update their data-fetching code to point to consolidated | ||
| endpoints, requiring cross-team coordination. | ||
| * Maintaining both old and new endpoints during the compatibility window increases | ||
| short-term maintenance burden. | ||
| * Existing MFE client code that expects legacy response shapes may need updates; | ||
| these changes should be tracked per-MFE in migration tickets. | ||
|
|
||
| Alternatives Considered | ||
| ----------------------- | ||
|
|
||
| * **Keep existing undocumented endpoints**: Rejected because fragile couplings continue | ||
| to cause runtime breakages and block external integrators from building on MFE-facing | ||
| APIs. | ||
| * **Document each MFE's APIs independently without consolidation**: Rejected because it | ||
| perpetuates duplication across repositories and does not reduce the total number of | ||
| internal endpoints that MFEs depend on. | ||
| * **Introduce a Backend-for-Frontend (BFF) layer**: Rejected for now because it adds a | ||
| new service to deploy and maintain. The consolidated config endpoint achieves the | ||
| primary goal (stable contracts) with less operational overhead. A BFF can be revisited | ||
| if MFE data-fetching patterns grow significantly more complex. | ||
|
|
||
| Rollout Plan | ||
| ------------ | ||
|
|
||
| 1. Audit all MFE repositories to catalog backend API dependencies. Track results in a | ||
| shared spreadsheet or wiki page linked from this ADR. | ||
| 2. Prioritize high-impact MFEs: Learning, ORA, Progress, Discussions. | ||
| 3. Document the existing ``/api/mfe_config/v1`` contract in OpenAPI. | ||
| 4. Create shared serializer utilities (e.g., ``MFEConfigSerializer``) in | ||
| ``openedx/core/djangoapps/`` for reuse across endpoints. | ||
| 5. Extend the endpoint with optional ``course_id`` and user-role context. | ||
| 6. Provide a compatibility window (minimum 6 months / one named release, per OEP-21) | ||
| where both old and new endpoints are available. | ||
| 7. File DEPR tickets for each deprecated internal endpoint and track migration status | ||
| per MFE in a dashboard or project board. | ||
| 8. Deprecate ad-hoc internal endpoints once replacements are stable and all MFEs have | ||
| migrated. | ||
|
|
||
| References | ||
| ---------- | ||
|
|
||
| * `OEP-21: Deprecation and Removal Process <https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0021-proc-deprecation.html>`_ | ||
| * `OEP-65: Frontend Composability <https://docs.openedx.org/projects/openedx-proposals/en/latest/architectural-decisions/oep-0065-arch-frontend-composability.html>`_ | ||
| (related — covers MFE shared dependency architecture) | ||
| * "Undocumented Internal APIs Used by MFEs" recommendation in the Open edX REST API | ||
| standardization notes. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have a list of these uses. It doesn't need to be comprehensive (certainly not in this ADR), but we should have a draft in the wiki we can point to. It will make this ADR's case much more compelling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I'll put together a draft wiki page (representative, not exhaustive) of the undocumented endpoints MFEs depend on today and link it from the revised ADR. Tracking this before the next revision.