Skip to content

Pattern Framework IGNIS

Tất cả các dịch vụ backend đều được xây dựng trên IGNIS Framework (@venizia/ignis + @venizia/ignis-helpers). Trang này tài liệu hóa các pattern mà mọi dịch vụ đều tuân theo.

Phân cấp Ứng dụng

  • IssuerApplication — chỉ @nx/identity extends lớp này (nó ký JWT và phục vụ endpoint JWKS)
  • VerifierApplication — tất cả các dịch vụ khác extends lớp này (chúng xác minh JWT bằng cách lấy /jw-certs của identity)

Pattern Lớp Application

src/application.ts của mỗi dịch vụ tuân theo cấu trúc chính xác này:

typescript
import { createAppConfig, VerifierApplication } from '@nx/core';

export const appConfig = createAppConfig();

export class Application extends VerifierApplication {
  override async boot() { return {}; }
  override getAppInfo() { return packageJson; }
  override getProjectRoot() { /* bind and return __dirname */ }

  override configureDatasources(): void {
    super.configureDatasources();
    this.dataSource(PostgresCoreDataSource);
  }

  override configureRepositories(): void {
    super.configureRepositories();
    this.repository(MyRepository);
  }

  override configureServices(): void {
    super.configureServices();
    this.service(MyService);
  }

  override configureControllers(): void {
    super.configureControllers();
    this.controller(MyController);
  }

  override configureComponents(): void {
    super.configureComponents();
    this.useCacheRedis({ bindingKey: BindingKeys.APPLICATION_REDIS_CACHE });
  }
}

Cấu trúc Component

Component nhóm các đăng ký DI có liên quan (repository, service, controller) có thể được thêm như một đơn vị:

typescript
export class MyComponent extends BaseComponent {
  constructor(
    @inject({ key: CoreBindings.APPLICATION_INSTANCE })
    protected application: BaseApplication,
  ) {
    super({
      scope: MyComponent.name,
      initDefault: { enable: true, container: application },
      bindings: {},
    });
  }

  override async binding(): Promise<void> {
    this.application.repository(MyRepository);
    this.application.service(MyService);
    this.application.controller(MyController);
  }
}

Đăng ký trong Application.configureComponents():

typescript
this.component(MyComponent);

Dependency Injection

IGNIS dùng DI dựa trên decorator. Inject các phụ thuộc trong constructor của service/controller:

typescript
import { inject, BaseService, BindingKeys, BindingNamespaces } from '@venizia/ignis';

export class MyService extends BaseService {
  constructor(
    @inject({
      key: BindingKeys.build({
        namespace: BindingNamespaces.REPOSITORY,
        key: MyRepository.name,
      }),
    })
    private myRepository: MyRepository,

    @inject({ key: 'custom/binding/key' })
    private redis: DefaultRedisHelper,
  ) {
    super({ scope: MyService.name });
  }
}

Pattern Binding Key

PatternVí dụ
RepositoryBindingKeys.build({ namespace: BindingNamespaces.REPOSITORY, key: 'MyRepository' })
ServiceBindingKeys.build({ namespace: BindingNamespaces.SERVICE, key: 'MyService' })
CustomChuỗi literal, ví dụ '@nx/licensing/redis/cache'

BaseService cung cấp this.logger với this.logger.for(methodName) trả về { info, error, warn, debug }.

Controller Factory (Auto CRUD)

ControllerFactory.defineCrudController() tạo ra một controller REST đầy đủ với các route được chuẩn hóa:

typescript
const _Controller = ControllerFactory.defineCrudController({
  repository: { name: MyRepository.name },
  authenticate: { strategies: [AuthenticateStrategy.JWT, AuthenticateStrategy.BASIC] },
  authorize: { action: AuthorizationActions.READ, resource: MyPermissions.FIND.code },
  controller: {
    name: 'MyController',
    basePath: '/my-entities',
    isStrict: { path: true, requestSchema: true },
  },
  entity: () => MyEntity,
  routes: {
    find:       { authorize: { action: AuthorizationActions.READ, resource: '...' } },
    findById:   { authorize: { ... } },
    count:      { authorize: { ... } },
    findOne:    { authorize: { ... } },
    create:     { request: { body: MyInsertSchema }, authorize: { ... } },
    updateById: { request: { body: MyUpdateSchema }, authorize: { ... } },
    deleteById: { authorize: { ... } },
    deleteBy:   { authorize: { ... } },
  },
});

