1.2.5 • Published 2 years ago

@pestras/micro v1.2.5

Weekly downloads
95
License
ISC
Repository
github
Last release
2 years ago

Pestras Micros

Pestras Microservice as PMS is built on nodejs framework using typescript, supporting nodejs cluster with messageing made easy between workers.

Although PMS is almost empty of features, its strength comes handy through its plugins.

Official Plugins

Creating Service

In order to create our service we need to use SERVICE decorator which holds the main configuration of our service class.

import { SERVICE } from '@pestras/microservice';

@SERVICE()
class Test {}

Service Configurations

NameTypeDefualtDescription
workersnumber0Number of node workers to run, if assigned to minus value will take max number of workers depending on os max cpus number.
logLevelLOGLEVELLOGLEVEL.INFO
tranferLogbooleanfalseAllow logger to transfer logs to the service onLog method.
stdinbooleanfalselisten on stdin data event.
healthCheckbooleantrueEnable health check interval.

LOGLEVEL Enum

PMS provides only four levels of logs grouped in an enum type LOGLEVEL

  • LOGLEVEL.ERROR
  • LOGLEVEL.WARN
  • LOGLEVEL.INFO
  • LOGLEVEL.DEBUG

Micro

After defining our service class we use the Micro object to run our service through the start method.

import { SERVICE, Micro } from '@pestras/microservice';

@SERVICE({
  // service config
})
export class TEST {}

Micro.start(Test);

Micro object has another properties and methods that indeed we are going to use as well later in the service.

NameTypeDescription
statusMICRO_STATUSINIT | EXIT| LIVE
loggerLoggerMicro logger instance
store() => anyreturn data store shared among main service and the all subservices and plugins.
message(msg: string, data: WorkerMessage, target: 'all' | 'others') => voidA helper method to broadcast a message between workers
exit(code: number = 0, signal: NodeJs.Signal = "SIGTERM") => voidUsed to stop service
plugin(plugin: MicroPlugin) => voidThe only way to inject plugins to our service

Sub Services

PM gives us the ability to modulerize our service into subservices for better code splitting.

SubServices are classes that are defined in seperate modules, then imported to the main service module then passed to Micro.start() method to be implemented.

// comments.service.ts
import { SubServiceEvents } from '@pestras/microservice';

export class Comments implements SubServiceEvents {

  async onInit() {
  }
}
// main.ts
import { Micro, SERVICE, ServiceEvents } from '@pestras/microservice';
import { Comments} from './comments.service'

@SERVICE()
class Articles {

  onInit() { 
  }
}

// pass sub services as an array to the second argument of Micro.start method
Micro.start(Articles, [Comments]);

Subservices have their own events onInit, onReady, onStdin and onExit.

STORE Decorator:

There are cases when sub serveses need to access each other methods even with the main service,
STORE decorators adds methods attached to to Micro store when each service instanciated, that way can be accessed any where.

// store.ts
import { Store } from '@pestras/micro';

export MicroStore {
  key: string;
  getArticleById: (id: string) => Promise<Article>
}

export const store = new Store<MicroStore>();
// index.ts
import { store } from './store';

@SERVICE()
class ArticlesService {

  onInit() {
    store.set("key", "value");
  }

  @STORE(store)
  async getArticleById(id: string) {
    // ...our fetch code
  }
}
// comments-service.ts
import { store } from './store';

class CommentsService {
  
  async insertComment(articleId: string, comment: string) {
    // access store values
    store.get('key');
    
    // use shared method
    let article await = store.get('getArticleById')(articleId);
  }
}

Note: stored methods overwite previous methods with same name.

Cluster

PMS uses node built in cluster api, and made it easy for us to manage workers communications.

First of all to enable clustering we should set workers number in our service configurations to some value greater than one.

import { SERVICE, WORKER_MSG } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher {}

To listen for a message form another process.

import { SERVICE, MSG } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher {

  @WORKER_MSG('some message')
  onSomeMessage(data: any) {}
}

To send a message to other processes we need to use Micro.message method, it accepts three parameters.

NameTypeRequiredDefaultDescription
messagestringtrue-Message name
dataanyfalsenullMessage payload
target'all' | 'others'false'others'If we need the same worker to receive the message as well.
import { SERVICE, Micro } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher {
  
  // some where in your service
  Micro.message('some message', { key: 'value' });
}

Also it is made easy to restart all workers or the current one.

