Files
lcbp3/specs/03-implementation/03-04-document-numbering.md
admin 83704377f4
Some checks are pending
Spec Validation / validate-markdown (push) Waiting to run
Spec Validation / validate-diagrams (push) Waiting to run
Spec Validation / check-todos (push) Waiting to run
251218:1701 On going update to 1.7.0: Documnet Number rebuild
2025-12-18 17:01:42 +07:00

25 KiB

Document Numbering Implementation Guide (Combined)


title: 'Implementation Guide: Document Numbering System' version: 1.6.2 status: APPROVED owner: Development Team last_updated: 2025-12-17 related:

  • specs/01-requirements/03.11-document-numbering.md
  • specs/04-operations/document-numbering-operations.md
  • specs/05-decisions/ADR-002-document-numbering-strategy.md

Overview

เอกสารนี้รวบรวม implementation details สำหรับระบบ Document Numbering โดยผนวกข้อมูลจาก:

  • document-numbering.md - Core implementation และ database schema
  • document-numbering-add.md - Extended features (Reservation, Manual Override, Monitoring)

Technology Stack

Component Technology
Backend Framework NestJS 10.x
ORM TypeORM 0.3.x
Database MariaDB 11.8
Cache/Lock Redis 7.x + Redlock
Message Queue BullMQ
Monitoring Prometheus + Grafana

1. Module Structure

backend/src/modules/document-numbering/
├── document-numbering.module.ts
├── controllers/
│   ├── document-numbering.controller.ts      # General endpoints
│   ├── document-numbering-admin.controller.ts # Admin endpoints
│   └── numbering-metrics.controller.ts       # Metrics endpoints
├── services/
│   ├── document-numbering.service.ts         # Main orchestration
│   ├── document-numbering-lock.service.ts    # Redis Lock
│   ├── counter.service.ts                    # Sequence counter logic
│   ├── reservation.service.ts                # Two-phase commit
│   ├── manual-override.service.ts            # Manual number handling
│   ├── format.service.ts                     # Template formatting
│   ├── template.service.ts                   # Template CRUD
│   ├── audit.service.ts                      # Audit logging
│   ├── metrics.service.ts                    # Prometheus metrics
│   └── migration.service.ts                  # Legacy import
├── entities/
│   ├── document-number-counter.entity.ts
│   ├── document-number-format.entity.ts
│   ├── document-number-audit.entity.ts
│   ├── document-number-error.entity.ts
│   └── document-number-reservation.entity.ts
├── dto/
│   ├── generate-number.dto.ts
│   ├── preview-number.dto.ts
│   ├── reserve-number.dto.ts
│   ├── confirm-reservation.dto.ts
│   ├── manual-override.dto.ts
│   ├── void-document.dto.ts
│   └── bulk-import.dto.ts
├── validators/
│   └── template.validator.ts
├── guards/
│   └── manual-override.guard.ts
├── decorators/
│   └── audit-numbering.decorator.ts
├── jobs/
│   └── counter-reset.job.ts
└── tests/
    ├── unit/
    ├── integration/
    └── e2e/

2. Database Schema

2.1 Format Template Table

CREATE TABLE document_number_formats (
  id INT AUTO_INCREMENT PRIMARY KEY,
  project_id INT NOT NULL,
  correspondence_type_id INT NULL, -- NULL = default format for project
  format_template VARCHAR(100) NOT NULL,
  reset_sequence_yearly TINYINT(1) DEFAULT 1,
  description VARCHAR(255),
  created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
  updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),

  UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='Document Number Format Templates';

2.2 Counter Table