@controller({ path: '/my-entities', transport: ControllerTransports.REST })
export class MyController extends _Controller {
  constructor(
    @inject({ key: 'repositories.MyRepository' })
    protected readonly repository: MyRepository,
  ) {
    super(repository);
  }
}

Nó tạo ra 10 route CRUD: find, findById, findOne, count, create, updateById, updateBy, deleteById, deleteBy. Các route tùy chỉnh được thêm bằng decorator @get(), @post(), v.v. trên các phương thức bổ sung.

Mã Quyền

crudPermissions(subject, labels) tạo các object quyền với mã:

Hành động
Subject.findList
Subject.findByIdGet by ID
Subject.findOneFind one
Subject.countCount
Subject.createCreate
Subject.updateByIdUpdate by ID
Subject.updateByBatch update
Subject.deleteByIdDelete by ID
Subject.deleteByBatch delete

Các quyền hành động tùy chỉnh được định nghĩa thủ công (xem License.issue, License.suspend của licensing, v.v.).

Bootstrap Helper

Điểm vào Application

typescript
// packages/*/src/index.ts
import { bootstrapApplication } from '@nx/core';
import { Application, appConfig } from './application';

bootstrapApplication({
  ApplicationClass: Application,
  config: appConfig,
  options: { bannerPath: resolve(__dirname, '../resources/banner.txt') },
});

Điểm vào Migration

typescript
// packages/*/src/migrate.ts
import { bootstrapMigration } from '@nx/core';
import { Application } from './application';
import { getMigrationProcesses } from './migrations/processes/migration-process';

bootstrapMigration({ ApplicationClass: Application, getMigrationProcesses });

Loader Process Migration

typescript
// packages/*/src/migrations/processes/migration-process.ts
import { createMigrationProcessLoader } from '@nx/core';

export const getMigrationProcesses = createMigrationProcessLoader({
  seedPaths: ['my-package-0001-seed-permissions'],
  importFn: path => import(`../processes/${path}.js`),
});

Mỗi file seed export một TMigrationProcess với options: { alwaysRun?: boolean }, một name, và một migrateFn.

Pattern Cơ sở Dữ liệu

Drizzle ORM với PostgreSQL

Mỗi dịch vụ dùng chung một PostgresCoreDataSource:

typescript
@datasource({ driver: 'node-postgres' })
export class PostgresCoreDataSource extends BaseDataSource {
  override configure(): void {
    const schema = this.getSchema();
    this.pool = new Pool(this.settings);
    this.connector = drizzle({ client: this.pool, schema });
  }
}

Repository Soft-Deletable

SoftDeletableRepository từ @nx/core tự động lọc các hàng có deleted_at IS NOT NULL:

typescript
@repository({ dataSource: PostgresCoreDataSource, model: MyEntity })
export class MyRepository extends SoftDeletableRepository<TSchema, TEntity, TPersist> {
  // Kế thừa: deleteById đặt deleted_at thay vì xóa cứng
  // findById, find, findOne đều loại trừ các hàng soft-deleted
}

Transaction

typescript
const tx = await this.repository.beginTransaction();

try {
  await this.repository.create({ data, options: { transaction: tx } });
  await this.otherRepository.updateById({ id, data, options: { transaction: tx } });
  await tx.commit();
} catch (err) {
  await tx.rollback();
  throw err;
}

Khóa mức hàng để đảm bảo an toàn đồng thời:

typescript
const row = await this.repository.findOne({
  filter: { where: { id } },
  options: { transaction: tx, lock: { strength: LockStrengths.UPDATE } },
});

Snowflake ID

