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
| Schema | Bảng | Dịch vụ sở hữu | Mục đích |
|---|---|---|---|
public | 15 | identity, commerce | Ngườ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 |
identity | 8 | identity | Thông tin xác thực người dùng, định danh, hồ sơ, token xác thực email |
pricing | 11 | pricing | Giá vé, quy tắc giá, chi phí, thuế, kết quả đánh giá giá |
sale | 9 | sale | Đơn hàng, mục đơn hàng, check, ticket bếp |
inventory | 12 | inventory | Vị trí kho, mục tồn kho, đơn mua, biến động kho |
allocation | 4 | commerce | Sắp chỗ sự kiện, sơ đồ địa điểm |
finance | 3 | finance | Ví, bản ghi giao dịch, danh mục tài chính |
payment | 1 | payment | Cấu hình webhook |
ledger | 2 | ledger | Tác vụ tạo ledger, tài liệu được tạo |
licensing | 5 | licensing | Chính sách, tính năng chính sách, license, kích hoạt, sự kiện license |
outreach | 2 | outreach | Yê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ột | Kiểu | Nullable | Mặc định | Ghi chú |
|---|---|---|---|---|
id | text | Không | crypto.randomUUID() | Snowflake ID, khóa chính |
created_at | timestamp(tz) | Không | now() | Tự động đặt khi insert |
modified_at | timestamp(tz) | Không | now() | Tự động cập nhật mỗi lần thay đổi |
deleted_at | timestamp(tz) | Có | — | Đánh dấu soft delete |
metadata | jsonb | Có | — | Metadata linh hoạt |
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ọn | Kiểu cột | Mặc định |
|---|---|---|
{ id: { dataType: 'string' } } | text PK, crypto.randomUUID() | Phổ biến nhất |
{ id: { dataType: 'number' } } | integer PK, GENERATED ALWAYS AS IDENTITY | Tự động tăng |
{ id: { dataType: 'big-number' } } | bigint PK, GENERATED ALWAYS AS IDENTITY | Chuỗi lớn |
generateTzColumnDefs()
Cột timestamp với khả năng cấu hình soft-delete:
// Có soft delete (mặc định)
generateTzColumnDefs({ deleted: { enable: true } })
// → createdAt, modifiedAt, deletedAt
// Không có soft delete
generateTzColumnDefs({ deleted: { enable: false } })
// → chỉ createdAt, modifiedAtgenerateDataTypeColumnDefs()
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ột | Kiểu | Mục đích |
|---|---|---|
data_type | text | Phân biệt kiểu (ví dụ 'BOOLEAN', 'NUMBER', 'TEXT', 'JSON') |
n_value | double precision | Giá trị số |
t_value | text | Giá trị văn bản |
bo_value | boolean | Giá trị boolean |
j_value | jsonb | Giá trị object JSON |
b_value | bytea | Dữ 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:
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:
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 NULLkhi gọifind,findOne,findById - Hiện thực
deleteByIdthànhUPDATE SET deleted_at = now()(không phảiDELETEcứng)
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
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:
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
# Một schema
make db-generate schema=public
# Tất cả schema
make db-generate-allLệ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
# 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=identitydb-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/:
// 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)
- Tạo file schema trong
packages/core/src/models/schemas/{schema}/{entity}/schema.ts - Tạo file model trong
packages/core/src/models/schemas/{schema}/{entity}/model.ts - Export từ index của schema trong
packages/core/src/models/schemas/{schema}/index.ts - Tạo migration:
make db-generate schema={schema} - Xem lại SQL trong
packages/core/src/migrations/drizzle/{schema}/ - Áp dụng:
make db-migrate schema={schema} - Tạo repository trong package sở hữu:
packages/{package}/src/repositories/ - Đăng ký repository trong
Application.configureRepositories()
Quy ước Đặt tên
| Phần tử | Quy ước | Ví dụ |
|---|---|---|
| Tên bảng | PascalCase | PolicyFeature |
| Tên cột | snake_case | policy_id, created_at |
| Index | IDX_{Table}_{column} | IDX_License_status |
| Foreign key | FK_{Table}_{column}_{RefTable}_{refColumn} | FK_License_policyId_Policy_id |
| Unique constraint | UQ_{Table}_{columns} | UQ_Activation_licenseId_fingerprint |
| Biến schema | {name}Schema | licensingSchema |
Target Database trong Makefile
| Target | Cách dùng |
|---|---|
make db-generate schema=<name> | Tạo SQL cho một schema |
make db-generate-all | Tạ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-dev | Lối tắt cho ledger |
Tên schema: public, pricing, allocation, sale, inventory, finance, payment, ledger, identity, licensing, outreach
Trang Liên quan
| Trang | Mô tả |
|---|---|
| Kiến trúc | Ánh xạ schema sang dịch vụ |
| IGNIS Patterns | Pattern model, repository soft-delete |
| Hệ thống Build | Tất cả target Makefile db-* |
| Core — Database | ERD chi tiết và tài liệu theo từng schema |