|
| 1 | +==================================== |
| 2 | +3. Plugin Slot Naming and Life Cycle |
| 3 | +==================================== |
| 4 | + |
| 5 | +Status |
| 6 | +====== |
| 7 | + |
| 8 | +Accepted |
| 9 | + |
| 10 | + |
| 11 | +Context |
| 12 | +======= |
| 13 | + |
| 14 | +The Frontend Plugin Framework introduced the concept of plugin slots as a way |
| 15 | +to customize micro-frontends. Slots are defined in each application's codebase |
| 16 | +with React, currently taking the form: |
| 17 | + |
| 18 | + <PluginSlot id="arbitrary_slot_name"> |
| 19 | + ... |
| 20 | + </PluginSlot> |
| 21 | + |
| 22 | +Operators can subsequently insert plugins into this slot by referencing |
| 23 | +"arbitrary_slot_name" in configuration as follows: |
| 24 | + |
| 25 | + pluginSlots: { |
| 26 | + arbitrary_slot_name: { |
| 27 | + plugins: [{ |
| 28 | + op: PLUGIN_OPERATIONS.Insert, |
| 29 | + widget: { |
| 30 | + id: 'arbitrary_plugin_name', |
| 31 | + ... |
| 32 | + } |
| 33 | + }] |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | +However, the following concerns were identified in relation to completely |
| 38 | +arbitrary slot names: |
| 39 | + |
| 40 | +1. The codebase can become progressively littered with slot names that are |
| 41 | + unintuitively or inconsistently named, making it harder to document, |
| 42 | + maintain, and use them |
| 43 | + |
| 44 | +2. There is no expectation that one should be able to infer purpose and |
| 45 | + location from the slot name |
| 46 | + |
| 47 | +3. While Frontend Plugin Framework supports defining multiple slots with the |
| 48 | + same name in a frontend app, as the number of slots across the codebase |
| 49 | + increases it becomes harder and harder for developers to avoid introducing |
| 50 | + accidental name collisions |
| 51 | + |
| 52 | +4. Without a versioning scheme, there's no way to modify a slot's API without |
| 53 | + making an implicit breaking change |
| 54 | + |
| 55 | +This is a common problem in computer science, one that has often been addressed |
| 56 | +by use of `reverse domain name notation`_. It can be seen everywhere, from |
| 57 | +Android package names to Open edX's own specification for `server event |
| 58 | +types`_. |
| 59 | + |
| 60 | +.. _reverse domain name notation: https://en.wikipedia.org/wiki/Reverse_domain_name_notation |
| 61 | +.. _server event types: https://docs.openedx.org/projects/openedx-proposals/en/latest/architectural-decisions/oep-0041-arch-async-server-event-messaging.html#id5 |
| 62 | + |
| 63 | +This technique allows for namespace uniqueness within a self-documentated |
| 64 | +hierarchy. For instance, take this fictitious slot name that uses said |
| 65 | +notation: |
| 66 | + |
| 67 | + org.openedx.frontend.layout.header.v1 |
| 68 | + |
| 69 | +Even without further information, it's possible to tell that: |
| 70 | + |
| 71 | +* The slot belongs to an app in the Open edX org |
| 72 | +* It's a frontend app |
| 73 | +* It's in the app's layout module |
| 74 | +* The slot probably wraps the header |
| 75 | +* This is version 1 of the slot, which indicates changes are possible in the |
| 76 | + future |
| 77 | + |
| 78 | +And last but not least: |
| 79 | + |
| 80 | +* There's little chance that a slot with the same name exists anywhere in the |
| 81 | + codebase other than where the layout header is defined |
| 82 | + |
| 83 | +Based on this concept, this ADR aims to define rules that govern how developers |
| 84 | +maintain plugin slots in Open edX frontend apps throughout their lifecycle. In |
| 85 | +particular, when adding, deprecating, or removing plugin slots. |
| 86 | + |
| 87 | + |
| 88 | +Decisions |
| 89 | +========= |
| 90 | + |
| 91 | +1. Naming format |
| 92 | +---------------- |
| 93 | + |
| 94 | +The full name of a plugin slot will be a ``string`` that follows the following |
| 95 | +format: |
| 96 | + |
| 97 | + {Reverse DNS}.{Subdomain}.{Module}.{Identifier}.{Version} |
| 98 | + |
| 99 | +Where: |
| 100 | + |
| 101 | +* *Reverse DNS* is always ``org.openedx`` |
| 102 | +* *Subdomain* is always ``frontend`` |
| 103 | +* *Module* denotes the frontend module where the slot is exposed, such as |
| 104 | + ``courseware``, or ``authoring`` |
| 105 | +* *Identifier* is a snake-case string that identifies the slot, which must be |
| 106 | + unique for the module that contains it |
| 107 | +* *Version* is either the string `beta`, denoting a slot with a yet unstable |
| 108 | + API, or a monotonically increasing integer prefaced by a `v` and starting |
| 109 | + with `v1`. |
| 110 | + |
| 111 | +For example: |
| 112 | + |
| 113 | +* org.openedx.frontend.layout.footer.beta |
| 114 | +* org.openedx.frontend.courseware.navigation_sidebar.v2 |
| 115 | + |
| 116 | +In practice, this is what the slot definition will look like: |
| 117 | + |
| 118 | + <PluginSlot id="org.openedx.frontend.courseware.navigation_sidebar.v2"> |
| 119 | + ... |
| 120 | + </PluginSlot> |
| 121 | + |
| 122 | +And this is how operators would configure it: |
| 123 | + |
| 124 | + pluginSlots: { |
| 125 | + org.openedx.frontend.courseware.navigation_sidebar.v2: { |
| 126 | + plugins: [{ |
| 127 | + op: PLUGIN_OPERATIONS.Insert, |
| 128 | + widget: { |
| 129 | + id: 'arbitrary_plugin_name', |
| 130 | + ... |
| 131 | + } |
| 132 | + }] |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | +Note that while this ADR does not prescribe a list of modules, whenever a new |
| 137 | +slot is introduced special care should be taken with the selection of the |
| 138 | +module name. In particular, slots that occur in multiple modules should have |
| 139 | +consistent names. For instance, while the "layout" module suggested above for |
| 140 | +the footer is not to be considered one of the decisions described here, it is a |
| 141 | +good example of a case where a single module name would apply to at least two |
| 142 | +slots that would be present in more than one codebase: ``layout.header`` and |
| 143 | +``layout.footer``. |
| 144 | + |
| 145 | + |
| 146 | +2. Versioning |
| 147 | +------------- |
| 148 | + |
| 149 | +For the purposes of versioning, a given slot's API contract is comprised of: |
| 150 | + |
| 151 | +* Its location, visual or otherwise, in the Module |
| 152 | +* The type (but not implementation!) of the content it is expected to wrap |
| 153 | +* The specific set of `pluginProps` it exposes |
| 154 | + |
| 155 | +If one of the above changes for a particular slot in such a way that existing |
| 156 | +plugins break or present undefined behavior, *and* if it still make sense to |
| 157 | +use the same Identifier, the version string appended to its name will be |
| 158 | +incremented by `1`. |
| 159 | + |
| 160 | +Note: a given slot's default content is explicitly *not* part of its contract. |
| 161 | +Changes to it do not result in a version bump. |
| 162 | + |
| 163 | +3. Deprecation process |
| 164 | +---------------------- |
| 165 | + |
| 166 | +When a slot changes sufficiently to require its version to be incremented, the |
| 167 | +developer will take care to: |
| 168 | + |
| 169 | +* Propose the previous version's deprecation via the official Open edX |
| 170 | + Deprecation Process |
| 171 | + |
| 172 | +* Keep the definition of the previously released version of the slot in the |
| 173 | + codebase for the duration of the deprecation process, which should include at |
| 174 | + least one Open edX release where it co-exists with the new version |
| 175 | + |
| 176 | +* Implement the new version of the slot in such a way that coexists with the |
| 177 | + previous one with no detriment to either's functionality |
| 178 | + |
| 179 | + |
| 180 | +Consequences |
| 181 | +============ |
| 182 | + |
| 183 | +The decisions above are intended to let plugin authors create and maintain |
| 184 | +plugins that are stable across releases of Open edX, while also allowing slots |
| 185 | +themselves to evolve. The naming convention itself has no significant |
| 186 | +downsides, and while the deprecation process does add some maintenance burden, |
| 187 | +it is expected to be offset by the additional stability provided. |
| 188 | + |
0 commit comments