Extensibility - Why it matters
Basically, every developer wants to create value. We don't want to re-invent the wheel; instead we'd like to re-use as much as possible. This is why we believe it is quite natural for many developers to somehow create "generic" solutions intended to be applicable to a wider range of problems than originally intended. However, we think there is no silver bullet – we cannot just build the most generic solution possible; and then we are "finished" for all our projects.
Instead, we need to find a sweet spot. We should develop features in a way applicable to as many use-cases as make sense, but no more. Finding this spot is a constant journey, involving lots of experience, discussions, and it will always be somewhat subjective.
The Basis: A Module System
In order to have an extensible framework, you need some kind of module or package system. A way to add code to the core codebase of a framework. Many systems provide this, be it Flow/Neos, Symfony, Spring, Ember.js, Rails, ...
When having a module system, you hopefully never need to fork or patch the base system.
Two of a Kind
We in the Neos team generally think about extensibility patterns in two very broad categories:
Planned extensibility means that the original author of the library/framework/system anticipated that a certain functionality shall be replaced. He basically guarantees that (usually following SemVer), no breaking changes appear when updating versions of the framework in the extensible areas.
As an analogy, this is somehow like a legally binding contract between the original author and the developer extending the system.
Examples for planned extensibility patterns include:
- Signals/Slots or Event Listeners
- explicit configuration in a system
- add middlewares to a HTTP stack
- Interfaces to be replaced by using Dependency Injection
- using registries
Special patterns inside Neos include:
Unplanned extensibility allows you to modify the system in any way possible. Basically, this is most useful if the code in the framework is mostly right, but when you check it, you figure out that just some (more or less) tiny change to the core is needed for your use-case.
If you do this, you basically loose any warranty – you can't be sure your changes will still work after upgrading the base system (no matter if it's a patch level, minor or major update).
Examples for unplanned extensibility patterns include:
- Aspect-Oriented Programming
- Dependency Injection
- overriding templates or partials
- intercepting some routes to different controllers
Extra patterns inside Neos include:
- overriding an arbitrary Fusion object
- require.js or webpack configuration
When to use which?
We think the rule of thumb is quite simple: Always use planned extensibility if you can. If this is not possible, use the unplanned extensibility mechanisms. Never patch the core.
As a sidenote: When looking at other frameworks and environments, it is quite common that systems and frameworks market themselves as extensible, albeit their implemented mechanisms very often only fall into either the planned or the unplanned extensibility category.
If you are a framework or library author, please think about both ways :-)
Closing Thoughts - The Learning Path
A personal note: When I joined the Neos team, I wanted to create architectures which steer the user into a certain direction. Basically, I wanted to build systems which encourage intended behavior, and discourage/punish unwanted behavior. You might already spot the error of thought in there: While the former works really nicely and helps educating your users, the latter does not work at all – it only makes the hack worse the user will come up with to fix his (relevant) problem. In the worst case, people might e.g. hack the front controller outside of the framework (like Web/index.php in case of Flow/Neos) to fix their particular problem.
So, as a take-away: It's great if a library/framework guides the user towards best practices; however, when the user wants to break the rules, he should be allowed to do this. Don't try to prevent this.
If you want to learn more, you can watch a presentation of Christian and me; or join the Neos Conference to meet us in person!
Of course, these thoughts are not carved in stone; rather they represent the current state of thinking for us.