March 2, 2016
We recently published bos-couchdb, our first reusable third-party service for BlueOak Server. Reusable services can be installed with npm and shared between projects. To understand what that means, let’s first talk about what a service is.
Anatomy of a BlueOak Service
A service is a mechanism for implementing reusable modules in a BlueOak application. These modules can be used for just about anything–calling REST APIs, performing business logic, connecting to database and more.
A BlueOak service is just a Node module. There is one thing, however, that makes a BlueOak service special: the init function. The init function is called when the server starts up. The parameters of the function are names of other services. The server ensures that the init functions for those services are called first. Once initialized, those services are injected into the init function of the dependent service.
In the example init function below, both the built-in logger and config services are injected at startup. The config service is used to read a value for a date format string, and the logger service is used to out the current date using that format. The callback parameter isn’t the name of a service but an optional callback function. This allows services to perform asynchronous operations during startup
When the server needs to load a service, there are a few places it will look. First, it looks for built-in services like config and logger. These come with the server and are always available in any BlueOak application. Next, it looks for services as .js files in the services directory of the application. Finally, it can use Node’s module loader to load services. This would include any module installed using npm. These are what we refer to as third-party services.
Third-party services, like bos-couchdb, can be installed with npm. Once installed, a service can be injected into other services through the init mechanism.
The bos-couchdb service uses the nano library under the covers to communicate with CouchDB. Why even bother wrapping another library as a BlueOak service? There are a few ways in which the bos-couchdb service is more convenient than using nano. For example, it has an option for creating the database if it doesn’t already exist that nano doesn’t have. More generally, there are several reasons to use a BlueOak service instead of an ordinary Node module.
BlueOak Server encourages the clear separation of configuration from code. Most Node modules, like nano, do not care where configuration comes from. Too often in Node.js applications, values that should be part of configuration, such as hostnames, ports, and credentials, are littered throughout code.
With BlueOak Server, configuration is stored within json files in the application’s config directory. The built-in config service will read the configuration so that developers don’t need to worry about details such as where the configuration is located or whether we’re in a development or production environment.
Let’s look at an example of a configuration block for the bos-couchdb service.
This configuration tells us that we have a CouchDB server at http://127.0.0.1:5984 containing a database named testdb. For any application using bos-couchdb, we know exactly where to look to find the CouchDB database configuration.
Clear Startup Lifecycle
We’ve already discussed how the init function works during startup to initialize a service and handle dependency injection. This can greatly reduce the complexity of dealing with asynchronous operations during server startup. In the case of a CouchDB database, verifying a valid connection to the database can only be performed asynchronously. By using the bos-couchdb service to handle connections, there’s a guarantee that your service won’t attempt to start until a connection has already been established.
If the bos-couchdb service can’t establish a connection to the database, startup stops with an appropriate error message. We’re using such a structured approach to server startup, the startup process becomes more deterministic with fewer potential points of failure.
One key benefit of dependency injection is that it’s simple to swap out services for testing purposes. Unit tests shouldn’t have any external dependencies. A service with a dependency on bos-couchdb shouldn’t need an actual CouchDB database to be up and running. Instead, a mock bos-couchdb service can be injected into the service’s init method in place of a real one. Ordinary Node modules, like nano, are much more difficult to swap out in unit tests.
Third-party services are a big step forward in creating an ecosystem of reusable modules for BlueOak Server. The bos-couchdb service is our first, but there will certainly be many more to come.