Skip to content

Application

Phat Nguyen edited this page Dec 31, 2024 · 8 revisions

Require to pre-setup ENV:

# Application
APP_ENV_APPLICATION_NAME
APP_ENV_APPLICATION_TIMEZONE
APP_ENV_APPLICATION_SECRET
APP_ENV_APPLICATION_ROLES

APP_ENV_LOGGER_FOLDER_PATH

APP_ENV_DATASOURCE_NAME

APP_ENV_APPLICATION_DS_MIGRATION
APP_ENV_APPLICATION_DS_AUTHORIZE

# Server
APP_ENV_SERVER_HOST
APP_ENV_SERVER_PORT
APP_ENV_SERVER_BASE_PATH

# PostgreSQL
APP_ENV_POSTGRES_HOST
APP_ENV_POSTGRES_PORT
APP_ENV_POSTGRES_USERNAME
APP_ENV_POSTGRES_PASSWORD
APP_ENV_POSTGRES_DATABASE

Application:

In Lb-infra we can extends from 2 kind of classes:

  • BaseApplication: This abstract class is very basic which extends from Loopback 4
  • DefaultRestApplication: This is a class that implements BaseApplication class.

Class Diagram for Application Layer:

---
title: Application Layer
---
classDiagram
    note for BootMixin "Loopback4"
    note for MyApplication "This class can be define by your own"
    BootMixin <|-- BaseApplication: extends
    BaseApplication <|-- DefaultRestApplication: extends
    Application <|.. BaseApplication : implements
    BaseApplication<|-- MyApplication : extends
    DefaultRestApplication<|-- MyApplication : extends
    class BootMixin{ }
    class Application{
      <<interface>>
      + models: Set~string~

      + staticConfigure() void
      + getProjectRoot() void
      + preConfigure() void
      + postConfigure() void
    }
    class BaseApplication {
      <<Abstract>>
      # logger: ApplicationLogger
      + models: Set~string~

      + staticConfigure()* void
      + getProjectRoot()* void
      + validateEnv()* EnvironmentResult
      + declareModels()* void
      + preConfigure()* void
      + postConfigure()* void
    }
    class DefaultRestApplication {
      <<Abstract>>
      # applicationRoles: string[]

      + DefaultRestApplication(opt) void
      + getApplicationRoles () string[]
      + validateEnv() EnvironmentResult
      + declareModels() Set~string~
      + configMigration() void
      + preConfigure() void
    }
    class MyApplication { }
Loading

Models:

Base Models:

Base models which extends default models: Base Application Models

Enhanced Custom Models from Base Models:

Instead of create models by hand we have:

  • defineUser(): return User model.
  • defineRole(): return Role model.
  • definePermission(): return Permission model.
  • definePermissionMapping(): return PermissionMapping model.
  • defineUserRole(): return UserRole model extends PrincipalMixin with principal class is Role.
2. Usage enhanced models:
  1. You can define models based on 5 function defined above.
  2. You can reuse the models which implemented from 5 those above.
// An example of using one of 5 function defined models before.
// user-role.model.ts which implemented defineUserRole()
const BaseUserRole = defineUserRole();

@model({
  settings: {
    postgresql: {
      schema: 'public',
      table: 'UserRole',
    },
    hiddenProperties: ['createdAt', 'modifiedAt'],
  },
})
export class UserRole extends BaseUserRole {
  constructor(data?: Partial<UserRole>) {
    super(data);
  }
}

Mixins:

Read more: https://loopback.io/doc/en/lb4/Mixin.html

Application Mixins


Datasources:

Read more: https://loopback.io/doc/en/lb4/DataSource.html

Base DataSources


Repositories:

Read more: https://loopback.io/doc/en/lb4/Repository.html

Base Repositories


Services:

Read more: https://loopback.io/doc/en/lb4/Service.html

Base DataSources

Example creating service

export class MyService extends BaseService {
  constructor(
    @inject('<injection_key>') private instance: InjectionableClass,
    // More injections
  ) {
    super({ scope: MyService.name });
  }

  // extends methods
}

Usage Application with DefaultRestApplication:

import { DefaultRestApplication } from '@lb/infra';

class MyApplication extends DefaultRestApplication {
  constructor(serverOptions: ApplicationConfig = {}) {
    super({ serverOptions });
  }

  configureRepositories() {
    this.logger.info('[configureRepositories] Initializing application repositories...');
    this.repository(RepositoryClass)
    ...
  }

  configureServices() {
    this.logger.info('[configureServices] Iniializing application services...');
    this.service(ServiceClass);
    ...
  }

