rediservice
Simple microservices with Redis pub/sub and caching.
Microservice principles
- Each microservice has a name, e.g. "text.join" or "text.caps" (see below)
- Microservices send data messages that are objects with keys and values
- A microservice has handlers to handle data matching particular "signatures"
- Signatures may specify that a particular data key must be present or absent
- Signatures may also specify that a particular data key has a particular value
- A microservice may listen to data messages sent by other microservices
- A microservice should not send data messags directly to other microservices
- Exception to principle 7: an orchestrator microservice that routes messages
- Microservices should be small, simple, independent and easily tested
Rediservice is designed to help you to achieve all these principles in your code.
How it works - an example
1. Install and run Redis
Visit https://redis.io to download, install and run Redis on your machine.
2. Create two "text" microservices with Rediservice
Here, we'll create a microservice called "text.join" to join a list of words together and set a result
value, and also a microservice called "text.caps" to capitalize a list of words, and return the new list together with a word count.
'use strict'; moduleexports = ;
In the examples above, the choice of result
as the key was arbitrary. You can merge any keys and values you like with the returned data object (e.g. count
in the second example).
The following methods are exported:
run(selector, options)
to run the microservices (in this case 'text.join' and 'text.caps')running()
to get an Object whose keys are the service names that are runningservice(serviceName, serviceDSL)
to define a new microservice using this DSLservices(selector)
to get an Object whose keys are service names and values are functions to start the serviceson(serviceName, match, fn)
to handle messages that match data "signatures"send(serviceName, data, newData)
to publish processed data to a microservicesetCache(key, data, ttl)
to set a value in the Redis cachegetCache(key)
to get a value from the Redis cache (as a Promise), or...getCache(key, next)
to get a value from the Redis cache (as a callback)setup(redisOpts)
to setup microservices by running the services DSL functionexports()
to return the Rediservice object having these methods
Relax! Typically, you'll just call the setup()
method then the run()
method to run the microservices. Parameters are optional, but some examples are included below as a demonstration.
3. Running the example microservices
Without parameters:
;
With some example parameters:
const textExample = ; // Run only the "text" microservicestextExample;
Yes, it's that simple. When run
is called with no first argument, all the services are run. When run is called with a String or RegExp first argument, then only services that match the argument are run. The second form is useful for testing (see below). A second argument allows you to pass options to the services that are being run, for example {debug: true}
to enable verbose logging.
4. Test the two microservices
Rediservice was designed to be easy to test.
By default Rediservice will use Redis database number 1. When Rediservice is run with NODE_ENV=test
it uses Redis database number 2 instead. The default Redis database 0 is never used. You're welcome to override this behavior by setting environment variable REDIS_DATABASE=3
for example.
If your Redis installation uses a password, then set REDIS_PASSWORD=mysecret
.
If your Redis installation is running on a remote machine or non-standard port, set environment variable REDIS_URL=redis://myserver:1234
(replacing "myserver" with your server host or IP, and "1234" with whatever port you're using). By default, Rediservice will run using Redis URL "redis://localhost:6379".
'use strict'; const assert = assert; const textExample = ; // (the code above) ;
5. How to cache data with Rediservice
Sometimes, microservices need to operate on data that is best stored in a cache.
And coincidentally, this is something for which Redis is perfect. To enable caching with Redis, rediservice
provides setCache
and getCache
methods:
'use strict'; const assert = assert; const rediservice = ; // set an arbirary cache key/value pair...rediservice; // get the value from the cache...rediservice;
Note that the default TTL for caching is 1 day (86400 seconds), but you can override that by passing a third argument to setCache
- the TTL integer value in seconds, for example rediservice.setCache('user123', {name: 'Kevin'}, 3600)
to set user details for an hour. Alternatively, set the environment variable REDIS_CACHE_TTL
to the number of seconds you require.
If you need to, you can set the environment variable REDIS_PREFIX
to avoid potential key collisions with any other caching you're performing on the Redis server.
What else can Rediservice do?
Rediservice is designed to be very small, fast and reliable, so currently no extra capabilities are included. I encourage you to write new NPM packages that leverage Rediservice to add the features you need. The API is stable at version 2, so you don't need to worry about changes breaking your module(s) in the future. This module only relies on the very excellent "redis" module: https://www.npmjs.com/package/redis
But there is one extra feature that could be useful called rediservice.types
:
rediservice.types.isArray(value)
- return whether value is an arrayrediservice.types.isBoolean(value)
- return whether value is booleanrediservice.types.isDate(value)
- return whether value is a Date objectrediservice.types.isFunction(value)
- return whether value is a functionrediservice.types.isNil(value)
- return whether value isnull
orundefined
rediservice.types.isNull(value)
- return whether value isnull
rediservice.types.isNumber(value)
- return whether value is a numberrediservice.types.isObject(value)
- return whether value is an objectrediservice.types.isString(value)
- return whether value is a stringrediservice.types.isUndefined(value)
- return whether value is undefinedrediservice.types.hasType(type, value)
- return whether value is of a typerediservice.types.notType(type, value)
- return whether value is not a type
Often when writing microservices, you'll want to check whether a value has been set in the data object, and to confirm it's of the type that you require.
Inside your microservice definitions, you can replace rediservice.types
with this.types
, for example:
if ( this.types.isArray( data.words ) ) { ...
See the example code below.
OK, so show me the code!
Here's Rediservice - at just 111 SLOC: lib/rediservice.js
And for the example code, see examples/text-service-example.js and test/examples/text-service-example.test.js
Author
Please email kevin.hutchinson@legendum.com with queries and suggestions. Thanks!