Skip to content

Commit

Permalink
docs: explain how the event bus works and how to use it
Browse files Browse the repository at this point in the history
  • Loading branch information
mariajgrimaldi committed Nov 29, 2024
1 parent a0e1f1e commit dbeabca
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 57 deletions.
3 changes: 3 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ help:
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

serve_docs: ## serve the built docs locally to preview the site in the browser
sphinx-autobuild . $(BUILDDIR)/html
Binary file added docs/_images/event-bus-overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/event-bus-workflow-service-a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/event-bus-workflow-service-b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions docs/concepts/event-bus.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
Open edX Event Bus
==================

Overview
--------

The suggested strategy for cross-service communication in the Open edX ecosystem is through an event-based architecture implemented via the event bus. This functionality used for asynchronous communication between services is built on top of sending Open edX Events (Open edX-specific Django signals) within a service.

What is the Open edX Event Bus?
-------------------------------

The event bus implements an event-driven architecture that enables asynchronous communication between services using the `publish/subscribe messaging pattern`_ (pub/sub). In the Open edX ecosystem, the event bus is used to broadcast Open edX Events to multiple services, allowing them to react to changes or actions in the system. The event bus is a key component of the Open edX architecture, enabling services to communicate without direct dependencies on each other.

.. image:: ../_images/event-bus-overview.png
:alt: Open edX Event Bus Overview
:align: center

Why use the Open edX Event Bus?
-------------------------------

The event bus can help us achieve loose coupling between services, replacing blocking requests between services and large sync jobs, leading to a faster, more reliable, and more extensible system. See event messaging architectural goals highlighted in :doc:`openedx-proposals:architectural-decisions/oep-0041-arch-async-server-event-messaging` to read more about its benefits. Here's a brief summary of some key points:

* **Eliminate Blocking, Synchronous Requests**: reduce site outages when services become overloaded with requests by replacing synchronous calls with asynchronous communication.
* **Eliminate Expensive, Delayed, Batch Synchronization**: replace expensive batch processing with near real-time data updates.
* **Reduce the need for Plugins**: reduce the computational load for plugins that don't need to run in the same process by allowing cross-service communication of lifecycle events.

How Does the Open edX Event Bus Work?
-------------------------------------

The Open edX platform uses the ``OpenEdxPublicSignals`` (Open edX-specific Django Signals) to send events within a service. The event bus extends these signals, allowing them to be broadcast and handled across multiple services. That's how Open edX Events are used for internal and external communication. For more details on how these Open edX-specific Django Signals are used by the event bus, refer to the :doc:`../decisions/0004-external-event-bus-and-django-signal-events` Architectural Decision Record (ADR).

Open edX Events provides an abstract implementation of the `publish/subscribe messaging pattern`_ (pub/sub) which is the chosen pattern for the event bus implementation as explained in :doc:`openedx-proposals:architectural-decisions/oep-0052-arch-event-bus-architecture`. It implements two abstract classes, `EventProducer`_ and `EventConsumer`_ which allow concrete implementations of the event bus based on different message brokers, such as Pulsar.

This abstraction allows for developers to implement their own concrete implementations of the event bus in their own plugins, there are currently two implementations of the event bus, Redis (`event-bus-redis`_) and Kafka (`event-bus-kafka`_). Redis streams is a data structure that acts like an append-only log, and Kafka is a distributed event streaming application. Both implementations handle event production and consumption with technology specific methods.

Architectural Diagram
*********************

These diagrams show what happens when an event is sent to the event bus. The event sending workflow follows the same steps as explained in :ref:`events-architecture`, with a key difference: when configured, the event bus recognizes events and publish them to the message broker for consumption by other services.

Components
~~~~~~~~~~

* **Service A (Producer)**: The service that emits the event. Developers may have emitted this event in a key section of the application logic, signaling that a specific action has occurred.
* **Service B (Consumer)**: The service that listens for the event and executes custom logic in response.
* **OpenEdxPublicSignal**: The class that implements all methods used to manage sending the event. This class inherits from Django's Signals class and adds Open edX-specific metadata and behaviors.
* **Event Producer**: The component that sends events to the event bus. The producer serializes the event data and enriches it with relevant metadata for the consumer.
* **Event Consumer**: The component that receives events from the event bus. The consumer deserializes the message and re-emits it as an event with the data that was transmitted.
* **Message Broker**: The technology that manages the message queue and ensures that messages are delivered to the correct consumers. The message broker is responsible for storing and delivering messages between the producer and consumer.
* **Event Bus Plugin**: The concrete implementation of the event bus based on a specific message broker, such as Pulsar. The plugin handles event production and consumption with technology-specific methods.

Workflow
~~~~~~~~

.. image:: ../_images/event-bus-workflow-service-a.png
:alt: Open edX Event Bus Workflow (Service A)
:align: center

**From Service A (Producer)**

