Skip to content

Hướng dẫn Cơ sở Dữ liệu

Tất cả các dịch vụ BANA dùng chung một cơ sở dữ liệu PostgreSQL duy nhất (nx_seller) với 11 schema có tên chứa 72 bảng. Các định nghĩa schema nằm trong @nx/core — các package dịch vụ riêng lẻ không định nghĩa bảng riêng.

Tổng quan Schema

SchemaBảngDịch vụ sở hữuMục đích
public15identity, commerceNgười dùng, vai trò, quyền, merchant, organizer, sản phẩm, danh mục, biến thể, thiết bị, kênh bán hàng, cấu hình
identity8identityThông tin xác thực người dùng, định danh, hồ sơ, token xác thực email
pricing11pricingGiá vé, quy tắc giá, chi phí, thuế, kết quả đánh giá giá
sale9saleĐơn hàng, mục đơn hàng, check, ticket bếp
inventory12inventoryVị trí kho, mục tồn kho, đơn mua, biến động kho
allocation4commerceSắp chỗ sự kiện, sơ đồ địa điểm
finance3financeVí, bản ghi giao dịch, danh mục tài chính
payment1paymentCấu hình webhook
ledger2ledgerTác vụ tạo ledger, tài liệu được tạo
licensing5licensingChính sách, tính năng chính sách, license, kích hoạt, sự kiện license
outreach2outreachYêu cầu, người đăng ký newsletter

Tất cả định nghĩa schema nằm trong packages/core/src/models/schemas/{schema}/.

Column Enricher

@nx/core cung cấp các helper định nghĩa cột nhằm chuẩn hóa cấu trúc bảng.

generateCommonColumnDefs()

Mặc định cho hầu hết các bảng. Thêm 5 cột:

CộtKiểuNullableMặc địnhGhi chú
idtextKhôngcrypto.randomUUID()Snowflake ID, khóa chính
created_attimestamp(tz)Khôngnow()Tự động đặt khi insert
modified_attimestamp(tz)Khôngnow()Tự động cập nhật mỗi lần thay đổi
deleted_attimestamp(tz)Đánh dấu soft delete
metadatajsonbMetadata linh hoạt
typescript
import { generateCommonColumnDefs } from '../../common';

export const MySchema = publicSchema.table('MyTable', {
  ...generateCommonColumnDefs(),
  name: text('name').notNull(),
});

generateIdColumnDefs()

Khi bạn chỉ cần ID mà không cần timestamp:

Tùy chọnKiểu cộtMặc định
{ id: { dataType: 'string' } }text PK, crypto.randomUUID()Phổ biến nhất
{ id: { dataType: 'number' } }integer PK, GENERATED ALWAYS AS IDENTITYTự động tăng
{ id: { dataType: 'big-number' } }bigint PK, GENERATED ALWAYS AS IDENTITYChuỗi lớn

generateTzColumnDefs()

Cột timestamp với khả năng cấu hình soft-delete:

typescript
// Có soft delete (mặc định)
generateTzColumnDefs({ deleted: { enable: true } })
// → createdAt, modifiedAt, deletedAt

// Không có soft delete
generateTzColumnDefs({ deleted: { enable: false } })
// → chỉ createdAt, modifiedAt

generateDataTypeColumnDefs()

Các cột giá trị đa hình cho bảng lưu trữ nhiều kiểu dữ liệu (dùng bởi PolicyFeature):

CộtKiểuMục đích
data_typetextPhân biệt kiểu (ví dụ 'BOOLEAN', 'NUMBER', 'TEXT', 'JSON')
n_valuedouble precisionGiá trị số
t_valuetextGiá trị văn bản
bo_valuebooleanGiá trị boolean
j_valuejsonbGiá trị object JSON
b_valuebyteaDữ liệu nhị phân

Định nghĩa một Bảng

Theo pattern này trong packages/core/src/models/schemas/{schema}/{entity}/schema.ts:

typescript
import { text, jsonb, index, foreignKey, unique } from 'drizzle-orm/pg-core';
import { generateCommonColumnDefs, isoTimestamp } from '../../common';
import { mySchema } from '../common';

const TABLE_NAME = 'MyEntity';

export const MyEntitySchema = mySchema.table(
  TABLE_NAME,
  {
    ...generateCommonColumnDefs(),
    name: text('name').notNull(),
    status: text('status').notNull().default('activated'),
    parentId: text('parent_id'),
    data: jsonb('data').$type<{ key: string }>(),
  },
  def => [
    index('IDX_MyEntity_status').on(def.status),
    foreignKey({
      columns: [def.parentId],
      foreignColumns: [ParentSchema.id],
      name: 'FK_MyEntity_parentId_Parent_id',
    }).onDelete('cascade'),
    unique('UQ_MyEntity_name').on(def.name),
  ],
);

Export kiểu

Mỗi schema nên export các kiểu đi kèm sau:

typescript
export type TMyEntity = TTableObject<typeof MyEntitySchema>;        // kiểu select
export type TMyEntityPersist = TTableInsert<typeof MyEntitySchema>; // kiểu insert
export const MyEntitySelectSchema = createSelectSchema(MyEntitySchema);
export const MyEntityInsertSchema = createInsertSchema(MyEntitySchema);
export const MyEntityUpdateSchema = MyEntityInsertSchema.partial();

Repository Soft-Deletable

