Overview

A LoopBack application has its own life cycles at runtime. There are two methodsto control the transition of states of Application.

  • start(): Start the application
  • stop(): Stop the applicationIt’s often desirable for various types of artifacts to participate in the lifecycles and perform related processing upon start and stop. Good examples ofsuch artifacts are:

  • Servers

    • start: Starts the HTTP server listening for connections.
    • stop: Stops the server from accepting new connections.
  • Components

    • A component itself can be a life cycle observer and it can also contributelife cycle observers
  • DataSources

    • connect: Connect to the underlying database or service
    • disconnect: Disconnect from the underlying database or service
  • Custom scripts

    • start: Custom logic to be invoked when the application starts
    • stop: Custom logic to be invoked when the application stops

The LifeCycleObserver interface

To react on life cycle events, a life cycle observer implements theLifeCycleObserver interface.

  1. import {ValueOrPromise} from '@loopback/context';
  2. /**
  3. * Observers to handle life cycle start/stop events
  4. */
  5. export interface LifeCycleObserver {
  6. start?(): ValueOrPromise<void>;
  7. stop?(): ValueOrPromise<void>;
  8. }

Both start and stop methods are optional so that an observer can opt incertain events.

Register a life cycle observer

A life cycle observer can be registered by calling lifeCycleObserver() of theapplication. It binds the observer to the application context with a specialtag - CoreTags.LIFE_CYCLE_OBSERVER.

  1. app.lifeCycleObserver(MyObserver);

Please note that app.server() automatically registers servers as life cycleobservers.

Life cycle observers can be registered via a component too:

  1. export class MyComponentWithObservers implements Component {
  2. /**
  3. * Populate `lifeCycleObservers` per `Component` interface to register life
  4. * cycle observers
  5. */
  6. lifeCycleObservers = [XObserver, YObserver];
  7. }

Discover life cycle observers

The Application finds all bindings tagged with CoreTags.LIFE_CYCLE_OBSERVERwithin the context chain and resolve them as observers to be notified.

There may be dependencies between life cycle observers and their order ofprocessing for start and stop need to be coordinated. For example, weusually start a server to listen on incoming requests only after other parts ofthe application are ready to handle requests. The stop sequence is typicallyprocessed in the reverse order. To support such cases, we introducetwo-dimension steps to control the order of life cycle actions.

Observer groups

First of all, we allow each of the life cycle observers to be tagged with agroup. For example:

  • datasource (connect/disconnect)

    • mongodb
    • mysql
  • server

    • rest
    • gRPCWe can then configure the application to trigger observers group by group asconfigured by an array of groups in order such as ['datasource', 'server'].

For example,

  1. app
  2. .bind('observers.MyObserver')
  3. .toClass(MyObserver)
  4. .tag({
  5. [CoreTags.LIFE_CYCLE_OBSERVER_GROUP]: 'g1',
  6. })
  7. .apply(asLifeCycleObserver);

The observer class can also be decorated with @bind to provide bindingmetadata.

  1. import {bind, createBindingFromClass} from '@loopback/context';
  2. import {CoreTags, asLifeCycleObserver} from '@loopback/core';
  3. @bind(
  4. {
  5. tags: {
  6. [CoreTags.LIFE_CYCLE_OBSERVER_GROUP]: 'g1',
  7. },
  8. },
  9. asLifeCycleObserver,
  10. )
  11. export class MyObserver {
  12. // ...
  13. }
  14. app.add(createBindingFromClass(MyObserver));

Or even simpler with @lifeCycleObserver:

  1. import {createBindingFromClass} from '@loopback/context';
  2. import {lifeCycleObserver} from '@loopback/core';
  3. @lifeCycleObserver('g1')
  4. export class MyObserver {
  5. // ...
  6. }
  7. app.add(createBindingFromClass(MyObserver));

The order of observers is controlled by a orderedGroups property ofLifeCycleObserverRegistry, which receives its options including theorderedGroups from CoreBindings.LIFE_CYCLE_OBSERVER_OPTIONS.

  1. export type LifeCycleObserverOptions = {
  2. /**
  3. * Control the order of observer groups for notifications. For example,
  4. * with `['datasource', 'server']`, the observers in `datasource` group are
  5. * notified before those in `server` group during `start`. Please note that
  6. * observers are notified in the reverse order during `stop`.
  7. */
  8. orderedGroups: string[];
  9. /**
  10. * Notify observers of the same group in parallel, default to `true`
  11. */
  12. parallel?: boolean;
  13. };

Thus the initial orderedGroups can be set as follows:

  1. app
  2. .bind(CoreBindings.LIFE_CYCLE_OBSERVER_OPTIONS)
  3. .to({orderedGroups: ['g1', 'g2', 'server']});

Or:

  1. const registry = await app.get(CoreBindings.LIFE_CYCLE_OBSERVER_REGISTRY);
  2. registry.setOrderedGroups(['g1', 'g2', 'server']);

Observers are sorted using orderedGroups as the relative order. If an observeris tagged with a group that is not defined in orderedGroups, it will comebefore any groups included in orderedGroups. Such custom groups are alsosorted by their names alphabetically.

In the example below, orderedGroups is set to['setup-servers', 'publish-services']. Given the following observers:

  • ‘my-observer-1’ (‘setup-servers’)
  • ‘my-observer-2’ (‘publish-services’)
  • ‘my-observer-4’ (‘2-custom-group’)
  • ‘my-observer-3’ (‘1-custom-group’)The sorted observer groups will be:
  1. {
  2. '1-custom-group': ['my-observer-3'], // by alphabetical order
  3. '2-custom-group': ['my-observer-4'], // by alphabetical order
  4. 'setup-servers': ['my-observer-1'], // by orderedGroups
  5. 'publish-services': ['my-observer-2'], // orderedGroups
  6. }

The execution order of observers within the same group is controlled byLifeCycleObserverOptions.parallel:

  • true (default): observers within the same group are notified in parallel
  • false: observers within the same group are notified one by one. The order isnot defined. If you want to have one to be invoked before the other, mark themwith two distinct groups.

Add custom life cycle observers by convention

Each application can have custom life cycle observers to be dropped intosrc/observers folder as classes implementing LifeCycleObserver.

During application.boot(), such artifacts are discovered, loaded, and bound tothe application context as life cycle observers. This is achieved by a built-inLifeCycleObserverBooter extension.

CLI command to generate life cycle observers

To make it easy for application developers to add custom life cycle observers,we introduce lb4 observer command as part the CLI.

  1. $ lb4 observer
  2. ? Observer name: test
  3. ? Observer group: g1
  4. create src/observers/test.observer.ts
  5. update src/observers/index.ts
  6. Observer test was created in src/observers/

See Life cycle observer generator for moredetails.