Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: refactor to IoC (inverse of control) pattern in serve package #18

Merged
merged 11 commits into from
Jul 14, 2022

Conversation

kokokuo
Copy link
Contributor

@kokokuo kokokuo commented Jul 4, 2022

Description

Rebase develop and adapt IoC pattern to inject dependencies by InversifyJS for the serve package. The PR includes the previous implemented and approved features from PRs:

After rebased, and Refactor to the IoC pattern, there are some things changed:

all packages

  • add setupFilesAfterEnv in jest.config.ts and import reflect-metadata first for jest testing could collect IoC inject in each package.

In the core package

  • refactor the defaultImport logistic to support importing multiple extensions and add mergedModules to merge multiple extension modules to the one module object.
/** dynamic import default module.  e.g: ['extensionsA', 'extensionsB']  */
export const defaultImport = async <T = any>(
  ...foldersOrFiles: Array<string>
) => {
  const modules = [] as Array<T>;
  for (const folderOrFile of foldersOrFiles) {
    const module = await import(folderOrFile);
    // if module default is undefined, then set the all export context to default
    const imported = module.default ? (module.default as T) : (module as T);
    modules.push(imported);
  }
  return modules;
};

/** merged multiple properties of each modules to the one module object */
export const mergedModules = async <T = any>(modules: Array<T>) => {
    const module = modules.reduce((merged: ModuleProperties, current: ModuleProperties, _) => {
      for (const extension of Object.keys(current)) {
        // if current extension property has been existed in merged module, concat it.
        if (extension in merged)
          merged[extension] = [...merged[extension], ...current[extension]];
        // if extension not in merged module, add new extension property
        else merged[extension] = current[extension];
      }
      return merged;
    },
    {} as T);
  • create pagination.ts in core package to define pagination model and move data-query folder and data-source folder to core package ( also include test cases ), because build and serve also need them.
  • update the executorModule to bind the real data query builder and update the test case for the test compiler.
export const executorModule = () =>
  new ContainerModule((bind) => {
    /**
     * TODO: bind mock data source, need to update after real data source implemented.
     */
    bind<IDataSource>(TYPES.IDataSource).toConstantValue(new MockDataSource());

    bind<Executor>(TYPES.Executor).toDynamicValue((context) => {
      const dataSource = context.container.get<MockDataSource>(
        TYPES.IDataSource
      );
      return {
        createBuilder: async (query: string) =>
          new DataQueryBuilder({ statement: query, dataSource }),
      };
    });
  });

In the serve package

  • refactor ServeConfig for inheriting CoreOptions and create AppConfig for middleware and VulcanApplication.
import { ICoreOptions } from '@vulcan/core';
import { APIProviderType } from '@vulcan/serve/route';
import { MiddlewareConfig } from './middlewareConfig';

// The serve package config
export interface ServeConfig extends ICoreOptions {
  /** The middleware config options */
  middlewares?: MiddlewareConfig;
  /* The API types would like to build */
  types: Array<APIProviderType>;
}

export type AppConfig = Omit<ServeConfig, 'artifact' | 'template' | 'types'>;
  • fix loadExtensions function to merged modules after defaultImport response updated.
export interface ExtensionModule {
 middlewares?: ClassType<BaseRouteMiddleware>[];
}
export const loadExtensions = async (extensions?: SourceOfExtensions) => {
 // if extensions setup, load middlewares classes in the extensions
 if (extensions) {
   // import extension which user customized
   const modules = await defaultImport<ExtensionModule>(...extensions);
   const module = await mergedModules<ExtensionModule>(modules);
   // return middleware classes in folder
   return module.middlewares || [];
 }
 return [];
};
  • refactor VulcanApplication for focusing on building middleware and route and create VulcanServer for running the server.
export class VulcanServer {
  private config: ServeConfig;
  private container: Container;
  private server?: http.Server;
  private schemas: Array<APISchema>;
  constructor(config: ServeConfig, schemas: Array<APISchema>) {
    this.config = config;
    this.schemas = schemas;
    this.container = new Container();
  }
  public async start(port = 3000) {
    if (this.server)
      throw new Error('Server has created, please close it first.');

    // Get generator
    await this.container.load(this.config);
    const generator = this.container.get<RouteGenerator>(TYPES.RouteGenerator);

    // Create application
    const app = new VulcanApplication(omit(this.config, 'template'), generator);
    await app.buildMiddleware();
    await app.buildRoutes(this.schemas, this.config.types);
    // Run server
    this.server = http.createServer(app.getHandler()).listen(port);
  }
  public async close() {
    if (this.server) this.server.close();
    this.container.unload();
  }
}

How To Test / Expected Results

For the test result, please see the below test cases that passed the unit test:

The core package

螢幕快照 2022-07-11 上午9 41 16

The build package

螢幕快照 2022-07-11 上午9 41 07

The serve package

螢幕快照 2022-07-11 上午9 40 59

Commit Message

Because the PR included rebased commits, here is only the list refactor to IoC commits:

  • a9a1633 - make validator loader by IoC, refactor default module import logistic, add required validator to make test case work.
    • Rename folder from data-type-validators to built-in-validators.
      • Add required validator test cases
      • refactor validateData method to any in IValidator.
      • refactor validateData type for IntegerTypeValidator, RequiredValidator.
      • add extensions property in ICoreOptions to collect different extension modules name / folder path.
      • add SourceOfExtensions TYPE and refactor to inject extensions in ValidatorLoader.
      • refactor defaultImport to import multiple modules
      • add mergedModules to merge multiple extension modules to the one module object.
      • change to container load and enhance test cases of validator loader to check to import multiple modules.
      • add setupFilesAfterEnv in jest.config.ts import reflect-metadata first for jest testing could collect IoC inject decorators.decorators.
  • 60edba9 - add setupFilesAfterEnv in jest.config.js, support extensions property in test cases.
  • ac21198 - move data query builder and data source modules to core package.
    • create pagination.ts in core package to define the pagination model.
    • move data-query folder and data-source folder to core package.
  • 90a82fa - make the data query builder and data source created by the IoC container.
    • update the execute module to bind the real data query builder.
    • update data query builder to return by IDataQueryBuilder.
    • update test case for test compiler.
  • 579249f - refactor the serve package by IoC pattern.
    • refactor import way from fs/promises to { promises as fs } from 'fs' for making jest debug work.
    • fix the ValidatorLoader inject optional field in the constructor.
    • add TYPES, "Container" and load routeGeneratorModule.
    • refactor ServeConfig for inheriting CoreOptions and create AppConfig for middleware and VulcanApplication.
    • fix loadExtensions function to merged modules after defaultImport response updated.
    • refactor VulcanApplication for focusing on building middleware and route.
    • create VulcanServer for running the server.
    • update all test cases for supporting IoC pattern in serve package.
  • 6a195f3 - feat(core, serve): fix the load extension module for throwing duplicate properties in multiple modules.
    • remove the logistic for throwing duplicate property in multiple modules in mergedModules.
    • add the checkSameNameValidator logistic for checking duplicate key name after creating validator by class from the module.
    • add the logistic for checking duplicate key names after creating middleware by class from module in VulcanApplication.
    • add more test cases for the validator loader to check load extensions.
    • move the setupFilesAfterEnv setting to jest.preset.ts.

kokokuo added 5 commits July 4, 2022 12:14
… api server from api schema

   - add serve package by  "nx generate" command
   - add "DateTypeValidator", "IntegerTypeValidator", "StringTypeValidator", "UUIDTypeValidator" and its test cases.
   - add "loadedValidators" for loading all built-in and user-defined validator dynamically.
   - install "joi", "dayjs", "faker", "ts-sinon", "uuid" plugins at vulcan "package.json".
   - add "ReuqestTransformer", "RequestValidator", for converting request data format and validating.
   - add "BaseRoute", "RestfulRoute", "GraphQLRoute" to create route.
   - add "RouteGenerator" to generate Route according api route type.
   - add "VulcanApplication" to add routes and create server.
   - add test cases for "ReuqestTransformer", "RequestValidator", "RouteGenerator" and "VulcanApplication"
   - install "suepertest" in package.json for testing koa server.
   - add "start" command to run serve package directly for testing in project.json.
   - create "normalizedStringValue" module to handle string value.
   - add "../../types/*.d.ts" in tsconfig of serve.
- add "DataQueryBuilder" to provide select, join, where, group, having, order, limit, offset by query chain
- add "JoinOnClause" to provide join-on methods
- add test cases of "DataQueryBuilder" providing select, join, where, group, having, order, limit, offset clauses.
- add test cases of "JoinOnClause"  provding on clauses.
- refactor "baseRoute" for passing template engine and use template engine.
- fix test cases of "RouteGenerator" and "VulcanApplication".
…a query builder

- add pagination field in "APISchema".
- add "PaginationStrategy" interface and offset, cursor, keyset-based strategy class.
- add "PaginationTransformer" to do transfrom from koa ctx .
- add data source interface "IDataSource".
- refactor "BaseRoute" for splitting request and pagination transformer to implemented restful & graphql class, because of graphql get request from the body.
- update test cases of each sql clause method of "DataQueryBuilder".
…ame.

- add "ValidatorLoader" to get validator according load validator by name.
- add "RequiredValidator" to check value is required.
- refactor to export default for "DataTypeValidator", "IntegerTypeValidator", "UUIDTypeValidator", "StringTypeValidator".
- refactor "RequestValidator" to validate data by using "ValidatorLoader"
- update related test cases in core and serve package.
…n middlewares and middleware loader

- add "tslog" package and create "getLogger" by "LoggerFactory".
- fix "transform" method in "PaginationTransformer".
- add "defaultImport" function for loader used.
- remove "default" for each validator classes and add "index.ts" in "data-type-validators" for export default.
- add built-in middlewares for cors, request Id, audit logging, rate limit, and add test cases
- add the middleware loader to load the middleware module.
- set middleware and add test cases in the vulcan app server.
- add ignore tag in "IPTypeValidator" class in test/ to prevent detecting unnecessary code.
@kokokuo kokokuo force-pushed the feature/serve-ioc-container branch from 2e9d1c1 to ca40da2 Compare July 5, 2022 07:21
@kokokuo
Copy link
Contributor Author

kokokuo commented Jul 5, 2022

rebase finished, start to refactor for using IoC pattern.

@kokokuo kokokuo changed the title Feature: using IoC (inverse of control) pattern to inject dependency in serve [WIP] Feature: using IoC (inverse of control) pattern to inject dependency in serve Jul 5, 2022
kokokuo added 4 commits July 6, 2022 16:10
…ule import logistic, add required validator.

- Rename folder from "data-type-validators" to "built-in-validators"
- Add "required" validator test cases
- refactor "validateData" method to any in "IValidator".
- refactor validateData type for "IntegerTypeValidator", "RequiredValidator".
- add "extensions" property in "ICoreOptions" to collect different extension modules name / folder path.
- add ""SourceOfExtensions" TYPE and refactor to inject extensions in "ValidatorLoader"
- refactor "defaultImport" to import multiple modules
- add "mergedModules" to merge multiple extension modules to the one module object.
- change to container load and enhance test cases of validator loader to check importing multiple modules.
- add "setupFilesAfterEnv" in "jest.config.ts" import "reflect-metadata" first for jest testing could collect IoC inject decorators.
… to core package.

- create "pagination.ts" in core package to define pagination model.
- move "data-query" folder and "data-source" folder to core package.
…C conatiner.

- update the "exectueModule" to bind real data query builder.
- update data query builder to return by "IDataQueryBuilder".
- update test case for test compiler.
@kokokuo kokokuo changed the title [WIP] Feature: using IoC (inverse of control) pattern to inject dependency in serve [WIP] Feature: refactor to IoC (inverse of control) pattern in serve package Jul 8, 2022
- refactor import way from "'fs/promises'" to "{ promises as fs } from 'fs'" for making jest debug work.
- fix the "ValidatorLoader" inject optional field in constructor.
- add "TYPES", "Container" and load "routeGeneratorModule".
- refactor "ServeConfig" for inheriting "CoreOptions" and create "AppConfig" for middleware and "VulcanApplication".
- fix "loadExtensions" function to merged modules after "defaultImport" response updated.
- refactor "VulcanApplication" for focusing on build middleware and route.
- create "VulcanServer" for running server.
- update all test cases for supporting IoC pattern in serve package.
@kokokuo kokokuo changed the title [WIP] Feature: refactor to IoC (inverse of control) pattern in serve package Feature: refactor to IoC (inverse of control) pattern in serve package Jul 8, 2022
@kokokuo
Copy link
Contributor Author

kokokuo commented Jul 8, 2022

Update all serve packages to use IoC Pattern and create by IoC Container, all test cases passed (core, build, serve).

@kokokuo kokokuo marked this pull request as ready for review July 8, 2022 10:15
@kokokuo kokokuo requested review from oscar60310 and wwwy3y3 July 11, 2022 02:09
Copy link
Contributor

@oscar60310 oscar60310 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM besides some questions about IoC.

@@ -16,11 +16,9 @@ export const checkValidator =
throw new Error('Validator name is required');
}

