484 lines
17 KiB
Markdown
484 lines
17 KiB
Markdown
# ADR-004: Database Schema Design Strategy
|
|
|
|
**Status:** ✅ Accepted (Implementation Ready)
|
|
**Date:** 2026-04-04
|
|
**Decision Makers:** Development Team, System Architect, Database Administrator
|
|
**Related Documents:**
|
|
|
|
- [Database Schema Tables](../03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql)
|
|
- [Data Dictionary](../03-Data-and-Storage/03-01-data-dictionary.md)
|
|
- [ADR-009: Database Migration Strategy](./ADR-009-database-migration-strategy.md)
|
|
- [ADR-019: Hybrid Identifier Strategy](./ADR-019-hybrid-identifier-strategy.md)
|
|
|
|
---
|
|
|
|
## 🎯 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 ต่อไปนี้:
|
|
|
|
```sql
|
|
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:**
|
|
|
|
```sql
|
|
-- 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:**
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
@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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
เป็นไปตาม:
|
|
|
|
- [Database Schema Tables](../03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql)
|
|
- [Data Dictionary](../03-Data-and-Storage/03-01-data-dictionary.md)
|
|
- [ADR-009: Database Migration Strategy](./ADR-009-database-migration-strategy.md)
|
|
- [ADR-019: Hybrid Identifier Strategy](./ADR-019-hybrid-identifier-strategy.md)
|
|
|
|
---
|
|
|
|
## Related ADRs
|
|
|
|
- [ADR-009: Database Migration Strategy](./ADR-009-database-migration-strategy.md) - Schema evolution
|
|
- [ADR-019: Hybrid Identifier Strategy](./ADR-019-hybrid-identifier-strategy.md) - UUID usage
|
|
- [ADR-001: Unified Workflow Engine](./ADR-001-unified-workflow-engine.md) - Workflow state management
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [MariaDB Documentation](https://mariadb.com/kb/en/)
|
|
- [TypeORM Documentation](https://typeorm.io/)
|
|
- [Database Design Best Practices](https://www.databasestar.com/)
|