Files
lcbp3/specs/06-Decision-Records/ADR-004-database-schema-design-strategy.md
T
admin c95e0f537e
CI / CD Pipeline / build (push) Successful in 4m34s
CI / CD Pipeline / deploy (push) Successful in 7m33s
690404:1139 Modify ADR
2026-04-04 11:39:56 +07:00

17 KiB

ADR-004: Database Schema Design Strategy

Status: Accepted (Implementation Ready) Date: 2026-04-04 Decision Makers: Development Team, System Architect, Database Administrator Related Documents:


🎯 Gap Analysis & Purpose

ปิด Gap จากเอกสาร:

  • Database Schema Tables - Section 1: "ตารางต้องมีโครงสร้างที่สอดคล้องกันและรองรับ Business Logic"
    • เหตุผล: ต้องการบันทึกการตัดสินใจเกี่ยวกับ Schema Patterns ที่ใช้จริง
  • Data Dictionary - Section 2: "การตั้งชื่อคอลัมน์และความสัมพันธ์ต้องสอดคล้องกัน"
    • เหตุผล: ต้องการทำให้ naming conventions เป็นมาตรฐานเดียวกัน

แก้ไขความขัดแย้ง:

  • Normalization vs Performance: ต้องการ Database Normalization แต่ต้องรักษา Performance สำหรับ DMS
    • การตัดสินใจนี้ช่วยแก้ไขโดย: ใช้ selective denormalization และ strategic indexing

Context and Problem Statement

LCBP3-DMS ต้องการ Database Schema Design ที่:

  1. Consistent: ทุกตารางใช้ Patterns เดียวกัน
  2. Scalable: รองรับข้อมูลจำนวนมากและ Complex Queries
  3. Maintainable: ง่ายต่อการ Modify และ Debug
  4. Performance: รองรับ High-volume Operations พร้อม Concurrent Access
  5. Audit-Ready: รองรับ Audit Trail และ Compliance

Key Challenges

  1. Identifier Strategy: การใช้ทั้ง INT PK และ UUID (ADR-019)
  2. Soft Delete: การจัดการการลบข้อมูลแบบ Soft Delete
  3. Audit Trail: การติดตามการเปลี่ยนแปลงข้อมูล
  4. Workflow State: การจัดการ State Machines ใน Database
  5. Document Relationships: การจัดการ Complex Many-to-Many Relationships

Decision Drivers

  • Data Integrity: ป้องกัน Data Corruption และ Inconsistencies
  • Performance: Query Response Times < 200ms (p90)
  • Scalability: รองรับ 100+ concurrent users
  • Maintainability: ง่ายต่อการ Add/Modify Columns
  • Auditability: Complete audit trail สำหรับ Compliance
  • Migration Safety: ง่ายต่อการ Schema Evolution

Considered Options

Option 1: Highly Normalized (3NF+)

แนวทาง: Strict normalization ทุกตาราง

Pros:

  • Data integrity สูงสุด
  • Minimal data duplication
  • Theoretical correctness

Cons:

  • Complex queries มากขึ้น
  • Performance impact จาก JOINs
  • ยากต่อการ maintain ในระยะยาว

Option 2: Denormalized for Performance

แนวทาง: Duplicate data สำหรับ performance

Pros:

  • Fast queries (fewer JOINs)
  • Simple read operations
  • Good for reporting

Cons:

  • Data synchronization complexity
  • Higher storage requirements
  • Risk of inconsistencies

Option 3: Selective Normalization with Strategic Patterns (Selected)

แนวทาง: Normalize ที่สำคัญ แต่ denormalize ที่จำเป็น พร้อม Standard Patterns

Pros:

  • Balance Performance & Integrity: Optimize สำหรับ use case จริง
  • Consistent Patterns: Standard conventions ทั่วทั้ง schema
  • Audit Trail Built-in: Soft delete + tracking tables
  • Migration Ready: ง่ายต่อการ evolve schema
  • Workflow Support: Native state management

Cons:

  • Requires architectural discipline
  • More complex initial design

Decision Outcome

Chosen Option: Option 3 - Selective Normalization with Strategic Patterns

Rationale

เลือก Selective Normalization เนื่องจาก:

  1. Business Reality: DMS มี both transactional และ reporting needs
  2. Performance Requirements: ต้องการ fast reads สำหรับ list views แต่ maintain integrity สำหรับ transactions
  3. Maintainability: Standard patterns ลดความซับซ้อนในระยะยาว
  4. Audit Requirements: Built-in audit trail จำเป็นสำหรับ document management

🔍 Impact Analysis

Affected Components (ส่วนประกอบที่ได้รับผลกระทบ)

Component Level Impact Description Required Action
Database Schema 🔴 High ทุกตารางต้องใช้ standard patterns Refactor all tables
TypeORM Entities 🔴 High Entity classes ต้อง映射 schema patterns Update entity definitions
Migration Scripts 🔴 High ต้องมี migration สำหรับ pattern changes Create migration strategy
Queries & Services 🟡 Medium ต้อง optimize queries สำหรับ new patterns Update service queries
Performance Testing 🟡 Medium ต้อง validate performance ของ patterns Load testing