1. When the event is sent, a registered event receiver `general_signal_handler`_ is called to send the event to the event bus. This receiver is registered by the Django Signal mechanism when the ``openedx-events`` app is installed, and it listens for all Open edX Events.
2. The receiver checks the ``EVENT_BUS_PRODUCER_CONFIG`` to look for the ``event_type`` of the event that was sent.
3. If the event type is found and it's enabled for publishing in the configuration, the receiver obtains the configured producer class (``EventProducer``) from the concrete event bus implementation. For example, the producer class for Redis or Kafka implemented in their respective plugins.
4. The ``EventProducer`` serializes the event data and enriches it with relevant metadata for the consumer.
5. The producer uses its technology-specific ``send`` method to publish a message to the configured broker (e.g., Redis or Kafka).

.. image:: ../_images/event-bus-workflow-service-b.png
:alt: Open edX Event Bus Workflow (Service B)
:align: center

**From Service B (Consumer)**

1. A worker process in Service B runs indefinitely, checking the broker for new messages.
2. When a new message is found, the ``EventConsumer`` deserializes the message and re-emits it as an event with the data that was transmitted.
3. The event sending and processing workflow repeats in Service B.

This approach of producing events via settings with the generic handler was chosen to allow for flexibility in the event bus implementation. It allows developers to choose the event bus implementation that best fits their needs, and to easily switch between implementations if necessary. See more details in the :doc:`../decisions/0012-producing-to-event-bus-via-settings` Architectural Decision Record (ADR).

How is the Open edX Event Bus Used?
-----------------------------------

The event bus is used to broadcast Open edX Events to multiple services, allowing them to react to changes or actions in the system. The event bus is a key component of the Open edX architecture, enabling services to communicate without direct dependencies on each other.

We encourage you to review the :doc:`../reference/real-life-use-cases` page for examples of how the event bus can be used in the Open edX ecosystem. Also, see the :doc:`../how-tos/using-the-event-bus` guide to get start sending events to the event bus.

.. _general_signal_handler: https://github.com/openedx/openedx-events/blob/main/openedx_events/apps.py#L16-L44
.. _EventProducer: https://github.com/openedx/openedx-events/blob/main/openedx_events/event_bus/__init__.py#L71-L91
.. _EventConsumer: https://github.com/openedx/openedx-events/blob/main/openedx_events/event_bus/__init__.py#L128-L139
.. _publish/subscribe messaging pattern: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
.. _event-bus-redis: https://github.com/openedx/event-bus-redis/
.. _event-bus-kafka: https://github.com/openedx/event-bus-kafka/
1 change: 1 addition & 0 deletions docs/concepts/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Concepts
:caption: Contents:

openedx-events
event-bus
2 changes: 2 additions & 0 deletions docs/concepts/openedx-events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ How do Open edX Events work?

Open edX Events are implemented by a class called `OpenEdxPublicSignal`_, which inherits from `Django's Signals class` and adds behaviors specific to the Open edX ecosystem. Thanks to this design, ``OpenEdxPublicSignal`` leverages the functionality of Django signals, allowing developers to apply their existing knowledge of the Django framework.

.. _events-architecture:

Architectural Diagram
*********************

Expand Down
7 changes: 7 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
'sphinxcontrib.mermaid',
'code_annotations.contrib.sphinx.extensions.openedx_events',
'sphinx.ext.intersphinx',
'code_annotations.contrib.sphinx.extensions.settings',
]

# Add any paths that contain templates here, relative to this directory.
Expand Down Expand Up @@ -131,4 +132,10 @@
f"https://docs.openedx.org/{rtd_language}/{rtd_version}",
None,
),
"openedx-proposals": (
# Not setting the version on purpose because we always want the latest version
# of OEPs
f"https://docs.openedx.org/projects/openedx-proposals/{rtd_language}/latest",
None,
),
}
42 changes: 0 additions & 42 deletions docs/how-tos/adding-events-to-event-bus.rst

This file was deleted.

2 changes: 1 addition & 1 deletion docs/how-tos/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ How-tos

creating-new-events
adding-events-to-a-service
adding-events-to-event-bus
using-events
using-the-event-bus
add-new-event-bus-concrete-implementation
79 changes: 79 additions & 0 deletions docs/how-tos/using-the-event-bus.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
Using the Open edX Event Bus
============================

After creating a new Open edX Event, you might need to send it across services instead of just within the same process. For this kind of use-cases, you might want to use the Open edX Event Bus. Here :doc:`../concepts/event-bus` you can find useful information about the event bus.

The Open edX Event Bus is a key component of the Open edX architecture, enabling services to communicate without direct dependencies on each other. This guide provides an overview of how to use the event bus to broadcast Open edX Events to multiple services, allowing them to react to changes or actions in the system.

Prerequisites
-------------

Before you start using the event bus, you need to have the following:

- A service that will consume the event
- A service that will produce the event
- The Open edX Event Bus concrete implementation (Django plugin) installed in both services
- The message broker (e.g., Kafka or Redis) set up
- The Open edX Event Bus configuration set up in both services

Configurations
--------------

Here are the available configurations for the event bus we'll be using:

.. settings::
:folder_path: openedx_events/event_bus

Setup
-----

To start producing and consuming events using the Open edX Event Bus, follow these steps:

#. **Produce the event**

