Best practices for extension points

This document establishes best practices for the creation of extension points. It is intended to provide guidance for specifying and implementing extension points, with a focus on avoiding breaking changes to extensions when changing the application. It should be read as complementary to the Best practices for extensions.

Generally speaking, extension points are mechanisms by which an application allows extensions to modify its behavior. MediaWiki provides a great number of extension points, which fall into three general categories: hooks, services, and handlers. Best practices for each of these categories are described below.

Hooks edit

Hooks provide a mechanism to inject callbacks into various parts of the code.

TBD: inline the outcome if phab:T212482 here, or link to the respective document.

Services edit

Services in the broader sense are things that only exist once within the application, and which an extension may want to replace.

Services in the narrower sense are singletons managed by MediaWikiServices (the service locator), or other ServiceContainers that can be found from the default MediaWikiServices instance.

Any service defined by the MediaWiki is a potential extension point, but many are not designed or intended to act as such. Which service acts as an extension point is constrained by the considerations described in the subclassing and implementing interfaces section below: only classes and interface explicitly marked as extension points should be extended or implemented by extensions. This means that extensions can only replace services that use a base class or interface that is defined to be an extension point.

TBD: talk about service wiring and the MediaWikiServices hook.

Handlers edit

Handlers provide a specialized implementations of some abstract concept that corresponds to a specific name (an action, a type, a format, kind, flavor, etc). Examples of handler concepts in MediaWiki are: SlotRoleHandler, ContentHandler, ApiModule, SpecialPage, etc.

Handlers should be managed by a registry service. A registry is a type of factory that returns a handler given a name. In contrast to a service container, all handlers returned by a registry implement the same base class or interface. A registry also provides a mechanism for defining new handlers, typically by providing a method for associating a handler instance with a name. Instead of providing an instance directly, it's usually better to provide an instantiator callback, to instantiation of the handler can be deferred.

In contrast to services, handlers are by nature designed to be extension points. They should therefore follow the considerations described in the subclassing and implementing interfaces section below, and should defined classes and interface explicitly marked as extension points.

TBD: talk about service manipulators

Subclassing and implementing interfaces edit

When defining extension points of the service or handler type, there are broadly two choices: defining an interface, or defining an abstract base class. Defining an interface may seem like the obvious textbook solution, but it's actually rather problematic, because it provides no forward compatibility: it's impossible to add a method to an interface, or a parameter to a method in such an interface, in a way that would not break all any extension implementing it. There is no good mechanism available for making such a change backwards compatible(*).

For this reason, services and handlers should use abstract base classes, never interfaces.

There is another instance in which extensions sometimes use subclassing: to control the behavior of an object which they then feed back to the application, or sometimes even use directly. This however is problematic in the light of the Deprecation Policy, which does not consider protected methods to be part of the stable public interface, unless explicitly marked as such.

For this reason, extensions should never subclass any class defined by the application, unless that class is explicitly marked as an extension point. The same is true for implementing interfaces, for the reasons laid out at the beginning of this section [TBD: perhaps this should be in Best_practices_for_extensions as well, or only there?].

Further reading edit


(*) the textbook approach would be to introduce a new interface that extends the old one. But this forced any application logic that wants to use the new interface to do an instanceof check, or complex logic to be in place to provide automatic wrapping of implementations of the new interface.