Published on

Day 09 - Database migration và data model

Authors

1. Mục tiêu bài học

Sau khoảng 2 tiếng, học viên có thể:

  • Dùng Claude Code để thiết kế data model cho taskflow-ai theo workflow có kiểm soát, không để AI tự suy diễn schema hoặc migration tool.
  • Tạo migration cho bảng userstasks với constraint, foreign key, index và timestamp hợp lý.
  • Phân biệt thay đổi schema an toàn và thay đổi destructive: DROP, TRUNCATE, DELETE không điều kiện, đổi type có rewrite lớn, xóa column, rename phá backward compatibility.
  • Review migration như reviewer production: lock, transaction, rollback strategy, forward-only plan, seed idempotent, dev/test DB và backup.
  • Viết seed data có thể chạy lại nhiều lần mà không duplicate dữ liệu.
  • Yêu cầu Claude Code lập plan, implement, review và test migration mà không được chạy destructive migration trên dữ liệu thật.

2. Bối cảnh thực tế

Day 08 tạo API CRUD ở tầng backend. Day 09 đi xuống tầng database, nơi lỗi nhỏ thường có hậu quả lớn hơn nhiều so với lỗi route handler. Một endpoint sai có thể rollback bằng patch. Một migration sai có thể lock bảng, mất dữ liệu, phá deploy, hoặc làm production outage.

Vấn đề thường gặp khi dùng AI cho database:

  • Claude Code tạo migration theo "schema lý tưởng" nhưng không đọc domain và query pattern hiện có.
  • AI thêm DROP TABLE, DROP COLUMN, TRUNCATE, DELETE FROM hoặc migration rollback phá dữ liệu vì nghĩ đang ở môi trường dev.
  • Migration chạy trên database thật do .env trỏ nhầm.
  • Seed chạy nhiều lần tạo duplicate user/task.
  • Index được thêm theo cảm tính, không gắn với query thực tế.
  • Constraint quá lỏng khiến data bẩn lọt vào database, hoặc quá chặt khiến migration fail khi có dữ liệu cũ.
  • Tool migration wrap mọi thứ trong transaction, nhưng một số lệnh như CREATE INDEX CONCURRENTLY của PostgreSQL không chạy được trong transaction block.

Claude Code hữu ích khi bạn dùng nó để đọc codebase, đề xuất schema, sinh migration và test. Nhưng với database, developer phải giữ quyền quyết định cuối cùng. Rule quan trọng nhất của bài này:

AI có thể soạn migration. Human quyết định DB target, review SQL, backup, chạy migration và giám sát kết quả.

Không nên dùng Claude Code để tự động chạy migration khi:

  • Database target chưa xác nhận là dev/test.
  • Migration có DROP, TRUNCATE, DELETE, ALTER COLUMN TYPE, SET NOT NULL trên bảng lớn, backfill lớn, hoặc index build trên bảng production.
  • Chưa có backup hoặc rollback/forward-fix plan.
  • Working tree có thay đổi của worker khác.
  • Bạn chưa biết project dùng migration tool nào: Prisma, Drizzle, Knex, TypeORM, node-pg-migrate, SQL thuần, Flyway, Liquibase hoặc custom script.

3. Kiến thức nền

Data model tốt bắt đầu từ behavior của sản phẩm, không bắt đầu từ ORM. Với taskflow-ai, domain tối thiểu là team kỹ thuật quản lý task. Day 09 chỉ tạo nền tảng cho user và task, chưa đi vào auth đầy đủ, role phức tạp hoặc multi-tenant nâng cao.

Một model tối thiểu:

EntityVai tròField cốt lõiRàng buộc quan trọng
usersNgười sở hữu hoặc được giao taskid, email, name, created_at, updated_atid primary key, email unique, email không rỗng
tasksCông việc trong hệ thốngid, user_id, title, description, status, priority, due_at, created_at, updated_atuser_id foreign key, title không rỗng, status thuộc tập hợp hợp lệ

Schema design

Schema không chỉ là danh sách column. Một schema production-ready cần trả lời:

  • Entity nào sở hữu entity nào? tasks.user_id phải tham chiếu users.id.
  • Field nào là identity? Dùng uuid giúp seed/test ổn định và dễ merge dữ liệu, nhưng cần app hoặc database tạo ID. Dùng bigserial đơn giản hơn nhưng khó seed cố định giữa môi trường.
  • Constraint nào thuộc database? Database nên giữ invariant không thể bị bypass: title không rỗng, status hợp lệ, priority trong range.
  • Index nào phục vụ query thật? Ví dụ list task theo user và status nên có index (user_id, status, created_at DESC).
  • Delete behavior là gì? ON DELETE RESTRICT an toàn hơn CASCADE nếu chưa có product decision rõ về xóa user kéo theo xóa task.
  • Timestamp dùng timezone hay không? Với PostgreSQL, ưu tiên TIMESTAMPTZ cho thời điểm tuyệt đối.

Migration safety

Migration an toàn là migration có thể review, test, chạy lặp trong pipeline, quan sát được, và có chiến lược sửa khi sai.

Các cấp độ rủi ro:

Thay đổiRủi roGhi chú
Tạo bảng mới chưa có dữ liệuThấpPhù hợp cho Day 09 nếu chạy dev/test
Thêm nullable columnThấp đến vừaÍt phá app cũ hơn
Thêm index thường trên bảng lớnVừa đến caoCó thể lock/write-block tùy DB và tool
Thêm NOT NULL khi có dữ liệu cũVừa đến caoCần backfill và validate trước
Rename column/tableCaoCó thể phá app phiên bản cũ đang chạy
Drop column/table, truncate, delete dataRất caoKhông để AI chạy trên production

Pattern phổ biến cho thay đổi production là expand -> backfill -> contract:

  1. Expand: thêm column/table/index tương thích ngược.
  2. Deploy app ghi cả field cũ và mới nếu cần.
  3. Backfill dữ liệu theo batch, có giám sát.
  4. Validate constraint.
  5. Contract: xóa field cũ sau khi chắc chắn không còn consumer.

Day 09 chỉ thực hành migration tạo bảng mới. Nhưng bạn phải học guardrail ngay từ đầu, vì thói quen này quyết định an toàn ở Day 16 đến Day 20.

Transaction

Transaction giúp migration hoặc seed thành công toàn bộ hoặc fail toàn bộ. Với PostgreSQL, nhiều DDL có thể chạy trong transaction, nhưng không phải tất cả. CREATE INDEX CONCURRENTLYDROP INDEX CONCURRENTLY không được chạy trong transaction block. Vì vậy:

  • Migration tạo bảng mới thường nên chạy trong transaction nếu tool hỗ trợ.
  • Migration tạo index concurrently cần tách riêng và cấu hình tool để không wrap transaction.
  • Seed data nên chạy trong transaction nếu seed nhiều bảng liên quan.
  • Không assume mọi migration tool xử lý transaction giống nhau. Bắt Claude Code đọc docs/config/script thật của project.

Index

Index không phải "thêm càng nhiều càng tốt". Index tăng tốc read nhưng làm write chậm hơn, tốn disk, tăng thời gian migration và tăng chi phí maintenance.

Với taskflow-ai, index hợp lý cho Day 09:

  • users.email unique index để đảm bảo không có hai user cùng email.
  • tasks(user_id, status, created_at DESC) cho màn list task theo user và trạng thái.
  • Partial index tasks(due_at) WHERE due_at IS NOT NULL nếu UI có filter task sắp đến hạn.

Không thêm index cho mọi column như title, description, priority nếu chưa có query pattern. Full-text search hoặc fuzzy search là feature riêng, không gộp vào migration nền tảng.

Constraint

Constraint là tuyến phòng thủ cuối cùng cho data integrity. App validation giúp trả lỗi đẹp, nhưng database constraint giữ dữ liệu sạch kể cả khi có bug, script thủ công hoặc service khác ghi vào DB.

Constraint nên có trong Day 09:

  • Primary key cho users.idtasks.id.
  • Foreign key tasks.user_id -> users.id.
  • Unique constraint hoặc unique index cho users.email.
  • Check constraint cho tasks.status.
  • Check constraint cho tasks.priority.
  • Check constraint cho tasks.title sau trim không rỗng.

Trade-off: constraint làm migration fail sớm nếu seed hoặc app ghi data sai. Đó là lợi ích, không phải nhược điểm, miễn là error được hiểu và test bắt được.

