-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[FC-0118] docs: add ADR for standardizing pagination across APIs #38300
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
Open
Abdul-Muqadim-Arbisoft
wants to merge
2
commits into
openedx:docs/ADRs-axim_api_improvements
Choose a base branch
from
edly-io:docs/ADR-standardize_pagination_usage
base: docs/ADRs-axim_api_improvements
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+176
−0
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| Standardize Pagination Across APIs | ||
| =================================== | ||
|
|
||
| :Status: Proposed | ||
| :Date: 2026-04-08 | ||
| :Deciders: API Working Group | ||
| :Technical Story: Open edX REST API Standards - Pagination standardization for consistency and scalability | ||
|
|
||
| Context | ||
| ------- | ||
|
|
||
| Open edX platform API endpoints use multiple, inconsistent pagination strategies. Some endpoints use ``limit``/``offset`` query parameters, others use ``page``/``page_size``, and several return complete result sets with no pagination at all. This inconsistency forces every API consumer — whether a frontend micro-frontend (MFE), a mobile client, an AI agent, or a third-party integration — to implement custom data-loading logic per endpoint. | ||
|
|
||
| The ``edx-drf-extensions`` library already provides a ``DefaultPagination`` class (a subclass of DRF's ``PageNumberPagination``) that standardizes on ``page``/``page_size`` parameters with a default page size of 10 and a maximum of 100. However, many endpoints either override this with ad-hoc pagination classes, use ``LimitOffsetPagination``, or bypass pagination entirely by returning raw lists or manually constructed JSON arrays. | ||
|
|
||
| Decision | ||
| -------- | ||
|
|
||
| We will standardize all Open edX REST APIs to use the existing ``DefaultPagination`` class from ``edx-drf-extensions`` as the platform-wide pagination standard. | ||
|
|
||
| Implementation requirements: | ||
|
|
||
| * All list-type API endpoints MUST use ``DefaultPagination`` (or a subclass of it) from ``edx-drf-extensions``. | ||
| * Endpoints currently using ``LimitOffsetPagination`` MUST be migrated to ``DefaultPagination`` with appropriate versioning. | ||
| * Endpoints returning unpaginated result sets MUST be updated to return paginated responses. | ||
| * All paginated responses MUST include the standard envelope: ``count``, ``next``, ``previous``, ``num_pages``, ``current_page``, ``start``, and ``results``. | ||
| * Views that subclass ``APIView`` directly (rather than ``GenericAPIView`` or ``ListAPIView``) MUST manually invoke the pagination API to return paginated responses. | ||
| * Custom ``page_size`` overrides per endpoint are acceptable when justified (e.g., mobile APIs may use a smaller default), but MUST be implemented by subclassing ``DefaultPagination`` rather than using an unrelated pagination class. | ||
| * Maintain backward compatibility for all APIs during migration. If a fully compatible migration is not possible, a new API version MUST be created and the old version deprecated following the standard deprecation process. | ||
|
|
||
| Relevance in edx-platform | ||
| -------------------------- | ||
|
|
||
| Current example patterns that should be migrated: | ||
|
|
||
| * **Completion API** (``/api/completion/v1/completion/``) — uses inconsistent pagination formats depending on request parameters; some paths return unpaginated results. | ||
| * **User Accounts API** (``/api/user/v1/accounts/``) — pagination behavior differs from other user-related APIs, making it difficult for consumers to use a single data-loading pattern. | ||
| * **Course Members API** (``/api/courses/v1/.../members/``) — returns all enrollments without pagination, relying on a ``COURSE_MEMBER_API_ENROLLMENT_LIMIT`` setting (default 1000) to cap results and raising ``OverEnrollmentLimitException`` instead of paginating. | ||
| * **Enrollment API** (``/api/enrollment/v1/``) — some list endpoints return full result sets without pagination support. | ||
| * **Course Blocks API** (``/api/courses/v2/blocks/``) — intentionally returns unpaginated data for the entire course structure, which can result in very large response payloads. | ||
|
|
||
| Code example (target pagination usage) | ||
| --------------------------------------- | ||
|
|
||
| **Example using DefaultPagination with a ListAPIView:** | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # views.py | ||
| from rest_framework.generics import ListAPIView | ||
| from edx_rest_framework_extensions.paginators import DefaultPagination | ||
| from .serializers import EnrollmentSerializer | ||
|
|
||
| class EnrollmentListView(ListAPIView): | ||
| """ | ||
| Returns a paginated list of enrollments for the authenticated user. | ||
|
|
||
| Pagination parameters: | ||
| - page (int): The page number to retrieve. Default is 1. | ||
| - page_size (int): Number of results per page. Default is 10, max is 100. | ||
|
|
||
| Response envelope: | ||
| - count (int): Total number of results. | ||
| - num_pages (int): Total number of pages. | ||
| - current_page (int): The current page number. | ||
| - next (str|null): URL for the next page, or null. | ||
| - previous (str|null): URL for the previous page, or null. | ||
| - start (int): The starting index of the current page. | ||
| - results (list): The list of enrollment objects. | ||
| """ | ||
| serializer_class = EnrollmentSerializer | ||
| pagination_class = DefaultPagination | ||
|
|
||
| def get_queryset(self): | ||
| return CourseEnrollment.objects.filter( | ||
| user=self.request.user, | ||
| is_active=True, | ||
| ).order_by('-created') | ||
|
|
||
| **Example subclassing DefaultPagination for a mobile endpoint with a smaller page size:** | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # paginators.py | ||
| from edx_rest_framework_extensions.paginators import DefaultPagination | ||
|
|
||
| class MobileDefaultPagination(DefaultPagination): | ||
| """ | ||
| Pagination tuned for mobile clients with smaller payloads. | ||
| """ | ||
| page_size = 5 | ||
| max_page_size = 50 | ||
|
|
||
| **Example using DefaultPagination with a plain APIView (manual invocation):** | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # views.py | ||
| from rest_framework.views import APIView | ||
| from rest_framework.response import Response | ||
| from edx_rest_framework_extensions.paginators import DefaultPagination | ||
|
|
||
| class CompletionListView(APIView): | ||
| pagination_class = DefaultPagination | ||
|
|
||
| def get(self, request): | ||
| completions = BlockCompletion.objects.filter( | ||
| user=request.user | ||
| ).order_by('-modified') | ||
| paginator = self.pagination_class() | ||
| page = paginator.paginate_queryset(completions, request) | ||
| serializer = CompletionSerializer(page, many=True) | ||
| return paginator.get_paginated_response(serializer.data) | ||
|
|
||
| Consequences | ||
| ------------ | ||
|
|
||
| Positive | ||
| ~~~~~~~~ | ||
|
|
||
| * External systems and AI agents can implement a single, reusable data loader for all Open edX list endpoints. | ||
| * Consumers can reliably pre-calculate batch sizes using the ``count`` and ``num_pages`` fields in every paginated response. | ||
| * Eliminates unbounded response sizes that currently risk overloading clients and timing out requests (e.g., large enrollment or discussion lists). | ||
| * Enables consistent OpenAPI schema generation for all list endpoints. | ||
| * Leverages the already-existing ``DefaultPagination`` class, minimizing new code. | ||
|
|
||
| Negative / Trade-offs | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| * Endpoints that currently return full result sets (e.g., Course Blocks API) will require consumers to implement pagination loops where they previously did not need to. | ||
| * Requires refactoring views that use ``APIView`` directly without DRF's generic pagination machinery. | ||
| * Migrating ``limit``/``offset`` endpoints to ``page``/``page_size`` is a breaking change for existing consumers of those specific endpoints and must be versioned. | ||
| * Some internal consumers (e.g., modulestore aggregation) may need to be updated to handle paginated results instead of full lists. | ||
|
|
||
| Alternatives Considered | ||
| ----------------------- | ||
|
|
||
| * **Standardize on LimitOffsetPagination instead of PageNumberPagination**: Rejected because ``edx-drf-extensions`` already ships ``DefaultPagination`` based on ``PageNumberPagination``, and a significant portion of the platform already uses it. Additionally, ``limit``/``offset`` pagination degrades in performance with large offsets because the database must scan and skip all preceding rows, making it unsuitable for large Open edX datasets such as enrollments and completions. | ||
|
bradenmacdonald marked this conversation as resolved.
Outdated
|
||
| * **Adopt CursorPagination as the platform standard**: Rejected because cursor-based pagination, while performant for large and frequently-changing datasets, does not support random page access (jumping to page N). This would break existing MFE patterns that display numbered page controls. Cursor pagination also requires a stable, unique, sequential sort key on every queryset, which not all Open edX models guarantee today. | ||
|
bradenmacdonald marked this conversation as resolved.
Outdated
|
||
| * **Allow each API app to choose its own pagination style**: Rejected because this is the current state, and it is the root cause of the inconsistency this ADR aims to resolve. | ||
| * **Do nothing and document the differences**: Rejected because documentation alone does not reduce the integration burden on consumers or prevent future inconsistencies. | ||
|
|
||
| Rollout Plan | ||
| ------------ | ||
|
|
||
| 1. Audit all list-type API endpoints in ``edx-platform`` to categorize them as: already using ``DefaultPagination``, using a different pagination class, or unpaginated. | ||
| 2. Add a custom ``pylint`` or ``edx-lint`` check that warns when a ``ListAPIView`` or list-returning ``APIView`` does not specify ``DefaultPagination`` (or a subclass). | ||
| 3. Migrate high-impact unpaginated endpoints first (Course Members, Completion, Enrollment). | ||
| 4. Migrate ``limit``/``offset`` endpoints by introducing new API versions that use ``DefaultPagination``, and deprecating the old versions. | ||
| 5. Update MFEs and known external consumers to adopt the new pagination parameters where versions change. | ||
| 6. Update API documentation and OpenAPI specs to reflect the standardized pagination envelope. | ||
|
|
||
| References | ||
| ---------- | ||
|
|
||
| * ``edx-drf-extensions`` ``DefaultPagination`` class: https://github.com/openedx/edx-drf-extensions/blob/master/edx_rest_framework_extensions/paginators.py | ||
| * Django REST Framework Pagination documentation: https://www.django-rest-framework.org/api-guide/pagination/ | ||
| * Open edX REST API Standards: "Pagination" recommendations for API consistency. | ||
| * Open edX API Thoughts wiki: https://openedx.atlassian.net/wiki/spaces/AC/pages/16646635/API+Thoughts | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.