12 KiB
12 KiB
Task BE-DB-01: Database Schema Strategy Implementation
Phase: Database Standardization & Optimization ADR Compliance: ADR-004 (Database Schema Design), ADR-009 (Migration Strategy), ADR-019 (UUID Strategy) Priority: 🟡 Important - Data Integrity & Performance
Context: การ implement Selective Normalization Patterns ตาม ADR-004 เพื่อให้ database schema สอดคล้องกันและมีประสิทธิภาพ
📋 Implementation Tasks
DB-1.1: Base Entity Pattern Implementation
- Create Base Entity Classes:
- File:
backend/src/common/entities/base.entity.ts - Features: UUID, timestamps, soft delete, audit fields
- Inheritance: All entities extend from base
- File:
- Create UuidBaseEntity (ADR-019):
- File:
backend/src/common/entities/uuid-base.entity.ts - Properties:
id(INT PK),uuid(UUID public),createdAt,updatedAt - Decorators:
@Exclude()for internal ID,@Expose()for UUID
- File:
- Update All Entity Classes:
- Directory:
backend/src/modules/*/entities/ - Extend from base entities
- Add proper decorators and relationships
- Ensure UUID handling compliance
- Directory:
DB-1.2: Audit Trail Implementation
- Create Audit Log Entity:
- File:
backend/src/common/entities/audit-log.entity.ts - Table:
audit_logs - Fields:
tableName,recordId,action,oldValues,newValues,userId - Indexes: Performance optimization for queries
- File:
- Create Audit Trail Service:
- File:
backend/src/common/services/audit-trail.service.ts - Methods:
logCreate(),logUpdate(),logDelete(),logSoftDelete() - Features: Automatic change detection, JSON diff, user tracking
- File:
- Implement Audit Interceptor:
- File:
backend/src/common/interceptors/audit.interceptor.ts - Trigger: Before/After database operations
- Scope: All entity operations (CRUD)
- Performance: Async logging to avoid blocking
- File:
DB-1.3: Workflow State Management
- Create Workflow State Entities:
- File:
backend/src/modules/workflow/entities/ - Entities:
WorkflowState,WorkflowInstance,WorkflowHistory - Relationships: State machine definitions and instances
- File:
- Implement State Transition Logic:
- File:
backend/src/modules/workflow/services/workflow-state.service.ts - Methods:
validateTransition(),executeTransition(),getCurrentState() - Features: Business rule validation, history tracking
- File:
- Create Workflow History Tables:
- Tables:
workflow_histories,correspondence_histories,rfa_histories - Purpose: Track all state changes with context
- Features: JSON snapshots, user attribution, timestamps
- Tables:
DB-1.4: Schema Optimization & Indexing
- Analyze Current Query Patterns:
- Tools: MySQL slow query log, EXPLAIN plans
- Focus: Correspondence list, RFA workflows, user permissions
- Document: Current performance bottlenecks
- Implement Strategic Indexes:
- Foreign key indexes: All FK columns indexed
- Composite indexes: Common query patterns
- Partial indexes: Active records only (WHERE deleted_at IS NULL)
- Optimize Table Structures:
- Review: Data types, column sizes, NULL constraints
- Normalize: Where beneficial for integrity
- Denormalize: Where beneficial for performance
DB-1.5: Migration & Evolution Strategy
- Create Migration Scripts:
- Directory:
backend/src/database/migrations/ - Format: SQL scripts (following ADR-009)
- Features: Rollback capability, dependency tracking
- Directory:
- Implement Schema Validation:
- File:
backend/src/common/validators/schema.validator.ts - Checks: Naming conventions, required fields, relationships
- Automation: Pre-commit hooks, CI validation
- File:
- Create Database Documentation:
- Auto-generation: From TypeORM entities
- Format: Markdown with ER diagrams
- Include: Field descriptions, relationships, indexes
🔧 Technical Implementation Details
Base Entity Implementation
// File: backend/src/common/entities/base.entity.ts
import { CreateDateColumn, UpdateDateColumn, DeleteDateColumn, Column } from 'typeorm';
import { UuidBaseEntity } from './uuid-base.entity';
export abstract class BaseEntity extends UuidBaseEntity {
@CreateDateColumn({
type: 'timestamp',
name: 'created_at',
comment: 'Record creation time'
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
name: 'updated_at',
comment: 'Last update time'
})
updatedAt: Date;
@DeleteDateColumn({
type: 'datetime',
name: 'deleted_at',
nullable: true,
comment: 'Soft delete timestamp'
})
deletedAt?: Date;
@Column({
type: 'int',
name: 'created_by',
nullable: true,
comment: 'User who created record'
})
createdBy?: number;
@Column({
type: 'int',
name: 'updated_by',
nullable: true,
comment: 'User who last updated record'
})
updatedBy?: number;
}
UuidBaseEntity (ADR-019)
// File: backend/src/common/entities/uuid-base.entity.ts
import { PrimaryGeneratedColumn, Column, Exclude, Expose } from 'typeorm';
export abstract class UuidBaseEntity {
@PrimaryGeneratedColumn()
@Exclude()
id: number;
@Column({
type: 'uuid',
unique: true,
name: 'uuid',
comment: 'Public UUID identifier (ADR-019)'
})
@Expose({ name: 'id' }) // Expose UUID as 'id' in API
uuid: string;
}
Audit Trail Service
// File: backend/src/common/services/audit-trail.service.ts
@Injectable()
export class AuditTrailService {
constructor(
@InjectRepository(AuditLog)
private auditLogRepo: Repository<AuditLog>
) {}
async logCreate(entity: BaseEntity, userId: number): Promise<void> {
const auditLog = this.auditLogRepo.create({
tableName: entity.constructor.name,
recordId: entity.id,
recordUuid: entity.uuid,
action: 'CREATE',
newValues: entity,
userId
});
await this.auditLogRepo.save(auditLog);
}
async logUpdate(
entity: BaseEntity,
oldValues: any,
newValues: any,
userId: number
): Promise<void> {
const changedFields = this.getChangedFields(oldValues, newValues);
const auditLog = this.auditLogRepo.create({
tableName: entity.constructor.name,
recordId: entity.id,
recordUuid: entity.uuid,
action: 'UPDATE',
oldValues,
newValues,
changedFields,
userId
});
await this.auditLogRepo.save(auditLog);
}
private getChangedFields(old: any, new: any): string[] {
const changes: string[] = [];
for (const key in new) {
if (old[key] !== new[key]) {
changes.push(key);
}
}
return changes;
}
}
Audit Interceptor
// File: backend/src/common/interceptors/audit.interceptor.ts
@Injectable()
export class AuditInterceptor implements NestInterceptor {
constructor(private auditService: AuditTrailService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const userId = request.user?.id;
return next.handle().pipe(
tap(async (result) => {
// Log successful operations
if (result && typeof result === 'object') {
await this.auditService.logCreate(result, userId);
}
}),
catchError(async (error) => {
// Log failed operations
console.error('Operation failed:', error);
throw error;
})
);
}
}
Workflow State Management
// File: backend/src/modules/workflow/entities/workflow-instance.entity.ts
@Entity('workflow_instances')
export class WorkflowInstance extends BaseEntity {
@Column({ type: 'varchar', length: 50 })
workflowCode: string;
@Column({ type: 'varchar', length: 50 })
entityType: string; // 'correspondence', 'rfa', etc.
@Column({ type: 'int' })
entityId: number;
@Column({ type: 'varchar', length: 50 })
currentState: string;
@Column({
type: 'enum',
enum: ['ACTIVE', 'COMPLETED', 'CANCELLED'],
default: 'ACTIVE'
})
status: string;
@Column({ type: 'json', nullable: true })
context: any; // Workflow-specific context
// Relationships
@ManyToOne(() => WorkflowState)
@JoinColumn({
name: 'workflow_code',
referencedColumnName: 'workflow_code',
referencedColumnName: 'state_name'
})
currentStateDefinition: WorkflowState;
@OneToMany(() => WorkflowHistory, history => history.instance)
histories: WorkflowHistory[];
}
Schema Migration Example
-- File: backend/src/database/migrations/001_add_audit_trail.sql
-- Create audit log table
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,
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),
INDEX idx_audit_record_uuid (record_uuid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='Audit trail for all data changes';
-- Add base columns to existing tables (example)
ALTER TABLE correspondences
ADD COLUMN created_by INT NULL COMMENT 'User who created record',
ADD COLUMN updated_by INT NULL COMMENT 'User who last updated record',
ADD INDEX idx_correspondences_created_by (created_by),
ADD INDEX idx_correspondences_updated_by (updated_by);
📊 Success Criteria
Functional Requirements
- All entities extend from base classes
- Audit trail captures all data changes
- Workflow state management implemented
- Database performance optimized
- Migration strategy in place
Non-Functional Requirements
- Query response times < 200ms (p90)
- Audit logging doesn't impact performance
- Schema validation automated
- Documentation auto-generated
- Zero data loss during migrations
Compliance Requirements
- ADR-004 patterns followed consistently
- ADR-009 migration strategy implemented
- ADR-019 UUID strategy enforced
- Audit trail meets compliance requirements
🚀 Deployment & Rollout
Phase 1: Base Pattern Implementation (Week 1)
- Create base entity classes
- Update core entities (users, organizations, projects)
- Implement audit trail foundation
Phase 2: Workflow & Audit Implementation (Week 2-3)
- Implement workflow state management
- Add audit logging to all modules
- Create migration scripts
Phase 3: Optimization & Documentation (Week 4)
- Performance optimization and indexing
- Schema validation automation
- Documentation generation
📋 Dependencies & Prerequisites
Must Have
- ✅ ADR-004 Approved
- ✅ ADR-009 Migration Strategy
- ✅ ADR-019 UUID Strategy
- ✅ TypeORM configuration
- ✅ Database backup strategy
Nice to Have
- Database performance monitoring
- Automated schema testing
- Visual ER diagram generation
🔄 Review & Acceptance
Code Review Checklist
- All entities follow base pattern
- Audit logging comprehensive
- Workflow state correct
- Indexes optimized
- Migration scripts safe
- Documentation complete
Acceptance Criteria
- All entities extend base classes
- Audit trail captures 100% of changes
- Performance benchmarks met
- Migration scripts tested
- Schema validation passes
- Documentation generated
Owner: Backend Team Lead + DBA
Estimated Effort: 4 weeks (1-2 developers)
Risk Level: High (Database schema changes)
Rollback Plan: Full database backups, migration rollback scripts