Seed data idempotent

Seed data dùng để dev/test có dữ liệu mẫu. Seed tốt phải:

  • Chạy được nhiều lần không duplicate.
  • Dùng ID cố định để test ổn định.
  • Không chứa secret thật, email khách hàng thật, access token, production dump.
  • Tách seed dev/test khỏi seed production.
  • Có transaction và ON CONFLICT hoặc upsert theo migration tool.

Ví dụ SQL seed idempotent cho PostgreSQL:

INSERT INTO users (id, email, name)
VALUES
  ('00000000-0000-4000-8000-000000000001', 'alice@example.test', 'Alice Nguyen')
ON CONFLICT (id) DO UPDATE
SET email = EXCLUDED.email,
    name = EXCLUDED.name,
    updated_at = now();

File path ví dụ: backend/db/seeds/001_dev_seed.sql hoặc path seed theo tool thật của project. Mục đích là tạo dữ liệu dev/test ổn định. Cách test là chạy seed hai lần rồi kiểm tra số row không tăng. Edge case cần kiểm tra: conflict theo id, conflict theo email, task tham chiếu user chưa tồn tại, seed chạy trong transaction và rollback khi một row fail.

4. Step-by-step thực hành

Mục tiêu thực hành: dùng Claude Code để tạo migration bảng users, tasks và seed data trong project taskflow-ai. Học viên phải chạy trên dev/test DB, không chạy trên production DB. Tên command migration phụ thuộc project thật, nên bài này luôn yêu cầu Claude đọc package.json và thư mục migration trước khi đề xuất command.

Bước 1: Kiểm tra working tree và phạm vi

Chạy trong thư mục gốc taskflow-ai:

git status --short

Lệnh này kiểm tra file đang thay đổi. Output kỳ vọng là rỗng hoặc chỉ có file bạn hiểu rõ. Rủi ro: nếu có thay đổi của worker khác, migration của bạn có thể chồng lên patch chưa review. Không rollback toàn repo; chỉ làm việc với file thuộc task của bạn.

Chạy trong thư mục gốc taskflow-ai:

find . -maxdepth 4 -name package.json

Lệnh này tìm package Node để biết backend nằm ở root, backend, apps/api hay workspace khác. Output kỳ vọng có ít nhất một package.json. Rủi ro thấp vì read-only; trên Windows PowerShell có thể dùng lệnh tương đương Get-ChildItem -Recurse -Filter package.json -Depth 4.

Chạy trong thư mục gốc taskflow-ai:

rg "\"(migrate|migration|seed|db):" -n package.json backend/package.json apps

Lệnh này tìm script liên quan tới migration và seed. Output kỳ vọng là các script như db:migrate, db:migrate:status, db:seed hoặc tên tương tự. Rủi ro thấp vì read-only; nếu path không tồn tại, rg có thể báo lỗi, khi đó đọc package.json thật trước.

Bước 2: Xác nhận database target là dev/test

Không paste full DATABASE_URL vào prompt vì có thể chứa credential. Hãy kiểm tra database bằng query metadata.

Chạy trong thư mục backend hoặc root nơi env dev/test được load:

psql "$DATABASE_URL" -c "select current_database(), current_user, inet_server_addr(), inet_server_port();"

Lệnh này kết nối bằng DATABASE_URL hiện tại và in database name, user, host, port. Output kỳ vọng cho thấy database dev/test, ví dụ taskflow_ai_dev hoặc taskflow_ai_test, không phải database production. Rủi ro: nếu DATABASE_URL trỏ nhầm production, chỉ riêng lệnh select này vẫn read-only, nhưng bạn phải dừng ngay và sửa env trước khi migrate.

Nếu project dùng Docker Compose local, chạy trong root taskflow-ai:

docker compose ps

Lệnh này hiển thị container database local đang chạy. Output kỳ vọng có service PostgreSQL local, ví dụ postgres hoặc db, trạng thái running. Rủi ro: Docker command read-only này an toàn, nhưng không chạy các command xóa volume như docker compose down -v nếu chưa chắc chắn vì có thể mất dữ liệu dev của người khác.

Bước 3: Mở Claude Code ở plan mode để khảo sát database layer

Chạy trong root taskflow-ai:

claude --permission-mode plan

Lệnh này mở Claude Code ở mode đọc/lập plan. Output kỳ vọng là session sẵn sàng nhận prompt. Rủi ro thấp hơn edit mode, nhưng Claude vẫn có thể suy diễn nếu bạn không bắt nó nêu file đã đọc.

Prompt khám phá:

Bạn đang ở repo taskflow-ai. Hãy khảo sát database layer để chuẩn bị migration Day 09.

Ràng buộc:
- Chưa sửa file.
- Chỉ đọc file cần thiết.
- Tìm migration tool, seed mechanism, database client/ORM, test DB setup và script trong package.json.
- Nêu rõ file đã đọc và bằng chứng từ code.
- Không đề xuất chạy migration.
- Không đề xuất DROP, TRUNCATE, DELETE không điều kiện, reset database hoặc xóa Docker volume.
- Nếu không tìm thấy migration tool, đề xuất 2 phương án tối thiểu nhưng chưa implement.

Kỳ vọng: Claude cho biết project dùng tool nào, migration folder ở đâu, command nào tồn tại, seed chạy bằng gì, test DB setup ra sao. Nếu Claude không đọc package.json, docker-compose.yml, .env.example, migration folder hoặc database client, yêu cầu đọc bổ sung.

Bước 4: Chốt data model trước khi tạo migration

Gửi prompt trong cùng session:

Dựa trên file đã đọc, hãy đề xuất data model Day 09 cho bảng users và tasks.

Output bắt buộc:
- Bảng column cho users và tasks: name, type, nullable, default, constraint.
- Foreign key và delete behavior.
- Index đề xuất, mỗi index phải gắn với query pattern cụ thể.
- Constraint đề xuất và lỗi dữ liệu mà constraint chặn.
- Migration safety review: lock risk, destructive risk, transaction, rollback/forward-only.
- Seed data plan idempotent.
- Chưa sửa file và chưa chạy command.

Một schema SQL tham khảo cho PostgreSQL:

CREATE TABLE users (
  id uuid PRIMARY KEY,
  email text NOT NULL,
  name text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT users_email_non_empty CHECK (length(btrim(email)) > 0),
  CONSTRAINT users_name_non_empty CHECK (length(btrim(name)) > 0)
);

CREATE UNIQUE INDEX users_email_unique_idx ON users (email);

CREATE TABLE tasks (
  id uuid PRIMARY KEY,
  user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
  title text NOT NULL,
  description text,
  status text NOT NULL DEFAULT 'todo',
  priority smallint NOT NULL DEFAULT 3,
  due_at timestamptz,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT tasks_title_non_empty CHECK (length(btrim(title)) > 0),
  CONSTRAINT tasks_title_length CHECK (char_length(title) <= 200),
  CONSTRAINT tasks_status_valid CHECK (status IN ('todo', 'in_progress', 'done', 'archived')),
  CONSTRAINT tasks_priority_range CHECK (priority BETWEEN 1 AND 5)
);

CREATE INDEX tasks_user_status_created_idx ON tasks (user_id, status, created_at DESC);
CREATE INDEX tasks_due_at_idx ON tasks (due_at) WHERE due_at IS NOT NULL;

File path sẽ phụ thuộc tool thật, ví dụ backend/db/migrations/20260514090000_create_users_tasks.sql. Mục đích của đoạn SQL là tạo schema nền tảng cho dev/test. Cách test là chạy migration trên database dev/test sạch, kiểm tra table, constraint, index, sau đó chạy API/test. Edge case cần xem: email trùng, title chỉ có khoảng trắng, status không hợp lệ, priority ngoài range, task tham chiếu user không tồn tại.

Bước 5: Lập plan file-by-file

Prompt lập plan:

Lập plan implement migration Day 09.

Ràng buộc:
- Tối đa 7 bước.
- Mỗi bước ghi file sẽ sửa/tạo.
- Chỉ tạo migration cho users/tasks và seed dev/test idempotent.
- Không đổi API route, service hoặc frontend trong Day 09 nếu không cần cho test.
- Không thêm dependency nếu project đã có migration tool.
- Không chạy migration, seed, reset DB hoặc Docker command.
- Nếu cần command để verify, chỉ liệt kê command, ghi rõ môi trường dev/test và rủi ro.
- Chờ tôi approve trước khi edit.

Plan tốt thường có:

1. Tạo migration create_users_tasks theo convention hiện có.
2. Thêm seed dev/test idempotent cho user và task mẫu.
3. Nếu project có migration index/manifest, cập nhật đúng file manifest.
4. Thêm hoặc cập nhật test migration/seed nếu project đã có pattern.
5. Không chạy migration; chỉ ghi command verify để human chạy.

Nếu Claude đề xuất sửa API CRUD Day 08, đổi ORM, đổi .env, thêm dependency, hoặc tạo auth system, yêu cầu thu hẹp scope.

Bước 6: Cho Claude Code implement với boundary hẹp

Sau khi duyệt plan, mở session edit trong root taskflow-ai:

claude --permission-mode default --tools "Read,Write,Edit,Bash" --allowedTools "Bash(git status *)" "Bash(git diff *)"

Lệnh này giới hạn built-in tool family vào đọc/sửa file và Bash, đồng thời auto-approve riêng Git read-only. Output kỳ vọng là session sẵn sàng. Rủi ro: command migration không được auto-approve nhưng Claude vẫn có thể đề xuất và xin quyền chạy; hãy từ chối cho đến khi review diff, xác nhận DB dev/test và kiểm tra rollback path. Nếu bạn thêm Bash(*), Claude có thể chạy command ngoài ý muốn, gồm migration hoặc reset DB.

Prompt implement:

Implement migration Day 09 theo plan đã approve.

Ràng buộc bắt buộc:
- Chỉ chạm file trong plan.
- Không chạy migration, seed, docker, npm install, git add, git commit, git reset, git clean hoặc command xóa dữ liệu.
- Migration chỉ tạo users/tasks, constraint, foreign key và index đã duyệt.
- Không dùng DROP, TRUNCATE, DELETE không điều kiện, reset DB hoặc destructive rollback.
- Seed phải idempotent, chạy lại không duplicate dữ liệu.
- Nếu migration tool yêu cầu transaction config, ghi chú rõ. Không tự đoán nếu chưa thấy pattern.
- Sau khi edit, tóm tắt file changed, SQL chính, command human nên chạy và rủi ro còn lại.

Kỳ vọng: Claude tạo migration và seed đúng pattern. Nếu Claude muốn chạy command migration, từ chối và yêu cầu chỉ liệt kê command.

Bước 7: Review diff migration trước khi chạy

Chạy trong root taskflow-ai:

git diff --stat

Lệnh này cho biết phạm vi patch. Output kỳ vọng chỉ có file migration, seed và test/manifest liên quan nếu plan có. Rủi ro: --stat không cho thấy SQL nguy hiểm, chỉ dùng để phát hiện patch rộng bất thường.

Chạy trong root taskflow-ai:

git diff

Lệnh này hiển thị patch chi tiết. Output kỳ vọng không có thay đổi ngoài plan, không có command destructive, seed idempotent, constraint/index đúng contract. Rủi ro: diff SQL dài dễ bỏ sót; reviewer nên tìm thủ công các từ khóa rủi ro.

Chạy trong root taskflow-ai:

git diff -- backend/db/migrations

Lệnh này giới hạn review vào migration folder ví dụ. Output kỳ vọng chỉ có migration Day 09. Rủi ro thấp vì read-only; thay path bằng migration folder thật của project.

Prompt review migration:

Review diff migration hiện tại như senior database reviewer, không sửa file.

Tập trung:
- Có DROP, TRUNCATE, DELETE không điều kiện, reset DB, CASCADE nguy hiểm hoặc destructive rollback không.
- Constraint có đúng domain không và có fail với seed không.
- Foreign key delete behavior có an toàn không.
- Index có gắn với query pattern thật không.
- Migration có transaction phù hợp không.
- Có command nào có thể chạy nhầm production không.
- Seed có idempotent không.
- Có file ngoài plan bị chạm không.

Kết luận theo format: Blocker, Should fix, Nice to have, Test gaps.

Bước 8: Chạy migration trên dev/test DB

Chỉ chạy sau khi đã xác nhận database target là dev/test. Đọc package.json để dùng script thật. Các command dưới đây là ví dụ script phổ biến, không phải bắt buộc tồn tại.

Chạy trong folder có package.json backend:

npm run db:migrate:status

Lệnh này kiểm tra migration đã applied/chưa applied nếu project có script status. Output kỳ vọng thấy migration Day 09 ở trạng thái pending. Rủi ro: script có thể không tồn tại hoặc có thể kết nối nhầm DB nếu env sai; kiểm tra DB target trước.

Chạy trong folder có package.json backend:

npm run db:migrate:dev

Lệnh này chạy migration trên database dev nếu project định nghĩa script này. Output kỳ vọng báo applied thành công migration Day 09. Rủi ro cao nếu env trỏ production; không để Claude tự chạy, human phải xác nhận dev/test và backup trước.

Nếu project chỉ có một script migrate chung, chạy trong folder backend sau khi xác nhận env:

npm run db:migrate

Lệnh này chạy migration theo script thật của project. Output kỳ vọng là migration applied, exit code 0. Rủi ro: tên script chung không nói rõ môi trường, nên phải xác nhận DATABASE_URL, .env, Docker service và branch trước.

Bước 9: Verify schema, index và constraint

Chạy trong folder backend hoặc root nơi DATABASE_URL dev/test được load:

psql "$DATABASE_URL" -c "\d users"

Lệnh này mô tả bảng users. Output kỳ vọng có column id, email, name, timestamp, primary key và unique index email. Rủi ro thấp nếu DB target dev/test; nếu không có quyền hoặc env sai, dừng và không tự sửa bằng quyền production.

Chạy trong cùng nơi:

psql "$DATABASE_URL" -c "\d tasks"

Lệnh này mô tả bảng tasks. Output kỳ vọng có foreign key tới users, check constraint cho status, priority, title, và index liên quan. Rủi ro thấp nếu read-only trên dev/test.

Chạy trong cùng nơi:

psql "$DATABASE_URL" -c "select tablename, indexname from pg_indexes where tablename in ('users', 'tasks') order by tablename, indexname;"

Lệnh này liệt kê index của userstasks. Output kỳ vọng có unique index email, index list task theo user/status và partial index due date nếu đã duyệt. Rủi ro thấp vì read-only.

Chạy trong cùng nơi:

psql "$DATABASE_URL" -c "insert into tasks (id, user_id, title, status, priority) values ('00000000-0000-4000-8000-000000009999', '00000000-0000-4000-8000-000000009998', 'Invalid FK', 'todo', 3);"

Lệnh này cố tình insert task với user_id không tồn tại để kiểm tra foreign key trên dev/test. Output kỳ vọng là lỗi foreign key violation. Rủi ro: đây là lệnh ghi dữ liệu và cố tình fail; chỉ chạy trên dev/test, không chạy production.

Bước 10: Chạy seed idempotent

Chạy trong folder backend nếu project có script seed:

npm run db:seed

Lệnh này chạy seed theo script project. Output kỳ vọng báo seed thành công, tạo user/task mẫu. Rủi ro: nếu seed không idempotent sẽ tạo duplicate; nếu env sai có thể ghi vào database không mong muốn. Chỉ chạy dev/test.

Chạy lại cùng lệnh trong folder backend:

npm run db:seed

Lệnh này kiểm tra seed có idempotent không. Output kỳ vọng vẫn thành công và không tăng số row ngoài dự kiến. Rủi ro giống lần chạy đầu; nếu row tăng, seed chưa đạt yêu cầu.

Chạy trong folder backend hoặc root:

psql "$DATABASE_URL" -c "select count(*) as users_count from users; select count(*) as tasks_count from tasks;"

Lệnh này kiểm tra số row seed. Output kỳ vọng số lượng ổn định sau hai lần seed. Rủi ro thấp nếu read-only trên dev/test.

Bước 11: Chạy test liên quan

Chạy trong folder backend có package.json:

npm run test -- --run tasks

Lệnh này chạy test liên quan tới tasks nếu test runner hỗ trợ filter. Output kỳ vọng test pass và exit code 0. Rủi ro: cú pháp filter phụ thuộc Vitest/Jest script; nếu không hỗ trợ, dùng script test chuẩn của project.

Chạy trong folder backend:

npm run test -- --run

Lệnh này chạy test một lần nếu project dùng Vitest hoặc script tương đương. Output kỳ vọng toàn bộ backend test pass. Rủi ro: integration test có thể ghi DB; chỉ dùng DB test/dev đã xác nhận.

Nếu test fail, dùng prompt:

Test migration/seed fail như sau. Hãy phân tích nguyên nhân trước, chưa sửa file.

Phân loại:
- Schema bug.
- Seed không idempotent.
- Test setup sai DB.
- Constraint quá chặt hoặc quá lỏng.
- Migration tool/transaction config sai.

Đề xuất patch nhỏ nhất và file cần sửa. Không chạy command.

Bước 12: Rollback hoặc forward-fix khi migration sai

Nếu migration chưa được applied, rollback file bằng Git là an toàn hơn rollback DB.

Chạy trong root taskflow-ai:

git restore -- backend/db/migrations/path-to-day-09-migration.sql backend/db/seeds/path-to-seed.sql

Lệnh này rollback các tracked file ví dụ về trạng thái Git. Output thường rỗng nếu thành công. Rủi ro: mất thay đổi chưa commit trong các file đó; chỉ dùng với file bạn vừa tạo/sửa và đã review.

Nếu migration đã applied trên dev/test disposable DB, bạn có thể dùng rollback command của tool nếu project có và bạn hiểu nó.

Chạy trong folder backend, chỉ trên dev/test:

npm run db:migrate:rollback

Lệnh này rollback migration gần nhất nếu project có script. Output kỳ vọng báo reverted migration. Rủi ro cao: rollback có thể drop table hoặc mất data dev/test; tuyệt đối không để Claude tự chạy và không dùng trên production.

Với production, ưu tiên forward-only fix:

Không rollback destructive trên production.
Tạo migration mới để sửa trạng thái về phía trước, sau khi có backup, review và maintenance plan.

Đây không phải shell command mà là rule vận hành. Với production data, "quay lại" thường là migration mới, restore backup có kiểm soát hoặc hotfix app, không phải để AI tự undo.

5. Prompt mẫu nên dùng

Prompt khám phá database layer

Hãy khảo sát database layer của taskflow-ai để chuẩn bị migration Day 09.

Yêu cầu:
- Chỉ đọc file, chưa sửa.
- Tìm migration tool, seed mechanism, database client/ORM, migration folder, test DB setup và script package.json.
- Nêu file đã đọc và bằng chứng.
- Không chạy migration, seed, reset DB hoặc Docker command.
- Không đề xuất destructive SQL.

Prompt lập data model

Thiết kế data model users/tasks cho taskflow-ai theo convention hiện có.

Output:
- Column table cho users và tasks.
- Constraint, foreign key, index và query pattern tương ứng.
- Quyết định delete behavior.
- Seed data idempotent.
- Migration safety: transaction, lock, rollback/forward-only, dev/test DB.

Chưa implement.

Prompt lập plan migration

Lập plan implement migration và seed Day 09.

Ràng buộc:
- Tối đa 7 bước.
- Mỗi bước ghi file sẽ tạo/sửa.
- Không đổi API/frontend.
- Không thêm dependency nếu project đã có migration tool.
- Không chạy migration, seed, reset DB, Docker volume command hoặc Git destructive command.
- Chờ tôi approve trước khi edit.

Prompt implement migration

Implement migration users/tasks và seed dev/test theo plan đã approve.

Giới hạn:
- Chỉ chạm file trong plan.
- Không chạy command.
- Không dùng DROP, TRUNCATE, DELETE không điều kiện, CASCADE nguy hiểm hoặc destructive rollback.
- Seed phải idempotent và dùng ID cố định.
- Constraint phải chặn title rỗng/whitespace, status không hợp lệ, priority ngoài range.
- Index phải đúng query pattern đã duyệt.
- Sau khi edit, tóm tắt SQL chính, file changed, command human cần chạy và rủi ro.

Prompt review migration

Review diff migration hiện tại như senior database reviewer, không sửa file.

Kiểm tra:
- Destructive SQL hoặc rollback nguy hiểm.
- DB target và command có nguy cơ production không.
- Transaction có phù hợp không, đặc biệt nếu có index concurrently.
- Constraint có đúng domain và có test được không.
- Index có cần thiết không, có ảnh hưởng write không.
- Seed có idempotent không.
- Có file ngoài plan không.

Kết luận: Blocker, Should fix, Nice to have, Test gaps.

Prompt viết test và verify

Hãy đề xuất test/verification cho migration Day 09, chưa sửa file.

Yêu cầu:
- Test schema: table, constraint, foreign key, index.
- Test seed chạy hai lần không duplicate.
- Test API task CRUD sau migration nếu project có integration test.
- Ghi command cần chạy, chạy ở folder nào, output kỳ vọng và rủi ro.
- Không đề xuất reset DB hoặc dùng production data.

6. Trade-offs

Dùng database constraint làm hệ thống cứng hơn nhưng lỗi sẽ xuất hiện ở tầng DB nếu app validation thiếu. Đây là trade-off tốt cho data integrity, nhưng API vẫn cần map lỗi DB thành response rõ ràng.

UUID giúp seed/test dễ ổn định và tránh phụ thuộc sequence. Đổi lại, index lớn hơn bigint, insert locality kém hơn nếu UUID random, và cần thống nhất cách generate ID. Với Day 09, dùng UUID cố định trong seed là hợp lý, nhưng production có thể dùng app-generated UUID, database extension hoặc sequence tùy team.

Unique index trên email đảm bảo invariant quan trọng. Nhưng nếu email cần case-insensitive, bạn phải quyết định dùng normalized email lower-case, citext, hoặc unique index trên expression. Day 09 nên chọn phương án đơn giản: normalize email ở app/seed và giữ unique index trên email.

ON DELETE RESTRICT bảo vệ task khỏi bị xóa dây chuyền khi user bị xóa nhầm. Nhược điểm là xóa user khó hơn, cần flow archive/anonymize riêng. ON DELETE CASCADE tiện hơn cho test và demo, nhưng nguy hiểm nếu domain chưa quyết định.

Index giúp list task nhanh hơn nhưng làm insert/update chậm hơn và migration nặng hơn. Chỉ thêm index cho query đã có hoặc chắc chắn sắp có. Không thêm index chỉ vì column "có vẻ hay filter".

Migration trong transaction dễ rollback khi fail. Nhưng một số operation cần chạy ngoài transaction, ví dụ CREATE INDEX CONCURRENTLY trong PostgreSQL. Tool migration có thể wrap transaction mặc định, nên reviewer phải biết behavior của tool trước khi dùng operation đặc biệt.

Rollback migration nghe có vẻ an toàn nhưng với production thường không đơn giản. Nếu migration đã làm app phiên bản mới ghi dữ liệu theo schema mới, rollback schema có thể mất dữ liệu. Forward-only migration thường an toàn hơn: tạo migration sửa lỗi, giữ dữ liệu, deploy tiếp.

Seed idempotent tốn công hơn seed insert thẳng. Đổi lại, developer có thể chạy lại seed sau mỗi reset hoặc test mà không tạo duplicate. Với team thật, đây là tiêu chuẩn tối thiểu.

7. Best practices

  • Luôn bắt đầu bằng read-only exploration. Bắt Claude Code đọc migration tool, folder convention, seed script và test setup trước khi thiết kế.
  • Không cho Claude Code chạy migration trên database thật. AI có thể viết SQL và command, human chạy sau khi xác nhận môi trường.
  • Không paste production credential, .env thật hoặc database dump vào prompt.
  • Trước khi chạy migration, xác nhận DB target bằng query metadata, không chỉ nhìn tên terminal.
  • Với production migration, cần backup, review SQL, estimated lock time, deploy order, rollback hoặc forward-fix plan.
  • Dùng forward-only mindset. Rollback file Git khác rollback dữ liệu đã applied.
  • Với bảng có dữ liệu lớn, tránh migration rewrite toàn bảng trong giờ cao điểm.
  • Thêm NOT NULL hoặc constraint mới trên dữ liệu cũ theo nhiều bước: add nullable, backfill, validate, enforce.
  • Index production trên bảng lớn cần chiến lược riêng. Với PostgreSQL, cân nhắc CONCURRENTLY, nhưng nhớ command này không chạy trong transaction block.
  • Constraint nên có tên rõ, ví dụ tasks_status_valid, để log lỗi dễ debug.
  • Seed dev/test phải idempotent, dùng ID cố định, không dùng data khách hàng thật.
  • Không dùng CASCADE trong migration nếu chưa có lý do rõ và reviewer approve.
  • Không tự đổi migration history đã merge hoặc đã applied ở môi trường chung. Tạo migration mới.
  • Review diff tìm từ khóa rủi ro: DROP, TRUNCATE, DELETE, CASCADE, ALTER COLUMN, SET NOT NULL, TYPE, RENAME, down, rollback.
  • Trong repo nhiều worker, chỉ rollback theo file bạn sở hữu. Không dùng git reset --hard, git clean -fd hoặc command xóa rộng.

8. Performance / cost / context

Database task thường làm Claude Code đọc nhiều file: migration folder, ORM schema, database client, route/service, test setup, Docker Compose và env example. Nếu không kiểm soát, context sẽ phình nhanh và Claude dễ lẫn giữa app schema, test schema và production assumptions.

Cách giảm token và chi phí:

  • Bắt đầu bằng câu hỏi hẹp: "tìm migration tool và seed mechanism", không yêu cầu đọc toàn repo.
  • Yêu cầu Claude nêu file đã đọc và bằng chứng, không viết essay dài.
  • Chốt data model trong bảng ngắn trước khi implement.
  • Chia migration thành một slice: tạo bảng mới users/tasks; không gộp auth, roles, comments, audit log.
  • Khi test fail, chỉ đưa failure output liên quan và schema diff, không paste toàn bộ log Docker.
  • Dùng summary khi session dài: tool migration, file list, schema đã duyệt, command verify, constraint/index.

Performance runtime của database:

  • Index tăng tốc read nhưng làm write chậm hơn. Với task CRUD nhỏ, 2-3 index có chủ đích là đủ.
  • Unique email check cần index; nếu không có index, kiểm tra uniqueness không khả thi ở scale.
  • Foreign key cũng có chi phí write nhưng bảo vệ data integrity. Với tasks.user_id, index query theo user_id thường cũng giúp join/list.
  • CREATE INDEX trên bảng lớn có thể lock write; CREATE INDEX CONCURRENTLY giảm lock nhưng chạy lâu hơn, không chạy trong transaction block và cần giám sát.
  • Backfill lớn nên chạy theo batch, không viết migration một transaction khổng lồ nếu bảng production lớn.

Cost của lỗi database lớn hơn cost token. Đừng tiết kiệm một lượt review để rồi chạy migration sai. Với DB, review SQL thủ công là bước bắt buộc.

9. Checklist cuối bài

  • Tôi đã kiểm tra git status --short trước khi cho Claude Code sửa file.
  • Tôi đã xác nhận migration chỉ chạy trên dev/test DB.
  • Tôi không paste credential hoặc full production DATABASE_URL vào prompt.
  • Claude Code đã đọc migration tool, seed mechanism và script thật trước khi đề xuất command.
  • Data model users/tasks có column, type, nullable, default và constraint rõ.
  • Foreign key tasks.user_id -> users.id có delete behavior được duyệt.
  • Index được gắn với query pattern cụ thể, không thêm theo cảm tính.
  • Seed data idempotent, chạy lại không duplicate.
  • Migration không có DROP, TRUNCATE, DELETE không điều kiện, reset DB hoặc CASCADE nguy hiểm.
  • Tôi đã review git diff --statgit diff trước khi chạy migration.
  • Tôi biết command migration chạy ở đâu, làm gì, output kỳ vọng và rủi ro.
  • Tôi đã verify table, constraint, index và seed sau khi chạy dev/test migration.
  • Tôi không để Claude Code tự chạy destructive migration trên data thật.
  • Với production, tôi ưu tiên backup, forward-only fix và maintenance plan.

10. Bài tập

Bài cơ bản: mở taskflow-aiclaude --permission-mode plan, yêu cầu Claude khảo sát database layer và viết data model users/tasks. Không cho sửa file. Kết quả cần có: file đã đọc, migration tool, seed mechanism, schema proposal, constraint, index, risk review.

Bài thực tế: sau khi duyệt data model, cho Claude Code tạo migration và seed idempotent trong dev/test. Không cho Claude chạy migration. Human review diff, xác nhận DB target, rồi tự chạy migration bằng script thật của project. Ghi lại command, output chính và rủi ro.

Bài nâng cao: thêm verification test cho seed idempotent và constraint quan trọng. Test phải chứng minh chạy seed hai lần không duplicate, task không thể tham chiếu user không tồn tại, title whitespace bị reject và status ngoài enum bị reject.

Bài áp dụng project cá nhân: chọn một migration từng làm ở project thật hoặc repo sandbox. Viết lại theo format: schema contract, migration safety, forward-only plan, seed idempotency, verification command. Yêu cầu Claude review migration cũ và chỉ ra ít nhất 5 rủi ro production.


Tài liệu

Tóm tắt kiến thức

Day 09 tập trung vào database migration và data model cho taskflow-ai. Trọng tâm không phải chỉ là tạo bảng userstasks, mà là cách dùng Claude Code để làm việc với database có guardrail rõ ràng.

Nguyên tắc chính:

  • Claude Code được phép đọc codebase, đề xuất schema, soạn migration và seed.
  • Human phải xác nhận database target, review SQL, quyết định backup và chạy migration.
  • Không cho AI chạy destructive migration trên production data.
  • Migration tạo bảng mới là rủi ro thấp, nhưng vẫn cần review constraint, index, foreign key và seed.
  • Seed dev/test phải idempotent: chạy nhiều lần không duplicate.
  • Index phải xuất phát từ query pattern, không thêm theo cảm tính.
  • Constraint giữ data integrity ở tầng database, bổ sung cho app validation.
  • Production migration nên theo tư duy forward-only: sửa bằng migration mới thay vì rollback phá dữ liệu.

Data model Day 09 tham khảo:

BảngMục đíchField chínhConstraint chính
usersNgười sở hữu/giao taskid, email, name, created_at, updated_atprimary key, unique email, email/name không rỗng
tasksCông việc trong hệ thốngid, user_id, title, description, status, priority, due_at, timestampsforeign key, title không rỗng, status hợp lệ, priority trong range

Migration an toàn cần trả lời được 6 câu hỏi:

  1. Chạy trên DB nào: dev, test, staging hay production?
  2. Có backup chưa nếu không phải DB disposable?
  3. Có destructive SQL không?
  4. Có lock hoặc table rewrite đáng kể không?
  5. Rollback là rollback file, rollback DB dev/test, hay forward-only fix?
  6. Verify bằng command và test nào?

Sơ đồ tư duy hoặc luồng xử lý

Yêu cầu Day 09
  |
  v
Kiểm tra Git state
  |
  +-- Có thay đổi lạ -> dừng, đọc diff, không rollback toàn repo
  |
  v
Xác định DB target
  |
  +-- Không chắc dev/test -> dừng, không migrate
  |
  v
Claude Code plan mode
  |
  v
Đọc database layer
  |
  +-- package.json scripts
  +-- migration folder
  +-- ORM/database client
  +-- seed mechanism
  +-- docker/test DB setup
  |
  v
Thiết kế schema contract
  |
  +-- users columns
  +-- tasks columns
  +-- constraints
  +-- foreign keys
  +-- indexes
  +-- seed idempotent
  |
  v
Migration safety review
  |
  +-- destructive SQL?
  +-- transaction?
  +-- lock risk?
  +-- rollback/forward-only?
  +-- backup?
  |
  v
Developer approve
  |
  v
Claude implement file migration/seed
  |
  v
git diff --stat -> git diff
  |
  v
Human chạy migration trên dev/test
  |
  v
Verify schema + seed + tests
  |
  +-- Đạt -> commit/PR
  |
  +-- Sai -> patch nhỏ hoặc forward-only migration mới

Luồng prompt nên dùng:

Explore database layer
  -> Data model proposal
  -> Migration safety review
  -> Plan file-by-file
  -> Implement migration/seed without running commands
  -> Review diff
  -> Human-run dev/test migration
  -> Verify

Bảng so sánh

Chủ đềNên làmKhông nên làm
DB targetXác nhận bằng metadata query trước khi migrateTin vào tên terminal hoặc giả định .env đúng
MigrationTạo migration nhỏ, review được, forward-onlyGộp schema, backfill, refactor, seed production trong một patch
Destructive SQLChặn DROP, TRUNCATE, DELETE không điều kiện nếu chưa có RFC/backupĐể Claude tự chạy rollback hoặc reset DB
ConstraintĐặt constraint cho invariant quan trọngChỉ rely vào app validation
IndexGắn với query pattern cụ thểIndex mọi column để "phòng xa"
SeedIdempotent, ID cố định, dev/test onlyDùng production dump hoặc tạo duplicate mỗi lần chạy
TransactionDùng transaction cho migration/seed phù hợpAssume mọi DDL đều chạy được trong transaction
ProductionBackup, review SQL, plan deploy, monitoringChạy trực tiếp từ prompt của AI
Thay đổi schemaMức rủi roCách xử lý
Tạo bảng mớiThấpPhù hợp Day 09, vẫn cần review constraint/index
Thêm nullable columnThấp đến vừaDeploy app tương thích ngược
Thêm unique constraint trên data cũVừaKiểm tra duplicate trước, clean/backfill
Thêm NOT NULLVừa đến caoBackfill trước, validate sau
Tạo index trên bảng lớnVừa đến caoCân nhắc concurrent/index window
Rename column/tableCaoExpand/contract, tránh phá app cũ
Drop data/schemaRất caoKhông để AI chạy; cần backup/RFC
CommandChạy ở đâuDùng để làm gìOutput kỳ vọngRủi ro
git status --shortRoot taskflow-aiKiểm tra working treeRỗng hoặc file đã hiểuChồng lên thay đổi worker khác nếu bỏ qua
find . -maxdepth 4 -name package.jsonRoot taskflow-aiTìm package backendDanh sách package.jsonRead-only, rủi ro thấp
`rg ""(migratemigrationseeddb):" -n package.json backend/package.json apps`Root taskflow-ai
psql "$DATABASE_URL" -c "select current_database(), current_user, inet_server_addr(), inet_server_port();"Root/backend có env dev/testXác nhận DB targetTên DB dev/test, user, host, portNếu ra production thì dừng ngay
claude --permission-mode planRoot taskflow-aiCho Claude đọc/lập planSession sẵn sàngPlan sai nếu Claude đọc thiếu file
claude --permission-mode default --tools "Read,Write,Edit,Bash" --allowedTools "Bash(git status *)" "Bash(git diff *)"Root taskflow-aiGiới hạn tool family và auto-approve Git read-onlySession sẵn sàngCommand migration vẫn có thể được Claude đề xuất và xin quyền, không approve trước khi xác nhận DB dev/test
git diff --statRoot taskflow-aiXem phạm vi patchFile list khớp planKhông thấy SQL nguy hiểm
git diffRoot taskflow-aiReview patch chi tiếtSQL/seed đúng planDiff dài dễ bỏ sót
npm run db:migrate:statusFolder backend có scriptXem migration pending/appliedDay 09 pending trước khi chạyScript/env sai có thể trỏ DB khác
npm run db:migrate:devFolder backend có scriptChạy migration devApplied thành côngNguy hiểm nếu env trỏ production
npm run db:seedFolder backend có scriptChạy seed dev/testThành công, không duplicate khi chạy lạiGhi DB sai nếu env sai
npm run test -- --run tasksFolder backendChạy test liên quan tasksTest passFilter phụ thuộc test runner
Index/constraintLý doRủi ro nếu thiếu
users.email uniqueKhông cho duplicate user emailApp có thể tạo user trùng
tasks.user_id foreign keyTask phải thuộc user tồn tạiOrphan task, join sai
tasks.status checkStatus thuộc workflow hợp lệData bẩn làm UI/API lỗi
tasks.priority checkPriority trong rangeSort/filter thiếu nhất quán
tasks.title non-emptyTask phải có nội dungAPI có task rỗng
tasks(user_id, status, created_at) indexList task theo user/statusQuery list chậm khi data tăng

Lỗi thường gặp

  1. Để Claude tự chọn migration tool
    Claude có thể thêm Prisma/Drizzle/Knex mới dù project đã có tool. Cách sửa: bắt Claude đọc package.json, migration folder và database client trước.

  2. Chạy migration khi chưa xác nhận DB target
    .env có thể trỏ staging hoặc production. Cách sửa: chạy query metadata và chỉ migrate dev/test.

  3. Seed không idempotent
    Chạy seed hai lần tạo duplicate user/task. Cách sửa: dùng fixed ID và ON CONFLICT hoặc upsert theo tool.

  4. Index không có query pattern
    Index dư làm write chậm, migration lâu, disk tăng. Cách sửa: mỗi index phải có query cụ thể.

  5. Constraint quá lỏng
    App validation có thể bị bypass. Cách sửa: những invariant cốt lõi phải nằm ở DB.

  6. Constraint quá chặt khi có data cũ
    Migration fail ở staging/prod. Cách sửa: kiểm tra data trước, backfill, validate theo bước.

  7. Rollback nhầm nghĩa
    git restore chỉ rollback file. Rollback DB sau khi migration applied có thể mất dữ liệu. Cách sửa: phân biệt rollback file, rollback dev DB, forward-only production fix.

  8. Dùng CASCADE cho nhanh
    DROP ... CASCADE hoặc foreign key cascade có thể xóa dây chuyền. Cách sửa: chỉ dùng khi domain và reviewer approve rõ.

  9. Tạo index concurrently trong migration transaction
    PostgreSQL không cho CREATE INDEX CONCURRENTLY trong transaction block. Cách sửa: tách migration hoặc cấu hình tool đúng.

  10. Dùng production dump làm seed
    Có thể lộ dữ liệu khách hàng. Cách sửa: seed synthetic data bằng domain .example.test, ID cố định, không chứa secret.

Cách debug

Khi không biết project dùng migration tool nào, chạy trong root taskflow-ai:

rg "\"(migrate|migration|seed|db):" -n package.json backend/package.json apps

Lệnh này tìm script liên quan database. Output kỳ vọng là tên script migration/seed thật. Rủi ro thấp vì read-only; nếu không ra kết quả, yêu cầu Claude đọc thêm folder backend và docs nội bộ.

Prompt tiếp theo:

Không tìm thấy script migration rõ ràng.
Hãy đọc package.json, database client setup và folder db/migrations nếu có.
Chỉ đọc file, không đề xuất thêm dependency ngay.
Nêu 2 phương án tối thiểu và trade-off.

Khi nghi ngờ env trỏ nhầm DB, chạy trong folder backend hoặc root có env:

psql "$DATABASE_URL" -c "select current_database(), current_user, inet_server_addr(), inet_server_port();"

Lệnh này xác nhận database đang kết nối. Output kỳ vọng là DB dev/test. Rủi ro thấp vì read-only; nếu output không rõ, dừng migration và hỏi team.

Khi migration fail do constraint, chạy trong DB dev/test:

psql "$DATABASE_URL" -c "select conname, contype from pg_constraint where conrelid in ('users'::regclass, 'tasks'::regclass) order by conname;"

Lệnh này liệt kê constraint của userstasks. Output kỳ vọng có constraint đã đặt tên rõ. Rủi ro thấp vì read-only; nếu table chưa tồn tại, query báo lỗi và cần kiểm tra migration applied.

Đưa lỗi vào Claude:

Migration fail với lỗi constraint sau. Hãy phân tích, chưa sửa file.

Hãy xác định:
- Constraint nào fail.
- Data hoặc seed nào vi phạm.
- Schema sai hay seed sai.
- Patch nhỏ nhất là gì.
- Có cần thay đổi data model đã duyệt không.

Khi seed bị duplicate, chạy trong DB dev/test:

psql "$DATABASE_URL" -c "select email, count(*) from users group by email having count(*) > 1;"

Lệnh này tìm email duplicate. Output kỳ vọng là 0 row. Rủi ro thấp vì read-only. Nếu có row, seed chưa idempotent hoặc unique constraint thiếu.

Khi cần review SQL nguy hiểm, chạy trong root taskflow-ai:

git diff | rg -n "DROP|TRUNCATE|DELETE FROM|CASCADE|ALTER COLUMN|SET NOT NULL|TYPE|RENAME|down|rollback"

Lệnh này tìm từ khóa rủi ro trong diff. Output kỳ vọng rỗng hoặc chỉ có nội dung đã được giải thích. Rủi ro thấp vì read-only; đây không thay thế review thủ công.

Khi muốn rollback file trước khi applied, chạy trong root taskflow-ai:

git restore -- backend/db/migrations/path-to-day-09-migration.sql backend/db/seeds/path-to-seed.sql

Lệnh này rollback file tracked ví dụ. Output thường rỗng. Rủi ro: mất thay đổi trong file đó; chỉ dùng cho file bạn sở hữu trong task.


Bài tập

Bài 1 — Cơ bản

Mục tiêu: dùng Claude Code ở plan mode để khảo sát database layer của taskflow-ai và thiết kế schema users/tasks, chưa sửa file.

Yêu cầu:

  1. Mở terminal tại thư mục gốc taskflow-ai.

  2. Kiểm tra working tree:

git status --short

Lệnh này chạy ở root repo để xem file đang thay đổi. Output kỳ vọng là rỗng hoặc chỉ có file bạn hiểu rõ. Rủi ro: nếu có file của worker khác, bạn có thể tạo migration trên nền code chưa ổn định; dừng lại và đọc diff trước.

  1. Tìm migration/seed script:
rg "\"(migrate|migration|seed|db):" -n package.json backend/package.json apps

Lệnh này chạy ở root repo để tìm script database trong các package phổ biến. Output kỳ vọng có script migration/seed hoặc không có gì nếu project chưa thiết lập. Rủi ro thấp vì read-only; nếu path không tồn tại, đọc package.json thật thay vì đoán.

  1. Xác nhận DB target nếu đã có DATABASE_URL:
psql "$DATABASE_URL" -c "select current_database(), current_user, inet_server_addr(), inet_server_port();"

Lệnh này chạy ở root hoặc backend nơi env dev/test được load. Output kỳ vọng cho thấy DB dev/test, ví dụ taskflow_ai_dev hoặc taskflow_ai_test. Rủi ro: nếu hiện ra DB production/staging không mong muốn, dừng toàn bộ migration task.

  1. Mở Claude Code:
claude --permission-mode plan

Lệnh này chạy ở root repo để mở Claude ở mode đọc/lập plan. Output kỳ vọng là session sẵn sàng. Rủi ro: Claude vẫn có thể suy diễn nếu không bị yêu cầu nêu bằng chứng.

  1. Gửi prompt khám phá:
Bạn đang ở repo taskflow-ai. Hãy khảo sát database layer cho Day 09.

Ràng buộc:
- Chỉ đọc file, chưa sửa.
- Tìm migration tool, migration folder, seed mechanism, database client/ORM, test DB setup và script package.json.
- Nêu rõ file đã đọc và bằng chứng từ code.
- Không chạy migration, seed, Docker command hoặc Git command destructive.
- Không đề xuất thêm dependency nếu project đã có migration tool.
  1. Gửi prompt thiết kế schema:
Thiết kế data model users/tasks cho Day 09.

Output cần có:
- Bảng columns cho users và tasks: name, type, nullable, default.
- Constraint: primary key, unique, foreign key, check.
- Index và query pattern tương ứng.
- Delete behavior cho foreign key.
- Seed data plan idempotent.
- Migration safety review: destructive risk, transaction, lock, rollback/forward-only.

Chưa implement.

Kết quả cần nộp:

  • File list Claude đã đọc.
  • Migration tool và seed mechanism tìm được.
  • Schema proposal cho userstasks.
  • Danh sách constraint/index kèm lý do.
  • 5 rủi ro nếu chạy migration mà không review.

Bài 2 — Thực tế

Mục tiêu: tạo migration và seed idempotent cho users/tasks bằng Claude Code, nhưng human mới là người chạy migration trên dev/test.

Yêu cầu:

  1. Yêu cầu Claude lập plan file-by-file:
Lập plan implement migration và seed Day 09 cho users/tasks.

Ràng buộc:
- Tối đa 7 bước.
- Mỗi bước ghi file sẽ tạo/sửa.
- Chỉ dùng migration tool/convention hiện có.
- Không đổi API, service, frontend hoặc README.
- Không thêm dependency nếu chưa được approve.
- Không chạy migration, seed, reset DB, Docker command, git add, git commit, git reset hoặc git clean.
- Seed phải idempotent.
- Chờ tôi approve trước khi edit.
  1. Sau khi approve plan, mở session edit:
claude --permission-mode default --tools "Read,Write,Edit,Bash" --allowedTools "Bash(git status *)" "Bash(git diff *)"

Lệnh này chạy ở root taskflow-ai để giới hạn tool family vào đọc/sửa file và Bash, đồng thời auto-approve riêng Git read-only. Output kỳ vọng là session sẵn sàng. Rủi ro: migration không được auto-approve nhưng Claude vẫn có thể xin quyền chạy; chỉ approve sau khi xác nhận DB dev/test và review diff. Nếu bạn mở rộng thành Bash(*), AI có thể chạy command ngoài plan.

  1. Gửi prompt implement:
Implement migration users/tasks và seed dev/test theo plan đã duyệt.

Ràng buộc:
- Chỉ chạm file trong plan.
- Không chạy command.
- Không dùng DROP, TRUNCATE, DELETE không điều kiện, reset DB, Docker volume command hoặc destructive rollback.
- users phải có id, email, name, created_at, updated_at.
- tasks phải có id, user_id, title, description, status, priority, due_at, created_at, updated_at.
- tasks.user_id tham chiếu users.id với delete behavior đã duyệt.
- title không được rỗng sau trim, status thuộc tập hợp hợp lệ, priority trong range.
- Index phải có query pattern cụ thể.
- Seed dùng ID cố định và chạy lại không duplicate.
- Sau khi edit, tóm tắt diff, SQL chính, command human nên chạy và rủi ro.
  1. Review phạm vi patch:
git diff --stat

Lệnh này chạy ở root repo để xem file changed. Output kỳ vọng chỉ có migration, seed và file manifest/test nếu plan có. Rủi ro: không thấy logic SQL, chỉ dùng để phát hiện patch rộng.

  1. Review chi tiết:
git diff

Lệnh này chạy ở root repo để xem toàn bộ patch. Output kỳ vọng không có destructive SQL, seed idempotent, constraint/index đúng plan. Rủi ro: diff dài dễ bỏ sót, cần review keyword rủi ro.

  1. Tìm keyword nguy hiểm:
git diff | rg -n "DROP|TRUNCATE|DELETE FROM|CASCADE|ALTER COLUMN|SET NOT NULL|TYPE|RENAME|down|rollback"

Lệnh này chạy ở root repo để lọc từ khóa rủi ro trong diff. Output kỳ vọng rỗng hoặc có dòng đã được giải thích và approve. Rủi ro thấp vì read-only; không thay thế review thủ công.

  1. Human tự chạy migration trên dev/test bằng script thật. Ví dụ nếu project có script:
npm run db:migrate:dev

Lệnh này chạy trong folder backend có package.json và env dev/test. Output kỳ vọng báo migration Day 09 applied thành công. Rủi ro cao nếu env trỏ production; phải xác nhận DB target trước.

  1. Chạy seed hai lần:
npm run db:seed

Lệnh này chạy trong folder backend để tạo seed dev/test. Output kỳ vọng seed thành công. Rủi ro: nếu seed không idempotent sẽ duplicate.

npm run db:seed

Lệnh này chạy lại để kiểm tra idempotency. Output kỳ vọng vẫn thành công và số row không tăng ngoài dự kiến. Rủi ro giống lần đầu.

Kết quả cần nộp:

  • Diff summary.
  • Migration file path và seed file path.
  • Command migration/seed thật đã chạy.
  • Output chính.
  • Kết quả chạy seed hai lần.
  • Issue còn lại nếu có.

Bài 3 — Nâng cao

Mục tiêu: verify constraint, foreign key, index và seed bằng command/test cụ thể; yêu cầu Claude Code review nhưng không tự sửa.

Yêu cầu:

  1. Kiểm tra schema sau migration:
psql "$DATABASE_URL" -c "\d users"

Lệnh này chạy ở root hoặc backend nơi env dev/test được load. Output kỳ vọng thấy column, primary key và unique index email. Rủi ro thấp nếu DB là dev/test.

psql "$DATABASE_URL" -c "\d tasks"

Lệnh này chạy ở cùng nơi để xem bảng tasks. Output kỳ vọng thấy foreign key tới users, check constraint và index. Rủi ro thấp nếu read-only trên dev/test.

  1. Kiểm tra index:
psql "$DATABASE_URL" -c "select tablename, indexname from pg_indexes where tablename in ('users', 'tasks') order by tablename, indexname;"

Lệnh này chạy trên DB dev/test để liệt kê index. Output kỳ vọng có unique email và index phục vụ list task. Rủi ro thấp vì read-only.

  1. Kiểm tra foreign key bằng insert cố tình fail:
psql "$DATABASE_URL" -c "insert into tasks (id, user_id, title, status, priority) values ('00000000-0000-4000-8000-000000009999', '00000000-0000-4000-8000-000000009998', 'Invalid FK', 'todo', 3);"

Lệnh này chạy trên DB dev/test để thử tạo task tham chiếu user không tồn tại. Output kỳ vọng là lỗi foreign key violation. Rủi ro: đây là write command cố tình fail; không chạy trên production.

  1. Kiểm tra duplicate seed:
psql "$DATABASE_URL" -c "select email, count(*) from users group by email having count(*) > 1;"

Lệnh này chạy trên DB dev/test để tìm email duplicate. Output kỳ vọng là 0 row. Rủi ro thấp vì read-only.

  1. Chạy test liên quan:
npm run test -- --run tasks

Lệnh này chạy trong folder backend có package.json để chạy test liên quan tasks nếu test runner hỗ trợ filter. Output kỳ vọng test pass. Rủi ro: filter syntax phụ thuộc project; nếu không hỗ trợ, dùng script test chuẩn.

  1. Yêu cầu Claude review kết quả:
Review kết quả verify migration Day 09, không sửa file.

Input:
- Schema output users/tasks.
- Index list.
- Kết quả seed chạy hai lần.
- Test output.

Hãy phân loại:
- Blocker.
- Should fix.
- Nice to have.
- Test gaps.

Không đề xuất destructive rollback. Nếu cần sửa, đề xuất patch nhỏ hoặc forward-only migration.

Kết quả cần nộp:

  • Evidence rằng constraint hoạt động.
  • Evidence rằng seed idempotent.
  • Evidence rằng index tồn tại đúng mục tiêu.
  • Review của Claude và quyết định accept/reject của bạn.

Bài 4 — Review & Reflection

Mục tiêu: biến kinh nghiệm Day 09 thành rule vận hành cho team khi dùng Claude Code với database.

Trả lời các câu hỏi:

  1. Data model cuối cùng có những bảng, constraint và index nào?
  2. Constraint nào bạn đặt ở database thay vì chỉ ở app? Vì sao?
  3. Index nào có query pattern rõ? Index nào bạn quyết định không thêm?
  4. Seed có idempotent không? Bạn chứng minh bằng cách nào?
  5. Nếu migration này chạy nhầm production, rủi ro lớn nhất là gì?
  6. Claude Code có cố chạy migration hoặc đề xuất command nguy hiểm không? Bạn chặn thế nào?
  7. Với migration production trong tương lai, bạn sẽ yêu cầu backup, lock analysis và forward-only plan ra sao?
  8. Bạn sẽ thêm rule gì vào CLAUDE.md để Claude không tự chạy destructive migration?

Gợi ý prompt reflection:

Dựa trên Day 09, hãy giúp tôi viết rule database workflow cho CLAUDE.md của taskflow-ai.

Yêu cầu:
- Rule phải cụ thể cho migration, seed, constraint, index, transaction và production data.
- Có danh sách command/SQL bị cấm nếu chưa được human approve.
- Có rule xác nhận DB target dev/test trước khi migrate.
- Có rule seed idempotent.
- Có rule review diff trước khi chạy command.
- Không viết chung chung.

Kết quả cần nộp:

  • Reflection 10-15 dòng.
  • Rule đề xuất cho CLAUDE.md.
  • Một checklist cá nhân trước khi chạy migration.

Tiêu chí hoàn thành

  • Đã dùng claude --permission-mode plan để khảo sát database layer trước khi sửa.
  • Claude nêu rõ file đã đọc và bằng chứng.
  • Có data model users/tasks với column, type, nullable, default, constraint và index rõ ràng.
  • Có migration tạo bảng, foreign key, check constraint và index theo plan.
  • Có seed dev/test idempotent, chạy lại không duplicate.
  • Không có DROP, TRUNCATE, DELETE không điều kiện, reset DB, Docker volume delete hoặc destructive rollback.
  • Đã xác nhận DB target là dev/test trước khi chạy migration.
  • Đã review git diff --statgit diff.
  • Đã chạy migration bằng script thật của project trên dev/test.
  • Đã verify table, constraint, index và seed bằng command hoặc test.
  • Đã chạy test liên quan hoặc ghi rõ lý do nếu project chưa có test setup.
  • Biết phân biệt rollback file, rollback dev DB và forward-only production fix.

Gợi ý nếu bí

Nếu Claude không tìm được migration tool:

Hãy đọc package.json, database client setup, docker-compose.yml, env example và các folder db/prisma/drizzle/migrations nếu có.
Chỉ đọc file.
Không thêm dependency.
Nêu 2 phương án tối thiểu và trade-off.

Nếu Claude đề xuất migration destructive:

Từ chối phần destructive.
Hãy viết lại plan Day 09 chỉ gồm tạo bảng users/tasks, constraint, index và seed idempotent.
Không DROP, TRUNCATE, DELETE, reset DB, CASCADE nguy hiểm hoặc rollback destructive.
Chưa sửa file.

Nếu không chắc DB target:

Tôi chưa xác nhận DB target.
Hãy dừng mọi đề xuất chạy migration.
Chỉ liệt kê các bước read-only để xác nhận môi trường dev/test mà không lộ credential.

Nếu seed duplicate:

Seed chạy hai lần tạo duplicate.
Hãy phân tích nguyên nhân, chưa sửa file.
Kiểm tra seed dùng fixed ID/upsert chưa, unique constraint email có chưa, và conflict target có đúng không.
Đề xuất patch nhỏ nhất.

Nếu migration applied trên dev/test nhưng schema sai:

Migration đã applied trên dev/test và schema sai.
Không đề xuất production rollback.
Hãy đề xuất 2 phương án:
1. Sửa bằng rollback/reset chỉ cho dev/test disposable.
2. Sửa bằng forward-only migration mới.
Nêu rủi ro từng phương án.
Chưa sửa file.

Đáp án tham khảo hoặc expected result

Kết quả tốt cho Bài 1:

  • Claude đọc đúng package.json, migration folder, database client/ORM, seed setup và test DB setup.
  • Data model có userstasks rõ ràng.
  • tasks.user_id có foreign key tới users.id.
  • users.email unique.
  • tasks.title, tasks.status, tasks.priority có check constraint.
  • Index được giải thích bằng query pattern.
  • Không có đề xuất chạy migration hoặc reset DB.

Kết quả tốt cho Bài 2:

  • Có migration file theo convention thật của project.
  • Có seed file hoặc seed script theo convention thật của project.
  • Seed dùng ID cố định và upsert/ON CONFLICT.
  • git diff --stat chỉ có file trong plan.
  • git diff không có destructive SQL.
  • Migration chạy thành công trên dev/test.
  • Seed chạy hai lần không duplicate.

Ví dụ diff summary chấp nhận được:

2 files changed, 95 insertions(+)

Con số chỉ là ví dụ. Quan trọng là file list khớp plan và không có thay đổi ngoài scope.

Kết quả tốt cho Bài 3:

  • \d users cho thấy primary key và unique email.
  • \d tasks cho thấy foreign key, check constraint và index.
  • Insert task với user_id không tồn tại fail bằng foreign key violation.
  • Query duplicate email trả 0 row.
  • Test liên quan tasks pass hoặc test gap được ghi rõ.
  • Claude review không phát hiện blocker.

Kết quả tốt cho Bài 4:

Rule mẫu có thể đưa vào CLAUDE.md:

- Với database migration, luôn bắt đầu bằng read-only exploration và nêu file đã đọc.
- Không chạy migration, seed, reset DB, Docker volume command hoặc rollback destructive nếu chưa được human approve.
- Trước khi migrate, human phải xác nhận DB target là dev/test hoặc có production checklist riêng.
- Không dùng DROP, TRUNCATE, DELETE không điều kiện, CASCADE nguy hiểm, ALTER COLUMN TYPE hoặc SET NOT NULL trên bảng có data nếu chưa có plan.
- Mọi seed dev/test phải idempotent, dùng ID cố định và không chứa production data.
- Mỗi index mới phải có query pattern cụ thể.
- Mỗi constraint mới phải có edge case/test tương ứng.
- Sau khi edit migration, báo file changed, SQL chính, command verify và known risks; human review diff trước khi chạy.
- Production fix ưu tiên forward-only migration, backup và monitoring.