Skip to content
This repository has been archived by the owner on May 8, 2020. It is now read-only.

Commit

Permalink
fix(bootstrap): Major overhaul of bootstrapping behavior to allow ext…
Browse files Browse the repository at this point in the history
…ernal registered components to have correct injector providers
  • Loading branch information
zakhenry committed Jul 4, 2016
1 parent 2515c9f commit efa00ff
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/server/bootstrap/bootstrap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const providers: any[] = [
{provide: RemoteCli, useClass: RemoteCliMock},
];

describe('Bootstrap', () => {
fdescribe('Bootstrap', () => {

beforeEachProviders(() => providers);

Expand Down
27 changes: 13 additions & 14 deletions src/server/bootstrap/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { ReflectiveInjector, Provider, Type, ResolvedReflectiveProvider } from '
import { Server } from '../servers/abstract.server';
import { AbstractController } from '../controllers/abstract.controller';
import { Logger, LogLevel } from '../../common/services/logger.service';
import { coreInjector } from '../main';
import { mainProviders, mainLoadClasses } from '../main';
import { registry } from '../../common/registry/entityRegistry';
import { ControllerBootstrapper } from './controllers.bootstrapper';
import { ModelBootstrapper } from './models.bootstrapper';
import { SeederBootstrapper } from './seeders.bootstrapper';
import { EntityBootstrapper } from './entity.bootstrapper';
import { MigrationBootstrapper } from './migrations.bootstrapper';
import { ServiceBootstrapper } from './services.bootstrapper';
import { Database } from '../services/database.service';

export type ProviderType = Type | Provider | {
[k: string]: any;
Expand Down Expand Up @@ -62,16 +63,20 @@ function handleBootstrapError(e: Error, logger: Logger) {

export function bootstrap(loadClasses: ClassDictionary<any>[], providers: ProviderDefinition[] = [], afterBootstrap?: (bootstrap: BootstrapResponse)=>void): () => Promise<BootstrapResponse> {

mainLoadClasses.concat(loadClasses);

let logger: Logger;

deferredLog('debug', registry);
return (): Promise<BootstrapResponse> => {

deferredLog('info', 'Bootstrapping server');

return Promise.all(providers)
return Promise.all(providers.concat(mainProviders))
.then((providers: ProviderType[]) => {

const resolvedProviders = ReflectiveInjector.resolve(providers);

//initialize all bootstrappers (in order they need to be created)
const resolvedBootstrappers: EntityBootstrapper[] = [
new ModelBootstrapper,
Expand All @@ -81,22 +86,15 @@ export function bootstrap(loadClasses: ClassDictionary<any>[], providers: Provid
new ControllerBootstrapper,
];

// extract all of the resolved entities from the bootstrappers for registration with the
// injector
const bootrapperProviders = resolvedBootstrappers.reduce((result: ResolvedReflectiveProvider[], bootstrapper: EntityBootstrapper) => {
return result.concat(bootstrapper.getResolvedEntities());
return result.concat(bootstrapper.getResolvedProviders());
}, []);

// resolve all other user classes
const resolvedProviders: ResolvedReflectiveProvider[] = ReflectiveInjector
.resolve(providers)
.concat(bootrapperProviders)
;
const mergedProviders = resolvedProviders.concat(bootrapperProviders);

// get an injector from the resolutions, using the core injector as parent
const injector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, coreInjector);
// const registryInjector = ReflectiveInjector.fromResolvedProviders(bootrapperProviders);
const injector = ReflectiveInjector.fromResolvedProviders(mergedProviders);

// assign logger instance as soon as possible so the error handler might use it
logger = injector.get(Logger)
.source('bootstrap');

Expand All @@ -112,7 +110,8 @@ export function bootstrap(loadClasses: ClassDictionary<any>[], providers: Provid
return resolvedBootstrappers.reduce((current: Promise<void>, next: EntityBootstrapper): Promise<void> => {

return current.then((): Promise<void> => {
return Promise.resolve(next.invokeBootstrap(injector));

return Promise.resolve(next.setInjector(injector).invokeBootstrap());
});

}, Promise.resolve()) //initial value
Expand Down
8 changes: 5 additions & 3 deletions src/server/bootstrap/controllers.bootstrapper.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { ResolvedReflectiveProvider } from '@angular/core';
import { EntityBootstrapper } from './entity.bootstrapper';
import { AbstractController } from '../controllers/abstract.controller';

export class ControllerBootstrapper extends EntityBootstrapper {

public getResolvedEntities(): ResolvedReflectiveProvider[] {
public getResolvedProviders(): ResolvedReflectiveProvider[] {
return this.getResolvedFromRegistry('controller');
}

public bootstrap(): void {
this.resolvedEntityProviders.forEach((resolvedControllerProvider: ResolvedReflectiveProvider) => {
this.logger.info(`initializing ${resolvedControllerProvider.key.displayName}`);

this.injector.instantiateResolved(resolvedControllerProvider)
.registerInjector(this.injector)
let controller = this.getInstance<AbstractController>(resolvedControllerProvider);

controller.registerInjector(this.injector)
.registerRoutes();

});
Expand Down
38 changes: 33 additions & 5 deletions src/server/bootstrap/entity.bootstrapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'core-js';
import 'reflect-metadata';
import { ReflectiveInjector, ResolvedReflectiveProvider } from '@angular/core';
import { ReflectiveInjector, ResolvedReflectiveProvider, NoProviderError } from '@angular/core';
import { Logger } from '../../common/services/logger.service';
import { registry, EntityType, RegistryEntityStatic } from '../../common/registry/entityRegistry';

Expand All @@ -10,14 +10,42 @@ export abstract class EntityBootstrapper {
protected injector: ReflectiveInjector;
protected logger: Logger;

public abstract getResolvedEntities(): ResolvedReflectiveProvider[];
public abstract getResolvedProviders(): ResolvedReflectiveProvider[];

public invokeBootstrap(injector: ReflectiveInjector): void | Promise<void> {
this.logger = injector.get(Logger).source(this.constructor.name);
this.injector = injector;
public invokeBootstrap(): void | Promise<void> {
this.logger = this.injector.get(Logger)
.source(this.constructor.name);
return this.bootstrap();
}

public setInjector(injector: ReflectiveInjector):this {
this.injector = injector;
return this;
}

protected getInstance<T extends Object>(resolvedInstanceProvider: ResolvedReflectiveProvider): T {
let instance: T;
try {
instance = this.injector.get(resolvedInstanceProvider.key.token);
} catch (e) {
if (!(e instanceof NoProviderError)) {
console.log('ERROR!', resolvedInstanceProvider.key.displayName, e);
throw e;
}
instance = this.injector.instantiateResolved(resolvedInstanceProvider);
}

let logMessage = `Resolved ${instance.constructor.name}`;

if (instance.constructor.name !== resolvedInstanceProvider.key.displayName) {
logMessage += ` as ${resolvedInstanceProvider.key.displayName}`;
}

this.logger.info(logMessage);

return instance;
}

protected abstract bootstrap(): void | Promise<void>;

protected getFromRegistry(type: EntityType): RegistryEntityStatic[] {
Expand Down
8 changes: 4 additions & 4 deletions src/server/bootstrap/migrations.bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import { AbstractMigration } from '../migrations/index';

export class MigrationBootstrapper extends EntityBootstrapper {

public getResolvedEntities(): ResolvedReflectiveProvider[] {
public getResolvedProviders(): ResolvedReflectiveProvider[] {
return this.getResolvedFromRegistry('migration');
}

public bootstrap(): Promise<void> {

this.logger.debug(`Running [${this.resolvedEntityProviders.length}] migrations`);

const allMigrationPromises = this.resolvedEntityProviders.map((resolvedControllerProvider: ResolvedReflectiveProvider) => {
const allMigrationPromises = this.resolvedEntityProviders.map((resolvedMigrationProvider: ResolvedReflectiveProvider) => {

this.logger.info(`migrating ${resolvedControllerProvider.key.displayName}`);
return (this.injector.instantiateResolved(resolvedControllerProvider) as AbstractMigration)
this.logger.info(`migrating ${resolvedMigrationProvider.key.displayName}`);
return this.getInstance<AbstractMigration>(resolvedMigrationProvider)
.migrate()
.catch((error) => {
if (error.code === 'ECONNREFUSED'){
Expand Down
2 changes: 1 addition & 1 deletion src/server/bootstrap/models.bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ModelMetadata } from '../../common/metadata/metadata';

export class ModelBootstrapper extends EntityBootstrapper {

public getResolvedEntities(): ResolvedReflectiveProvider[] {
public getResolvedProviders(): ResolvedReflectiveProvider[] {
return [];
}

Expand Down
10 changes: 5 additions & 5 deletions src/server/bootstrap/seeders.bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import { AbstractSeeder } from '../seeders/index';

export class SeederBootstrapper extends EntityBootstrapper {

public getResolvedEntities(): ResolvedReflectiveProvider[] {
public getResolvedProviders(): ResolvedReflectiveProvider[] {
return this.getResolvedFromRegistry('seeder');
}

public bootstrap(): Promise<void> {
// iterate seeders, to fill the db @todo change to register "unseeded" with the remote cli so
// they can be executed on demand
const allSeederPromises = this.resolvedEntityProviders.map((resolvedControllerProvider: ResolvedReflectiveProvider) => {
const allSeederPromises = this.resolvedEntityProviders.map((resolvedSeederProvider: ResolvedReflectiveProvider) => {

this.logger.info(`seeding ${resolvedControllerProvider.key.displayName}`);
return (this.injector.instantiateResolved(resolvedControllerProvider) as AbstractSeeder).seed();
this.logger.info(`seeding ${resolvedSeederProvider.key.displayName}`);
return this.getInstance<AbstractSeeder>(resolvedSeederProvider).seed();

}, []);

return Promise.all(allSeederPromises);
}

Expand Down
10 changes: 5 additions & 5 deletions src/server/bootstrap/services.bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import { AbstractService } from '../../common/services/service';

export class ServiceBootstrapper extends EntityBootstrapper {

public getResolvedEntities(): ResolvedReflectiveProvider[] {
public getResolvedProviders(): ResolvedReflectiveProvider[] {
return this.getResolvedFromRegistry('service');
}

public bootstrap(): Promise<void> {

this.logger.debug(`Initializing [${this.resolvedEntityProviders.length}] services`);

const allServicePromises = this.resolvedEntityProviders.map((resolvedControllerProvider: ResolvedReflectiveProvider) => {
const allServicePromises = this.resolvedEntityProviders.map((resolvedServiceProvider: ResolvedReflectiveProvider) => {

this.logger.info(`Initializing ${resolvedControllerProvider.key.displayName}`);
const service = (this.injector.instantiateResolved(resolvedControllerProvider) as AbstractService).initialize();
return Promise.resolve(service);
let service = this.getInstance<AbstractService>(resolvedServiceProvider);

return Promise.resolve(service.initialize());
}, []);

return Promise.all(allServicePromises);
Expand Down
27 changes: 22 additions & 5 deletions src/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'core-js';
import 'reflect-metadata';
import { ReflectiveInjector } from '@angular/core';
import { Server } from './servers/abstract.server';
import { AbstractController } from './controllers/abstract.controller';
import { Database } from './services/database.service';
import { RemoteCli } from './services/remoteCli.service';
import { Logger } from '../common/services/logger.service';
Expand All @@ -12,6 +11,7 @@ import { ExpressServer } from './servers/express.server';
import * as dotenv from 'dotenv';
import * as path from 'path';
import * as _ from 'lodash';
import { ProviderDefinition } from './bootstrap/bootstrap';

/**
* Load .env variables into process.env.*
Expand All @@ -22,17 +22,34 @@ dotenv.config({

process.env = _.mapKeys(process.env, (value: any, key: string) => key.replace(/^PUBLIC_/, ''));

export const mainLoadClasses:any[] = [
Database,
RemoteCli
];
// /**
// * The core injector is exported so implementations can pick up already registered injectables
// * without having to register them themselves.
// * @type {ReflectiveInjector}
// */
// export const coreInjector: ReflectiveInjector = ReflectiveInjector.resolveAndCreate([
// // Database,
// RemoteCli,
// DebugLogMiddleware,
// // {provide: Server, useClass: HapiServer},
// {provide: Server, useClass: ExpressServer},
// {provide: Logger, useClass: ConsoleLogger},
// ]);

/**
* The core injector is exported so implementations can pick up already registered injectables
* without having to register them themselves.
* @type {ReflectiveInjector}
*/
export const coreInjector: ReflectiveInjector = ReflectiveInjector.resolveAndCreate([
AbstractController,
Database,
export const mainProviders: ProviderDefinition[] = [
// Database,
RemoteCli,
DebugLogMiddleware,
// {provide: Server, useClass: HapiServer},
{provide: Server, useClass: ExpressServer},
{provide: Logger, useClass: ConsoleLogger},
]);
];
2 changes: 2 additions & 0 deletions src/server/services/remoteCli.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export class RemoteCli extends AbstractService {

this.vantage.banner(displayBanner);

this.logger.debug('Remote cli initialized');

return this.registerCommands();
}

Expand Down

0 comments on commit efa00ff

Please sign in to comment.