Disclaimer:
- This project is currently in stable alpha and needs use cases and contributions to start growing.
- The name "Schema Dependency Principles" is very much a woking title.
- This article will also give the backdrop for the ideas presented, so the tldr is: Modular code is cool, dependencies is not. Depend on information schemas, not classes or named modules!
Introduction
NanoQueue is an idea that came to me when working on a microservice based concept for the municipal sector here in Norway. It is heavily influenced by the message driven architecture we worked on and is in itself not the real answer to any problems like many frameworks claim.
NanoQueue is a minimalistic message broker and module registry for your browser. It is currently implemented in less than 70 lines of JavaScript, including white space lines, curly braces and the works.
The mission NanoQueue is on is to help modularise your code and split your pages into smaller, self-contained components with minimal outside dependancies. I believe in the principles of building apps using the appropriate pieces, like a jigsaw puzzle, rather than buying into gigantic, heavy frameworks that solve all your problems, creates new issues you never knew you had and sucks up mobile data on a scale never seen before.
Let's face it, the Land of Front-End is a place of magnificent wonder and unspeakable horrors, all at once.
The mission
The concept I collaborated on sprung out from the need to get Master Data Management in check, and we soon uncovered a growing pile of troubles related to that. Some of the troubles we saw was vendor lock-in of our data, incompatibilities between systems needing to share data and the sheer complexities of the ginormous monoliths available to the public sector.
We quickly discovered that killing the monoliths was the most viable way forward, but splitting the monolith into a myriad of microservices that were still tightly coupled and relied heavily on each other smelled funny to us. At this time microservices was the new holy in the cloud sphere, and even Docker was not really a thing yet, or just in it's early infancies.
After some more work on this, we concluded that the dependencies of a service should lie in it's needed data, and what data it spits out. In a message driven architecture this is possible as the services never directly talk to each other. They only publish and subscribe to topics, queues or message types on a common message broker. Even the dependency for the broker can be somewhat loosened by using standard protocols like AMQP.
This lead us to the core of the concept: Depend upon an information schema published in standardised messages, not services or modules.
The key benefit is that you can replace any part with another part that consumes and produces at least the same schemas. There was of course a lot more to the meat of the concept, but this is the core of it, and the core I'm trying to take with me into the front-end space.
But front-end?
Yes, this core principle can be just as valuable on a web page with many components sharing data and events, relying on something to happen or data to be fetched, even user actions.
Now, this is not an attempt to attack, devalue or reason agains using tools like Redux, React, Vue, Angular and similar, as they are all great tools in their own way and area, and they absolutely solve a lot of problems. They can however be quite complex, and are often used just because that is what is being done.
In many cases all your component needs is to be notified of changed relevant data, relevant events or even requests for jobs done.
State
A component should care about and handle its own state, and share changes in its state with others who depend on that data as well. It should only change the state when something internal happens, and when it receives messages with new or updated data it cares about, that something internal should happen to process that data and possibly influence the state.
UI components
All components should know how to change its own appearances based on messages received or internal events like user actions on the component itself. UI components should only care about displaying data or interactive elements, and mostly be ignorant to how that data is retrieved or what happens because of the interaction.
Some exceptions here is internal actions in the component that need to happen in response to the interaction, like changing a colour, checking a checkmark or other types of UI changes to indicate to the user that they did interact with the component.
Non-UI components
These components are typically the ones maintaining some master data records, being the current truth to resolve disputes or what have you. They also do the background tasks like talking to a server, managing sessions, cookies, local storage and such, perform computations, translations, conversions, parsing or other type of short or long-running tasks that are request based in nature. Acting as a middleware for incoming data, automatically parsing etc. before emitting the result, would also be a great application.
NanoQueue's role
The part NanoQueue can play, and other brokers like it, is the central hub that handles the passing of the messages. Like I stated in the introduction, NanoQueue does not really solve the problem, but it facilitates the solution.
What I hope to see, is that we can build a community effort to creating standardised message schemas for specific types of messages.
A simple example
Let's take a component that displays a user profile. It needs data like the username, display name, e-mail, profile picture and similar. Some profiles are naturally more complex than others, but for this example, we'll keep it simple and assume that the extra data can be supplied in "schema extensions" or similar.
{
username: "some_dev",
email: "some_dev@email.some-dev.com",
display_name: "Some Dev 👨💻",
profile_picture: "http://some-dev.com/pic.png"
}
This data could be requested using a topic like general.user.profile.request
or something similar. This structure lends itself to easily parse the topic for specificity increasing for each dot, and makes looking up stuff in a dictionary really easy. It is also somewhat the standard in at least some server side message brokers.
The component that knows how to handle requests for a user profile, will do just that, get the profile from the server if it does not already have the data, and return it through another message, for example using a topic like general.user.profile
to make the data available to everyone wanting that data.
Some mechanisms for ensuring only the sender receives the response could easily be implemented using contract-bound callbacks, component generated one-off topics like general.callback.[some unique id]
which is given in the request as the callback address. To ensure only the requester can subscribe to the replies, NanoQueue would need a little feature extension, but this should be relatively trivial.
Now, the messages themselves would possibly look something like the following:
The request:
{
publish_topic: "general.user.profile.request",
data: {
user: "some user id"
},
callback_topic: "general.callback.123456789"
}
The public response:
{
publish_topic: "general.user.profile",
data: {
username: "some_dev",
email: "some_dev@email.some-dev.com",
display_name: "Some Dev 👨💻",
profile_picture: "http://some-dev.com/pic.png"
}
}
And the private response:
{
publish_topic: "general.callback.123456789",
data: {
username: "some_dev",
email: "some_dev@email.some-dev.com",
display_name: "Some Dev 👨💻",
profile_picture: "http://some-dev.com/pic.png"
}
}
Bonus feature
As an added bonus feature, NanoQueue supports taking the role as the module registry. If you have a dependency which is a module, not a schema, or want to separate the HTML from the JavaScript code as much as possible, you can have the modules self-register with NanoQueue when they load, for example using self executing anonymous functions ( immediately invoked anonymous function is another name I've seen) to load the module when the script loads.
You can then access the modules using their name:
const myModule = _Q.getModule("myModule");
This also saves you from clobbering the window-object with loads of modules and random variables. You have one place to find your data and dependencies. Only NanoQueue needs to be globally accessible through the window object.
Conclusion
I have not gotten so far in this project, but the broker works. The concept is formulated, but not implemented. NanoQueue lives in a repo on GitHub:
NanoQueue
Minimal Pub/Sub Message Broker and Module Registry for messaging between components on a page. See https://nanoqueue.dittdesign.com for a live example usage.
Introduction
NanoQueue is a minimalistic messaging broker and module registry for decoupling modules and components on webpages.
NanoQueue registers itself in the global window
object as NanoQueue
and with the short form _Q
. Accessing the framework is as simple as window.NanoQueue
, window._Q
or in its simplest form, just _Q
.
Messaging
NanoQueue is mainly used for sending messages between components, allowing you to have a simple way to make sure hard-to-reach components get their status updates. You can also register multiple components to subscribe to the same topics, allowing you to very easily send the same info to many receivers at once.
This architecture is well known in the cloud native, microservices and scaling spaces as a "Publish/Subscribe" architecture, or as a message driven architechture.
Working…
I'm guessing the standardised message schemas and the whole concept should have it's own space somewhere, and possibly some tooling to validate the schemas.
I am open for suggestions and comments, contributions and collaborators who can help me shed some light on the concept and spread the word.
Thank you for reading!
Top comments (2)
Great read! Sounds like a really neat approach, similar to have I've done things in back-end systems.
Thanks! I'd love to hear more about your take on it and how you've done things. I'm guessing it was related to a message driven architecture?