From 684283fea2c8b2f4767f7957ad2eedae573ebb95 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Tue, 17 Dec 2024 22:29:03 +0100 Subject: [PATCH 01/14] docs: add ADR with event design suggestions based on industry standards --- .../decisions/0016-event-design-practices.rst | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/decisions/0016-event-design-practices.rst diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst new file mode 100644 index 00000000..648e6099 --- /dev/null +++ b/docs/decisions/0016-event-design-practices.rst @@ -0,0 +1,33 @@ +Event Design Practices +###################### + +Status +------ + +Proposed + +Context +------- + +It is important to follow standards to ensure that the events are consistent, maintainable, and reusable. The design of the events should be self-descriptive, self-contained, and provide enough information for consumers to understand the message. This ADR aims to provide a set of suggested practices for designing Open edX events that are consistent with the architecture and contribute to the overall quality of the Open edX ecosystem. + +Decision +-------- + +We have compiled a list of suggested practices taken from `Event-Driven Microservices`_ and the `Event-Driven article`_ that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. + +#. An event should describe as accurately as possible what happened (what) with its and why it happened (why). It must contain enough information for consumers to understand the message. +#. Design events to be self-descriptive and self-contained. It should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. +#. Avoid ambiguous data fields or fields with multiple meaning. +#. Design events with a single responsibility in mind. Each event should represent a single action or fact. +#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what happened. +#. Use appropriate data types and formats for the event fields. +#. Design the event so it is small, well-defined and only contain relevant information. Avoid including unnecessary or unrelated context. +#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. +#. Manage the granularity of the event so it is not too course (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. +#. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? + +Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. + +.. _Event-Driven Microservices: https://www.oreilly.com/library/view/building-event-driven-microservices/9781492057888/ +.. _Event-Driven article: https://martinfowler.com/articles/201701-event-driven.html From 724e87f6abc74e1af6f428477ffd0e8965a2efe6 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Tue, 17 Dec 2024 23:37:20 +0100 Subject: [PATCH 02/14] docs: apply suggestions from code review --- docs/decisions/0016-event-design-practices.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 648e6099..c56ab6b8 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -4,7 +4,7 @@ Event Design Practices Status ------ -Proposed +Draft Context ------- @@ -16,15 +16,15 @@ Decision We have compiled a list of suggested practices taken from `Event-Driven Microservices`_ and the `Event-Driven article`_ that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. -#. An event should describe as accurately as possible what happened (what) with its and why it happened (why). It must contain enough information for consumers to understand the message. -#. Design events to be self-descriptive and self-contained. It should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. +#. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. +#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. #. Avoid ambiguous data fields or fields with multiple meaning. -#. Design events with a single responsibility in mind. Each event should represent a single action or fact. -#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what happened. +#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. +#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. #. Use appropriate data types and formats for the event fields. #. Design the event so it is small, well-defined and only contain relevant information. Avoid including unnecessary or unrelated context. #. Ensure the event carries all necessary data to prevent runtime dependencies with other services. -#. Manage the granularity of the event so it is not too course (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. +#. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. From 81ea9f3227e858a5048257cca42f547e169adeb5 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 00:18:19 +0100 Subject: [PATCH 03/14] docs: improve suggestions with examples --- .../decisions/0016-event-design-practices.rst | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index c56ab6b8..11ec02fc 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -1,5 +1,5 @@ -Event Design Practices -###################### +16. Event Design Practices +########################### Status ------ @@ -9,21 +9,20 @@ Draft Context ------- -It is important to follow standards to ensure that the events are consistent, maintainable, and reusable. The design of the events should be self-descriptive, self-contained, and provide enough information for consumers to understand the message. This ADR aims to provide a set of suggested practices for designing Open edX events that are consistent with the architecture and contribute to the overall quality of the Open edX ecosystem. +It is important to follow standards to ensure that the events are consistent, maintainable, and reusable. The design of the events should be self-descriptive, self-contained, and provide enough information for consumers to understand the message. This ADR aims to provide a set of suggested practices for designing Open edX events. Decision -------- We have compiled a list of suggested practices taken from `Event-Driven Microservices`_ and the `Event-Driven article`_ that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. -#. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. -#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. -#. Avoid ambiguous data fields or fields with multiple meaning. -#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. -#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. -#. Use appropriate data types and formats for the event fields. -#. Design the event so it is small, well-defined and only contain relevant information. Avoid including unnecessary or unrelated context. -#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. +#. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. +#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. This is known as the `Event-Carried State Transfer pattern`_. +#. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. +#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. +#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. So, design the event so it is small, well-defined and only contain relevant information. +#. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. +#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to process the event. This might be not always possible, but it is good to keep in mind. #. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? From 66846f47584a17c42fe80995d26bc26fbea41095 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 00:21:03 +0100 Subject: [PATCH 04/14] docs: drop references to event carried state transfer pattern --- docs/decisions/0016-event-design-practices.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 11ec02fc..b72f9bbf 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -17,12 +17,12 @@ Decision We have compiled a list of suggested practices taken from `Event-Driven Microservices`_ and the `Event-Driven article`_ that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. #. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. -#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. This is known as the `Event-Carried State Transfer pattern`_. +#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. #. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. #. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. So, design the event so it is small, well-defined and only contain relevant information. #. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. -#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to process the event. This might be not always possible, but it is good to keep in mind. +#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind. #. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? From dc51ca3a89924aa4c7f5039e24807bbfbdf4fa83 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 00:43:48 +0100 Subject: [PATCH 05/14] docs: add suggestion about thinking about the design of events --- docs/decisions/0016-event-design-practices.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index b72f9bbf..5548dd1d 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -22,9 +22,10 @@ We have compiled a list of suggested practices taken from `Event-Driven Microser #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. #. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. So, design the event so it is small, well-defined and only contain relevant information. #. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. -#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind. +#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind to decrease the coupling between services. #. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? +#. Design events carefully to minimize breaking changes for consumers. Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. From 0f86b3d5710f1dc6b630fc4b98d3c8f56bf986ec Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 00:45:43 +0100 Subject: [PATCH 06/14] docs: reference new ADR in index tree --- docs/decisions/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/decisions/index.rst b/docs/decisions/index.rst index 1a1f053a..9bbea87d 100644 --- a/docs/decisions/index.rst +++ b/docs/decisions/index.rst @@ -20,3 +20,4 @@ Architectural Decision Records (ADRs) 0013-special-exam-submission-and-review-events 0014-new-event-bus-producer-config 0015-outbox-pattern-and-production-modes + 0016-event-design-practices From c71bb7fdbbff02a8346cc9c18375b335c0b59fde Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 01:00:00 +0100 Subject: [PATCH 07/14] docs: organize into clusters --- docs/decisions/0016-event-design-practices.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 5548dd1d..f82892ea 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -18,16 +18,18 @@ We have compiled a list of suggested practices taken from `Event-Driven Microser #. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. #. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. +#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind to decrease the coupling between services. +#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. +#. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. #. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. -#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. So, design the event so it is small, well-defined and only contain relevant information. -#. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. -#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind to decrease the coupling between services. #. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? #. Design events carefully to minimize breaking changes for consumers. -Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. +Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. So, design the event so it is small, well-defined and only contain relevant information. + +In addition to these practices, review the Architectural Decision Records (ADRs) related to events to understand the naming, versioning, payload, and other practices that are specific to Open edX events. .. _Event-Driven Microservices: https://www.oreilly.com/library/view/building-event-driven-microservices/9781492057888/ .. _Event-Driven article: https://martinfowler.com/articles/201701-event-driven.html From 608bd4341bdb3d4ea69df783a1410767660b52bd Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 12:44:40 +0100 Subject: [PATCH 08/14] docs: remove duplicate unnecessary point --- docs/decisions/0016-event-design-practices.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index f82892ea..865a65b0 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -14,18 +14,23 @@ It is important to follow standards to ensure that the events are consistent, ma Decision -------- -We have compiled a list of suggested practices taken from `Event-Driven Microservices`_ and the `Event-Driven article`_ that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. +We have compiled a list of suggested practices taken from the following sources: + +- `Event-Driven Microservices`_ +- `Event-Driven article`_ +- `Thin Events - The lean muscle of event-driven architecture`_ + +These are the practices that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. #. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. #. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. -#. Ensure the event carries all necessary data to prevent runtime dependencies with other services. An event doesn't need to contain all the data of the entities involved, but it should contain enough information for consumers to not immediately need to query other services to understand what took place. This might be not always possible, but it is good to keep in mind to decrease the coupling between services. -#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. +#. Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls to retrieve additional information but always consider the trade-offs of adding dependencies with other services. +#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. +#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. #. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. #. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. -#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. -#. Manage the granularity of the event so it is not too coarse (generic name with too much information) or too fine-grained (specific name with too little information). It should be expressive enough to be useful. #. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? -#. Design events carefully to minimize breaking changes for consumers. +#. Design events carefully from the start to minimize breaking changes for consumers, although it is not always possible to avoid breaking changes. Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. So, design the event so it is small, well-defined and only contain relevant information. @@ -33,3 +38,4 @@ In addition to these practices, review the Architectural Decision Records (ADRs) .. _Event-Driven Microservices: https://www.oreilly.com/library/view/building-event-driven-microservices/9781492057888/ .. _Event-Driven article: https://martinfowler.com/articles/201701-event-driven.html +.. _Thin Events - The lean muscle of event-driven architecture: https://www.thoughtworks.com/insights/blog/architecture/thin-events-the-lean-muscle-of-event-driven-architecture From e8c16da81c9dcd8896162af69d0036827152bd24 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 12:46:49 +0100 Subject: [PATCH 09/14] docs: remove duplicated entry --- docs/decisions/0016-event-design-practices.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 865a65b0..804086c1 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -23,7 +23,6 @@ We have compiled a list of suggested practices taken from the following sources: These are the practices that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. #. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. -#. Design events to be self-descriptive and self-contained. The event should contain all the information necessary about what took place for consumers to react to the event without consulting other data sources. #. Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls to retrieve additional information but always consider the trade-offs of adding dependencies with other services. #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. #. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. From 361d47896239d22b977d27060ea55212596d650a Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 12:50:46 +0100 Subject: [PATCH 10/14] docs: add note about avoiding calls to the source service --- docs/decisions/0016-event-design-practices.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 804086c1..0cea2d00 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -24,6 +24,7 @@ These are the practices that we recommend reviewing and following when designing #. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. #. Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls to retrieve additional information but always consider the trade-offs of adding dependencies with other services. +#. Avoid immediately contacting the source service to retrieve additional information. Instead, consider adding the necessary information to the event payload by managing the granularity of the event. If the event requires additional information, consider adding a field to the event that contains the necessary information. This will reduce the number of dependencies between services and make the event more self-contained. #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. #. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. #. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. From 63435f7610d7bdf5e4c074c5a64b40d04cd225c4 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 18 Dec 2024 13:00:10 +0100 Subject: [PATCH 11/14] docs: add note about triggering logic --- docs/decisions/0016-event-design-practices.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 0cea2d00..8bfc080a 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -26,6 +26,7 @@ These are the practices that we recommend reviewing and following when designing #. Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls to retrieve additional information but always consider the trade-offs of adding dependencies with other services. #. Avoid immediately contacting the source service to retrieve additional information. Instead, consider adding the necessary information to the event payload by managing the granularity of the event. If the event requires additional information, consider adding a field to the event that contains the necessary information. This will reduce the number of dependencies between services and make the event more self-contained. #. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. +#. Ensure that the triggering logic is consistent and narrow. For instance, if an event is triggered when a user enrolls in a course, it should be trigger only when the user enrolls in a course in all ways possible to enroll in a course. If the event is triggered when a user enrolls in a course through the API, it should also be triggered when the user enrolls in a course through the UI. #. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. #. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. #. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. From 41cda0f5d9ded8d278e7fef7687a29d6043afc2c Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Thu, 19 Dec 2024 11:47:05 +0100 Subject: [PATCH 12/14] docs: cluster decisions into categories for better understanding --- .../decisions/0016-event-design-practices.rst | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 8bfc080a..3f738a10 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -22,16 +22,32 @@ We have compiled a list of suggested practices taken from the following sources: These are the practices that we recommend reviewing and following when designing an Open edX Event and contributing to the library. The goal is to implement events that are consistent with the architecture, reusable, and maintainable over time. -#. An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. -#. Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls to retrieve additional information but always consider the trade-offs of adding dependencies with other services. -#. Avoid immediately contacting the source service to retrieve additional information. Instead, consider adding the necessary information to the event payload by managing the granularity of the event. If the event requires additional information, consider adding a field to the event that contains the necessary information. This will reduce the number of dependencies between services and make the event more self-contained. -#. Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. -#. Ensure that the triggering logic is consistent and narrow. For instance, if an event is triggered when a user enrolls in a course, it should be trigger only when the user enrolls in a course in all ways possible to enroll in a course. If the event is triggered when a user enrolls in a course through the API, it should also be triggered when the user enrolls in a course through the UI. -#. Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. -#. Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. -#. Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. -#. When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? -#. Design events carefully from the start to minimize breaking changes for consumers, although it is not always possible to avoid breaking changes. +Event Purpose and Content +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- An event should describe as accurately as possible what happened (what) and why it happened (why). It must contain enough information for consumers to understand the message. For instance, if an event is about a user enrollment, it should contain the user's data, the course data, and the enrollment status and the event should be named accordingly. +- Avoid immediately contacting the source service to retrieve additional information. Instead, consider adding the necessary information to the event payload by managing the granularity of the event. If the event requires additional information, consider adding a field to the event that contains the necessary information. This will reduce the number of dependencies between services and make the event more self-contained. +- Keep the event size small. Avoid adding unnecessary information to the event. If the information is not necessary for consumers to react to the event, consider removing it. +- Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. + +Responsibility and Granularity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Design events with a single responsibility in mind. Each event should represent a single action or fact that happened in the system. If an event contains multiple actions, consider splitting it into multiple events. For instance, if the course grade is updated to pass or fail, there should be two events: one for the pass action and another for the fail action. +- Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls from the consumer side to retrieve additional information but always consider the trade-offs of adding dependencies with other services. +- Ensure that the triggering logic is consistent and narrow. For instance, if an event is triggered when a user enrolls in a course, it should be trigger only when the user enrolls in a course in all ways possible to enroll in a course. If the event is triggered when a user enrolls in a course through the API, it should also be triggered when the user enrolls in a course through the UI. + +Event Structure and Clarity +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. +- Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. + +Consumer-Centric Design +~~~~~~~~~~~~~~~~~~~~~~~ + +- When designing an event, consider the consumers that will be using it. What information do they need to react to the event? What data is necessary for them to process the event? +- Design events carefully from the start to minimize breaking changes for consumers, although it is not always possible to avoid breaking changes. Some of these practices might not be applicable to all events, but they are a good starting point to ensure that the events are consistent and maintainable over time. So, design the event so it is small, well-defined and only contain relevant information. From 3be70eedfda5021a864290f9c8cef1147ab41435 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Mon, 30 Dec 2024 13:25:04 +0100 Subject: [PATCH 13/14] docs: add examples to illustrate ADR practices --- .../decisions/0016-event-design-practices.rst | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index 3f738a10..e048e67a 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -30,6 +30,52 @@ Event Purpose and Content - Keep the event size small. Avoid adding unnecessary information to the event. If the information is not necessary for consumers to react to the event, consider removing it. - Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. +Here is an example of an event that follows these practices ``COURSE_ENROLLMENT_CREATED``: + +.. code-block:: python + + # Location openedx_events/learning/signal.py + # .. event_type: org.openedx.learning.course.enrollment.created.v1 + # .. event_name: COURSE_ENROLLMENT_CREATED + # .. event_description: emitted when the user's enrollment process is completed. + # .. event_data: CourseEnrollmentData + COURSE_ENROLLMENT_CREATED = OpenEdxPublicSignal( + event_type="org.openedx.learning.course.enrollment.created.v1", + data={ + "enrollment": CourseEnrollmentData, + } + ) + +Where: + +- The event name indicates what happened: ``COURSE_ENROLLMENT_CREATED``. +- The event description explains why the event happened: ``emitted when the user's enrollment process is completed``. +- The event data contains data directly related to what happened ``CourseEnrollmentData``: + +.. code-block:: python + + # Location openedx_events/learning/data.py + @attr.s(frozen=True) + class CourseEnrollmentData: + """ + Attributes defined for Open edX Course Enrollment object. + + Arguments: + user (UserData): user associated with the Course Enrollment. + course (CourseData): course where the user is enrolled in. + mode (str): course mode associated with the course enrollment. + is_active (bool): whether the enrollment is active. + creation_date (datetime): creation date of the enrollment. + created_by (UserData): if available, who created the enrollment. + """ + + user = attr.ib(type=UserData) + course = attr.ib(type=CourseData) + mode = attr.ib(type=str) + is_active = attr.ib(type=bool) + creation_date = attr.ib(type=datetime) + created_by = attr.ib(type=UserData, default=None) + Responsibility and Granularity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -37,12 +83,58 @@ Responsibility and Granularity - Manage the granularity of the event so it is not too coarse (generic with too much information) or too fine-grained (specific with too little information). When making a decision on the granularity of the event, start with the minimum required information for consumers to react to the event and add more information as needed with enough justification. If necessary, leverage API calls from the consumer side to retrieve additional information but always consider the trade-offs of adding dependencies with other services. - Ensure that the triggering logic is consistent and narrow. For instance, if an event is triggered when a user enrolls in a course, it should be trigger only when the user enrolls in a course in all ways possible to enroll in a course. If the event is triggered when a user enrolls in a course through the API, it should also be triggered when the user enrolls in a course through the UI. +For instance, consider the following events: + +.. code-block:: python + + # Location openedx_events/learning/signal.py + # .. event_type: org.openedx.learning.course.grade.passed.v1 + # .. event_name: COURSE_GRADE_PASSED + # .. event_description: emitted when the user's course grade is updated to pass. + # .. event_data: CourseGradeData + COURSE_GRADE_PASSED = OpenEdxPublicSignal( + event_type="org.openedx.learning.course.grade.passed.v1", + data={ + "grade": CourseGradeData, + } + ) + + # Location openedx_events/learning/signal.py + # .. event_type: org.openedx.learning.course.grade.failed.v1 + # .. event_name: COURSE_GRADE_FAILED + # .. event_description: emitted when the user's course grade is updated to fail. + # .. event_data: CourseGradeData + COURSE_GRADE_FAILED = OpenEdxPublicSignal( + event_type="org.openedx.learning.course.grade.failed.v1", + data={ + "grade": CourseGradeData, + } + ) + +Where: + +- The event name indicates what happened: ``COURSE_GRADE_PASSED`` and ``COURSE_GRADE_FAILED``. +- The event description explains why the event happened: ``emitted when the user's course grade is updated to pass`` and ``emitted when the user's course grade is updated to fail``. +- The event data contains data directly related to what happened ``CourseGradeData`` which should contain the necessary information to understand the event, like the user, the course, the grade, and the date of the grade update. +- The granularity of the event is managed by having two events: one for the pass action and another for the fail action. + +Each of these practices should be reviewed with each case, and the granularity of the event should be adjusted according to the use case and the information required by the consumers. + Event Structure and Clarity ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Use appropriate data types and formats for the event fields. Don't use generic data types like strings for all fields. Use specific data types like integers, floats, dates, or custom types when necessary. - Avoid ambiguous data fields or fields with multiple meaning. For instance, if an event contains a field called ``status`` it should be clear what the status represents. If the status can have multiple meanings, consider splitting the event into multiple events or adding a new field to clarify the status. +For instance, consider the ``CourseEnrollmentData`` class: + +- The ``mode`` field is a string that represents the course mode. It could be a string like "verified", "audit", "honor", etc. +- The ``is_active`` field is a boolean that represents whether the enrollment is active or not. +- The ``creation_date`` field is a datetime that represents the creation date of the enrollment. +- The ``created_by`` field is a ``UserData`` that represents the user who created the enrollment. +- The ``user`` field is a ``UserData`` that represents the user associated with the Course Enrollment. +- The ``course`` field is a ``CourseData`` that represents the course where the user is enrolled in. + Consumer-Centric Design ~~~~~~~~~~~~~~~~~~~~~~~ From e2dcc094903c74793558ce0dd41fa9566f094b2e Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Mon, 30 Dec 2024 13:36:45 +0100 Subject: [PATCH 14/14] refactor: use a different example from the how-to --- .../decisions/0016-event-design-practices.rst | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/docs/decisions/0016-event-design-practices.rst b/docs/decisions/0016-event-design-practices.rst index e048e67a..0df1ddff 100644 --- a/docs/decisions/0016-event-design-practices.rst +++ b/docs/decisions/0016-event-design-practices.rst @@ -30,51 +30,27 @@ Event Purpose and Content - Keep the event size small. Avoid adding unnecessary information to the event. If the information is not necessary for consumers to react to the event, consider removing it. - Avoid adding flow-control information or business logic to events. Events should be solely a representation of what took place. If a field is necessary to control the behavior of the consumer, consider moving it to the consumer side. If adding additional data to the event is absolutely necessary document the reasoning behind it and carefully study the use case and implications. -Here is an example of an event that follows these practices ``COURSE_ENROLLMENT_CREATED``: +Here is an example of an event that follows these practices which is emitted when the a user registers: .. code-block:: python # Location openedx_events/learning/signal.py - # .. event_type: org.openedx.learning.course.enrollment.created.v1 - # .. event_name: COURSE_ENROLLMENT_CREATED - # .. event_description: emitted when the user's enrollment process is completed. - # .. event_data: CourseEnrollmentData - COURSE_ENROLLMENT_CREATED = OpenEdxPublicSignal( - event_type="org.openedx.learning.course.enrollment.created.v1", + # .. event_type: org.openedx.learning.student.registration.completed.v1 + # .. event_name: STUDENT_REGISTRATION_COMPLETED + # .. event_description: emitted when the user registration process in the LMS is completed. + # .. event_data: UserData + STUDENT_REGISTRATION_COMPLETED = OpenEdxPublicSignal( + event_type="org.openedx.learning.student.registration.completed.v1", data={ - "enrollment": CourseEnrollmentData, + "user": UserData, } ) Where: -- The event name indicates what happened: ``COURSE_ENROLLMENT_CREATED``. -- The event description explains why the event happened: ``emitted when the user's enrollment process is completed``. -- The event data contains data directly related to what happened ``CourseEnrollmentData``: - -.. code-block:: python - - # Location openedx_events/learning/data.py - @attr.s(frozen=True) - class CourseEnrollmentData: - """ - Attributes defined for Open edX Course Enrollment object. - - Arguments: - user (UserData): user associated with the Course Enrollment. - course (CourseData): course where the user is enrolled in. - mode (str): course mode associated with the course enrollment. - is_active (bool): whether the enrollment is active. - creation_date (datetime): creation date of the enrollment. - created_by (UserData): if available, who created the enrollment. - """ - - user = attr.ib(type=UserData) - course = attr.ib(type=CourseData) - mode = attr.ib(type=str) - is_active = attr.ib(type=bool) - creation_date = attr.ib(type=datetime) - created_by = attr.ib(type=UserData, default=None) +- The event name indicates what happened: ``STUDENT_REGISTRATION_COMPLETED``. +- The event description explains why the event happened: ``emitted when the user registration process in the LMS is completed``. +- The event data contains data directly related to what happened ``UserData`` which should contain the necessary information to understand the event, like the username and email of the user. Responsibility and Granularity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~