Jump to content

MediaWiki Product Insights/Artifacts/KR 5.2: Simplify feature development/5.2.3: Domain Event Dispatcher

From mediawiki.org

Hypothesis: If we conduct an experiment to reimplement at least [1-3] existing Core and Extension features using a new Domain Event and Listener platform component pattern as an alternative to traditional hooks, we will be able to confirm our assumption of this intervention enabling simpler implementation with more consistent feature behavior.

The goal of the Event Dispatcher project is to create a facility for emitting events that reflect MediaWiki state changes, which can be received by in-process listeners in other components in core or in extensions. Notifying listeners running as separate processes will be addressed in a later stage of the larger Domain Event System project.

Introducing an event dispatcher facility into MediaWiki was identified as an opportunity to improve the way extensions integrate in the MediaWiki as part of the hook survey conducted in Q1, see Hook Survey#Event_Listeners.

Problem statement

[edit]

MediaWiki core currently lacks clearly defined component boundaries. Part of the problem is that there is no mechanism to notify one part of the code about changes in another without calling it directly (ie:  observer pattern). Without this pattern established, code that changes state needs to know about all other components that need to be informed about that change. Such entanglement also limits our ability to evolve the platform, as we cannot execute changes with confidence that there will not be unintended consequences.

Although extensions currently utilize the observer pattern to a degree, boundaries are similarly muddied by hook based implementation patterns. There are three main areas that impact extension sustainability: First, PHP hook method signatures cannot be updated to include new data points, resulting in a variety of tightly coupled, purpose-built methods keying off of the same underlying state change yet offering slightly different functionality. Second, uncertainty and lack of support for controlling transactional context without self-managed hook handler logic, which results in a high risk of misimplementation of boilerplate logic, as well as a high cost of change to reimplement across handlers. Third, because multiple extensions may be utilizing the same transaction, there is a high risk of unexpected behavior as multiple extensions attempt to make changes, including failing to roll back incremental changes if the transaction is ultimately unsuccessful.

Goals

[edit]

The overall goal of this work is to make MediaWiki and extension development more sustainable. We expect the application of the listener pattern to have the following benefits for the MediaWiki platform, supporting KR 5.2:

  • Improve component boundaries between core components by applying the listener pattern (aka observer pattern). Listeners remove the need for the code that affects a change to know about all code that needs to be informed about it.
  • Clarify the semantics of the notification received by the extension, particularly with respect to transactional context.
  • Make the extension interface more future proof by avoiding the rigidity imposed by using PHP interfaces to define hook parameters. Due to limitations of PHP, method signatures defined by extensions can’t be modified in a backwards-compatible way.
  • Prepare for the creation of a generic relay mechanism for broadcasting events over a bus. Broadcasting itself is not in scope for the initial phase, but accommodating that use case is a design goal.
  • Standardize deferred update behavior, which will reduce boilerplate code and risk of misimplementation. This includes addressing a widespread issue where deferred updates are not cancelled if the original transaction fails. By nature, the correct behavior is implemented in the event itself. Events framework uses this feature correctly and can prevent firing if the transaction does not complete.

Solution Strategy

[edit]

To solve the problems listed above, we need a new, robust interface to enable further decoupling. We are starting with addressing the event-like hooks identified through the FY24Q1 hook survey. We believe that by having a robust event object interface, we will be able to improve MediaWiki sustainability by reducing brittleness and enforcing better component boundaries. The initial approach will cover emitting events and allowing listeners to register in code. This will make the events available to internal MediaWiki components, in addition to offering events as a new extension interface.

In order to introduce the concepts of events listeners into MediaWiki core, we will define interfaces for emitting events and for registering listeners. We will implement a mechanism for dispatching events to the relevant listeners if and when the change in state has been successfully committed to the storage layer.

The proposed system can be implemented as a self-contained system, built on top of existing infrastructure such as DeferredUpdates in a straightforward way. It will be backwards compatible so existing extensions remain functional without any change, and implemented in such a way that allows us flexibility to change the implementation later, without affecting listeners. This enables us to experiment with the new system on a small selection of extensions and core components, and to adjust the interfaces and implementation as we expand usage.