CREATE TABLE document_number_counters (
  project_id INT NOT NULL,
  correspondence_type_id INT NULL,
  originator_organization_id INT NOT NULL,
  recipient_organization_id INT NOT NULL DEFAULT 0, -- 0 = no recipient (RFA)
  sub_type_id INT DEFAULT 0,
  rfa_type_id INT DEFAULT 0,
  discipline_id INT DEFAULT 0,
  reset_scope VARCHAR(20) NOT NULL,
  last_number INT DEFAULT 0 NOT NULL,,
  version INT DEFAULT 0 NOT NULL,
  created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
  updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),

  PRIMARY KEY (
    project_id,
    originator_organization_id,
    COALESCE(recipient_organization_id, 0),
    correspondence_type_id,
    sub_type_id,
    rfa_type_id,
    discipline_id,
    reset_scope
  ),

  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
  FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,

  INDEX idx_counter_lookup (project_id, correspondence_type_id, reset_scope),
  INDEX idx_counter_org (originator_organization_id, reset_scope),
  INDEX idx_counter_updated (updated_at),

  CONSTRAINT chk_last_number_positive CHECK (last_number >= 0),
  CONSTRAINT chk_reset_scope_format CHECK (
    reset_scope IN ('NONE') OR
    reset_scope LIKE 'YEAR_%' OR
    reset_scope LIKE 'MONTH_%' OR
    reset_scope LIKE 'CONTRACT_%'
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  COMMENT='Running Number Counters';

2.3 Audit Table

CREATE TABLE document_number_audit (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  document_id INT NULL COMMENT 'FK to documents (NULL initially)',
  document_type VARCHAR(50),
  document_number VARCHAR(100) NOT NULL,
  operation ENUM('RESERVE', 'CONFIRM', 'CANCEL', 'MANUAL_OVERRIDE', 'VOID', 'GENERATE') NOT NULL,
  status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID', 'MANUAL'),
  counter_key JSON NOT NULL COMMENT 'Counter key used (JSON format)',
  reservation_token VARCHAR(36) NULL,
  originator_organization_id INT NULL,
  recipient_organization_id INT NULL,

  template_used VARCHAR(200) NOT NULL,
  old_value TEXT NULL,
  new_value TEXT NULL,
  user_id INT NULL COMMENT 'FK to users (Allow NULL for system generation)',
  ip_address VARCHAR(45),

  user_agent TEXT,
  is_success BOOLEAN DEFAULT TRUE,

  retry_count INT DEFAULT 0,
  lock_wait_ms INT COMMENT 'Lock acquisition time in milliseconds',
  total_duration_ms INT COMMENT 'Total generation time',
  fallback_used ENUM('NONE', 'DB_LOCK', 'RETRY') DEFAULT 'NONE',
  metadata JSON NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_document_id (document_id),
  INDEX idx_user_id (user_id),
  INDEX idx_status (status),
  INDEX idx_operation (operation),
  INDEX idx_document_number (document_number),
  INDEX idx_created_at (created_at),
  FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB COMMENT='Document Number Generation Audit Trail';

2.4 Error Log Table

CREATE TABLE document_number_errors (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  error_type ENUM(
    'LOCK_TIMEOUT',
    'VERSION_CONFLICT',
    'DB_ERROR',
    'REDIS_ERROR',
    'VALIDATION_ERROR',
    'SEQUENCE_EXHAUSTED',
    'RESERVATION_EXPIRED',
    'DUPLICATE_NUMBER'
  ) NOT NULL,
  error_message TEXT,
  stack_trace TEXT,
  context_data JSON COMMENT 'Request context (user, project, etc.)',
  user_id INT,
  ip_address VARCHAR(45),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  resolved_at TIMESTAMP NULL,

  INDEX idx_error_type (error_type),
  INDEX idx_created_at (created_at),
  INDEX idx_user_id (user_id)
) ENGINE=InnoDB COMMENT='Document Numbering Error Log';

2.5 Reservation Table

CREATE TABLE document_number_reservations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,

  -- Reservation Details
  token VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4',
  document_number VARCHAR(100) NOT NULL UNIQUE,
  status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID') NOT NULL DEFAULT 'RESERVED',

  -- Linkage
  document_id INT NULL COMMENT 'FK to documents (NULL until confirmed)',

  -- Context (for debugging)
  project_id INT NOT NULL,
  correspondence_type_id INT NOT NULL,
  originator_organization_id INT NOT NULL,
  recipient_organization_id INT DEFAULT 0,
  user_id INT NOT NULL,

  -- Timestamps
  reserved_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
  expires_at DATETIME(6) NOT NULL,
  confirmed_at DATETIME(6) NULL,
  cancelled_at DATETIME(6) NULL,

  -- Audit
  ip_address VARCHAR(45),
  user_agent TEXT,
  metadata JSON NULL COMMENT 'Additional context',

  -- Indexes
  INDEX idx_token (token),
  INDEX idx_status (status),
  INDEX idx_status_expires (status, expires_at),
  INDEX idx_document_id (document_id),
  INDEX idx_user_id (user_id),
  INDEX idx_reserved_at (reserved_at),

  -- Foreign Keys
  FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE SET NULL,
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  COMMENT='Document Number Reservations - Two-Phase Commit';

3. Core Services

3.1 Number Generation Process

sequenceDiagram
    participant C as Client
    participant S as NumberingService
    participant L as LockService
    participant CS as CounterService
    participant DB as Database
    participant R as Redis

    C->>S: generateDocumentNumber(dto)
    S->>L: acquireLock(counterKey)
    L->>R: REDLOCK acquire
    R-->>L: lock acquired
    L-->>S: lock handle
    S->>CS: incrementCounter(counterKey)
    CS->>DB: BEGIN TRANSACTION
    CS->>DB: SELECT FOR UPDATE
    CS->>DB: UPDATE last_number
    CS->>DB: COMMIT
    DB-->>CS: newNumber
    CS-->>S: sequence
    S->>S: formatNumber(template, seq)
    S->>L: releaseLock()
    L->>R: REDLOCK release
    S-->>C: documentNumber

3.2 Two-Phase Commit (Reserve/Confirm)

sequenceDiagram
    participant C as Client
    participant RS as ReservationService
    participant SS as SequenceService
    participant R as Redis

    Note over C,R: Phase 1: Reserve
    C->>RS: reserve(documentType)
    RS->>SS: getNextSequence()
    SS-->>RS: documentNumber
    RS->>R: SETEX reservation:{token} (TTL: 5min)
    RS-->>C: {token, documentNumber, expiresAt}

    Note over C,R: Phase 2: Confirm
    C->>RS: confirm(token)
    RS->>R: GET reservation:{token}
    R-->>RS: reservationData
    RS->>R: DEL reservation:{token}
    RS-->>C: documentNumber (confirmed)

3.3 Counter Service Implementation

// services/counter.service.ts
@Injectable()
export class CounterService {
  private readonly logger = new Logger(CounterService.name);

  constructor(
    @InjectRepository(DocumentNumberCounter)
    private counterRepo: Repository<DocumentNumberCounter>,
    private dataSource: DataSource,
  ) {}

  async incrementCounter(counterKey: CounterKey): Promise<number> {
    const MAX_RETRIES = 2;

    for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
      try {
        return await this.dataSource.transaction(async (manager) => {
          // ใช้ Optimistic Locking
          const counter = await manager.findOne(DocumentNumberCounter, {
            where: this.buildWhereClause(counterKey),
          });

          if (!counter) {
            const newCounter = manager.create(DocumentNumberCounter, {
              ...counterKey,
              lastNumber: 1,
              version: 0,
            });
            await manager.save(newCounter);
            return 1;
          }

          counter.lastNumber += 1;
          await manager.save(counter); // Auto-check version
          return counter.lastNumber;
        });
      } catch (error) {
        if (error instanceof OptimisticLockVersionMismatchError) {
          this.logger.warn(`Version conflict, retry ${attempt + 1}/${MAX_RETRIES}`);
          if (attempt === MAX_RETRIES - 1) {
            throw new ConflictException('เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่');
          }
          continue;
        }
        throw error;
      }
    }
  }
}

3.4 Redis Lock Service

// services/document-numbering-lock.service.ts
@Injectable()
export class DocumentNumberingLockService {
  private readonly logger = new Logger(DocumentNumberingLockService.name);
  private redlock: Redlock;

  constructor(@InjectRedis() private readonly redis: Redis) {
    this.redlock = new Redlock([redis], {
      driftFactor: 0.01,
      retryCount: 5,
      retryDelay: 100,
      retryJitter: 50,
    });
  }

  async acquireLock(counterKey: CounterKey): Promise<Redlock.Lock> {
    const lockKey = this.buildLockKey(counterKey);
    const ttl = 5000; // 5 seconds

    try {
      const lock = await this.redlock.acquire([lockKey], ttl);
      this.logger.debug(`Acquired lock: ${lockKey}`);
      return lock;
    } catch (error) {
      this.logger.error(`Failed to acquire lock: ${lockKey}`, error);
      throw error;
    }
  }

  async releaseLock(lock: Redlock.Lock): Promise<void> {
    try {
      await lock.release();
    } catch (error) {
      this.logger.warn('Failed to release lock (may have expired)', error);
    }
  }

  private buildLockKey(key: CounterKey): string {
    return `lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
           `${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
           `${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`;
  }
}

3.5 Reservation Service

// services/reservation.service.ts
@Injectable()
export class ReservationService {
  private readonly TTL = 300; // 5 minutes

  constructor(
    private redis: Redis,
    private sequenceService: SequenceService,
    private auditService: AuditService,
  ) {}

  async reserve(
    documentType: string,
    scopeValue?: string,
    metadata?: Record<string, any>,
  ): Promise<Reservation> {
    // 1. Generate next number
    const documentNumber = await this.sequenceService.getNextSequence(
      documentType,
      scopeValue,
    );

    // 2. Generate reservation token
    const token = uuidv4();
    const expiresAt = new Date(Date.now() + this.TTL * 1000);

    // 3. Save to Redis
    const reservation: Reservation = {
      token,
      document_number: documentNumber,
      document_type: documentType,
      scope_value: scopeValue,
      expires_at: expiresAt,
      metadata,
    };

    await this.redis.setex(
      `reservation:${token}`,
      this.TTL,
      JSON.stringify(reservation),
    );

    // 4. Audit log
    await this.auditService.log({
      operation: 'RESERVE',
      document_type: documentType,
      document_number: documentNumber,
      metadata: { token, scope_value: scopeValue },
    });

    return reservation;
  }

  async confirm(token: string, userId: number): Promise<string> {
    const reservation = await this.getReservation(token);

    if (!reservation) {
      throw new ReservationExpiredError(
        'Reservation not found or expired. Please reserve a new number.',
      );
    }

    await this.redis.del(`reservation:${token}`);

    await this.auditService.log({
      operation: 'CONFIRM',
      document_type: reservation.document_type,
      document_number: reservation.document_number,
      user_id: userId,
      metadata: { token },
    });

    return reservation.document_number;
  }

  async cancel(token: string, userId: number): Promise<void> {
    const reservation = await this.getReservation(token);

    if (reservation) {
      await this.redis.del(`reservation:${token}`);

      await this.auditService.log({
        operation: 'CANCEL',
        document_type: reservation.document_type,
        document_number: reservation.document_number,
        user_id: userId,
        metadata: { token },
      });
    }
  }

  @Cron('0 */5 * * * *') // Every 5 minutes
  async cleanupExpired(): Promise<void> {
    const keys = await this.redis.keys('reservation:*');
    for (const key of keys) {
      const ttl = await this.redis.ttl(key);
      if (ttl <= 0) {
        await this.redis.del(key);
      }
    }
  }
}

4. Template System

4.1 Supported Tokens

Token Description Example Output
{PROJECT} Project Code LCBP3
{ORIGINATOR} Originator Organization Code คคง.
{RECIPIENT} Recipient Organization Code สคฉ.3
{CORR_TYPE} Correspondence Type Code L
{SUB_TYPE} Sub Type Code TD
{RFA_TYPE} RFA Type Code RFA
{DISCIPLINE} Discipline Code CV
{SEQ:n} Sequence Number (n digits) 0001
{YEAR:CE} Year (Common Era) 2025
{YEAR:BE} Year (Buddhist Era) 2568
{REV} Revision Number A

4.2 Template Validation

// validators/template.validator.ts
@Injectable()
export class TemplateValidator {
  private readonly ALLOWED_TOKENS = [
    'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
    'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV',
  ];

  validate(template: string, correspondenceType: string): ValidationResult {
    const tokens = this.extractTokens(template);
    const errors: string[] = [];

    // ตรวจสอบ Token ที่ไม่รู้จัก
    for (const token of tokens) {
      if (!this.ALLOWED_TOKENS.includes(token.name)) {
        errors.push(`Unknown token: {${token.name}}`);
      }
    }

    // กฎพิเศษสำหรับแต่ละประเภท
    if (correspondenceType === 'RFA') {
      if (!tokens.some((t) => t.name === 'PROJECT')) {
        errors.push('RFA template ต้องมี {PROJECT}');
      }
      if (!tokens.some((t) => t.name === 'DISCIPLINE')) {
        errors.push('RFA template ต้องมี {DISCIPLINE}');
      }
    }

    if (correspondenceType === 'TRANSMITTAL') {
      if (!tokens.some((t) => t.name === 'SUB_TYPE')) {
        errors.push('TRANSMITTAL template ต้องมี {SUB_TYPE}');
      }
    }

    // ทุก template ต้องมี {SEQ}
    if (!tokens.some((t) => t.name.startsWith('SEQ'))) {
      errors.push('Template ต้องมี {SEQ:n}');
    }

    return { valid: errors.length === 0, errors };
  }
}

5. API Endpoints

5.1 General Endpoints (/document-numbering)

Endpoint Method Permission Description
/logs/audit GET system.view_logs Get audit logs
/logs/errors GET system.view_logs Get error logs
/sequences GET correspondence.read Get counter sequences
/counters/:id PATCH system.manage_settings Update counter value
/preview POST correspondence.read Preview number without generating
/reserve POST correspondence.create Reserve a document number
/confirm POST correspondence.create Confirm a reservation
/cancel POST correspondence.create Cancel a reservation

5.2 Admin Endpoints (/admin/document-numbering)

Endpoint Method Permission Description
/templates GET system.manage_settings Get all templates
/templates POST system.manage_settings Create/update template
/templates/:id DELETE system.manage_settings Delete template
/metrics GET system.view_logs Get metrics
/manual-override POST system.manage_settings Override counter value
/void-and-replace POST system.manage_settings Void and replace number
/cancel POST system.manage_settings Cancel a number
/bulk-import POST system.manage_settings Bulk import counters

6. Monitoring & Observability

6.1 Prometheus Metrics

@Injectable()
export class NumberingMetrics {
  // Counter: Total numbers generated
  private readonly numbersGenerated = new Counter({
    name: 'numbering_sequences_total',
    help: 'Total document numbers generated',
    labelNames: ['document_type'],
  });

  // Gauge: Sequence utilization (%)
  private readonly sequenceUtilization = new Gauge({
    name: 'numbering_sequence_utilization',
    help: 'Sequence utilization percentage',
    labelNames: ['document_type'],
  });

  // Histogram: Lock wait time
  private readonly lockWaitTime = new Histogram({
    name: 'numbering_lock_wait_seconds',
    help: 'Time spent waiting for lock acquisition',
    labelNames: ['document_type'],
    buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
  });

  // Counter: Lock failures
  private readonly lockFailures = new Counter({
    name: 'numbering_lock_failures_total',
    help: 'Total lock acquisition failures',
    labelNames: ['document_type', 'reason'],
  });
}

6.2 Alert Rules

Alert Condition Severity Action
SequenceCritical Utilization > 95% Critical Extend max_value
SequenceWarning Utilization > 90% Warning Plan extension
HighLockWaitTime p95 > 1s Warning Check Redis health
RedisUnavailable Redis cluster down Critical Switch to DB-only mode
HighErrorRate > 10 errors/sec Warning Check logs

7. Error Handling

7.1 Error Codes

Code Name Description
NB001 CONFIG_NOT_FOUND Config not found for type
NB002 SEQUENCE_EXHAUSTED Sequence reached max value
NB003 LOCK_TIMEOUT Failed to acquire lock
NB004 RESERVATION_EXPIRED Reservation token expired
NB005 DUPLICATE_NUMBER Number already exists
NB006 INVALID_FORMAT Number format invalid
NB007 MANUAL_OVERRIDE_NOT_ALLOWED Manual override disabled
NB008 REDIS_UNAVAILABLE Redis connection failed

7.2 Fallback Strategy

flowchart TD
    A[Generate Number Request] --> B{Redis Available?}
    B -->|Yes| C[Acquire Redlock]
    B -->|No| D[Use DB-only Lock]
    C --> E{Lock Acquired?}
    E -->|Yes| F[Increment Counter]
    E -->|No| G{Retry < 3?}
    G -->|Yes| C
    G -->|No| H[Fallback to DB Lock]
    D --> F
    H --> F
    F --> I[Format Number]
    I --> J[Return Number]

8. Testing

8.1 Unit Tests

# Run unit tests
pnpm test:watch -- --testPathPattern=document-numbering

8.2 Integration Tests

# Run integration tests
pnpm test:e2e -- --testPathPattern=numbering

8.3 Concurrency Test

// tests/load/concurrency.spec.ts
it('should handle 1000 concurrent requests without duplicates', async () => {
  const promises = Array.from({ length: 1000 }, () =>
    request(app.getHttpServer())
      .post('/document-numbering/reserve')
      .send({ document_type: 'COR' })
  );

  const results = await Promise.all(promises);
  const numbers = results.map(r => r.body.data.document_number);
  const uniqueNumbers = new Set(numbers);

  expect(uniqueNumbers.size).toBe(1000);
});

9. Best Practices

9.1 DO's

  • Always use two-phase commit (reserve + confirm)
  • Implement fallback to DB-only if Redis fails
  • Log every operation to audit trail
  • Monitor sequence utilization (alert at 90%)
  • Test under concurrent load (1000+ req/s)
  • Use pessimistic locking in database
  • Set reasonable TTL for reservations (5 min)
  • Validate manual override format
  • Skip cancelled numbers (never reuse)

9.2 DON'Ts

  • Never skip validation for manual override
  • Never reuse cancelled numbers
  • Never trust client-generated numbers
  • Never increase sequence without transaction
  • Never deploy without load testing
  • Never modify sequence table directly
  • Never skip audit logging

10. Environment Variables

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_CLUSTER_NODES=redis-1:6379,redis-2:6379,redis-3:6379

# Database
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=lcbp3
DB_PASSWORD=
DB_DATABASE=lcbp3_db
DB_POOL_SIZE=20

# Numbering Configuration
NUMBERING_LOCK_TIMEOUT=5000       # 5 seconds
NUMBERING_RESERVATION_TTL=300     # 5 minutes
NUMBERING_RETRY_ATTEMPTS=3
NUMBERING_RETRY_DELAY=200         # milliseconds

# Monitoring
PROMETHEUS_PORT=9090
GRAFANA_PORT=3000

References


Document Version: 2.0.0 Created By: Development Team Last Updated: 2025-12-17