const validator = loader.getLoader(validatorRequest.name);
const validator = await loader.load(validatorRequest.name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I'll call getLocal multiple times, it appeared to me that we have imported the code of validators to memory, we just initialized them here. But with your code, we seem to import the code via .load function, will it cause performance issues?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @oscar60310 for reviewing and suggesting, after discussing in the morning, this part will keep and do it after a meeting discussion.

Comment on lines 22 to 27
// Data Query Builder
IDataQueryBuilder: Symbol.for('IDataQueryBuilder'),
// Data Source
IDataSource: Symbol.for('IDataSource'),
// Validator
ValidatorLoader: Symbol.for('ValidatorLoader'),
IValidatorLoader: Symbol.for('IValidatorLoader'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember we've discussed this, but I still have some questions:
Why did we use the leading "I" for the identifiers? It makes sense to use "I" to indicate the difference between interface and class like this:

interface Ixxxx {}
class xxxx implement Ixxxx {}

But here we don't care about what type we'll get, we just want an instance, it might confuse us if we don't use a consistent naming rule, that is, TYPE.Executor but TYPE.IDataSource.

Did you want to let the identifiers have the same name as their interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @oscar60310 for reviewing and suggesting, after discussion, we keep the TYPES no need to add prefix I and make the interface add the prefix I for those needed to be implemented by the class.

Comment on lines 43 to 45
createBuilder: async (query: string) =>
new DataQueryBuilder({ statement: query, dataSource }),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: How about creating an Executor class instead of using a pure object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @oscar60310 for reviewing, I have created a pure class QueryExecutor to do it.

@kokokuo kokokuo force-pushed the feature/serve-ioc-container branch from 6a195f3 to 553671e Compare July 12, 2022 11:15
…te property in multiple module.

- remove the logistic for throwing duplicate property in multiple module in "mergedModules".
- add the "checkSameNameValidator" logistic for checking duplicate key name after creating valicator by class from module.
- add the logistic for checking duplicate key name after creating middleware by class from module in "VulcanApplication".
- add more test cases for validator loader to checking load extensions.
- move the "setupFilesAfterEnv" setting to "jest.preset.ts"
- rename for removing prefix "I" in symbol in the "types.ts" from core and package.
- add "QueryExecutor" to create data query builder.
@kokokuo kokokuo force-pushed the feature/serve-ioc-container branch from 553671e to fa3d166 Compare July 13, 2022 10:37
@kokokuo
Copy link
Contributor Author

kokokuo commented Jul 13, 2022

Hi @oscar60310 all suggestion has been fixed, please check it, thanks too much.

Copy link
Contributor

@oscar60310 oscar60310 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@kokokuo kokokuo merged commit 3d3678b into develop Jul 14, 2022
@kokokuo kokokuo deleted the feature/serve-ioc-container branch July 14, 2022 02:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants