-
Notifications
You must be signed in to change notification settings - Fork 1
Home
doix
is a middleware framework using naming conventions for routing.
You may clone the sample application and play with it consulting the step by step guide.
In essence, doix
is a general purpose request handler.
Unlike many frameworks, it is not at all Web centric. It's intended for processing all kinds of incoming requests: HTTP* for sure, but also AMQP ones, the likes and, overall, any object considered a "request":
- using injected resources, including pooled ones,
- in an observable and manageable way,
- with a fixed simple, clean, but totally configurable lifecycle.
Each request
is handled by a one-off object called job, specifically, which a single asynchronous call await job.outcome ()
which:
- performs initialization (adjusts the request details, sets up default values etc.);
- selects the right business method from the application's modules according to its naming conventions;
- executes it in the context of the job available as
this
variable; - frees the resources used and reports on the completion.
In a viable project, you never name a procedure creating a shipping order, say, "f00B4r_BAZZ". It's always "ShippingOrder.create" or "create_shipping_order" or like. Every incoming request invoking such a procedure will probably carry some "create" mark, be it an HTTP verb, SOAP action header, payload fragment etc. And you never want your team mates to name "add" or "make" exactly the same action for the next entity. You need meaningful and consistent rules, just to save time by avoiding the mess.
With such a discipline, the explicit request-to-procedure routing used in several frameworks looks redundant and even a bit error prone. On the other hand, using naming conventions as a part of application logic not only makes your code cleaner, but enforces the adopted rules automatically. Two birds, one stone.
The core Job
class implementing the simple and straightforward lifecycle is meant to never be extended. But it's completely event driven, hence, deeply customizable at several levels.
That customization is achieved by developing factory classes extending JobSource
. Every job comes from some source which sets it up upon creation, including setting event handlers. For instance, the base WebService
(like any protocol adapter in doix
) is a direct JobSource
descendant that tells each newly created job how to fetch request parameters and what to do with the future response / error.
Unlike Job
, JobSource
is infinitely subclassable to implement not only transport level details, but business logic too. Custom security checks are typical example here. To massively impact the request processing, the JobSource
developer has two options:
- to add event handlers (which is flexible, but may cause some problems with the execution order);
- to override onJob*** methods (were the order is guaranteed).
doix
relies heavily on the DI concept. Application module's developers operate on Job
instances available as this
, with multiple properties injected during initialization: this.app
for the hosting application, this.request
for the parameters to process, this.user
for the current actor and so on. The point is to implement business logic having all necessary variables in hand without any extra code.
Pooled resources, such as database connections, are too set preemptively, so you never need to acquire them explicitly. That might cause a performance issue, but injected values are in fact lazy proxy objects, so underlying resources are actually taken from pools only on demand.
And, for sure, what is implicitly acquired, is released the same way. doix
takes care of the proper cleanup to prevent resource leakage.
In node.js, winston seems to be the de facto standard logging platform. So doix
uses it as such. Nearly every core object in doix
have a corresponding property named .logger
. For instance, it's one of the injected job's properties, so, in business methods, developers are free to add lines like
this.logger.log ({level, message})
Whether it will be actually written and where exactly, depends on the JobSource
, by default inheriting .logger
from the hosting Application.
But it's worth noting that, in most cases, no explicit logging is needed at all. The whole data processing is tracked automatically: each meaningful event related to a request
is reported, tagged for later analysis. Related events have related tags, e. g. messages from jobs coming from the same source have IDs with a common prefix and messages from one job's resources have IDs prefixed by this job's ID.
For each 'finish'
event, the time elapsed since the matching 'start'
is calculated and stored into the winston
's info object. So, with proper configuration, application logs contain hierarchical profiling data.
Although quite minimalistic, doix
provides some tools for not only calling the right methods for incoming requests, but also for controlling their frequency and execution time.
First of all, every JobSource
exposes its set of .pending
jobs letting count, list them and do whatever else developers need (e. g. forcibly close network connections).
To avoid resource overflows at early stages, the .pending
size is made easily limitable via the maxPending
option.
Another JobSource
's option, maxLatency
, is a simple measure against operation hang-ups. Finer tuning at per job level is available by calling setMaxLatency ()
prior to executing the business method.
Moreover, the special job source class, Queue
, offers a flow control solution that guarantees the required time lag between sequential operations.
One of the doix
's development goals is to avoid redundant dependencies. In ./node_modules
, your application should have nothing but the necessary minimum.
To this end, the grand project is split into multiple tiny npm
modules. As of this writing, the family consists of:
- the Web services boilerplate with cookie based authentication plugins using
- the relational DB interface with backends for
- adapters for Web UI frameworks:
To avoid version collisions, they rely one on another as peer dependencies. So should do your application.