The system can be summarized as follows:

  • Create a dispatcher class that builds on top of DeferredUpdate for deferred, transaction-bound dispatching
  • Allow listeners to be registered with the dispatcher. Use the subscriber pattern to allow listeners to be bundled to improve internal cohesion of consumer side code, and make listener registration simpler for extensions.
  • Allow lazy instantiation for subscribers: when registering a subscriber as an object spec, it will be invoked only when any of the events it registers for are triggered.
  • When an event is triggered, create any pending subscribers, and allow them to register their listeners. Then schedule a DeferredUpdate for invoking each listener for that event.
  • The DeferredUpdate will be bound to the current transaction of the database connection passed along with the event, so it will be canceled automatically if the transaction fails.
  • After the transaction is complete, and after the response has been sent to the client, the DeferredUpdates are executed, invoking each registered handler in a separate transaction context.
  • Provide an interface for registering listeners for a given event type.
  • Listeners should receive an immutable object representing the event.

See also the design document for the domain event dispatcher.

Outcome

[edit]

The hypothesis was confirmed. We were able to observe that extension code that was ported to the new pattern has less boiler plate code and is less likely to behave incorrectly in the case of transaction rollbacks. In MediaWiki core, we were able to reduce the coupling of classes that affect changes (e.g. PageUpdater) to other parts of the system (e.g. MessageCache or TalkPageNotificationManager).

Metrics:

  1. number/percentage of extensions adopting domain events over hooks.
    • Number of hooks that don’t have domain event alternatives available: 11 (down from 13). PageUpdated replaces PageSaveComplete and RevisionFromEditComplete
    • Number of extensions using domain events: 3 (up from 0). Linter, ContentTranslation, and GrowthExperiments (partial)
    • Number of hook handlers replaced with event subscribers: 3 (up from 0) in Linter, ContentTranslation, and GrowthExperiments (1 of 5)
  2. Reduce the amount of boilerplate code needed in extensions for handling deferred updates.
    • Linter: no significant change, since it wasn’t using a DeferredUpdate before.
    • ContentTranslation: removed about 20 ELoC for registering a DeferredUpdate. Improved behavior in case of a transaction rollback.
    • GrowthExperiments: removed about 2 ELoC for registering a DeferredUpdate. Improved behavior in case of a transaction rollback.
  3. Reduce the number of component boundary violations:
    • DerivedPageDataUpdater (once r1099177/4 is merged): removed 4 problematic dependencies: MessageCache, UserNameUtils, TalkPageNotificationManager, and PermissionManager
    • PageUpdater: removed 2 problematic dependencies: UserEditTracker and RecentChange (except for access to constants).
  4. Solicit feedback from early adopters. Although this will describe qualitative success, such interviews allow us to learn from a variety of user cases, so that we can continually refine and improve event interfaces and dispatching.
    • So far, there are 2 Wikimedia teams adopting the initial PageUpdated event: Growth, for the GrowthExperiments extension, and Languages for the ContentTranslation extension. Initial feedback is positive, and has already motivated us to improve inline documentation to support the new event pattern.

Next Steps

[edit]

The domain event dispatcher facility in MediaWiki is now functional, but still marked as experimental. In order to allow us to fully benefit from the new capability, the following steps should be taken over the remainder of FY24/25:

  • Finalize the interfaces of the event dispatcher facility (target: 1.44 release).
  • Finalize the modelling of the PageUpdated event (target: 1.44 release).
  • Finalize the contract of dispatch modes (target: 1.44 release)
  • Define more events to model the outcome different commonds on the page entity, such as delete and undelete.
  • Convert more hook handlers in more extensions to event listeners. The GrowthExperiments extension is a promissing candidate, because it covers a diverse set of use cases implemented in several handlers.
  • Implement an "in job" dispatch mode to allow for more boiler plate code to be removed from extensions.
  • Design an implement a facility for publishing events to an event bus. This would generalize some of the logic currently implemented in the EventBus extension.
  • Stretch: Design an implement a facility for receiving events from an event bus.