|
| 1 | +--- |
| 2 | +lang: en |
| 3 | +title: 'Life cycle events and observers' |
| 4 | +keywords: LoopBack 4.0, LoopBack 4 |
| 5 | +sidebar: lb4_sidebar |
| 6 | +permalink: /doc/en/lb4/Life-cycle.html |
| 7 | +--- |
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +A LoopBack application has its own life cycles at runtime. There are two methods |
| 12 | +to control the transition of states of `Application`. |
| 13 | + |
| 14 | +- start(): Start the application |
| 15 | +- stop(): Stop the application |
| 16 | + |
| 17 | +It's often desirable for various types of artifacts to participate in the life |
| 18 | +cycles and perform related processing upon `start` and `stop`. Good examples of |
| 19 | +such artifacts are: |
| 20 | + |
| 21 | +- Servers |
| 22 | + |
| 23 | + - start: Starts the HTTP server listening for connections. |
| 24 | + - stop: Stops the server from accepting new connections. |
| 25 | + |
| 26 | +- Components |
| 27 | + |
| 28 | + - A component can register life cycle observers |
| 29 | + |
| 30 | +- DataSources |
| 31 | + |
| 32 | + - connect: Connect to the underlying database or service |
| 33 | + - disconnect: Disconnect from the underlying database or service |
| 34 | + |
| 35 | +- Custom scripts |
| 36 | + - start: Custom logic to be invoked when the application starts |
| 37 | + - stop: Custom logic to be invoked when the application stops |
| 38 | + |
| 39 | +## The `LifeCycleObserver` interface |
| 40 | + |
| 41 | +To react on life cycle events, a life cycle observer implements the |
| 42 | +`LifeCycleObserver` interface. |
| 43 | + |
| 44 | +```ts |
| 45 | +import {ValueOrPromise} from '@loopback/context'; |
| 46 | + |
| 47 | +/** |
| 48 | + * Observers to handle life cycle start/stop events |
| 49 | + */ |
| 50 | +export interface LifeCycleObserver { |
| 51 | + start?(): ValueOrPromise<void>; |
| 52 | + stop?(): ValueOrPromise<void>; |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +Please note all methods are optional so that an observer can opt in certain |
| 57 | +events. Each main events such as `start` and `stop` are further divided into |
| 58 | +three sub-phases to allow the multiple-step processing. |
| 59 | + |
| 60 | +## Register a life cycle observer |
| 61 | + |
| 62 | +A life cycle observer can be registered by calling `lifeCycleObserver()` of the |
| 63 | +application. It binds the observer to the application context with a special |
| 64 | +tag - `CoreTags.LIFE_CYCLE_OBSERVER`. |
| 65 | + |
| 66 | +```ts |
| 67 | +app.lifeCycleObserver(MyObserver); |
| 68 | +``` |
| 69 | + |
| 70 | +Please note that `app.server()` automatically registers servers as life cycle |
| 71 | +observers. |
| 72 | + |
| 73 | +Life cycle observers can be registered via a component too: |
| 74 | + |
| 75 | +```ts |
| 76 | +export class MyComponentWithObservers { |
| 77 | + lifeCycleObservers: [XObserver, YObserver]; |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +## Discover life cycle observers |
| 82 | + |
| 83 | +The `Application` finds all bindings tagged with `CoreTags.LIFE_CYCLE_OBSERVER` |
| 84 | +within the context chain and resolve them as observers to be notified. |
| 85 | + |
| 86 | +## Notify life cycle observers of start/stop related events by order |
| 87 | + |
| 88 | +There may be dependencies between life cycle observers and their order of |
| 89 | +processing for `start` and `stop` need to be coordinated. For example, we |
| 90 | +usually start a server to listen on incoming requests only after other parts of |
| 91 | +the application are ready to handle requests. The stop sequence is typically |
| 92 | +processed in the reverse order. To support such cases, we introduce |
| 93 | +two-dimension steps to control the order of life cycle actions. |
| 94 | + |
| 95 | +### Observer groups |
| 96 | + |
| 97 | +First of all, we allow each of the life cycle observers to be tagged with a |
| 98 | +group. For example: |
| 99 | + |
| 100 | +- datasource |
| 101 | + |
| 102 | + - connect/disconnect |
| 103 | + - mongodb |
| 104 | + - mysql |
| 105 | + |
| 106 | +- server |
| 107 | + - rest |
| 108 | + - gRPC |
| 109 | + |
| 110 | +We can then configure the application to trigger observers group by group as |
| 111 | +configured by an array of groups in order such as `['datasource', 'server']`. |
| 112 | +Observers within the same group can be notified in parallel. |
| 113 | + |
| 114 | +For example, |
| 115 | + |
| 116 | +```ts |
| 117 | +app |
| 118 | + .bind('observers.MyObserver') |
| 119 | + .toClass(MyObserver) |
| 120 | + .tag({ |
| 121 | + [CoreTags.LIFE_CYCLE_OBSERVER_GROUP]: 'g1', |
| 122 | + }) |
| 123 | + .apply(asLifeCycleObserverBinding); |
| 124 | +``` |
| 125 | + |
| 126 | +The observer class can also be decorated with `@bind` to provide binding |
| 127 | +metadata. |
| 128 | + |
| 129 | +```ts |
| 130 | +@bind( |
| 131 | + { |
| 132 | + tags: { |
| 133 | + [CoreTags.LIFE_CYCLE_OBSERVER_GROUP]: 'g1', |
| 134 | + }, |
| 135 | + }, |
| 136 | + asLifeCycleObserverBinding, |
| 137 | +) |
| 138 | +export class MyObserver { |
| 139 | + // ... |
| 140 | +} |
| 141 | + |
| 142 | +app.add(createBindingFromClass(MyObserver)); |
| 143 | +``` |
| 144 | + |
| 145 | +The order of observers are controlled by a `groupsByOrder` property of |
| 146 | +`LifeCycleObserverRegistry`, which receives its options including the |
| 147 | +`groupsByOrder` from `CoreBindings.LIFE_CYCLE_OBSERVER_OPTIONS`. Thus the |
| 148 | +initial `groupsByOrder` can be set as follows: |
| 149 | + |
| 150 | +```ts |
| 151 | +app |
| 152 | + .bind(CoreBindings.LIFE_CYCLE_OBSERVER_OPTIONS) |
| 153 | + .to({groupsByOrder: ['g1', 'g2', 'server']}); |
| 154 | +``` |
| 155 | + |
| 156 | +Or: |
| 157 | + |
| 158 | +```ts |
| 159 | +const registry = await app.get(CoreBindings.LIFE_CYCLE_OBSERVER_REGISTRY); |
| 160 | +registry.setGroupsByOrder(['g1', 'g2', 'server']); |
| 161 | +``` |
| 162 | + |
| 163 | +Observers are sorted using `groupsByOrder` as the relative order. If an observer |
| 164 | +is tagged with a group that are not in `groupsByOrder`, it will come before any |
| 165 | +groups within `groupsByOrder`. Such custom groups are also sorted by their names |
| 166 | +alphabetically. |
| 167 | + |
| 168 | +In the example below, `groupsByOrder` is set to `['g1', 'g2']`. Given the |
| 169 | +following observers: |
| 170 | + |
| 171 | +- 'my-observer-1' ('g1') |
| 172 | +- 'my-observer-2' ('g2') |
| 173 | +- 'my-observer-4' ('2-custom-group') |
| 174 | +- 'my-observer-3' ('1-custom-group') |
| 175 | + |
| 176 | +The sorted observer groups will be: |
| 177 | + |
| 178 | +```ts |
| 179 | +{ |
| 180 | + '1-custom-group': ['my-observer-3'], |
| 181 | + '2-custom-group': ['my-observer-4'], |
| 182 | + 'g1': ['my-observer-1'], |
| 183 | + 'g2': ['my-observer-2'], |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +### Event phases |
| 188 | + |
| 189 | +It's also desirable for certain observers to do some processing before, upon, or |
| 190 | +after the `start` and `stop` events. To allow that, we notify each observer in |
| 191 | +three phases: |
| 192 | + |
| 193 | +- start: preStart, start, and postStart |
| 194 | +- stop: preStop, stop, and postStop |
| 195 | + |
| 196 | +Combining groups and event phases, it's flexible to manage multiple observers so |
| 197 | +that they can be started/stopped gracefully in order. |
| 198 | + |
| 199 | +For example, with a group order as `['datasource', 'server']` and three |
| 200 | +observers registered as follows: |
| 201 | + |
| 202 | +- datasource group: MySQLDataSource, MongoDBDataSource |
| 203 | +- server group: RestServer |
| 204 | + |
| 205 | +The start sequence will be: |
| 206 | + |
| 207 | +1. MySQLDataSource.preStart |
| 208 | +2. MongoDBDataSource.preStart |
| 209 | +3. RestServer.preStart |
| 210 | + |
| 211 | +4. MySQLDataSource.start |
| 212 | +5. MongoDBDataSource.start |
| 213 | +6. RestServer.start |
| 214 | + |
| 215 | +7. MySQLDataSource.postStart |
| 216 | +8. MongoDBDataSource.postStart |
| 217 | +9. RestServer.postStart |
| 218 | + |
| 219 | +## Add custom life cycle observers by convention |
| 220 | + |
| 221 | +Each application can have custom life cycle observers to be dropped into |
| 222 | +`src/observers` folder as classes implementing `LifeCycleObserver`. |
| 223 | + |
| 224 | +During application.boot(), such artifacts are discovered, loaded, and bound to |
| 225 | +the application context as life cycle observers. This is achieved by a built-in |
| 226 | +`LifeCycleObserverBooter` extension. |
| 227 | + |
| 228 | +## CLI command |
| 229 | + |
| 230 | +To make it easy for application developers to add custom life cycle observers, |
| 231 | +we introduce `lb4 observer` command as part the CLI. |
| 232 | + |
| 233 | +To add a life cycle observer: |
| 234 | + |
| 235 | +1. cd <my-loopback4-project> |
| 236 | +2. lb4 observer |
| 237 | + |
| 238 | +``` |
| 239 | +? Observer name: Hello |
| 240 | + create src/observers/my.hello-observer.ts |
| 241 | + update src/observers/index.ts |
| 242 | +
|
| 243 | +Observer Hello was created in src/observers/ |
| 244 | +``` |
| 245 | + |
| 246 | +The generated class looks like: |
| 247 | + |
| 248 | +```ts |
| 249 | +import {bind} from '@loopback/context'; |
| 250 | +import { |
| 251 | + /* inject, Application, */ |
| 252 | + CoreBindings, |
| 253 | + LifeCycleObserver, |
| 254 | +} from '@loopback/core'; |
| 255 | + |
| 256 | +/** |
| 257 | + * This class will be bound to the application as a `LifeCycleObserver` during |
| 258 | + * `boot` |
| 259 | + */ |
| 260 | +@bind({tags: {[CoreBindings.LIFE_CYCLE_OBSERVER_GROUP]: ''}}) |
| 261 | +export class HelloObserver implements LifeCycleObserver { |
| 262 | + /* |
| 263 | + constructor( |
| 264 | + @inject(CoreBindings.APPLICATION_INSTANCE) private app: Application, |
| 265 | + ) {} |
| 266 | + */ |
| 267 | + |
| 268 | + /** |
| 269 | + * This method will be invoked when the application starts |
| 270 | + */ |
| 271 | + async start(): Promise<void> { |
| 272 | + // Add your logic for start |
| 273 | + } |
| 274 | + |
| 275 | + /** |
| 276 | + * This method will be invoked when the application stops |
| 277 | + */ |
| 278 | + async stop(): Promise<void> { |
| 279 | + // Add your logic for start |
| 280 | + } |
| 281 | +} |
| 282 | +``` |
0 commit comments