Page MenuHomePhabricator

Break asymmetric direct cyclic dependencies of top level components of mediawiki core
Open, Needs TriagePublic

Description

Now that T353458: Make core classes follow PSR-4 is mostly done, it opens a lot of possibilities on dependency analysis on component level and improving modularity of core. Here is a simple example:

Here is the list of direct cyclic dependencies between top level components of mediawiki: P88801 (it's 440 edges, meaning 220 of them)

To prioritize breaking such cyclic dependencies, we can easily start by top asymmetric ones (basically, you give weight to the graph edge based on number of classes that have dependencies in that component). Now a clear todo list is emerging: P88835

For example, the second line says MediaWiki\Specials classes have 48 different dependencies to MediaWiki\Exception but the other way around is only one case: MediaWiki\Exception\UserNotLoggedIn is having a dependency to MediaWiki\Specials\Helpers\LoginHelper::getValidErrorMessages(). I‌ can easily imagine this static method being moved to a class in MediaWiki\Exception\ component breaking this cyclic dependency between top level modules of mediawiki core.

Similarly, the cyclic dependency of User and Page (line three) can be easily broken by deprecating and removing four methods in User (::probablyCan, ::definitelyCan, ::authorizeRead, ::authorizeWrite). Most other cases are rather straightforward to fix

Event Timeline

I'm sketpical about treating present-day namespaces as meaningful component boundaries. Neither "all special pages" nor "all exceptions" is a reasonable component where not depending on other components would be a useful goal, IMO.

I'm sketpical about treating present-day namespaces as meaningful component boundaries.

That's the idea to iteratively evolve the top level namespaces to become meaningful boundaries rather than arbitrary chosen set of classes.

Neither "all special pages" nor "all exceptions" is a reasonable component where not depending on other components would be a useful goal, IMO.

It is actually an important metric. Specials component belongs to the presentation layer (roughly) and lower layers shouldn't depend on higher layers. This is fixing layering.

A number of MWException subclasses do presentation layer work, UserNotLoggedIn is one of them. Not great but doesn't have much to do with cross-namespace dependencies IMO. Distributing exception classes across namespaces, so that exceptions related to a specific component are within that component (like we do, somewhat, for hooks) would be a reasonable arrangement, but having presentation logic in exception classes would be still bad.

A good component hierarchy would probably introduce an extra level in the namespace hierarchy. You'd have classes like MediaWiki\Authentication\Service\AuthManager and MediaWiki\Authentication\Exception\UserNotLoggedIn and MediaWiki\Authentication\Special\SpecialUserlogin.

A number of MWException subclasses do presentation layer work, UserNotLoggedIn is one of them. Not great but doesn't have much to do with cross-namespace dependencies IMO. Distributing exception classes across namespaces, so that exceptions related to a specific component are within that component (like we do, somewhat, for hooks) would be a reasonable arrangement, but having presentation logic in exception classes would be still bad.

I'm not sure LoginHelper::getValidErrorMessages() is presentation logic.

A good component hierarchy would probably introduce an extra level in the namespace hierarchy. You'd have classes like MediaWiki\Authentication\Service\AuthManager and MediaWiki\Authentication\Exception\UserNotLoggedIn and MediaWiki\Authentication\Special\SpecialUserlogin.

What you're describing is vertical modelling and it would work if we had microservice architecture (or SOA) but we don't. As general architecture rigor, it's better to do split first horizontal (presentation, business, ...) and then vertical (Auth, etc.)

I'm not sure LoginHelper::getValidErrorMessages() is presentation logic.

It is not. LoginHelper::showReturnToPage() is, though.

I'm talking about moving LoginHelper::getValidErrorMessages() that is in MediaWiki\Specials component (arguably in presentation layer) to some layer below somewhere else (probably in MediaWiki\Exception that is not presentation layer). I'm not talking about LoginHelper::showReturnToPage() nor ever mentioned it here and as I said, the whole class is in presentation layer (because it's under MediaWiki\Specials top-level component) so obviously that method is part of it too.

First if all: this kind of analysis is great, and we should use it to break cyclic dependencies.

That said, curent namespaces are often pattern based or layer base. Fixing layering problems is nice, but is unlikely to allow us to enforce component boundaries. I'd like to start thinking about creating component boundaries (doesn't have to be many) and moving classes into their respective components.

But we shouldn't allow the perfect the be the enemy of the good here. There is no reason no to start by fixing layering issues.

Similarly, the cyclic dependency of User and Page (line three) can be easily broken by deprecating and removing four methods in User (::probablyCan, ::definitelyCan, ::authorizeRead, ::authorizeWrite).

That is unfortunately not as simple as it may seem: this would mean that User no longer implements the Authority interface. That's a good goal, but the refactoring needed to make this happen is quite substantial.