Long lived solutions
Enterprise tools often have a much longer life cycle than typical consumer web pages and applications.
They must be robust and survive several version upgrades of the underlying system with little or minimal adaptation needed.
Standardized web APIs with multiple browser implementations are guaranteed to be available long term. Also, basically all the functionality necessary to effectively build full web based enterprise applications is now part of standards and built into all evergreen browsers.
This means that what used to be exclusive to frameworks is now available in the standard web APIs and it's possible to build applications relying on those rather than being dependent on an extra layer of non-standard APIs.
I've been working on several critical systems in different larger enterprises, and I'd like to share some of the knowledge gained on how to utilize the web platform directly to remove the need for additional frameworks.
In this post, I'll briefly mention simple pure web platform solutions, to two of the most common problems, where people have said: "But, you'll need a framework for that!", while really - you don't ;).
NOTE: A deep dive into each of these and more will be posted separately.
Dispatching events
In standard enterprise applications, it's often necessary for a Service component, interacting with a server API, sending notifications to Model or UI components in the web application.
Different frameworks have different ways of solving this with their own mechanisms for sending notifications between loosely coupled components.
However, for quite some years now, it's been possible to extend EventTarget to have event dispatching functionality from any pure platform components like DOM nodes have (Apple finally added it in Safari 14 too).
This makes it possible to implement a Service like this:
export const MyService = new class extends EventTarget {
// ...
onStuffHappened(stuff) {
this.dispatchEvent(new CustomEvent('my-event', {detail: { stuff }}));
}
// ...
}
..and any component listening on MyService
for my-event
will now be notified:
//...
MyService.addEventListener('my-event', e => { handleStuff(e.detail.stuff) });
//...
Read more about using EventTarget here
Single page app router
The web is very powerful when it comes to sharing content by just providing a direct deep link, copied from whatever is in the address bar and sent on to whoever needs to see that same information.
This, however, requires the application developer to have thought of a consistent 'URL to content' scheme that - hopefully - doesn't break over time (e.g. with application updates).
For applications with simple content, it's possible to keep a fairly simple nested tree structure that just points to e.g. a category and item like https://myapp.example.com/customers/42
, but most often, more complex applications will have multiple dimensions that makes this approach suboptimal.
A well known example is Google Maps that can have multiple independent pieces of information (e.g. geolocation of the view, route from A to B and some specific restaurant) that a user might want to store for later or share with a friend and expect to display exactly the same whenever viewed again.
This can be handled by dynamically interacting with the link displayed in the address bar through the History API to keep the displayed link in sync with the relevant view states.
This might sound overly complicated and - for sure - require some huge external component to solve the problem. But in reality, by combining the History API with the above mentioned EventTarget, this could be solved by making a small 'broker service' between the URL bar and application state that also dispatches state changes to potential listeners when the application is running.
Here is a simplified example to give an idea:
export class AppStateURLBroker extends EventTarget {
...
constructor() {
super();
// Fetch all state values on page load from the link provided
for (let [name, value] of new URLSearchParams(location.search).entries()) {
this.#myStates.set(name, value);
}
}
...
setStateValue(name, value) {
...
// Send the new 'value' for this 'name' to all potential listeners...
this.dispatchEvent(new CustomEvent(`${name}-changed`, {detail: value}));
// Now, update the URL bar...
this.#myStates.set(name, value);
const stateURL = URLSearchParams();
for (let [i,j] of this.#myStates) {
stateURL.set(i, j);
}
window.history.replaceState({}, '', `${location.pathname}?${params.toString()}`);
}
}
If you want to prettify certain state fields in the URL to make it readable/writable, it's possible to add handlers for those specific
values.
Data validation is also something that should be handled to prevent users from entering corrupt or malicious data.
Final thoughts
While working on a fairly complex application for a larger enterprise, based on pure web platform APIs for the main application and Lit for fast and easier DOM updates of visual components, we discovered a few key benefits:
- The application code could be kept simple and stable (relying on long lived web standards)
- The payload size could be kept fairly small (no larger framework to carry around)
- Traditional backend developers could easily contribute - because there was no non-standard 'framework magic' involved and it was easy for them to comprehend what went on in the application
As mentioned above, I'll make separate posts, where we will go in detail on how the above and more can be used when creating enterprise applications.
Enjoy ;)