import { SERVICE, Micro } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher {

  // some where in our service

  // restarting all workers
  Micro.message('restart all');

  // restarting the current worker
  Micro.message('restart');
}

Plugins

To create our own plugins, it is just easy as creating a sub service as follows:

import { MICRO, MicroPlugin } from '@pestras/micro';

export interface PluginConfigInterface {}

class MyPlugin extends MicroPlugin {

  constructor(private config: PluginConfigInterface) {
    
  }

  async init() {} // init method is required

  onReady() {}

  onStdin() {}

  onStdinEnd() {}

  onExit() {}
}

export { MyPlugin }; 
  import { Micro } from '@pestras/micro';
  import { MyPlugin } from 'mypluginPath';

  Micro.plugins(new MyPlugin(config));
}

Lifecycle & Events Methods

PMS will try to call some service methods in specific time or action if they were already defined in our service.

onInit

When defined, will be called once our service is instantiated but nothing else, this method is useful when we need to connect to a databese or to make some async operations before start listening one events or http requests.

It can return a promise or nothing.

import { SERVICE, ServiceEvents } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher implements ServiceEvents {

  async onInit() {
    // connect to a databese
  }
}

onReady

This method is called once all our listeners are ready.

import { SERVICE, ServiceEvents } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher implements ServiceEvents {

  onReay() {}
}

onExit

Called once our service is stopped when calling Micro.exit() or when any of termination signals are triggerred SIGTERM, SIGINT, SIGHUP,

Exit code with the signal are passed as arguments.

import { SERVICE, ServiceEvents } from '@pestras/microservice';

@SERVICE({ workers: 4 })
class Publisher implements ServiceEvents {

  onExit(code: number, signal: NodeJS.Signals) {
    // disconnecting from the databese
  }
}

onLog

PMS has a built in lightweight logger that logs everything to the console.

In order to change that behavior we can define onLog event method in our service and PMS will detect that method and will transfer all logs to it, besides enabling transferLog options in service config.

import { SERVICE, Micro, ServiceEvents } from '@pestras/microservice';

@SERVICE({
  transferLog: process.env.NODE_ENV === 'production'
})
class Test implements ServiceEvents {

  onLog(level: LOGLEVEL, msg: any, extra: any) {
    // what ever you code
  }

  onExit(code: number, signal: NodeJS.Signals) {
    Micro.logger.warn('exiting service');
  }
}

onStdin

PMS listens to stdin by default unless it is disabled in service config decorator, it will call this event whenerver inputs are injected to stdin.

import { SERVICE, Micro, ServiceEvents } from '@pestras/microservice';

@SERVICE()
class Test implements ServiceEvents {

  onStdin(chunk: Buffer) {
    console.log(chunk.toString());
  }
}

"exit" input will exit the process.

Health Check

PMS makes an interval check for the service health state members (healthy, ready, live), and any value that is undefined will be considered as true value.

@SERVICE({ workers: 4 })
class Publisher implements ServiceEvents, HealthState {
  healthy = false;
  ready = false;
  live = false;

  async onInit() {
    this.healthy = true;

    // check readiness
    this.ready = true;

    // check liveness
    this.live = true;
  }
}

Be aware that even plugins are health checked as well, so even your service is healthy that does not mean that the healthcheck result should be healthy as well.

Even sub services have their individual health check.

Healh state is saved in a file under a directory specicfied in HEALTH_CHECK_DIR environment variable, defaults to "~/".

To complete the health check process you need to enable health check in Dockerfile or docker-compose, as well as readiness and liveness check in k8s if used.

HEALTHCHECK --interval=30s --timeout=2s CMD npx health-check healthy
healthcheck:
  test: ["CMD", "npx", "health-check", "healthy"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 40s

Last argument can be one of (healthy, ready, live) kewords, it is optional defaults to healthy.

Thank you

1.2.5

2 years ago

1.2.4

2 years ago

1.2.3

2 years ago

1.2.2

2 years ago

1.2.1

2 years ago

1.2.0

3 years ago

1.1.6

3 years ago

1.1.5

3 years ago

1.1.4

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.1.3

3 years ago

1.1.2

3 years ago

1.0.0

3 years ago

0.1.4

3 years ago

0.1.3

3 years ago

0.1.5

3 years ago

0.1.0

3 years ago

0.1.2

3 years ago

0.1.1

3 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.3

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago