ADR-011: Oracle Dual-Profile Support
Context
Cần chứng minh khả năng làm việc với hệ quản trị CSDL doanh nghiệp (Oracle phổ biến trong ngân hàng Việt Nam) mà không ảnh hưởng đến 45 test đang pass trên PostgreSQL.
Options Considered
Phương án A — Migrate hoàn toàn sang Oracle, thay thế Postgres
- Loại bỏ hoàn toàn nền tảng Postgres đã ổn định, đánh đổi lấy 1 CSDL doanh nghiệp duy nhất.
Phương án B (đã chọn) — Chạy song song 2 database qua Spring Profile riêng, cùng 1 codebase
- Chi phí lõi (viết lại SQL cho từng dialect) giống hệt Phương án A, nhưng không đụng đến 45 test đã ổn định trên Postgres.
- Câu chuyện portfolio mạnh hơn: "thiết kế portable, chứng minh bằng test thật trên cả 2 DB" thay vì chỉ chuyển đổi một chiều.
Decision
Oracle Database Free (gvenzl/oracle-free:23-slim-faststart) qua Docker, host port 1522. Tách thư mục migration theo vendor (db/migration/postgresql/, db/migration/oracle/), Flyway locations cấu hình riêng theo profile (application.properties trỏ db/migration/postgresql, application-oracle.properties trỏ db/migration/oracle). Test chạy qua container tĩnh từ docker-compose (nhất quán với cách project đã test Postgres), không dùng Testcontainers cho DB chính — Testcontainers chỉ dùng cho Kafka/Redis (môi trường cần cô lập/port động).
Vấn đề kỹ thuật thực tế gặp phải khi viết lại 7 migration
- UUID →
RAW(16)— PostgreSQLUUIDkhông có kiểu tương đương trực tiếp trên Oracle, dùngRAW(16)cho mọi cột id/khóa ngoại. - Rewrite logic phụ thuộc dialect:
UPDATE ... FROM(cú pháp riêng của Postgres) → correlated subquery ở V3 (UPDATE ... SET currency = (SELECT ... WHERE EXISTS ...)), và →MERGE INTOở V4 (backfillreversal_of_transaction_id). JSONB→JSON— dùng kiểuJSONnative của Oracle 23ai thay choJSONBcủa Postgres.- Partial index → function-based index tương đương — Oracle không hỗ trợ
CREATE INDEX ... WHERE ...(partial index); thay bằng index trên biểu thứcCASE WHEN published_at IS NULL THEN created_at END, đạt cùng mục đích (chỉ index các dòng chưa publish). - Phát hiện phụ:
columnDefinition = "jsonb"khai báo trênOutboxEvent.javakhông cần sửa khi build cho Oracle, vìspring.jpa.hibernate.ddl-auto=validatekhông dùngcolumnDefinitionđể validate thực tế —@JdbcTypeCode(SqlTypes.JSON)tự thích ứng theo dialect đang chạy.
Consequences
Tích cực:
- 45/45 test pass trên cả 2 database, chứng minh domain model portable thật sự (không chỉ về lý thuyết).
Phát hiện quan trọng:
- Quá trình chạy test trên Oracle phát hiện ra 1 bug thật trong domain model (
Moneykhông tự chuẩn hóa scale) — xem ADR-012 riêng.
Đánh đổi:
- Duy trì 2 bộ migration song song tăng chi phí bảo trì dài hạn — mọi thay đổi schema từ nay phải viết 2 lần (1 cho mỗi dialect).
Liên quan
- ADR-012 — bug phát hiện được trong quá trình chạy test trên Oracle ở ADR này.