Microservices vs ArchieJs Modules — comparison

Discussing Archiejs with Ian Tinsley (CTO, Lux group).

*** Ian: One of the main advantages of a Microservice to me is it represents a bounded context which contains private implementation details about how to persist data (if necessary), it’s more than a Module, it encapsulates a cacheing and hosting strategy, whether to use Redis, NOSQL, Postgres etc or just write to flat files. All of these details are private, and consumers of the service don’t need to know anything about the internal implementation details. In some cases you may decide to proxy all service endpoints to a commercial SaaS provider, it will make no difference to consumers of the service.

The question is about

Enforcing stricter boundaries

Microservices ensure strict boundaries —

(a) Persistence layer of their own

(b) Tests of their own

How do modular monoliths compare with microservices in these regards?

Services and persistence layer are usually separated by network. So while only one service can access a DB (or only a set of common services can); what enforces this isolation — ?

How are archiejs modules implemented?

(a) Each module is a directory and has a package.json (reminds us of npm modules)

(b) Package.json have a provides/consumes constructs. Below is an example of package.json of a leaderboard module

See provides and consumes tags above

If we look into “plugin” json above, we can see that there is an explicit dependency on “db.Game” and we are injecting db’s to the module using IoC. While the db’s are centralized, it being a monolith, there is a way to specify dependencies explicitly via some constructs.

Short digression, another important point is that the unit tests are contained within the modules. Its easier to mock modules because they are larger than individual files and define clear service boundaries. This allows us to come up with interesting ideas and tools around creating testcases.

Back to the main question of enforcing isolation with regards to DBs - a way to get past above boundary is by using join’s . Since we have a monolith; the collections in our DB has references to other collections.

Relationships in db

In code, we may want to populate this schema and it will lead to coupling. Something like,

this.gameDb.findById(‘some-id’).populate(‘userId’)

Perhaps a way to solve this would be to create a different module, dbWrappers and move all db couplings there:-

dbWrappers/gamedb.js

dbWrappers/questdb.js

…etc

It will be a common module imported by all modules that need to access more complex queries.

When the time comes, since all our persistence layer coupling is at one place, we can create new logic to do joins in application code — instead of using populate. So tomorrow when we are splitting leaderboard into a separate service (with its one db — ie. gameDb) we will do as follows,

Move files as below

modules/dbWrappers/gamedb.js → modules/dbWrappers/gameDb/gamedb.js

models/commondb/game.js → models/gamedb/game.js

Note: we moved gamedb into it’s own module. In this we will write a more explicit code for doing join’s in application code. It can go something like this,

Technically same as a db join

*** Ian: To me, the advantage of starting with a monolith is that all the code has access to all the data. This makes development very quick in the short-term but leads to an unmaintainable system long-term. I’m not sure how Modules fits in with this. If you really want to modularise your system you would need to split your DB into modules and this would give you most of the disadvantages of Microservices with none of the rapid development advantages of a monolith.

In Archiejs, there is an easy way to split DB into modules. We have a concept of enhancers or decorators, that can provide wrappers over a module for things that are not a part of the main business logic. We have a mongo enhancer that picks each of the schema, and makes a module out of it (so not much boiler plate). → https://github.com/archiejs/archiejs-mongo-enhancer

Just to add some other examples of wrappers, a wrapper that provided RPC functionality (we created one for Kue which works on top of redis). → https://github.com/archiejs/archiejs-kue-enhancer

Below is code snippet of mongo enhancer.

Additional “packageEnhancer” construct above

As a reminder/revision, to import the gameDb in leaderboard we had consumed ‘db.Game’ in its package.json file.

So a quick overview :-

  1. Dbs are passed to modules using IoC
  2. Joins are moved to db wrapper modules
  3. All this happens via configuration files (provides and consumes of dependencies) and its very easy to develop tooling around parsing configuration files.
  4. Microservices or monolith becomes more of a deployment question than a development question (as it should be).
  5. Services can be broken up and tested using simpler RPC mechanisms during development phase, before they are rolled out in full fledged microservices environment.
  6. Unit tests can be clubbed inside modules; and modules can also be mocked much more easily (as service boundaries are more stable).

--

--

Naval Saini
Naval’s Products, Engineering and Startups Blog

Hacker and into fitness and adventure. Leads a simple life and does not try to fit in too much. :-)