Skip to content

Commit

Permalink
refactor(healthcheck): Update health check architecture
Browse files Browse the repository at this point in the history
- Add dog sample app
- Upgrade dependencies
  • Loading branch information
BrunnerLivio committed Nov 2, 2018
1 parent 15c97ec commit fceb4ba
Show file tree
Hide file tree
Showing 39 changed files with 1,139 additions and 13,902 deletions.
34 changes: 15 additions & 19 deletions e2e/terminus.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { INestApplication, DynamicModule } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { TerminusModule } from '../lib/terminus.module';
import { TerminusOptions } from '../lib/interfaces/terminus-options';
import { async } from 'rxjs/internal/scheduler/async';
import { TerminusBootstrapService } from '../lib/terminus-bootstrap.service';
import { TerminusLibProvider } from '../lib/terminus-lib.provider';
import { HTTP_SERVER_REF } from '@nestjs/core';
import {
TerminusModuleAsyncOptions,
TerminusOptionsFactory,
TerminusModuleOptions,
} from '../lib/interfaces';
import { TerminusOptions } from '@godaddy/terminus';

describe('Terminus', () => {
let app: INestApplication;
Expand All @@ -20,10 +19,19 @@ describe('Terminus', () => {
};
let terminusOptions: TerminusOptions = {
healthChecks: {
'/health': async () => true,
'/health': expect.any(Function),
},
};

let terminusModuleOptions: TerminusModuleOptions = {
endpoints: [
{
url: '/health',
healthIndicators: [async () => ({ key: true })],
},
],
};

async function bootstrapModule(options: DynamicModule) {
const module = await Test.createTestingModule({
imports: [options],
Expand All @@ -42,7 +50,7 @@ describe('Terminus', () => {
it('should correctly call Terminus with useFactory', async () => {
await bootstrapModule(
TerminusModule.forRootAsync({
useFactory: async (): Promise<TerminusOptions> => terminusOptions,
useFactory: () => terminusModuleOptions,
}),
);
expect(terminusLibProvider).toHaveBeenCalledWith(
Expand All @@ -52,14 +60,10 @@ describe('Terminus', () => {
});

it('should correctly call Terminus with useClass', async () => {
const onShutdown = async () => {
return 'working';
};
class TerminusService implements TerminusOptionsFactory {
createTerminusOptions(): TerminusOptions {
return { onShutdown };
createTerminusOptions(): TerminusModuleOptions {
return terminusModuleOptions;
}
public onShutdown = onShutdown;
}

app = await bootstrapModule(
Expand All @@ -68,14 +72,6 @@ describe('Terminus', () => {
}),
);

expect(terminusLibProvider).toHaveBeenCalledWith(httpServer, {
onShutdown,
});
});

it('should correctly call Terminus with synchronous forRoot', async () => {
app = await bootstrapModule(TerminusModule.forRoot(terminusOptions));

expect(terminusLibProvider).toHaveBeenCalledWith(
httpServer,
terminusOptions,
Expand Down
1 change: 0 additions & 1 deletion index.d.ts

This file was deleted.

6 changes: 0 additions & 6 deletions index.js

This file was deleted.

1 change: 0 additions & 1 deletion index.ts

This file was deleted.

18 changes: 18 additions & 0 deletions lib/interfaces/health-indicator.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type HealthIndicatorResult = {
[key: string]: any;
};

export type HealthIndicatorFunction = () => Promise<HealthIndicatorResult>;

/**
* Represents a health indicator of a health check
*/
export interface HealthIndicator {
/**
* If the health indicator is healthy
*
* @param {string} key The key of the health check which will be used in the result object
* @param {any} [options] The options to configure the health indicator
*/
isHealthy(key: string, options?: any): Promise<HealthIndicatorResult>;
}
1 change: 1 addition & 0 deletions lib/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './terminus-module-options.interface';
export * from './health-indicator.interface';
32 changes: 29 additions & 3 deletions lib/interfaces/terminus-module-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
import { Type } from '@nestjs/common';
import { ModuleMetadata } from '@nestjs/common/interfaces';
import { TerminusOptions } from '@godaddy/terminus';
import { HealthIndicatorFunction } from './health-indicator.interface';

export type TerminusModuleOptions = TerminusOptions;
/**
* Represents one endpoint / health check
*/
export interface TerminusEndpoints {
/**
* The url of the endpoint / health check
*/
url: string;
/**
* The health checks which should get executed.
*/
healthIndicators: HealthIndicatorFunction[];
}

/**
* The options of the terminus module
*/
export interface TerminusModuleOptions {
/**
* A list of endpoints
*/
endpoints: TerminusEndpoints[];
}

/**
* The interface for the factory which provides the Terminus options
Expand All @@ -28,7 +50,11 @@ export interface TerminusModuleAsyncOptions
/**
* The class which should be used to provide the Terminus options
*/
useClass?: Type<TerminusModuleOptions>;
useClass?: Type<TerminusOptionsFactory>;
/**
* Import existing providers from other module
*/
useExisting?: Type<TerminusOptionsFactory>;
/**
* The factory which should be used to provide the Terminus options
*/
Expand Down
85 changes: 76 additions & 9 deletions lib/terminus-bootstrap.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { Injectable, Inject, OnModuleInit, HttpServer } from '@nestjs/common';
import {
Injectable,
Inject,
OnApplicationBootstrap,
HttpServer,
} from '@nestjs/common';
import { TERMINUS_MODULE_OPTIONS, TERMINUS_LIB } from './terminus.constants';
import { TerminusModuleOptions } from './interfaces';
import { TerminusModuleOptions, HealthIndicator } from './interfaces';
import { HTTP_SERVER_REF } from '@nestjs/core';
import { Server } from 'http';
import { Terminus } from '@godaddy/terminus';
import { HealthCheckError, Terminus } from '@godaddy/terminus';

/**
* Bootstraps the third party Terminus library with the
* configured Module options
*/
@Injectable()
export class TerminusBootstrapService implements OnModuleInit {
export class TerminusBootstrapService implements OnApplicationBootstrap {
/**
* The http server of NestJS
*/
private httpServer: Server;

/**
* Intiailizes the service
* @param options The terminus module options
Expand All @@ -25,13 +35,70 @@ export class TerminusBootstrapService implements OnModuleInit {
) {}

/**
* Gets called when the Module gets initialized.
*
* Executes the given health indicators and stores the caused
* errors and results
* @param healthIndicators The health indicators which should get executed
*/
private async executeHealthIndicators(
healthIndicators,
): Promise<{ results: any[]; errors: any[] }> {
const results: any[] = [];
const errors: any[] = [];
await Promise.all<HealthIndicator>(
healthIndicators
// Register all promises
.map(healthIndicator => healthIndicator())
.map(p => p.catch(error => error && errors.push(error.causes)))
.map(p => p.then(result => result && results.push(result))),
);

return { results, errors };
}

/**
* Prepares the health check using the configured health
* indicators
*/
private prepareHealthChecks() {
const healthChecks = {};
this.options.endpoints.forEach(endpoint => {
const healthCheck = async () => {
const { results, errors } = await this.executeHealthIndicators(
endpoint.healthIndicators,
);
const info = (results || [])
.concat(errors)
.reduce((previous, current) => Object.assign(previous, current), {});

if (errors.length) {
throw new HealthCheckError('Healthcheck failed', info);
} else {
return info;
}
};

healthChecks[endpoint.url] = healthCheck;
});

return healthChecks;
}

/**
* Bootstraps the third party terminus library with
* the given module options
*/
public onModuleInit() {
const httpServer: HttpServer = this.httpAdapter.getHttpServer();
this.terminus(httpServer, this.options);
private bootstrapTerminus() {
const healthChecks = this.prepareHealthChecks();
this.terminus(this.httpServer, {
healthChecks,
});
}

/**
* Gets called when the application gets bootstrapped.
*/
public onApplicationBootstrap() {
this.httpServer = this.httpAdapter.getHttpServer();
this.bootstrapTerminus();
}
}
11 changes: 7 additions & 4 deletions lib/terminus-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import { TerminusModule } from './terminus.module';
* with the third party Terminus library and Nest
*/
@Global()
@Module({})
@Module({
providers: [TerminusLibProvider, TerminusBootstrapService],
exports: [],
})
export class TerminusCoreModule {
constructor(
@Inject(TERMINUS_MODULE_OPTIONS)
Expand All @@ -34,7 +37,7 @@ export class TerminusCoreModule {
* synchronously and sets the correct providers
* @param options The options to bootstrap the module synchronously
*/
static forRoot(options: TerminusModuleOptions = {}): DynamicModule {
static forRoot(options: TerminusModuleOptions): DynamicModule {
const terminusModuleOptions = {
provide: TERMINUS_MODULE_OPTIONS,
useValue: options,
Expand Down Expand Up @@ -76,7 +79,7 @@ export class TerminusCoreModule {
private static createAsyncProviders(
options: TerminusModuleAsyncOptions,
): Provider[] {
if (options.useFactory) {
if (options.useFactory || options.useExisting) {
return [this.createAsyncOptionsProvider(options)];
}
return [
Expand Down Expand Up @@ -107,7 +110,7 @@ export class TerminusCoreModule {
provide: TERMINUS_MODULE_OPTIONS,
useFactory: async (optionsFactory: TerminusOptionsFactory) =>
await optionsFactory.createTerminusOptions(),
inject: [options.useClass],
inject: [options.useClass || options.useExisting],
};
}
}
6 changes: 3 additions & 3 deletions lib/terminus-lib.provider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { TERMINUS_LIB } from './terminus.constants';
import * as terminus from '@godaddy/terminus';
import { createTerminus } from '@godaddy/terminus';

/**
* The type of the Terminus instance
*/
export type TerminusLib = typeof terminus;
export type TerminusLib = typeof createTerminus;

/**
* Create a wrapper so it is injectable & easier to test
*/
export const TerminusLibProvider = {
provide: TERMINUS_LIB,
useValue: terminus as any,
useValue: createTerminus as any,
};
Loading

0 comments on commit fceb4ba

Please sign in to comment.