Required Changes (การเปลี่ยนแปลงที่ต้องดำเนินการ)

🔴 Critical Changes (ต้องทำทันที)

  • Define Schema Standards - สร้างมาตรฐานการออกแบบตาราง
  • Implement Base Entity Pattern - สำหรับ common fields
  • Update Core Tables - ใช้ standard patterns
  • Create Audit Trail Tables - สำหรับ tracking

🟡 Important Changes (ควรทำภายใน 1 สัปดาห์)

  • Optimize Indexes - สำหรับ performance
  • Create Migration Scripts - สำหรับ evolution
  • Update TypeORM Entities - reflect new patterns
  • Performance Testing - validate improvements

🟢 Nice-to-Have (ทำถ้ามีเวลา)

  • Database Documentation - auto-generate from schema
  • Performance Monitoring - query analysis tools
  • Schema Validation - automated checks

Implementation Details

Schema Design Patterns

1. Base Table Pattern

ทุกตารางต้องมี base columns ต่อไปนี้:

CREATE TABLE example_table (
  -- Primary Keys (ADR-019)
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'Internal PK - never exposed',
  uuid UUID NOT NULL DEFAULT UUID() COMMENT 'Public UUID (ADR-019)',

  -- Business Columns
  name VARCHAR(255) NOT NULL,
  -- ... other business columns

  -- Standard Metadata
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time',
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last update time',
  created_by INT NULL COMMENT 'User who created record',
  updated_by INT NULL COMMENT 'User who last updated record',
  deleted_at DATETIME NULL COMMENT 'Soft delete timestamp',

  -- Indexes
  UNIQUE INDEX idx_example_uuid (uuid),
  INDEX idx_example_created_at (created_at),
  INDEX idx_example_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

2. Naming Conventions

Table Names:

  • Plural snake_case: organizations, correspondences, workflow_instances
  • Join Tables: table1_table2 (e.g., correspondence_recipients)
  • Lookup Tables: Prefix with type: correspondence_types, organization_roles

Column Names:

  • Primary Keys: id (INT), uuid (UUID)
  • Foreign Keys: {table}_id (e.g., organization_id, project_id)
  • Boolean Columns: is_ prefix: is_active, is_deleted
  • Timestamp Columns: _at suffix: created_at, updated_at, deleted_at
  • User References: _by suffix: created_by, updated_by

3. Relationship Patterns

One-to-Many:

-- Projects have many Contracts
CREATE TABLE contracts (
  id INT PRIMARY KEY AUTO_INCREMENT,
  uuid UUID NOT NULL DEFAULT UUID(),
  project_id INT NOT NULL,
  -- ... other columns
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);

Many-to-Many:

-- Correspondences have many Recipients
CREATE TABLE correspondence_recipients (
  id INT PRIMARY KEY AUTO_INCREMENT,
  correspondence_id INT NOT NULL,
  user_id INT NOT NULL,
  recipient_type ENUM('TO', 'CC', 'BCC') NOT NULL,
  received_at TIMESTAMP NULL,
  -- Metadata
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  UNIQUE KEY unique_correspondence_recipient (correspondence_id, user_id)
);

4. Workflow State Pattern

-- Workflow States
CREATE TABLE workflow_states (
  id INT PRIMARY KEY AUTO_INCREMENT,
  workflow_code VARCHAR(50) NOT NULL,
  state_name VARCHAR(50) NOT NULL,
  is_initial BOOLEAN DEFAULT FALSE,
  is_terminal BOOLEAN DEFAULT FALSE,
  allowed_transitions JSON NULL, -- Array of next states
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY unique_workflow_state (workflow_code, state_name)
);

-- Workflow Instances
CREATE TABLE workflow_instances (
  id INT PRIMARY KEY AUTO_INCREMENT,
  uuid UUID NOT NULL DEFAULT UUID(),
  workflow_code VARCHAR(50) NOT NULL,
  entity_type VARCHAR(50) NOT NULL, -- 'correspondence', 'rfa', etc.
  entity_id INT NOT NULL,
  current_state VARCHAR(50) NOT NULL,
  status ENUM('ACTIVE', 'COMPLETED', 'CANCELLED') DEFAULT 'ACTIVE',
  context JSON NULL,
  -- Metadata
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (workflow_code, current_state) REFERENCES workflow_states(workflow_code, state_name)
);

5. Audit Trail Pattern

-- Generic Audit Trail
CREATE TABLE audit_logs (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  table_name VARCHAR(50) NOT NULL,
  record_id INT NOT NULL,
  record_uuid UUID NULL,
  action ENUM('CREATE', 'UPDATE', 'DELETE', 'SOFT_DELETE') NOT NULL,
  old_values JSON NULL,
  new_values JSON NULL,
  changed_fields JSON NULL, -- Array of changed field names
  user_id INT NULL,
  ip_address VARCHAR(45) NULL,
  user_agent TEXT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_audit_table_record (table_name, record_id),
  INDEX idx_audit_user (user_id, created_at),
  INDEX idx_audit_created (created_at)
);

-- Document-specific History (for critical tables)
CREATE TABLE correspondence_histories (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  correspondence_id INT NOT NULL,
  revision_number INT NOT NULL,
  changes JSON NOT NULL, -- Full snapshot or delta
  changed_by INT NULL,
  change_reason VARCHAR(255) NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE,
  UNIQUE KEY unique_correspondence_revision (correspondence_id, revision_number)
);

6. Master Data Pattern

-- Reference Data Tables
CREATE TABLE correspondence_types (
  id INT PRIMARY KEY AUTO_INCREMENT,
  type_code VARCHAR(20) NOT NULL UNIQUE, -- LETTER, RFI, MEMO
  type_name VARCHAR(100) NOT NULL,
  description TEXT NULL,
  display_order INT DEFAULT 0,
  is_active BOOLEAN DEFAULT TRUE,
  -- Metadata
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT='Master data: Correspondence types';

TypeORM Entity Patterns

Base Entity

import { CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { UuidBaseEntity } from './uuid-base.entity';

export abstract class BaseEntity extends UuidBaseEntity {
  @CreateDateColumn({ type: 'timestamp' })
  createdAt: Date;

  @UpdateDateColumn({ type: 'timestamp' })
  updatedAt: Date;

  @DeleteDateColumn({ type: 'datetime', nullable: true })
  deletedAt?: Date;

  @Column({ type: 'int', nullable: true })
  createdBy?: number;

  @Column({ type: 'int', nullable: true })
  updatedBy?: number;
}

Entity Example

@Entity('correspondences')
export class Correspondence extends BaseEntity {
  @Column({ type: 'varchar', length: 50 })
  documentNumber: string;

  @Column({ type: 'varchar', length: 255 })
  subject: string;

  @Column({ type: 'text', nullable: true })
  content?: string;

  @ManyToOne(() => CorrespondenceType)
  @JoinColumn({ name: 'correspondence_type_id' })
  type: CorrespondenceType;

  @ManyToOne(() => Organization)
  @JoinColumn({ name: 'originator_id' })
  originator: Organization;

  @OneToMany(() => CorrespondenceRecipient, recipient => recipient.correspondence)
  recipients: CorrespondenceRecipient[];
}

Indexing Strategy

Primary Indexes

-- UUID for public access
CREATE UNIQUE INDEX idx_table_uuid ON table_name (uuid);

-- Performance indexes
CREATE INDEX idx_table_created_at ON table_name (created_at);
CREATE INDEX idx_table_updated_at ON table_name (updated_at);
CREATE INDEX idx_table_deleted_at ON table_name (deleted_at);

Foreign Key Indexes

-- All foreign keys should be indexed
CREATE INDEX idx_correspondence_project_id ON correspondences (project_id);
CREATE INDEX idx_correspondence_originator_id ON correspondences (originator_id);
CREATE INDEX idx_correspondence_type_id ON correspondences (correspondence_type_id);

Query-Specific Indexes

-- Composite indexes for common queries
CREATE INDEX idx_correspondences_project_status ON correspondences (project_id, status, deleted_at);
CREATE INDEX idx_correspondences_date_range ON correspondences (created_at, deleted_at) WHERE deleted_at IS NULL;

Consequences

Positive

  1. Consistency: Standard patterns ทั่วทั้ง database
  2. Performance: Strategic indexing และ selective denormalization
  3. Audit Trail: Complete tracking สำหรับ compliance
  4. Maintainability: Clear naming conventions และ patterns
  5. Migration Safety: Evolution-friendly schema design
  6. Type Safety: TypeORM entities ที่สอดคล้องกับ schema

Negative

  1. Initial Complexity: ต้องเรียนรู้ patterns และ conventions
  2. Storage Overhead: Audit tables ใช้พื้นที่เพิ่ม
  3. Development Overhead: ต้อง maintain patterns อย่างสม่ำเสมอ

Mitigation Strategies

  • Complexity: Comprehensive documentation และ examples
  • Storage: Partition audit tables และ implement retention policies
  • Development: Code generation สำหรับ boilerplate entities

🔄 Review Cycle & Maintenance

Review Schedule

  • Next Review: 2026-10-04 (6 months from creation)
  • Review Type: Scheduled (Schema Strategy Review)
  • Reviewers: System Architect, DBA, Backend Team Lead

Review Checklist

  • ยังคงตอบโจทย์ Performance Requirements หรือไม่?
  • มี schema patterns ใหม่ที่ควรพิจารณาหรือไม่?
  • มีปัญหา Storage หรือ Performance หรือไม่?
  • ต้องการ Update หรือ Deprecate patterns ใดหรือไม่?

Version History

Version Date Changes Status
1.0 2026-04-04 Initial version - Selective Normalization Strategy Accepted

Compliance

เป็นไปตาม:



References