  configureSecurity() {
    this.logger.info('[configureSecurity] Initializing application security...');

    // Security - Authentication
    this.bind<string>(AuthenticateKeys.APPLICATION_SECRET).to(App.SECRET);
    this.bind<IAuthenticateTokenOptions>(AuthenticateKeys.TOKEN_OPTIONS).to({
      tokenSecret: Authentication.ACCESS_TOKEN_SECRET,
      tokenExpiresIn: Authentication.ACCESS_TOKEN_EXPIRES_IN,
      refreshSecret: Authentication.REFRESH_TOKEN_SECRET,
      refreshExpiresIn: Authentication.REFRESH_TOKEN_EXPIRES_IN,
    });
    this.component(AuthenticateComponent);

    // Security - Authorization
    this.bind(AuthorizerKeys.AUTHORIZE_DATASOURCE).toInjectable(PostgresDataSource);
    this.component(AuthorizeComponent);

    this.bind(AuthorizerKeys.ALWAYS_ALLOW_ROLES).to([FullAuthorizationRoles.SUPER_ADMIN, FullAuthorizationRoles.ADMIN]);
    this.bind(AuthorizerKeys.CONFIGURE_OPTIONS).to({
      confPath: path.resolve(__dirname, 'authorize_model.conf'),
      useCache: false,
    });
  }

  staticConfigure(): void {
    this.static('/', path.join(__dirname, '../public'));
  }

  getProjectRoot(): string {
    return __dirname;
  }

  configureMiddlewares() {
    this.logger.info('[configureMiddlewares] Initializing application middlewares...');
  }

  preConfigure(): void {
    super.preConfigure();

    // Datasources
    this.dataSource(PostgresHistoryDataSource);

    // Middlewares
    this.configureMiddlewares();

    // Repositories
    this.configureRepositories();

    // Services
    this.configureServices();

    // Security
    this.configureSecurity();

    // Saga
    SagaHelper.getInstance();
  }

  postConfigure(): void {
    // Explorer
    this.configure(RestExplorerBindings.COMPONENT).to({
      path: '/explorer',
      indexTitle: 'API Explorer',
    });
    this.component(RestExplorerComponent);
  }
}

//Run application
const serverProps = {
  port: +(process.env.APP_ENV_SERVER_PORT ?? 3000),
  host: process.env.APP_ENV_SERVER_HOST,
  basePath: process.env.APP_ENV_SERVER_BASE_PATH,
};

export const beConfigs = {
  rest: {
    ...serverProps,
    gracePeriodForClose: 5000,
    openApiSpec: {
      endpointMapping: {
        '/openapi.json': { version: '3.0.0', format: 'json' },
        '/openapi.yaml': { version: '3.0.0', format: 'yaml' },
      },
      servers: [{ url: process.env.APP_ENV_APPLICATION_EXPLORER_URL }],
    },
    cors: {
      origin: '*',
      methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
      preflightContinue: false,
      optionsSuccessStatus: 204,
      maxAge: 86400,
      credentials: true,
    },
    requestBodyParser: {
      json: { limit: '10mb' },
    },
  },
};

const runApplication = async () => {
  const app = new MyApplication(beConfigs);
  applicationContext.bind(BindingKeys.APPLICATION_INSTANCE).to(app);

  logger.info(' Getting ready to start up %s Application...', applicationName);

  await app.boot();
  await app.start();

  const logFolder = path.resolve(__dirname, process.env.APP_ENV_LOGGER_FOLDER_PATH ?? '').toString();
  const { url } = app.restServer;
  logger.info(' %s Server is now running...', applicationName);
  logger.info(' Server URL: %s', url);
  logger.info(' Log folder: %s', logFolder);
  return app;
};

Custom logger:

APP_ENV_APPLICATION_NAME: log label

# Should configure
APP_ENV_LOGGER_FOLDER_PATH: output log folder

# Extra configure (Optional)
APP_ENV_LOGGER_DGRAM_HOST: forwared IP Address
APP_ENV_LOGGER_DGRAM_PORT: forwared Port
APP_ENV_LOGGER_DGRAM_LABEL: forwared log label 
APP_ENV_LOGGER_DGRAM_LEVELS: levels allowed to send to forwared destination

Example env configs:

APP_ENV_APPLICATION_NAME=app_name

APP_ENV_LOGGER_FOLDER_PATH=./logs

APP_ENV_LOGGER_DGRAM_HOST=0.0.0.0
APP_ENV_LOGGER_DGRAM_PORT=11111
APP_ENV_LOGGER_DGRAM_LABEL=app_name
APP_ENV_LOGGER_DGRAM_LEVELS=error,emerg

Default Log Levels:

error:      1 - red
alert:      1 - red
emerg:      1 - red
warn:       2 - yellow
info:       3 - green
http:       4 - magenta
verbose:    5 - gray
debug:      6 - blue
silly:      7 - gray

Minimal Technology Vietnam

Clone this wiki locally