-
Notifications
You must be signed in to change notification settings - Fork 73
Abstraction layers and Engines
Modular nature of the core allows the Core modules to expose their functionality in a diverse manner. A nice example of this diversity is a Server Core module (perhaps an even nicer example may be a Database Server Core module, but it seems that the Server Core module is simpler, so it is a better candidate for an example).
A generic MEAN stack has the E letter standing for Express. Express is a software library that allows you to "teach" a running http(s) server to treat urls of incoming Server requests as RPC requests (RPC == Remote Procedure Call). So, certain urls will be interpreted as names of functions to run, and the provided parameters in the requests' query string will be the input parameters to the function to call. By using this library you turn a plain http(s) server (request handling server) into an Application server (RPC server).
It turns out that Express needs not be the only way to "RPC-ize" your http(s) server. There are other libraries out there that serve more or less the same purpose. So, it may be nice to allow the Server Core module to serve its purpose using different RPC-izing libraries.
So, the Server Core module should "stand in front" of RPC libraries, insulating the mean.io world from the internals of using Express (or any other library of a kind, hapijs being a notable example). Thereby, the Server Core module becomes an Abstraction layer, abstracting out the underlying library.
In order to do its work, an Abstraction layer needs to provide methods for all the operations needed by mean.io packages. These methods will call the internals of the underlying library.
Defining the methods of the Abstraction layer turns out to be the tricky part. Abstraction layer will turn out to be as capable as the least capable underlying library it supports.
At the moment, the Server core module supports just the Express library, and the Abstraction is done only as a proof of concept (although a sufficiently functional one).
The Server Core module's implementation is found in meanio/lib/core_modules/server
. The index.js file provides means to actually start an http(s) server and an instance of an Abstraction layer.
The Abstraction layer is implemented in the engine.js
file, as the ServerEngine class. This is the fundamental class, providing methods to the outer world. Currently only two methods exist on the ServerEngine, beginBootstrap
and endBootstrap
. These methods will be used in the index.js file. The mechanism is broken down into following steps:
index.js registers the entry function named supportServer
that the Core will call and provide the Meanio function (the Core).
This function does a single thing: registers an "event handler" for the onInstance
event. In other terms, the Server Core module will not generically alter the prototype of the Meanio instance and provide no extra methods on it - simply because there is no generic functionality to be provided. Server Core module's purpose is to provide a dependency named app
, and that dependency can be registered on an existing Meanio instance - hence the "event handler". (Mind the "event handler" being in quotes, because this event handler receives a q defer object that needs to be resolve
d in order for the Meanio instance to actually start). The handler for the onInstance
"event" is named onInstance
.
Now that the meanioinstance
is obtained, a method named serve
is inserted into the Meanio.prototype, because this method will actually use the meanioinstance
. One may "insert" this function to be a property of the meanioinstance
itself, but I prefer not to change shapes of instantiated objects (memory consumption issues). It is completely valid to change a prototype of an already existing instance (although this approach turns out to be extremely dirty and confusing in many cases; Meanio situation with a Singleton behavior is exactly not that type of a case).
Except for inserting the serve
method, the chainware property is inserted into the Meanio.prototype (again - into the prototype instead of into the very meanioinstance
). However, the chainware is about to be substituted with a much more efficient approach - Request prototype shaping.
Now, the very server operation exposed through the serve
method cannot be done without a working database
connection. So the serve
method just registers a handler for the database
dependency (aside from trivial setting of active flag and remembering the options).
Mind how the serve
is implemented - as a bind
-ed version of the genericServe
(bound to the Meanio function). genericServe
acts a fully fledged prototype method, having this
point to the very instance of Meanio. Once it gets hold of the database
, several functions of a similar kind will implicitly become methods of Meanio (because this
will point to Meanio instance within), but invisible methods (not accessible from outside of the index.js). These are serveWithDB
and serveReady
.
Real server bootstrap occurs here. The Server Abstraction layer factory method produceEngine
is called and an instance of the Engine is obtained, according to the value of the serverengine
field in the config (default value is, of course, 'express'). Engine is the instance of the Abstraction layer. In this case the engine will be an instance of the ExpressEngine class (found in ExpressEngine.js
) that inherits from ServerEngine (found in engine.js
).
beginBootstrap
method will be called on the Engine. The method is expected to be synchronous, just to create an instance of the underlying RPC library and perform initial settings (at this point the mean/config/express.js
is require
d). The main purpose of beginBootstrap
is to register the app
dependency. I chose to actually perform the real setup of the Express instance in the 'app' dependency resolver function, although there seem to be no constraints on doing everything directly in the beginBootstrap
.
After calling beginBootstrap
, serveWithDB
waits for all the "event handlers" of the onInstance
event to resolve
their q defers. After that, endBootstrap
of the Engine is called by passing it the callback; endBootstrap
is expected to be asynchronous.
- Many more methods need to be defined on the fundamental ServerEngine. This is a harder part of the job, almost impossible to do without an accompanying HapiEngine to actually test the validity of the Abstraction layer prototype layout. Because this job is far from being done, a dirty hack is applied in a single line, the last line of the 'app' resolver function, the
initApp
method:return this.app
. The clean way would bereturn this
. Because of this subtle difference, the Server Core module acts just like a mockup, being an Abstraction Layer only to the very Server Core module that callsbeginBootstrap
andendBootstrap
methods of the ServerEngine. All the mean.io universe still actually uses an instance of Express (this.app
). The final goal is to have the whole mean.io universe use the instance of the ExpressEngine (this
) - Even though the mean.io now uses an instance of Express, there is still one method that the Express instance cannot provide, and mean.io needs it badly - the useStatic method. This method allows a package to define additional static paths to resource files in the file system. This is because of the very Express architecture that defines static as a method of the Express function, not as a method of the Express instance. So a dirty hack is introduced by "inserting" a useStatic function directly into the express instance (yes, changing the shape; yes, just once, but again...). However, this dirty hack removed all the
require('express')
line of code scattered all around mean.io packages. express is no more a dependency of the mean repository, so the dirty hack is rather beneficial here. Once the hack #1 described above is resolved, the ExpressEngine will proudly expose the useStatic method in a clean manner and all will end well.