Manual:Domain events/Modeling
The idea of domain event in MediaWiki is inspired by domain driven design where the term is used to describe something that happened in the past that users care about, typically something that changes the visible state of the system. Martin Fowler writes that "the essence of a Domain Event is that you use it to capture things that can trigger a change to the state of the application you are developing" .
This page describes how to model domain events. For a description of the event dispatcher, see the architecture documentation. For an overview of events defined in MediaWiki core, see the event hierarchy.
Modeling principles
Events are an extension interface
Events represent a stable interface that other components and extensions rely on, so they should be modeled carefully to avoid breaking changes in the future. The stable interface policy applies to domain event classes.
Events must be modeled as immutable value objects and must not expose any stateful objects through getters. The information exposed by events should be modeled based on the needs of consumers, eposing implementation details should be avoided. Events should be designed to allow reliable serialization and broadcasting on an event bus.
Modeling from the perspective of the listener
Modeling should be based on the perspective of listeners, of what they should and shouldn’t know about the system, and what they may be interested in.
Consequences:
- Focus on changes to entities and their properties, rather than user intent.
- Provide sufficient context within the event object model to enable listeners to appropriately differentiate and filter events.
Note that User actions often trigger multiple events, to cover different changes to all affected entities. For example, a single "move page" action may involve renaming sub-pages and talk pages as well, and for each page that is renamed, it may require deleting the target page, and creating a redirect page under the old name. Modeling all these changes as a single event is not very useful to listeners. It is better to emit events that represent specific changes to specific pages (deletion, creation, renaming, content changes, etc).
Events are emitted by entities
Events should represent the effect of a user action on the state of entities (compare Event_(computing)).
Conceptually, an entity is a stateful object, an object that has a continuous identity while its state changes. Typically, the state of entities is persisted in a database, and changes to the entity are performed atomically, to guarantee consistency among all parts of the state. Entities can form complex nested structures (“aggregates”) with rules that govern the integrity and consistency of that structure. An entity’s (or aggregate’s) state is changed by executing well defined “commands” or "actions" on it. It is the command’s responsibility to guarantee atomicity, and to emit any relevant events. Each concrete type of event should be emitted from only one place in the code.
To properly represent this fact, event classes should be named in a way that makes it obvious what kind of change on what entity they represent. Also, objects should provide a way to determine the identity of the entity that was changed (e.g. PageDeletedEvent would have a getPageId() method to determine which page was deleted).
Events emitted by the same entity or otherwise sharing properties may be grouped together using abstract base types. See the section about the type hierarchy below.
Events types form a hierarchy
Events are organized in a type hierarchy. When an event is dispatched, it is dispatched to listeners registered for the event's own type as well as for any of its parent types (the event type chain). In the hierarchy, events emitted by a given kind of entity are typically subtypes of a shared abstract base type. This provides flexibility with respect to the granularity of modeling - we can always introduce a subtype or a parent type to adjust granularity, without breaking compatibility.
Based on their role in the type hierarchy, event types can be classified as follows:
- Concrete event types are emitted explicitly by code that changes the state of an entity (a "command" or "action"). Concrete event types are modeled by PHP objects that are instantiated directly be the code that emits the event. They represent the effect the command has on the state of the entity, and should be named in a way that makes it obvious what change happened on what entity.
- Abstract event types serve as a base type for all events emitted for changes on a given kind of entity. Abstract events types are modeled by abstract base classes. They are emitted implicitly by the EventDispatcher when an event of one of their sub-types is emitted. Abstract event types should be named after the entity they cover.
Note that this classification is useful to guide modeling, but it is generally not relevant to listeners. They should not need to know whether they are subscribed to a concrete or an abstract event type.
The relationship between concrete and abstract event types is implemented in code using subclassing as well as defining a "chain" of event types for each event object, rather than a single type. Each subclass adds to the chain by calling the declareEventType()
method in its constructor, so the chain of types corresponds to the list parent classes:
class PageDeletedEvent extends PageStateEvent {
public const TYPE = 'PageDeleted';
public function __construct( ... ) {
parent::__construct( ... );
$this->declareEventType( self::TYPE );
}
...
}
Multiple Inheritance
It is possible to use multiple inheritance for event types, but it is currently undecided whether this is desirable, because having many interfaces that can be mixed and matched may make it harder to understand which event types an extension should subscribe to, and how.
Multiple inheritance would be possible using aspect event types which are similar to base types in that they group together related events with shared properties. However, they could be applied across the hierarchy and do not have to be limited to a specific entity. They would be modeled by PHP interfaces, not classes. Event class that supports the aspect then registers the additional event type before its own type in the constructor:
class PageDeletedEvent extends PageStateEvent implements LoggedPageEvent {
public const TYPE = 'PageDeleted';
public function __construct( ... ) {
parent::__construct( ... );
// Declare the event types represented by this class.
// Declare self::TYPE last, so it will be the type returned by getEventType().
$this->declareEventType( LoggedPageEvent::LOGGED_EVENT_TYPE );
$this->declareEventType( self::TYPE );
}
...
}
Events model changes to properties of entities

