Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/decisions/0018-supporting-subdomain-modules.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
.. _ADR-18:

0018: Supporting Subdomain Modules for Cross-Domain Events
##########################################################

Status
******

**Proposed**

Context
*******

Events in ``openedx-events`` are organized into domain modules (e.g., ``learning``,
``course_authoring``) following the Open edX architecture subdomains in
:ref:`Architecture Subdomains Reference`. This works well when an event belongs
to a single subdomain.

The `edX DDD Bounded Contexts`_ documentation classifies subdomains as core,
supporting, or generic. Supporting subdomains provide capabilities that multiple
core subdomains depend on, without belonging to any of them. ``analytics`` is the
existing example in ``openedx-events``.

Authorization has the same character: role assignment events originate from
``openedx-authz`` and are consumed across learning, content authoring, enterprise,
and other areas. Its domain definition is independent of any single application,
so the ``authz`` module introduced in this ADR is classified as supporting.

Prior to this decision, there was no explicit guidance for supporting subdomain
events, leaving contributors to make ad-hoc placement choices.

Decision
********

We introduce dedicated top-level modules in ``openedx-events`` for supporting
subdomains when their events cannot be meaningfully attributed to a single
existing domain module.

A supporting subdomain module is warranted when:

* The subdomain provides a capability that multiple core subdomains depend on.
* The events are meaningful to consumers across existing domain modules without
a clear primary owner among them.
* Placing the events in any single existing domain module would reflect a
current implementation detail rather than a stable domain boundary.

The ``authz`` module introduced in this branch applies this pattern to
authorization events. The existing ``analytics`` module is recognized as a prior
instance of the same concept.

Consequences
************

1. Supporting subdomain events have a principled home grounded in the Open edX
DDD taxonomy, rather than an arbitrary placement.
2. The pattern is consistent with how ``analytics`` is already organized, and
both are now explicitly grounded in the same classification.
3. Supporting subdomain modules can evolve independently of any single
application's release cycle.
4. Deciding whether a concern qualifies as a supporting subdomain requires
judgment. Without discipline, this can lead to module proliferation.

Rejected Alternatives
*********************

* **Place authorization events under ``course_authoring``**: role assignment is
currently performed through an admin console accessed via Studio, but that is
a transitional implementation detail. The admin console is planned to evolve
into its own application, and role assignment is semantically an authorization
concern, not a content authoring one.
* **Create a generic ``admin`` module**: authorization has a self-contained domain
definition that stands independently of any UI surface. "Admin" describes a user
role and an interface where tasks from multiple domains are aggregated - the
tasks themselves belong to their respective domains.
* **Extend an existing module with a subdirectory**: this would misrepresent the
domain ownership of the events and contradict the existing top-level module
structure.

References
**********

- `edX DDD Bounded Contexts`_
- :ref:`Architecture Subdomains Reference`

.. _edX DDD Bounded Contexts: https://openedx.atlassian.net/wiki/spaces/AC/pages/663224968/edX+DDD+Bounded+Contexts
1 change: 1 addition & 0 deletions docs/decisions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ Architectural Decision Records (ADRs)
0015-outbox-pattern-and-production-modes
0016-event-design-practices
0017-event-signal-for-external-grader-score-submission
0018-supporting-subdomain-modules
6 changes: 6 additions & 0 deletions openedx_events/authz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
Package for events related to the Open edX authorization framework.

This package is not attached to a specific subdomain, as the events defined
here are used across multiple subdomains.
"""
27 changes: 27 additions & 0 deletions openedx_events/authz/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Data attributes for events related to the authorization framework."""

import attr


@attr.s(frozen=True)
class RoleAssignmentData:
"""
Data related to a specific role assignment.

A role assignment represents the assignment of a role to a subject (e.g., user)
within a specific scope (e.g., course, organization).

Attributes:
operation (str): The operation being performed (e.g., 'created', 'deleted').
subject (str): The subject to which the role is assigned (e.g., 'user^john_doe').
role (str): The role that is assigned (e.g., 'course_admin').
scope (str): The scope in which the role is assigned (e.g., 'course-v1:edX+DemoX+Demo_Course').
actor (str): Username of the user performing the operation.
None if not known or not applicable (system-initiated actions).
"""

operation = attr.ib(type=str)
subject = attr.ib(type=str)
role = attr.ib(type=str)
scope = attr.ib(type=str)
actor = attr.ib(type=str, default=None)
36 changes: 36 additions & 0 deletions openedx_events/authz/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Standard Open edX events related to the Open edX authorization framework.

This module defines signals that are used to notify other parts of the system
about changes or actions related to authorization.
"""

from openedx_events.authz.data import RoleAssignmentData
from openedx_events.tooling import OpenEdxPublicSignal

# .. event_type: org.openedx.authz.role_assignment.created
# .. event_name: ROLE_ASSIGNMENT_CREATED
# .. event_key_field: user.pii.username
# .. event_description: Emitted when a role assignment is created in Open edX.
# .. event_data: RoleAssignmentData
# .. event_trigger_repository: openedx/openedx-authz
ROLE_ASSIGNMENT_CREATED = OpenEdxPublicSignal(
event_type="org.openedx.authz.role_assignment.created",
data={
"role_assignment": RoleAssignmentData,
}
)


# .. event_type: org.openedx.authz.role_assignment.deleted
# .. event_name: ROLE_ASSIGNMENT_DELETED
# .. event_key_field: user.pii.username
# .. event_description: Emitted when a role assignment is deleted in Open edX.
# .. event_data: RoleAssignmentData
# .. event_trigger_repository: openedx/openedx-authz
ROLE_ASSIGNMENT_DELETED = OpenEdxPublicSignal(
event_type="org.openedx.authz.role_assignment.deleted",
data={
"role_assignment": RoleAssignmentData,
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "CloudEvent",
"type": "record",
"doc": "Avro Event Format for CloudEvents created with openedx_events/schema",
"fields": [
{
"name": "role_assignment",
"type": {
"name": "RoleAssignmentData",
"type": "record",
"fields": [
{
"name": "operation",
"type": "string"
},
{
"name": "subject",
"type": "string"
},
{
"name": "role",
"type": "string"
},
{
"name": "scope",
"type": "string"
},
{
"name": "actor",
"type": [
"null",
"string"
],
"default": null
}
]
}
}
],
"namespace": "org.openedx.authz.role_assignment.created"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "CloudEvent",
"type": "record",
"doc": "Avro Event Format for CloudEvents created with openedx_events/schema",
"fields": [
{
"name": "role_assignment",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question, I’m not familiar with these events. Should we add a suffix like _delete here?

"type": {
"name": "RoleAssignmentData",
"type": "record",
"fields": [
{
"name": "operation",
"type": "string"
},
{
"name": "subject",
"type": "string"
},
{
"name": "role",
"type": "string"
},
{
"name": "scope",
"type": "string"
},
{
"name": "actor",
"type": [
"null",
"string"
],
"default": null
}
]
}
}
],
"namespace": "org.openedx.authz.role_assignment.deleted"
}