Further Blurring the Line Between Client and Server

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 :crossed_fingers: :revolving_hearts:

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 and Meteor.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?

1 Like

I personally avoid isServer isClient code where I can favoring Meteor’s built in separation of client and server code by folder location or import entry points via package.js

The old way to do this was the built in client, lib and server folders.

For new projects I use the built in client and server imports entry points and simply import common stuff In both client main.js and server main.js

1 Like

I like organizing my projects in client, server, and import folders to let Meteor handle the client/server loading.

I haven’t done this in my projects yet, but it sounds like we are at about similar points.

Right now I have a very large methods.js and publications.js in my imports directory. I’ve been thinking of breaking those into smaller files to create more structure.

You can make as many separate JS files and then just import them to the single master methods.js file and single master publications.js file.

Method Imports & Exports: http://www.petecorey.com/blog/2016/08/01/method-imports-and-exports/

This blog post by @pcorey covers several ways of doing that, with some logic about the best approach. Thanks for this post @pcorey!

import { Meteor } from "meteor/meteor";

export const methods = {
    createPayment(options) {
        ...
    }
};

Meteor.methods(methods);

I declare all my domain specific entities in one JS File (even the run functions of methods) and only pass them through different factories/builders on server/client.

Makes dev so easy and fast, debugging is no big issue anymore and junior is understanding the domain specific logic, too.