Commands (aka actions) modify the properties of entities. Events model the outcome of commands as changes of certain properties.
To properly represent the effect of commands on the properties of entities, event objects should provide the following:
- Documentation of which property changes the event represents, and what guarantees it provides to listeners (see the section on continuity below).
- Documentation on what user actions typically trigger them.
- The identity of the entity that was affected
- The state of the entity after the change, including at least the properties that the event covers.
- The state of the entity before the change, including at least the properties that the event covers.
Events are emitted when the change to the entity is complete. Each concrete type of event should only be emitted from one place in the code. This is especially important to remember when considering events emitted by from nested entity structures: the change to the "outer" entity will finish last, because all the changes to "inner" entities have to be completed first. So the event representing the change that was initiated on the root entity will be delivered to listeners last, after all the events emitted from nested entities.
Continuity Models
Each event type should clearly document which property changes it models, and how it represents the state of this property before and after the event. In particular, it should be clear how the before-state and after-state of one event relates to the before-state and after-state of the next event of the same type on the same entity (the continuity of the property). there are several continuity models that may apply:
- continuous chain: Listeners will be informed of all changes to the property, and each event represents a change to this property (unless the event is a reconciliation request). The after-state of the property in one event will be the same as the before-state of that property on the next event, forming a continuous chain. This should be the case for the property that the event primarily models.
- complete history: Listeners will be informed of all changes to the property, but some events of this type may not represent a change to that property. Also, the before-state and after-state may not form chain, due to nesting of sub-commands; But the after-states of consecutive events provide a complete chronological sequence of changes to the property. This will often be the case for properties provided by an abstract base type, because some of the concrete subtypes represent nested sub-commands. For example, an import may trigger a page creation and a dummy revision, so the event representing the import could share the before-state of the page creation but the after-state of the dummy revision.
- partial history: Listeners will be informed of some kinds of changes to the property, but not about others kinds, which are covered by a different event type. This may be the case when modeling different kinds of changes to the property as different events. For example, PageCreated and PageDeleted both provide a partial view of changes to the page_id. Only when combined they together provide a continuous stream of page_id changes.
- incidental: Listeners will be informed of some changes to the property, but not about others. This will typically be the case for properties that are included in the event for convenience, without modeling a semantic relationship.
Each event type should specify which continuity model it provides for the properties that are included in its before-state and in its after-state. Ideally, for each property there is one event that listeners can use to receive a continuous chain to follow changes to that property. However, it is sufficient if for each property there is an event that provides a complete history of changes, or a set of event types that that provide a partial history up updates, but can be combined to provide a complete history.
Events can represent reconcilliation requests
Event listeners are often used to maintain derived data, and keep it up to date with state changes elsewhere in the system. For example, when page content changes, the search index must be updates.
Somtimes such updates fail due to bugs or system outages, causing inconsistencies in the derived data. In that case, it is useful to be able to trigger events as reconcilliation requests. This is similar to replaying events in an event sourcing system, except that reconcilliation events are not required to fully represent the state before the original event - they are based on the current state of the system. For example, a reconcilliation request for a change in page content would have the current revision ID as the before state as well as the after state.
Listeners should default to treating reconcilliation events like regular events - the idea is that they should behave as if the actual change had occurred. This requires listeners to be implemented in an idempotent manner, so processing the same event modultiple times doesn't have any unwanted effects. Listeners that are not idempodent, e.g. a listener incrementing the user edit counter, need to check whether an incoming event is a reconcilliation request, and skip processing as needed.