Thoughts of a completely modular, contract-driven system
I've been pondering lately about modern frameworks and systems, and how coupled they are, and how they constrain you into a neatly crafted box. Yet, I've rarely seen a system that fits in a box. Most things evolve, requirements change, and suddenly that box becomes a limitation, a liability, often causing a complete re-write or abandonment of the system.
Is there a way to architect a framework which functions more like a lego and less like a box? One where you can change the pieces around to form new constraints, replace things, change things?
What if, let's say in the scope of a website, routing wasn't a linear map of routes corresponding to functions, but routes were actions dependent on a contract which is molded by the user?
Instead of:
app.get('/', () => console.log('hello, world'));
You'd do something like
subscribe('http.request', (payload) => {
if (payload.method === "GET" && payload.path === "/") {
dispatch('http.response', ...);
}
});
Then the core system itself really just deals with listening to events, and doing something as a result of those events. Though, if the system itself decided what it would do with those events, then it again starts to couple the whole thing into a box, so it's you, the user, who decides.
subscribe('http.response', (payload) => {
// decide what you want to do with this
});
The above is a over-simplification of my thoughts, but should paint an idea of a fully modular, extendable system. Of course, the system should come with many built-in event resolvers, so you wouldn't have to reinvent the whole wheel each time, but that you could, if you wanted to.
Contract-driven
How do you make sure that each part fits together? How do you ensure that wrong parts don't try to communicate and blow the whole thing up in unexpected ways? Well, perhaps with contracts.
Each subscription should themselves determine what is the acceptable payload they get, and if they do not get that, they fail.
It could look something like this:
const homeRequest = {
validator: (payload) => {
// Validate that the payload is correct
return true;
},
resolver: (payload) => {
// Do something with the payload.
}
};
subscribe('http.request', homeRequest);
Now you can dispatch events to it, and the subscribe
function will run the validator, and discard the listener if validation does not pass. You could abstract the validator to a reusable function, thus making a contract stating that a HTTP request must correspond to X contract.
Perhaps the helper could be something like:
const isHttpRequest = (method, path) => {
return (payload) => {
return payload.method === method && payload.path === path;
}
}
Which you then use as:
const homeRequest = {
validator: isHttpRequest("GET", "/"),
resolver: (payload) => {
// Do something with the payload.
}
};
subscribe('http.request', homeRequest);
Debugging
One of the nice things about an entirely event-driven system is that you can log every single thing happening, since it all goes through a centralized dispatcher, which would greatly improve your ability to understand what part does what, and what data goes where.
Perhaps I'll build a prototype of this and see what it feels like.