In the producing/host application, include ``openedx_events`` in ``INSTALLED_APPS`` settings and add ``EVENT_BUS_PRODUCER_CONFIG`` setting. This setting is a dictionary of event_types to dictionaries for topic-related configuration. Each topic configuration dictionary uses the topic as a key and contains:

- A flag called ``enabled`` denoting whether the event will be published.
- The ``event_key_field`` which is a period-delimited string path to event data field to use as event key.

.. note:: The topic names should not include environment prefix as it will be dynamically added based on ``EVENT_BUS_TOPIC_PREFIX`` setting.

Here's an example of the producer configuration:

.. code-block:: python
EVENT_BUS_PRODUCER_CONFIG = {
'org.openedx.content_authoring.xblock.published.v1': {
'content-authoring-xblock-lifecycle': {'event_key_field': 'xblock_info.usage_key', 'enabled': True},
'content-authoring-xblock-published': {'event_key_field': 'xblock_info.usage_key', 'enabled': True}
},
'org.openedx.content_authoring.xblock.deleted.v1': {
'content-authoring-xblock-lifecycle': {'event_key_field': 'xblock_info.usage_key', 'enabled': True},
},
}
The ``EVENT_BUS_PRODUCER_CONFIG`` is read by ``openedx_events`` and a handler is attached which does the leg work of reading the configuration again and pushing to appropriate handlers.

To let the openedx-events know about which event bus implementation to use (e.g., Kafka or Redis), you need to set the ``EVENT_BUS_PRODUCER`` setting. This setting should be the dotted path to the concrete implementation class.

#. **Consume the Event**

In the consuming service, include ``openedx_events`` in ``INSTALLED_APPS`` settings and add ``EVENT_BUS_CONSUMER_CONFIG`` setting. Then, you should implement a receiver for the event type you are interested in.


.. code-block:: python
@receiver(XBLOCK_DELETED)
def update_some_data(sender, **kwargs):
... do things with the data in kwargs ...
... log the event for debugging purposes ...
To let the openedx-events know about which event bus implementation to use (e.g., Kafka or Redis), you need to set the ``EVENT_BUS_CONSUMER`` setting. This setting should be the dotted path to the concrete implementation class.
#. **Run the consumer**: Run the consumer process in the consuming service to listen for events.
#. **Send the event**: Send the event from the producing service.
#. **Check the consumer**: Check the consumer logs to see if the event was received.
.. TODO: add more details about how to run the consumer and send the event. https://github.com/openedx/openedx-events/blob/main/openedx_events/management/commands/consume_events.py
25 changes: 14 additions & 11 deletions docs/reference/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@ An event has multiple components that are used to define, trigger, and handle th
An event is a signal that is emitted when a specific action occurs in the platform. The event definition is the instantiation of the ``OpenEdxPublicSignal`` class that defines the structure and metadata of an event. This definition includes information such as the event name, description, payload, and version. Event definitions are used to create events which are later imported into the services and are triggered by using the ``send_event`` method.

Event Bus
To be added soon.
Code that handles asynchronous message transfer between services.

Event Bus Producer
To be added soon.
Message
A unit of information sent from one service to another via a message broker.

Event Bus Consumer
To be added soon.
Message Broker
A service that receives, organizes, and stores messages so other services can query and retrieve them.

Event Bus Topic
To be added soon.
Worker
A machine or process that runs service code without hosting the web application. Workers are used to process messages from the message broker.

Event Bus Producer Config
To be added soon.
Producer
A service that sends events to the event bus. The producer serializes the event data and enriches it with relevant metadata for the consumer.

Event Bus Settings
To be added soon.
Consumer
A service that receives events from the event bus. The consumer deserializes the message and re-emits it as an event with the data that was transmitted.

Topic
How the event bus implementation groups related events, such as streams in Redis. Producers publish events to topics, and consumers subscribe to topics to receive events.

.. _Events Payload ADR: :doc: `/decisions/0003-events-payload`
4 changes: 1 addition & 3 deletions docs/reference/real-life-use-cases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ The following list of real-life use cases showcases the different ways Open edX
Cross-services communication
****************************

The suggested strategy for cross-service communication efficiently in the Open edX ecosystem is through an event-based architecture implemented via the Event Bus. This functionality used for asynchronous communication between services is built on top of sending Open edX Events (Open edX-specific Django signals) within a service. The Event Bus extends these signals, allowing them to be broadcast and handled across multiple services. For more details on the Event Bus, please see `How to Start Using the Event Bus`_.

.. TODO: replace event bus confluence page with native docs
As mentioned in :doc:`../concepts/event-bus`, the suggested strategy for cross-service communication in the Open edX ecosystem is through an event-based architecture implemented via the Event Bus. This functionality used for asynchronous communication between services is built on top of sending Open edX Events (Open edX-specific Django signals) within a service. For more details on the Event Bus, please see :doc:`../how-tos/using-the-event-bus`.

Here are some examples of how the Event Bus can be used to facilitate communication between IDAs:

Expand Down
Loading

0 comments on commit dbeabca

Please sign in to comment.