SoftDeletableRepository từ @nx/core bao bọc Drizzle và tự động:

  • Lọc ra các hàng có deleted_at IS NOT NULL khi gọi find, findOne, findById
  • Hiện thực deleteById thành UPDATE SET deleted_at = now() (không phải DELETE cứng)
typescript
import { SoftDeletableRepository } from '@nx/core';

@repository({ dataSource: PostgresCoreDataSource, model: MyEntity })
export class MyRepository extends SoftDeletableRepository<
  typeof MyEntitySchema,
  TMyEntity,
  TMyEntityPersist
> {}

Đối với các entity cần xóa cứng (ví dụ LicenseEvent), hãy dùng lớp Repository cơ sở thay thế, và định nghĩa schema với generateTzColumnDefs({ deleted: { enable: false } }).

Transaction

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

try {
  // Tất cả các thao tác chia sẻ cùng transaction
  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 },  // SELECT ... FOR UPDATE
  },
});

Quy trình Migration

Migration được quản lý bởi Drizzle Kit với một wrapper CLI tùy chỉnh trong @nx/core.

Tạo migration

bash
# Một schema
make db-generate schema=public

# Tất cả schema
make db-generate-all

Lệnh này chạy drizzle-kit generate với cấu hình theo schema cụ thể từ packages/core/src/migrations/migrators/{schema}.ts. Các file SQL được tạo nằm trong packages/core/src/migrations/drizzle/{schema}/.

Áp dụng migration

bash
# Một schema
make db-migrate schema=public

# Tất cả schema
make db-migrate-all

# Theo package (cũng chạy seed của package)
make db-migrate-package-dev package=identity

db-migrate chạy drizzle-kit migrate để áp dụng các file SQL chưa được áp dụng theo thứ tự. db-migrate-package-dev cũng chạy các migration seed riêng của package (quyền, cấu hình mặc định).

Migration seed theo từng package

Mỗi package có thể định nghĩa migration seed trong src/migrations/processes/:

typescript
// packages/licensing/src/migrations/processes/licensing-0001-seed-permissions.ts
const migrationProcess: TMigrationProcess = {
  options: { alwaysRun: true },  // áp dụng lại mỗi lần khởi động
  name: __filename.slice(__dirname.length + 1),
  migrateFn: async ({ context: application }) => {
    const permRepo = application.get<PermissionRepository>({ key: 'repositories.PermissionRepository' });
    for (const permission of LicensingPermissions) {
      const existing = await permRepo.findOne({ filter: { where: { code: permission.code } } });
      if (!existing) await permRepo.create({ data: permission });
      else await permRepo.updateById({ id: existing.id, data: permission });
    }
  },
};

Các migration này chạy khi dịch vụ khởi động (qua bootstrapMigration()) hoặc khi bạn gọi bun run migrate:dev.

Cấu trúc cấu hình migration

Mỗi schema có file cấu hình Drizzle riêng:

packages/core/src/migrations/
├── migrators/
│   ├── cli.ts                 # Điểm vào CLI (generate | migrate [--all])
│   ├── public.ts              # Cấu hình drizzle-kit cho schema public
│   ├── pricing.ts             # Cấu hình drizzle-kit cho schema pricing
│   ├── allocation.ts
│   ├── sale.ts
│   ├── inventory.ts
│   ├── finance.ts
│   ├── payment.ts
│   ├── ledger.ts
│   ├── identity.ts
│   ├── licensing.ts
│   └── outreach.ts
└── drizzle/
    ├── public/                # Các file SQL migration được tạo
    ├── pricing/
    ├── ...

Thêm bảng mới (từng bước)

  1. Tạo file schema trong packages/core/src/models/schemas/{schema}/{entity}/schema.ts
  2. Tạo file model trong packages/core/src/models/schemas/{schema}/{entity}/model.ts
  3. Export từ index của schema trong packages/core/src/models/schemas/{schema}/index.ts
  4. Tạo migration: make db-generate schema={schema}
  5. Xem lại SQL trong packages/core/src/migrations/drizzle/{schema}/
  6. Áp dụng: make db-migrate schema={schema}
  7. Tạo repository trong package sở hữu: packages/{package}/src/repositories/
  8. Đăng ký repository trong Application.configureRepositories()

Quy ước Đặt tên

Phần tửQuy ướcVí dụ
Tên bảngPascalCasePolicyFeature
Tên cộtsnake_casepolicy_id, created_at
IndexIDX_{Table}_{column}IDX_License_status
Foreign keyFK_{Table}_{column}_{RefTable}_{refColumn}FK_License_policyId_Policy_id
Unique constraintUQ_{Table}_{columns}UQ_Activation_licenseId_fingerprint
Biến schema{name}SchemalicensingSchema

Target Database trong Makefile

TargetCách dùng
make db-generate schema=<name>Tạo SQL cho một schema
make db-generate-allTạo SQL cho tất cả 11 schema
make db-migrate schema=<name>Áp dụng migration cho một schema
make db-migrate-allÁp dụng tất cả migration
make db-migrate-package-dev package=<name>Chạy seed mức package ở dev
make db-migrate-ledger-devLối tắt cho ledger

Tên schema: public, pricing, allocation, sale, inventory, finance, payment, ledger, identity, licensing, outreach

Trang Liên quan

TrangMô tả
Kiến trúcÁnh xạ schema sang dịch vụ
IGNIS PatternsPattern model, repository soft-delete
Hệ thống BuildTất cả target Makefile db-*
Core — DatabaseERD chi tiết và tài liệu theo từng schema

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