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 14 schema có tên chứa 151 thực thể. 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

SchemaThực thểDịch vụ sở hữuMục đích
helpdesk30helpdeskTicket, agent, tag, bình luận, SLA, knowledge base
public26identity, 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
inventory17inventoryVị trí kho, mục tồn kho, đơn mua, biến động kho
sale21saleĐơn hàng, mục đơn hàng, check, ticket bếp
invoice9invoiceHóa đơn, mục hóa đơn, thông tin thuế, tích hợp hóa đơn điện tử
pricing9pricingGiá vé, quy tắc giá, chi phí, thuế, kết quả đánh giá giá
identity8identityThông tin xác thực người dùng, định danh, hồ sơ, token xác thực email
ledger8ledgerTác vụ tạo ledger, tài liệu được tạo
finance6financeVí, bản ghi giao dịch, danh mục tài chính, tích hợp thanh toán
tax6taxationĐịnh nghĩa thuế, nhóm thuế, quy tắc thuế
licensing5licensingChính sách, tính năng chính sách, license, kích hoạt, sự kiện license
allocation4commerceSắp chỗ sự kiện, sơ đồ địa điểm
outreach2outreachYêu cầu, người đăng ký newsletter
payment0paymentNamespace dự trữ (PaymentIntegration nằm trong schema finance)

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()Khóa chính; $defaultFn mặc định (một schema có thể chọn dùng Snowflake id qua IdGenerator)
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
metadatajsonb-Metadata 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 env=development

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

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 env=development

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

# Theo package (cũng chạy seed của package)

# Dạng rút gọn tương đương, mỗi package một target riêng
make db-migrate-identity-dev

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-<pkg>-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
│   ├── identity.ts            # một config mỗi schema (14 cái)
│   ├── pricing.ts
│   ├── allocation.ts
│   ├── sale.ts
│   ├── inventory.ts
│   ├── finance.ts
│   ├── tax.ts
│   ├── payment.ts
│   ├── ledger.ts
│   ├── invoice.ts
│   ├── licensing.ts
│   ├── helpdesk.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} env={env}
  5. Xem lại SQL trong packages/core/src/migrations/drizzle/{schema}/
  6. Áp dụng: make db-migrate schema={schema} env={env}
  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> env=<env>Tạo SQL cho một schema
make db-generate-all env=developmentTạo SQL cho tất cả 14 schema
make db-migrate schema=<name> env=<env>Áp dụng migration cho một schema
make db-migrate-all env=developmentÁp dụng tất cả migration
make db-migrate-<pkg>-devTương tự, dạng target riêng cho từng package (ví dụ: db-migrate-ledger-dev)

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

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.