Tất cả khóa chính là Snowflake ID (số nguyên 64-bit dưới dạng chuỗi) được tạo bởi IdGenerator.getInstance().nextId(). Mỗi dịch vụ có một APP_ENV_SNOWFLAKE_WORKER_ID duy nhất để tránh trùng lặp.

Pattern Model

Drizzle schema + validation Zod:

typescript
// Định nghĩa schema (trong @nx/core)
export const MyEntitySchema = licensingSchema.table('MyEntity', {
  ...generateCommonColumnDefs(),  // id, createdAt, modifiedAt, deletedAt
  name: text('name').notNull(),
  status: text('status').notNull().default('activated'),
  data: jsonb('data').$type<MyJsonType>(),
}, def => [
  index('IDX_MyEntity_status').on(def.status),
]);

// Export kiểu
export type TMyEntity = TTableObject<typeof MyEntitySchema>;
export type TMyEntityPersist = TTableInsert<typeof MyEntitySchema>;

// Schema Zod (tự động sinh từ Drizzle)
export const MyEntitySelectSchema = createSelectSchema(MyEntitySchema);
export const MyEntityInsertSchema = createInsertSchema(MyEntitySchema);
export const MyEntityUpdateSchema = MyEntityInsertSchema.partial();

generateCommonColumnDefs() thêm: id (Snowflake, text PK), createdAt, modifiedAt, deletedAt (soft-delete).

Đối với entity không có soft-delete:

typescript
{
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
  ...generateTzColumnDefs({ deleted: { enable: false } }),
}

Quan hệ Entity

typescript
@model({
  type: 'entity',
  settings: { hiddenProperties: ['createdAt', 'modifiedAt', 'deletedAt'] },
})
export class MyEntity extends BaseEntity<typeof MyEntitySchema> {
  static override TABLE_NAME = 'MyEntity';
  static override schema = MyEntitySchema;

  static override relations = (): TRelationConfig[] => [
    { type: RelationTypes.ONE, name: 'parent', schema: ParentSchema, ... },
    { type: RelationTypes.MANY, name: 'children', schema: ChildSchema, ... },
  ];
}

Quan hệ được sử dụng bởi controller CRUD cho các query include — ví dụ ?filter[include][0][relation]=children.

Cấu hình Môi trường

Mỗi package dùng dotenv-flow để quản lý môi trường:

FileMục đíchBị gitignore
.env.developmentCấu hình dev serverKhông
.env.testCấu hình testThường là có
.env.localOverride cục bộ

Giá trị NODE_ENV quyết định file nào được nạp. Xem Tham khảo Biến Môi trường để có danh mục biến đầy đủ.

Tạo Key JWKS (ES256, PKCS#8)

Chỉ dành cho dịch vụ identity:

bash
# Tạo cặp key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
  | tee private.pem | openssl pkey -pubout > public.pem

# Định dạng cho .env (single-line với \n)
awk 'NF {printf "%s\\n", $0}' private.pem
awk 'NF {printf "%s\\n", $0}' public.pem

Private key bắt buộc phải là định dạng PKCS#8 (-----BEGIN PRIVATE KEY-----), không phải SEC1.

File Mã nguồn Chính

PatternĐường dẫn file
Phân cấp Applicationpackages/core/src/application/{base,default,issuer,verifier}.ts
Repository soft-deletepackages/core/src/repositories/soft-deletable.repository.ts
Generator Snowflake IDpackages/core/src/utilities/id-generator.utility.ts
Hằng số & vai tròpackages/core/src/common/constants.ts
Key môi trườngpackages/core/src/common/environments.ts
Factory app configpackages/core/src/common/app-config.ts
Bootstrap helperpackages/core/src/helpers/bootstraps/application.ts
Migration helperpackages/core/src/helpers/migration/
Quyền CRUDpackages/core/src/common/constants.tscrudPermissions()
Application của dịch vụpackages/*/src/application.ts

Trang Liên quan

TrangMô tả
Bắt đầuHướng dẫn cài đặt cục bộ
Hệ thống BuildTarget Makefile và đồ thị phụ thuộc
Tham khảo Biến Môi trườngDanh mục biến môi trường đầy đủ
Package CoreTham khảo chi tiết @nx/core

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.