Before I start, I hope that you and the people around you are healthy and well in these crazy times.
Hopefully we’ll evolve as a species from all of this - towards global compassion and love, I hope
Discussing a Different Architecture
As stated in this article, one of Meteor’s biggest selling point is the way it blurs the line between client and server. I personally think this general direction is the future of software engineering.
I don’t like technologies where the project structure is highly influenced by the technology characteristics. A strong example of this is React Native combined with Redux, which commonly introduce ‘reducers’, ‘actions’, ‘containers’ and ‘components’ directories to the project structure. To me, the fact that a developer that’s unfamiliar with these technologies wouldn’t understand the structure hints its lack of elegance and simplicity.
Meteor took a step forward with ‘server’, ‘client’, ‘api’ and ‘ui’ directories - super straightforward for almost any developer. It took an even bigger step forward allowing us to share server and client code in the same file by scoping the code using the Meteor.isServer
and Meteor.isClient
booleans.
This opens a window to fuse files in some cases and introduce a little bit different project structure, and in general, a different software architecture.
I wanted to discuss with you guys the architecture I use for my Meteor web app and see what you think. Here are 2 basic examples:
Example 1: Storage Service (imports/infra/storage.js
)
storage.js
defines a singleton that manages Firebase storage for my web app.
It is a black box in the sense that I don’t need to think about the context (server/client) when I use it.
It handles everything internally, taking advantage of Meteor.isServer
and Meteor.isClient
to know with which library to access Firebase (server admin library or the browser JS sdk).
Related methods, collections, are all encapsulated in the same file - storage.js
.
Without the use of Meteor’s client and server scopes, a common alternative might have been to split this code into: publications.js
, methods.js
, and maybe even serverStorage.js
, and clientStorage.js
. Apart from not being as elegant, imagine the implications maintenance-wise.
Example 2: Push Notifications Service (imports/infra/push-notifications.js
)
As you can imagine, push-notifications.js
holds all the code related to push notifications in my web app, with the only exception being a dedicated server-worker which is used to intercept messages when the app is in the background.
In case you’re not familiar with push notifications in PWA (I’m pretty new to it myself) but from my initial research and understanding there are 3 main entities involved for a basic setup:
- Client code: request push notifications permission from the user and pass token to the server
- Server code: manage tokens and send push notifications
- Service-worker code: intercept notifications when your web app is in the background and push them to the user’s device.
Iv’e seen various implementations of this in PWA. All involved a lot of files with bits and pieces of code spread around, dozens of files in some cases.
With Meteor it can all boil down to the minimum possible - 2 files:
-
imports/infra/push-notificaions.js
: request push notifications permission from user and send token to the server (client scope) + manage tokens and send push notifications (server scope) -
public/server-worker.js
: intercept notifications when your web app is in the background and push them to the user’s device.
So push-notificaions.js
would look roughly like this:
if (Meteor.isServer) {
// relevant server messaging-library initialization
// init mongo collection (for tokens management)
// publish UPDATE_TOKEN_STATUS method (to be used from the client - below in this file)
// export sendPushNotification function (to be used anywhere on the server to trigger push notifications)
}
if (Meteor.isClient) {
// request push notifications permission from the user (via the browser)
// if approved, send token to the server using UPDATE_TOKEN_STATUS method (defined above)
// listen to token changes and update the server with UPDATE_TOKEN_STATUS
}
Summary
With the help of Meteor.isClient
and Meteor.isServer
, it’s possible to reduce code from a more general and less straightforward files such as publications.js
and methods.js
and have a more functional-oriented architecture where logical components (storage, push-notifications, etc.) can encapsulate, hopefully, all of their code (server + client) in one file.
So pure-server files and pure-client files still reside in server
and client
directories respectively, while common code is organized by functionality when appropriate.
Another big advantage of this approach is the ease of bringing-in new developers to learn/maintain a component. Project structure is straightforward and a logical component isn’t spread across several general files that do 1K other things.
My Experience So Far
My web app is in production for over a year now and in terms of maintenance and adding new such independent components - this architecture is a heaven.
I still use publications.js
and its friends for general services that don’t qualify as components by themselves.
Questions for You
I don’t know if this is a common architecture or not - do you use it in your projects?
Apart from the obvious importance of being careful and making sure you scope your code as you should, few questions come to my mind:
- Are there any built-in risks with this architecture in Meteor?
- Does an excessive use of
Meteor.isClient
andMeteor.isServer
considered bad practice? if so, why? - Will this scale well?
- How does this affect the bundle size?
Finally, if it’s new to you, what do you think of it?