nova-faas

FAAS (functions as a service) in nodeJS with promises.

Usage no npm install needed!

<script type="module">
  import novaFaas from 'https://cdn.skypack.dev/nova-faas';
</script>

README

nova-faas

CircleCI CodeFactor Coverage Status

nova-faas is a node.js library that helps you to implement microservices and scalability over rabbitmq service discovery.

  • Read CHANGELOG.md
  • Read PLUGINS.md

Advantages

  • Think better! Think KISS!
  • Configuration-driven oriented framework
  • Only one monolithic project in your github
  • Only one monolithic project to maintain
  • Start only the services you want with configuration file
  • Agnostics commands and queries, they just had to be promises
  • Tests are isolate, because of the structure
  • Deploy and scale your microservices like you want
  • Automatic services discovery! Thanks rabbitmq

Installation

$ npm i -S nova-faas

Description

Beware, you need a rabbitmq running in localhost for all those examples.

Server

The server is the main program, he needs to be start at first. Because the project is configuration-driven, you only need those lines of code to start all selected microservices in a row.

Glob patterns

This syntax will load all pomises in this dirpath and attach a 1:1 queue for execute the promise. And attach two 1:N queues (on for success event, and the second for error event).

server.use(path.resolve(__dirname, '../test/data/commands/*.js'))

Simple server

const path = require('path')

const Server = require('nova-faas').Server
const server = new Server()

// all options from servicebus (see npm)
const options = {
  host: 'localhost',
  port: 5672,
  user: 'guest',
  pass: 'guest',
  timeout: 2000,
  heartbeat: 10
}

server
  .use(path.resolve(__dirname, '../test/data/commands/*.js'))
  .use(path.resolve(__dirname, '../test/data/queries/*.js'))
  .start(options)

server.on('error', error => {
  console.log('server error')
  console.log(error)
})

server.on('ready', () => {
  console.log('server connected')
})

Service

A service is the base element of the system, it's like a microservice or a task. The application result of a service will automaticaly:

  • Send an event on the bus in case of success
  • Send an event on the bus in case of error

You will never have to use this class, Command and Query extend it.

Command

  • "A result" is either a successful application of the command, or an exception.
  • Because it extends Service, success event or error event will be automaticaly send on the bus.

How to create a Command ?

Step 1
You need to create a file who contains "Command" in his name.
path/you/want/BasicNopeCommand.js

Step 2
You need to module.exports a promise.

const handler = function () {
  return new Promise((resolve, reject) => {
    resolve()
  })
}

module.exports = handler

Query

From the library point of view a query is the same as a command, but because of queries roles in the architecture, this time data will be return.

  • "A result" is either data, or an exception
  • Because it extends Service, success event or error event will be automaticaly send on the bus

How to create a Query ?

Step 1
You need to create a file who contains "Query" in his name.
path/you/want/BasicNopeQuery.js

Step 2
You need to module.exports a promise.

const handler = function () {
  return new Promise((resolve, reject) => {
    resolve({data: true})
  })
}

module.exports = handler

Now it's time to start the server

Classic start:

$ node examples/server.js

But, you can run the server in debug mode.

$ DEBUG=nova:* node examples/server.js

Client

It's time to learn how to link all those services and events together, let's me introduce the Client object.

Definitions

A client could be wherever you need it to be, even on another server, or behind a hapiJS/Express server, or why not in another Server.
You will have three patterns to use the server events bus.

Sender/Receiver pattern

The Send / Receive object pair uses a direct exchange (1:1) inside of RabbitMQ

Publisher/Subscriber pattern

The Publish / Subscribe object pair uses a fanout exchange (1:N) inside of RabbitMQ, allowing you to have as many subscribers as you need. Think of pub/sub as an event that gets broadcast to anyone that cares, or no one at all if no one is listening.

Request/Response pattern

The request/response pair uses a "topic" exchange. With a request/response setup, you can send a request for information and respond to it.

Send a Query

  • When the server start and load your handlers, receivers are created in the server.
  • Sender client is a classic fire and forget on the bus. In return you will have only a result who informs you if the command or the query has been executed succesfully or not.

Create a file client-sender.js, and and this code in:

const Client = require('nova-faas').Client
const client = new Client()

// all options from servicebus (see npm)
const options = {
  host: 'localhost',
  port: 5672,
  user: 'guest',
  pass: 'guest',
  timeout: 2000,
  heartbeat: 10
}

client
  .subscribe('BasicNopeQuery.Success', (result) => {
    console.log('success', result)
    client.close()
  })
  .subscribe('BasicNopeQuery.Error', (result) => {
    console.log('error', result)
    client.close()
  })
  .start(options)

client.on('error', error => {
  console.log('client error')
  console.log(error)
})

client.on('ready', () => {
  console.log('client connected')
  client.send('BasicNopeQuery', {message: 'This is a query'})
})

Nota

  • The pattern BasicNopeQuery.* will receive Error and Success event for one specific Query.
  • The pattern *.Error will receive Errors for all Commands and Queries.

Now it's time to start the client-send

Classic start:

$ node examples/client-sender.js

But, you can run the client in debug mode.

$ DEBUG=nova:* node examples/client-sender.js

Result will be:

{ type: 'Query',
  name: 'BasicNopeQuery',
  event: 'BasicNopeQuery.Success',
  params: { message: 'This is a query' },
  exectime: 1004,
  result: { data: true } }

Request a Query

  • When the client start a specific queue is created on the server.
  • The server will have in the data received an automatic header to help him answering the client who called.
  • Events succes or error are also published.

Create a file client-request.js, and and this code in:

const Client = require('nova-faas').Client
const client = new Client()
client
  .subscribe('BasicNopeQuery.Success', (result) => {
    console.log('success', result)
    client.close()
  })
  .subscribe('BasicNopeQuery.Error', (result) => {
    console.log('error', result)
    client.close()
  })
  .start()

client.on('error', error => {
  console.log('client error')
  console.log(error)
})

client.on('ready', () => {
  console.log('client connected')
  client.request('BasicNopeQuery', {message: 'This is a query'}, (data) => {
    console.log('result', data)
  })
})

Result from the success event will be:

success { type: 'Query',
  name: 'BasicNopeQuery',
  event: 'BasicNopeQuery.Success',
  params:
   { message: 'This is a query',
     __headers:
      { 'x-client-id': 'Client.Response.38057d6b-bd49-4b4b-9727-35146c43789a',
        'x-request-id': 'cb0d0e12-8f8c-4267-a20f-fb05c653aba6' } },
  exectime: 1001,
  result: { data: true } }

Result from the response callback will be:

result { type: 'Query',
  name: 'BasicNopeQuery',
  event: 'BasicNopeQuery.Success',
  params: { message: 'This is a query' },
  exectime: 1001,
  result: { data: true } }