Updated: Oct 15
Introducing Repluggable - our new library that implements inversion of control for front end applications, and makes development of medium or high-complexity projects much easier. It helps power the Wix Editor, and is now also open source - https://github.com/wix/repluggable!
Without further ado, let’s dive in to understand what makes it so valuable.
Want to hear more about Repluggable?
Join our upcoming meetup: How to Build a Scalable Application? Introducing Repluggable
Most modern web applications are built using a very straightforward dependency model. In most cases this means simply importing the module you need and using it.
This is fine for small applications, but as a project grows, there are more entities, modules and dependencies - and the dependency graph becomes entangled. Then, of course, comes the call for action - separating concerns, refactoring and extracting things into standalone modules. And to top it all up, comes in the compatibility issue.
The native dependency model has many major drawbacks:
It’s hard to reuse logic in other projects, because they bring dependencies with them
It’s hard to replace modules, since they were not born separately, they need other existing modules from the app. And whatever is dependent on them may rely on their implementation
It’s hard to comprehend and refactor, because issues from other modules leak in and out. It’s sometimes hard to extend, because if something is not meant for extension from the beginning, you now have to create an extension mechanism for it
It’s hard to migrate between projects because of, well, dependencies that need to be brought in with it, which other modules may already rely on
All the infrastructure and operations are tailored to the project, and the process of replacing them is not flexible.
Concept and Dependency injection
There’s already a well known design pattern in programming, called dependency injection, which is used to create the inversion of control.
We can find uses of dependency injection mechanisms in many parts of various projects that solve tactical scoped problems, but rarely as a strategic robust solution for a whole project that was not meant to be modular from day one.
Let’s try to examine these applications and look at where we want them to be. Assume we are creating a panel - I’ll use React / TypeScript, but the concept stands true for any language. We’ll define 2 entities at the moment - the panel component, and a button that should be inside the panel. In most cases, we’ll import the button when creating the panel and use it inside, like so:
The more buttons we’ll need, the more imports we’ll add, and so on. If we need to create another panel with the same buttons in separate modules, we’ll need imports there as well.
Instead, what we’d like to do, is reverse the direction of the dependencies and introduce extending by contribution.
How do we make it work?
We set aside the mechanism of dependencies, and introduce contracts. A contract is defined by creating an interface and a key representing it. Exporting contracts and their implementations are separated. We can then call the API of this contract and define an invariant, determining that via API will be the ONLY way modules communicate. We define the dependency on the key rather than on the implementation. Then, we start with an empty host that handles the contract registration for us.
What we achieve by that is modules that are agnostic to their environment (in contrast to the native model, where they are completely aware). Which means we can easily reuse them in different environments. Migration of modules becomes almost as easy as cut-and-paste.
Since it’s contract-based, we can easily replace modules with others that provide a different implementation for the same API. We can easily extend without being aware of who’s extending. The concerns we get will most likely be very scoped and isolated. And we are free to use whatever infrastructure and operation process we want, since the modules are agnostic to them. Also, as experience shows, we get superb development velocity.
Modules can be very simple and quite oblivious in regard to what they know about the world they live in.
In order to provide a robust solution to address the problems above, we created Repluggable - a library that has the paradigm described above at its core.
Repluggable has several main entities:
The Host - the manager for the contract registration and the provider for the implementation.
Extension Slots - placeholder containers that are managed by the host and allow contribution between modules.
Entry Points - entities that each module provides in order for its content to be contributed to, and managed by, the host.
Shell - a terminal that allows every entry point to have its own scoped communication with the host (and therefore consumption of content from other modules).
Let’s code the entry points for the above example using Repluggable:
Repluggable can solely be used for the container and injection abilities, but we provide the full package for creating a stateful React and Redux application.
You can create layer over layer of modules handling different parts that extend other modules being completely unaware of the content they hold and not being involved in concerns that shouldn’t apply to them.
As a great testimonial for the powerful abilities of Repluggable, we used it to create our brand new editor - Editor X! A very complex application, to say the least, and using Repluggable made the modules management effort much easier for us.
Many thanks to my colleague and co-author of Repluggable - Felix Berman.
Want to hear more about it? Let me know in the comments.
Check out Repluggable - https://github.com/wix/repluggable
Check out EditorX - https://www.editorx.com/
Itay Shtekel - https://github.com/itsh01
Felix Berman - https://github.com/felixb-wix
This post was written by Itay Shtekel
For more engineering updates and insights: