# 02 — Kế hoạch tổng thể dự án E-commerce Phần 2
8.7 Sổ kế toán kép cho thanh toán
Tiền không được lưu bằng cách cộng trừ vào một cột balance. Mọi chuyển dịch tiền là một cặp bút toán cân bằng.
CREATE TABLE ledger_accounts (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
code text NOT NULL UNIQUE, -- 'cash.vnpay', 'ar.cod.ghn', 'revenue.sales', 'liability.giftcard'
type text NOT NULL, -- asset | liability | equity | revenue | expense
currency_code char(3) NOT NULL DEFAULT 'VND'
);
CREATE TABLE ledger_transactions (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
public_id uuid NOT NULL DEFAULT uuidv7(),
description text NOT NULL,
reference_type text, reference_id text,
occurred_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE ledger_entries (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
transaction_id bigint NOT NULL REFERENCES ledger_transactions(id),
account_id bigint NOT NULL REFERENCES ledger_accounts(id),
amount numeric(19,4) NOT NULL, -- dương = Nợ (debit), âm = Có (credit)
CONSTRAINT entries_nonzero CHECK (amount <> 0)
);
CREATE INDEX entries_txn_idx ON ledger_entries (transaction_id);
CREATE INDEX entries_acct_idx ON ledger_entries (account_id, id);
-- CSDL tự cưỡng chế: mỗi giao dịch phải cân bằng về 0, kiểm tra ở CUỐI transaction
CREATE OR REPLACE FUNCTION assert_ledger_balanced() RETURNS trigger AS $$
DECLARE s numeric;
BEGIN
SELECT COALESCE(SUM(amount), 0) INTO s
FROM ledger_entries WHERE transaction_id = NEW.transaction_id;
IF s <> 0 THEN
RAISE EXCEPTION 'Bút toán không cân bằng: transaction_id=% tổng=%', NEW.transaction_id, s;
END IF;
RETURN NULL;
END $$ LANGUAGE plpgsql;
CREATE CONSTRAINT TRIGGER ledger_balanced_trg
AFTER INSERT ON ledger_entries
DEFERRABLE INITIALLY DEFERRED -- ⬅ chỉ kiểm ở COMMIT, cho phép chèn từng dòng
FOR EACH ROW EXECUTE FUNCTION assert_ledger_balanced();
Số dư = SUM(amount) theo tài khoản (materialized view refresh định kỳ nếu cần tốc độ). Cách này khiến lệch sổ trở thành bất khả thi ở tầng CSDL, không phụ thuộc code PHP đúng hay sai.
Đặc biệt quan trọng với COD: tiền khách trả cho shipper nằm ở tài khoản ar.cod.ghn (phải thu từ hãng vận chuyển) cho đến khi hãng chuyển khoản đối soát. Không có sổ kép thì không thể biết hãng còn nợ bao nhiêu.
8.8 Outbox & Idempotency
CREATE TABLE outbox_messages (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
aggregate_type text NOT NULL,
aggregate_id text NOT NULL,
event_type text NOT NULL, -- 'order.paid'
payload jsonb NOT NULL,
headers jsonb NOT NULL DEFAULT '{}'::jsonb, -- trace_id để nối OTel qua queue
created_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz,
attempts smallint NOT NULL DEFAULT 0,
last_error text
);
CREATE INDEX outbox_pending_idx ON outbox_messages (id) WHERE processed_at IS NULL;
CREATE TABLE idempotency_keys (
key text PRIMARY KEY, -- HMAC(provider, provider_txn_id) hoặc header Idempotency-Key
scope text NOT NULL, -- 'webhook.vnpay' | 'api.checkout'
request_hash text NOT NULL,
response_code smallint,
response_body jsonb,
locked_at timestamptz,
completed_at timestamptz,
expires_at timestamptz NOT NULL
);
CREATE INDEX idem_expiry_idx ON idempotency_keys (expires_at);
Outbox relay đọc bằng FOR UPDATE SKIP LOCKED, ghi vào cùng transaction với thay đổi nghiệp vụ → không bao giờ có chuyện "đơn đã tạo nhưng event không phát" hoặc ngược lại. Ở P2–P6 relay đẩy vào Laravel queue; ở P7 đổi đích sang Kafka mà không đổi một dòng nào ở tầng Domain — đó chính là giá trị của việc làm outbox sớm.
8.9 Mở rộng & vận hành
| Chủ đề | Quyết định |
|---|---|
| Partitioning | orders, stock_movements, outbox_messages, audit_logs — RANGE theo tháng, dùng pg_partman tạo trước 3 tháng. Kích hoạt từ P7 hoặc khi bảng > 50 triệu dòng. |
| Read replica | Cấu hình read/write trong config/database.php. Cảnh báo: ngay sau khi ghi phải đọc từ primary (sticky) — Laravel hỗ trợ 'sticky' => true. Chỉ định tuyến báo cáo và trang danh mục sang replica. |
| PgBouncer | Transaction mode. Mất: prepared statement (phải PGBOUNCER=true / tắt prepare trong PDO), LISTEN/NOTIFY, session-level advisory lock, temp table. Chỉ thêm khi số connection > ~200. |
| Backup | pgBackRest — full hằng tuần, incremental hằng ngày, WAL archive liên tục → PITR. Diễn tập restore hằng quý. Backup chưa từng restore thử = không có backup. |
| HA | P0–P6: managed PostgreSQL (AWS RDS / Aiven / DigitalOcean) — đừng tự vận hành Patroni khi chưa có SRE. |
| Migration zero-downtime | Expand–contract. CREATE INDEX CONCURRENTLY. Thêm cột NOT NULL phải qua 3 bước (thêm nullable → backfill theo lô → set NOT NULL). Không bao giờ đổi tên/xoá cột trong cùng deploy với code dùng nó. |
| Quan sát query | pg_stat_statements + auto_explain (log query > 200ms) bật từ P0. |
| Dữ liệu benchmark | Seeder sinh 1 triệu variant + 500k đơn từ P1, để mọi quyết định index đều có số đo thật chứ không phải cảm giác. |
Mục tiêu hiệu năng (đo bằng k6 từ P5):
| Chỉ số | Mục tiêu |
|---|---|
| p95 API chi tiết sản phẩm | < 120 ms |
| p95 trang danh mục có facet | < 250 ms |
| p95 tìm kiếm | < 200 ms |
| p95 thêm vào giỏ | < 150 ms |
| p95 tạo đơn (ghi) | < 400 ms |
| Thông lượng đọc lúc flash sale | 10.000 req/s (chủ yếu do CDN + cache) |
| Thông lượng ghi đơn | 500 đơn/phút bền vững |
| Số lần oversell | 0 — không có ngưỡng chấp nhận |
9. Runtime & hiệu năng
9.1 FrankenPHP + Octane
Chọn FrankenPHP (worker mode) + laravel/octane v2.19, không chọn Swoole/RoadRunner.
| Lý do | Chi tiết |
|---|---|
| Một binary | Caddy + PHP trong một tiến trình — TLS tự động, HTTP/2, HTTP/3, không cần nginx + php-fpm riêng |
| Early Hints (103) | Đẩy sớm CSS/JS/ảnh hero — có lợi thật cho Core Web Vitals (LCP) của trang sản phẩm |
| Không cần extension lạ | Swoole đòi extension riêng, xung đột với một số package và khó debug |
| Hỗ trợ chính thức trong Octane | Không phải tự vá |
Bẫy của worker mode — phải test có chủ đích, không phát hiện được bằng mắt:
- State rò rỉ giữa request:
staticproperty, singleton giữ dữ liệu của user trước → lộ dữ liệu người này sang người khác. Đây là lỗi bảo mật, không phải lỗi hiệu năng. - Singleton nhận
Requestvào constructor → giữ mãi request đầu tiên. Str::macro,Blade::directiveđăng ký trongboot()bị đăng ký lặp.- Rò rỉ bộ nhớ tích luỹ → cấu hình
--max-requests=500để worker tự tái sinh. - Config/route cache phải được build lúc deploy, không lúc chạy.
Cách phòng, đưa vào CI: chạy bộ test feature hai lần trong cùng một tiến trình worker, so sánh kết quả; thêm arch test cấm inject Illuminate\Http\Request vào constructor của singleton. Đưa Octane vào P4, sau khi bộ test đã đủ dày — bật Octane ở P0 lúc test còn mỏng là công thức để có bug không tái hiện được.
9.2 Caching nhiều tầng
Tầng 0 — Trình duyệt Cache-Control, ETag, immutable cho asset có hash
Tầng 1 — CDN (Cloudflare) HTML trang danh mục/sản phẩm, stale-while-revalidate=60
Tầng 2 — Full-page cache Trang khách vãng lai, TTL 5′ + purge theo domain event
Tầng 3 — App cache (Valkey) Object cache: product, price, facet count, session, cart
Tầng 4 — Query result Chỉ cho truy vấn nặng đã đo, KHÔNG cache bừa
Tầng 5 — PostgreSQL shared_buffers, hit ratio mục tiêu > 99%
Invalidation theo domain event, không theo TTL mù quáng:
ProductPublished / PriceChanged / StockLevelChanged → purge cache tag tương ứng + purge CDN theo tag. TTL chỉ là lưới an toàn cuối cùng.
Chống cache stampede: khi cache miss, lấy lock (Cache::lock) — chỉ 1 request đi xuống DB, các request khác chờ hoặc trả bản cũ. Thêm jitter ±10% vào TTL để không hết hạn đồng loạt.
9.3 Flash sale — bài toán riêng, cần thiết kế riêng
Câu lệnh UPDATE ở mục 8.5 đúng tuyệt đối, nhưng khi 50.000 người cùng giành một hàng duy nhất trong bảng, PostgreSQL phải tuần tự hoá chúng — thông lượng thực tế chỉ vài trăm/giây và các request còn lại xếp hàng chờ khoá, kéo theo cạn connection pool.
Giải pháp cho flash sale (chỉ áp dụng cho SKU được đánh dấu flash sale):
- Kiểm soát nhận vào bằng Valkey + Lua — bộ đếm nguyên tử trong Redis quyết định ai được vào, độ trễ sub-millisecond, chịu được hàng chục nghìn/giây.
- Người thắng mới đi tiếp vào luồng
reservetrên PostgreSQL (lúc này chỉ còn đúng bằng số suất, không còn tranh chấp). - Đối soát định kỳ Redis ↔ PostgreSQL; PostgreSQL luôn là chân lý cuối. Nếu Redis chết, tắt flash sale — không rơi về đường chậm.
- Hàng đợi ảo (virtual waiting room) trên CDN cho các đợt cực lớn.
Tự "nhận ra" chi tiết này sau khi sập lần đầu là bài học đắt. Ghi ở đây để không phải học lại.
10. Tìm kiếm & AI
10.1 Lộ trình tìm kiếm
| Pha | Công nghệ | Đủ dùng khi |
|---|---|---|
| P1–P5 | PostgreSQL FTS + unaccent + pg_trgm |
Catalog < ~50k variant, facet đơn giản |
| P6 | Typesense 6 (khuyến nghị) hoặc Elasticsearch 9.5 | Cần typo tolerance tốt, facet nhiều chiều, relevance tuning, gợi ý tức thời |
| P6+ | + pgvector hybrid | Tìm theo ngữ nghĩa: "kem chống nắng cho da dầu mụn" |
Về tiếng Việt — cần nói thẳng một hạn chế: PostgreSQL không có bộ tách từ (word segmentation) cho tiếng Việt. to_tsvector('simple', unaccent(...)) tách theo khoảng trắng, nên "kem chống nắng" thành ba token rời. Kết hợp với pg_trgm thì kết quả chấp nhận được cho catalog nhỏ, nhưng đây chính là lý do phải chuyển sang Typesense/ES ở P6 chứ không phải vì "cho hiện đại".
Khuyến nghị Typesense thay vì Elasticsearch cho dự án này: typo tolerance mặc định tốt hơn cho tiếng Việt, cấu hình đơn giản hơn nhiều, RAM tiêu thụ thấp hơn một bậc, có laravel/scout driver. Elasticsearch mạnh hơn nhưng chi phí vận hành cao — chỉ chọn ES nếu đã cần phân tích log/aggregation phức tạp trên cùng cụm.
Đường đi chỉ mục: ProductPublished/PriceChanged/StockLevelChanged → outbox → job reindex. Mục tiêu độ trễ: < 60 giây (là một SLO chính thức ở mục 14).
10.2 AI — chỉ những thứ tạo giá trị đo được
| Ứng dụng | Cách làm | Pha |
|---|---|---|
| Tìm kiếm ngữ nghĩa (hybrid) | Embedding mô tả sản phẩm → pgvector; trộn BM25 + vector bằng Reciprocal Rank Fusion |
P6 |
| Gợi ý "sản phẩm tương tự" | Vector similarity trên cùng bảng embedding — không cần hệ recommendation riêng | P6 |
| Gợi ý "mua cùng nhau" | Đồng xuất hiện trong order_items — SQL thuần, không cần ML |
P4 |
| Làm giàu nội dung sản phẩm | Claude API sinh mô tả/SEO title/meta từ thông số — có người duyệt, không tự đăng | P6 |
| Chatbot CSKH (RAG) | Ngữ cảnh: chính sách + trạng thái đơn của chính khách đó | P6 |
| Phân loại & kiểm duyệt review | Phát hiện spam/độc hại/ảnh không hợp lệ | P6 |
| Chấm điểm gian lận COD | Đặc trưng: tỉ lệ từ chối nhận, địa chỉ, tần suất | P7 |
Toàn bộ AI nằm sau interface trong Domain/Port/ → thay nhà cung cấp không ảnh hưởng nghiệp vụ.
11. Tầng API & bảo mật
11.1 API
| Chủ đề | Quyết định |
|---|---|
| Phong cách | REST, contract-first bằng OpenAPI 3.1. Không dùng JSON:API nghiêm ngặt — chi phí học và độ dài payload không xứng với lợi ích ở quy mô này. |
| Version | Theo URL: /api/v1/.... Đổi phá vỡ = /v2, chạy song song tối thiểu 6 tháng. |
| Phân trang | Cursor-based cho mọi danh sách có thể lớn (đơn hàng, sản phẩm). OFFSET lớn là bẫy hiệu năng kinh điển. |
| GraphQL | Không ở P0–P5. Chỉ cân nhắc ở P6 nếu tách storefront Next.js và thực sự đau vì over-fetching. |
| gRPC | Chỉ khi đã tách service thật (P8). |
| Realtime (Reverb) | P4: tồn kho flash sale, trạng thái đơn, thông báo admin. Không dùng realtime cho thứ mà polling 30 giây là đủ. |
11.2 Xác thực & phân quyền
| Đối tượng | Cơ chế |
|---|---|
| Storefront web/mobile (first-party) | Sanctum v4.3.3 (php artisan install:api) |
| Admin | Session + MFA bắt buộc + giới hạn IP nếu có thể |
| Đối tác / nhà bán / bên thứ ba (P8) | Passport (OAuth 2.1 + OIDC) |
| Webhook đi ra | API key + chữ ký HMAC + timestamp |
| Webhook đi vào (VNPay/MoMo/GHN) | Xác thực chữ ký nhà cung cấp + chống replay + idempotency_keys |
Phân quyền dùng spatie/laravel-permission 8.3 ở tầng Presentation, cộng với kiểm tra quyền sở hữu trong Application layer. Chỉ dựa vào role mà không kiểm tra ownership chính là lỗ IDOR.
11.3 Mô hình mối đe doạ cho e-commerce
Đây là danh sách các cuộc tấn công thật sự xảy ra với sàn TMĐT, không phải checklist chung chung:
| Mối đe doạ | Biện pháp |
|---|---|
| Chiếm đoạt tài khoản (credential stuffing) | Rate limit theo IP + theo tài khoản, captcha thích ứng, cảnh báo đăng nhập lạ, passkey/WebAuthn (P5) |
| Lạm dụng mã giảm giá | coupon_redemptions có UNIQUE(coupon_id, customer_id), giới hạn tổng lượt, chống tạo tài khoản hàng loạt, không sinh mã đoán được |
| Bot gom hàng flash sale | Hàng đợi ảo, giới hạn theo tài khoản/thiết bị, Cloudflare bot management, yêu cầu tài khoản đã xác thực SĐT |
| Card testing (thử thẻ trộm) | Giới hạn số lần thanh toán fail/IP/thẻ, 3DS2, cảnh báo khi tỉ lệ fail vượt ngưỡng |
| Gian lận COD (đặt rồi từ chối nhận) | Điểm rủi ro theo lịch sử KH, yêu cầu đặt cọc/thanh toán trước với KH rủi ro cao, chặn địa chỉ đen |
| IDOR đơn hàng | Không bao giờ lộ ID tuần tự (dùng public_id UUIDv7) + kiểm tra ownership trong Application layer |
| Cào giá | Rate limit, phát hiện bất thường, không trả toàn bộ catalog trong một endpoint |
| Leo thang quyền admin | Nguyên tắc tối thiểu quyền, audit log bất biến, MFA, tách quyền duyệt hoàn tiền |
| XSS trong đánh giá/UGC | Escape mặc định của Blade, sanitize HTML, CSP header nghiêm |
| SSRF khi tải ảnh từ URL | Allowlist domain, chặn IP nội bộ, timeout |
| Mass assignment | $fillable tường minh, DTO ở biên (spatie/laravel-data) thay vì $request->all() |
| Chuỗi cung ứng (composer) | composer audit trong CI, composer.lock commit, Dependabot/Renovate, pin version chính xác cho package chưa 1.0 |
11.4 Thanh toán & PCI-DSS
Không bao giờ lưu số thẻ. Dùng hosted field/redirect của cổng thanh toán → dự án nằm trong phạm vi SAQ-A (mức nhẹ nhất). Bật 3DS2. Webhook có chữ ký + chống replay + idempotency. Đối soát cuối ngày tự động, chênh lệch → cảnh báo ngay.
11.5 Dữ liệu cá nhân — Nghị định 13/2023 (PDPD)
| Yêu cầu | Cách đáp ứng |
|---|---|
| Cơ sở pháp lý & đồng ý | Bảng consents ghi rõ mục đích, phiên bản chính sách, thời điểm, dấu vết (IP/UA). Đồng ý marketing tách riêng khỏi đồng ý xử lý đơn hàng. |
| Quyền truy cập / xoá / rút lại đồng ý | Chức năng tự phục vụ: xuất dữ liệu (JSON) + yêu cầu xoá; xoá = ẩn danh hoá (giữ chứng từ tài chính theo luật kế toán) |
| Tối thiểu hoá dữ liệu | Không thu thập thứ không dùng. CMND/CCCD không lưu trừ khi bắt buộc. |
| Mã hoá trường nhạy cảm | Laravel encrypted cast cho SĐT/địa chỉ/ngày sinh, hoặc pgcrypto |
| Nhật ký truy cập dữ liệu cá nhân | Bảng audit bất biến: ai xem hồ sơ khách nào, lúc nào |
| Đánh giá tác động (DPIA) | Lập hồ sơ trước khi lên production |
| Lưu trữ trong nước | Chọn vùng/nhà cung cấp phù hợp — quyết định trước khi chọn cloud |
| PII trong log | Redact tự động ở tầng log formatter (SĐT, email, địa chỉ, token) |
⚠ Khung pháp lý về dữ liệu cá nhân tại Việt Nam đang thay đổi (Nghị định 13/2023 và luật kế thừa). Trước khi lên production, cần rà soát lại với tư vấn pháp lý — kế hoạch này chỉ nêu yêu cầu kỹ thuật để đáp ứng.
12. Đặc thù thị trường Việt Nam
Đây là phần mà mọi mẫu kiến trúc e-commerce quốc tế đều bỏ trống, và cũng là phần khiến dự án thất bại lúc đưa vào vận hành thật.
12.1 Thanh toán
| Kênh | Ghi chú tích hợp |
|---|---|
| VNPay | Phổ biến nhất. Redirect + IPN. Chữ ký vnp_SecureHash. |
| MoMo | Ví + QR. Có nhiều luồng (ATM/thẻ/ví). |
| ZaloPay | Ví + QR. |
| COD | Không phải "không tích hợp gì" — cần: điểm rủi ro KH, hạn mức COD, đối soát tiền thu hộ với hãng vận chuyển, tài khoản ar.cod.* trong sổ kép (mục 8.7) |
| Chuyển khoản QR (VietQR/Napas 247) | Đối soát bằng nội dung chuyển khoản, cần cơ chế khớp lệnh |
| Thẻ quốc tế | Qua cổng có 3DS2 |
| Trả góp / BNPL | P7+ nếu có nhu cầu |
Bắt buộc với mọi cổng: một interface PaymentGateway trong Domain/Port/, mỗi cổng một adapter trong Infrastructure/. Thêm cổng thứ 4 không được sửa một dòng nào trong Order.
12.2 Vận chuyển & địa chỉ
| Hãng | Ghi chú |
|---|---|
| GHN, GHTK, Viettel Post, J&T, Best | Tạo vận đơn, tính phí, webhook trạng thái, in nhãn |
| Ahamove, Grab Express | Giao nhanh nội thành |
⚠ Rủi ro dữ liệu địa chỉ — điểm cần chú ý đặc biệt: Việt Nam đã tổ chức lại đơn vị hành chính (sáp nhập tỉnh, chuyển sang mô hình chính quyền hai cấp). Hệ quả kỹ thuật:
- Bảng địa chỉ phải có phiên bản (
administrative_unitsvớivalid_from/valid_to), không phải một bảng phẳng tỉnh/huyện/xã. - Cần bảng ánh xạ mã cũ → mã mới để không mất địa chỉ đã lưu của khách.
- Các hãng vận chuyển cập nhật danh mục ở thời điểm khác nhau → phải có lớp ánh xạ riêng cho từng hãng trong ACL.
- Địa chỉ trong
orderslà snapshot văn bản, không phải FK sang bảng địa chỉ — đơn cũ phải in đúng địa chỉ tại thời điểm đặt.
Đây là loại chi tiết chỉ lộ ra khi đã có dữ liệu thật; thiết kế sai từ đầu thì phải migrate hàng triệu địa chỉ.
12.3 Thuế & hoá đơn điện tử
- Hoá đơn điện tử có mã cơ quan thuế — tích hợp qua nhà cung cấp (Viettel, VNPT, MISA meInvoice, FPT). Đây là ACL, không phải gọi trực tiếp từ
Order. - Bảng
tax_ratesphải có hiệu lực theo thời gian (valid_from,valid_to) — thuế suất VAT thay đổi theo chính sách từng thời kỳ, và hoá đơn cũ phải giữ thuế suất tại thời điểm phát hành. - Thời điểm phát hành hoá đơn (khi giao hàng hay khi thu tiền) là quyết định kế toán, phải hỏi kế toán trước khi code.
- Xử lý hoá đơn điều chỉnh/thay thế khi trả hàng.
12.4 Đa kênh (P8)
Đồng bộ tồn kho với Shopee / Lazada / TikTok Shop. Bài toán khó: một nguồn tồn, nhiều kênh bán — cần cơ chế phân bổ tồn theo kênh hoặc đồng bộ gần thời gian thực với vùng đệm an toàn để không oversell chéo kênh.
12.5 Khác
- Thông báo/đăng ký website TMĐT với Bộ Công Thương (online.gov.vn).
- Zalo ZNS cho thông báo đơn hàng (tỉ lệ đọc cao hơn SMS/email tại VN).
- Với mỹ phẩm: số công bố sản phẩm, tem chống giả, quản lý lô/HSD (mục 8.6).
13. Chất lượng: kiểm thử & phân tích tĩnh
13.1 Tháp kiểm thử
| Tầng | Công cụ | Mục tiêu | Chạy khi |
|---|---|---|---|
| Unit / Domain | Pest 5.1 | Coverage ≥ 90% cho Domain/ |
Mọi commit |
| Architecture | Pest arch + deptrac | 0 vi phạm | Mọi commit |
| Integration | Pest + PostgreSQL thật | Coverage ≥ 75% tổng | Mọi PR |
| Contract | Schema-based (OpenAPI validator) | 100% endpoint công khai | Mọi PR |
| E2E | Playwright | 5 luồng doanh thu | Trước deploy |
| Load | k6 | Đạt mục tiêu mục 8.9 | Hằng tuần + trước sự kiện lớn |
| Mutation | Infection | MSI ≥ 80% cho Domain/ của Inventory, Order, Payment, Pricing |
Hằng đêm (không chạy mỗi PR — quá chậm) |
Không dùng SQLite in-memory cho test. Lý do cụ thể: SQLite không có jsonb, không có partial index, không có generated column giống PostgreSQL, không có FOR UPDATE SKIP LOCKED, khác biệt về kiểu và về hành vi transaction. Test xanh trên SQLite rồi vỡ trên production là kịch bản kinh điển. Dùng service container PostgreSQL trong CI + --parallel với database riêng cho mỗi tiến trình.
5 luồng E2E bắt buộc:
- Khách vãng lai → tìm sản phẩm → thêm giỏ → checkout → thanh toán VNPay → nhận email xác nhận.
- Khách đã đăng nhập → áp mã giảm giá → COD → đơn được tạo với đúng số tiền.
- Admin → tạo sản phẩm có biến thể → nhập kho theo lô → sản phẩm hiển thị trên storefront.
- Đơn hàng → tạo vận đơn GHN → webhook giao thành công → đơn
completed→ điểm loyalty được cộng. - Khách yêu cầu trả hàng → admin duyệt → hoàn tiền → tồn kho nhập lại → sổ kép cân bằng.
Test đồng thời bắt buộc: 100 request song song mua 1 sản phẩm còn 1 cái → đúng 1 thành công (mục 8.5).
13.2 Phân tích tĩnh & chất lượng mã
| Công cụ | Cấu hình |
|---|---|
| Larastan 3.10 | Bắt đầu level 6 + baseline, tăng 1 level mỗi sprint đến level 10 (max). Code mới không được thêm vào baseline. |
| Rector 2.6 | LaravelSetList cho nâng cấp framework + LevelSetList::UP_TO_PHP_84. Chạy thủ công, review diff, không chạy tự động trong CI. |
| Pint 1.27 | Preset laravel + rule bổ sung (strict types, ordered imports) |
| deptrac | Mục 7.4 |
| composer audit | Mọi PR |
| Infection | Hằng đêm |
Tính năng PHP 8.5 nên dùng có chủ đích (đã verify chạy được trên máy — mục 2.1):
readonlyclass +clone with→ Value Object bất biến trongDomain/ValueObject/.- Property hooks → thay thế getter/setter rườm rà trong entity.
- Asymmetric visibility (
public private(set)) → cho phép đọc công khai, chỉ sửa từ trong aggregate — rất hợp với bất biến của DDD. - Pipe operator
|>→ chuỗi biến đổi giá (áp KM → áp thuế → làm tròn) đọc tuyến tính.
14. CI/CD, Observability & SRE
14.1 Pipeline (GitHub Actions)
PR ──► lint (Pint) ~30s ┐
├──► static (Larastan + deptrac) ~90s │ song song
├──► unit + arch (Pest) ~60s │
└──► integration (PostgreSQL+Valkey) ~3′ ┘
└──► build image (buildx cache) ~2′
└──► e2e (Playwright) ~3′
└──► deploy staging (auto)
└──► deploy prod (thủ công duyệt)
Mục tiêu: PR check < 8 phút. Vượt 12 phút → coi là nợ kỹ thuật phải xử lý.
- Nhánh: trunk-based, nhánh sống < 2 ngày. Không dùng gitflow (release branch dài ngày là nguồn gốc của merge hell).
- Migration chạy trước khi đổi code, và migration phải tương thích ngược (expand–contract) → rollback code không cần rollback DB.
- Blue-green ở P5; canary ở P7.
laravel/pennant1.25 để tách deploy khỏi release: code lên production tắt sẵn, bật dần theo % người dùng. Đây là thứ khiến deploy hằng ngày trở nên an toàn.
14.2 Observability
| Pha | Công cụ |
|---|---|
| P1 | laravel/pulse 1.8 (miễn phí, tự host) + log có cấu trúc |
| P5 | OpenTelemetry PHP SDK 1.15 → Grafana LGTM (Loki + Tempo + Mimir) |
| Tuỳ chọn | laravel/nightwatch 1.28 (dịch vụ trả phí của Laravel, ít công vận hành hơn) |
Điểm hay bị bỏ sót: trace context không tự động truyền qua queue job. Phải bơm traceparent vào payload job/outbox (cột headers ở mục 8.8) và khôi phục ở worker — nếu không, mọi thứ xảy ra bất đồng bộ (tức là toàn bộ phần thú vị của e-commerce) sẽ vô hình trong trace.
14.3 SLO — cảnh báo theo tốc độ đốt ngân sách lỗi, không theo CPU
| SLI | SLO | Cửa sổ |
|---|---|---|
| Tỉ lệ thành công của checkout | 99,95% | 30 ngày |
| p95 độ trễ tìm kiếm | < 200 ms | 7 ngày |
| Tỉ lệ webhook thanh toán xử lý thành công | > 99,9% | 30 ngày |
| Độ trễ chỉ mục sản phẩm | < 60 s (p99) | 7 ngày |
| Số lần oversell | 0 | luôn luôn |
| Độ trễ hàng đợi (queue lag) | < 30 s (p95) | 7 ngày |
Cảnh báo dựa trên burn rate của error budget (nhanh: 2%/1 giờ → page; chậm: 5%/6 giờ → ticket). Không cảnh báo "CPU > 80%" — nó đánh thức người ta lúc 3 giờ sáng vì những chuyện không ai quan tâm, và huấn luyện đội ngũ bỏ qua cảnh báo.
Dashboard nghiệp vụ (quan trọng ngang dashboard kỹ thuật): GMV theo giờ, tỉ lệ chuyển đổi, tỉ lệ bỏ giỏ, tỉ lệ thanh toán thất bại theo cổng, số lần oversell, số đơn kẹt ở pending_payment, tồn kho sắp hết hạn.
15. Lộ trình thực thi
Thời lượng ước tính cho 1 lập trình viên toàn thời gian. Team 3 người rút được khoảng 40–50% (không phải 3 lần — phối hợp có chi phí).
P0 — Nền móng · 1 tuần
Mục tiêu: một dự án Laravel 13 sạch, chạy trên PostgreSQL, có CI xanh, có ranh giới module được cưỡng chế.
- [ ]
git init,.gitignore, conventional commits, nhánhmainđược bảo vệ - [ ] Tạo mới bằng
laravel newthay vì nâng cấp skeleton Laravel 10 (xem ghi chú bên dưới) - [ ]
compose.yaml: PostgreSQL 18 (+pgvector,pg_trgm,unaccent), Valkey 9, Mailpit, MinIO - [ ] Chuyển
.envsangpgsql,redischo cache/queue/session - [ ] Dựng bộ khung
modules/+ composer path repository +Sharedkernel - [ ] Pint, Larastan (level 6 + baseline), Rector, deptrac, Pest 5 + arch test
- [ ] GitHub Actions: lint → static → test
- [ ]
CLAUDE.md+docs/(mục 18)
Tiêu chí hoàn thành: php artisan about báo PostgreSQL 18 + Valkey · CI xanh · arch test chặn được một vi phạm ranh giới cố ý.
Ghi chú về nâng cấp Laravel 10 → 13: vì
example-appchưa có một dòng code nghiệp vụ nào, tạo mới bằnglaravel newlà lựa chọn đúng — nhanh hơn và sạch hơn nâng cấp qua 3 major. Những thay đổi lớn bạn sẽ thấy so với Laravel 10: không cònapp/Http/Kernel.phpvàapp/Console/Kernel.php(thay bằngbootstrap/app.php), thư mụcconfig/gọn đi nhiều, Sanctum không cài sẵn (php artisan install:api), broadcasting tách riêng (php artisan install:broadcasting), PHPUnit 12.
P1 — Catalog & mặt tiền đọc · 3–4 tuần
Mục tiêu: khách xem được sản phẩm; admin quản lý được catalog.
- [ ] Module
Catalog: product, variant, attribute registry, category (closure table), brand, media - [ ] Module
Customer+IAM: đăng ký/đăng nhập, sổ địa chỉ (có versioning đơn vị hành chính) - [ ] Admin bằng Filament 5.7
- [ ] Storefront: trang chủ, danh mục có facet, chi tiết sản phẩm (Blade + Livewire 4.4)
- [ ] i18n
product_translations, slug tiếng Việt - [ ] Tìm kiếm bằng PostgreSQL FTS +
unaccent+pg_trgm - [ ] Upload/xử lý ảnh (MinIO/S3), responsive image
- [ ] Seeder 1 triệu variant để benchmark
- [ ]
laravel/pulse
Tiêu chí hoàn thành: p95 chi tiết sản phẩm < 120 ms với 1 triệu variant · facet danh mục < 250 ms · coverage Domain ≥ 85%.
P2 — Bán được hàng · 3–4 tuần
Mục tiêu: một người lạ có thể mua hàng thật và trả tiền thật.
- [ ] Module
Cart: giỏ khách vãng lai + đã đăng nhập, merge khi đăng nhập - [ ] Module
Pricing: bảng giá, giá theo nhóm KH - [ ] Module
Inventory:stock_levels, reservation, sweeper, chống oversell (mục 8.5) - [ ] Module
Checkout: chốt giá snapshot, phí ship, thuế,expires_at - [ ] Module
Order: máy trạng thái đầy đủ (mục 6) - [ ] Module
Payment: VNPay + MoMo + COD, sổ kép (mục 8.7), webhook có idempotency - [ ]
outbox_messages+ relay worker - [ ] Horizon 5.48 + queue Redis (thay
sync) - [ ] Email giao dịch (Mailpit ở dev)
- [ ] Test đồng thời 100 request / 1 sản phẩm
Tiêu chí hoàn thành: đặt được đơn thật đầu đến cuối trên môi trường staging với tiền thật (sandbox) · 0 oversell trong test đồng thời · sổ kép cân bằng sau mọi luồng · webhook gọi lại 5 lần chỉ tạo 1 giao dịch.
P3 — Vận hành thật · 3 tuần
Mục tiêu: đơn hàng đi được đến tay khách và về được sổ kế toán.
- [ ] Module
Fulfilment: GHN + GHTK, tạo vận đơn, webhook trạng thái, in nhãn - [ ] Kho đa điểm, lô + hạn sử dụng (FEFO) (mục 8.6)
- [ ] Module
Procurement: nhập hàng, NCC, giá vốn theo lô, công nợ - [ ] Module
Returns: RMA, duyệt, hoàn tiền, nhập kho lại - [ ] Module
Tax & Invoice: hoá đơn điện tử (một nhà cung cấp),tax_ratescó hiệu lực theo thời gian - [ ] Đối soát COD với hãng vận chuyển
- [ ] Huỷ đơn một phần / tách đơn
- [ ] Báo cáo vận hành cơ bản (doanh thu, tồn, lãi gộp theo giá vốn lô)
Tiêu chí hoàn thành: đối soát COD tự động khớp 100% trên dữ liệu mẫu · hoá đơn phát hành thành công ở môi trường thử của nhà cung cấp · báo cáo lãi gộp khớp với sổ kép.
P4 — Tăng trưởng & hiệu năng · 3 tuần
- [ ] Module
Promotion: coupon, mua X tặng Y, combo, flash sale, KM theo bậc - [ ] Module
Loyalty: điểm, hạng thành viên - [ ] Module
Review: đánh giá + ảnh UGC + kiểm duyệt - [ ] FrankenPHP + Octane 2.19 (sau khi test đã dày — mục 9.1)
- [ ] Cache nhiều tầng + invalidation theo event (mục 9.2)
- [ ] CDN + tối ưu Core Web Vitals
- [ ] SEO: sitemap, canonical, structured data, URL tiếng Việt
- [ ] Reverb 1.11 cho tồn kho realtime + trạng thái đơn
- [ ] Thiết kế riêng cho flash sale (mục 9.3)
- [ ] Gợi ý "mua cùng nhau" bằng SQL
Tiêu chí hoàn thành: Lighthouse ≥ 90 trên mobile cho trang sản phẩm · chịu 5.000 req/s đọc trong k6 · flash sale 10.000 người tranh 100 suất → đúng 100 đơn.
P5 — Quan sát & độ tin cậy · 3 tuần
- [ ] OpenTelemetry 1.15 → Grafana LGTM, trace xuyên queue
- [ ] SLI/SLO + cảnh báo burn-rate (mục 14.3)
- [ ] Dashboard nghiệp vụ
- [ ] Saga bằng state machine + queue cho luồng đơn hàng (chưa dùng Temporal)
- [ ] k6 trong CI hằng tuần
- [ ]
laravel/pennant1.25 + blue-green deploy - [ ] Passkey/WebAuthn, MFA cho admin
- [ ] Runbook + quy trình on-call
- [ ] Larastan lên level 10
Tiêu chí hoàn thành: một đơn hàng hiển thị thành một trace liên tục từ HTTP đến webhook thanh toán đến job gửi mail · rollback staging thành công dưới 2 phút.
P6 — Tìm kiếm & AI · 4 tuần
- [ ] Typesense 6 (hoặc Elasticsearch 9.5) +
laravel/scout11.5 - [ ] Đường chỉ mục qua outbox, độ trễ < 60 s
- [ ] Tuning relevance: từ đồng nghĩa tiếng Việt, boost, chịu lỗi chính tả
- [ ] pgvector + hybrid search (BM25 + vector, RRF)
- [ ] Gợi ý sản phẩm tương tự
- [ ] Claude API: làm giàu nội dung (có người duyệt), chatbot RAG CSKH, kiểm duyệt review
- [ ] (Tuỳ chọn) tách storefront sang Next.js 16 headless — chỉ khi có nhu cầu thật về SEO/hiệu năng mà Blade không đáp ứng
Tiêu chí hoàn thành: p95 tìm kiếm < 200 ms · truy vấn tiếng Việt không dấu và sai chính tả trả kết quả đúng · tỉ lệ tìm-kiếm-không-kết-quả giảm đo được.
P7 — Quy mô & phân tán · 4 tuần
Chỉ bắt đầu khi đã chạm tiêu chí kích hoạt ở mục 17.
- [ ] Partition
orders,stock_movements,outbox_messages(pg_partman) - [ ] Read replica + định tuyến đọc/ghi + PgBouncer
- [ ] Kafka 4.3 (KRaft) + Schema Registry — outbox relay đổi đích
- [ ] Debezium CDC từ WAL PostgreSQL
- [ ] ClickHouse 26.7 + dbt cho báo cáo OLAP (ngừng chạy báo cáo trên OLTP)
- [ ] Chấm điểm gian lận COD
- [ ] Canary deploy
P8 — Phân tán nâng cao & mở rộng nghiệp vụ · mở
- [ ] Event Sourcing cho
Inventory+Payment ledger(spatie/laravel-event-sourcing7.15) - [ ] Temporal 2.17 cho saga phức tạp (đơn quốc tế, nhiều kiện, nhiều nhà bán)
- [ ] Kubernetes 1.36 + Helm + ArgoCD + KEDA (autoscale theo độ dài hàng đợi)
- [ ] Tách service đầu tiên (ứng viên:
Search, rồiNotification) - [ ] Marketplace đa nhà bán + RLS
- [ ] B2B: báo giá, hạn mức công nợ, đặt hàng theo hợp đồng
- [ ] Đa kênh: Shopee / Lazada / TikTok Shop
- [ ] Passport OAuth2/OIDC cho API đối tác
16. Tiêu chí kích hoạt công nghệ nặng
Bảng này là cơ chế tự vệ của dự án. Khi bị cám dỗ thêm một công nghệ mới, đọc lại bảng này.
| Công nghệ | Chỉ thêm khi | Không sớm hơn | Nếu thêm sớm thì mất gì |
|---|---|---|---|
| Kafka | Outbox relay > 2.000 msg/s hoặc đã có ≥ 2 service độc lập cần cùng luồng sự kiện | P7 | 1 cụm phải vận hành, một tầng lỗi mới, +2 tuần học, mà Laravel queue + outbox đang thừa sức |
| Debezium / CDC | Đã có Kafka và cần đồng bộ ≥ 2 hệ đích | P7 | Phức tạp schema evolution, replication slot đầy làm sập PostgreSQL |
| Temporal | Có ≥ 3 saga với > 5 bước bù trừ và state machine tự viết đã thực sự gây lỗi production | P8 | 1 cụm server + worker riêng, mô hình lập trình mới hoàn toàn |
| Event Sourcing | Cần trả lời "trạng thái tồn kho lúc 14:32 ngày 3/8 là gì" mà audit log không đủ | P8 | Replay, versioning event, upcasting — chi phí bảo trì gấp 3, khó tuyển người |
| Kubernetes | ≥ 3 dịch vụ triển khai độc lập hoặc cần autoscale theo hàng đợi | P8 | Cần người biết k8s trực 24/7. Trước đó: 1 VM + Docker Compose + systemd là đủ và ít lỗi hơn |
| Service mesh | Đã ở trên k8s và có ≥ 5 service cần mTLS/traffic splitting | Sau P8 | Sidecar ăn RAM, thêm một lớp để debug khi mạng lỗi |
| Elasticsearch / Typesense | Catalog > 50k variant hoặc tỉ lệ tìm-không-ra > 15% hoặc p95 FTS > 300 ms | P6 | Đồng bộ hai chiều, một nguồn sự thật thứ hai phải giữ nhất quán |
| ClickHouse | Báo cáo làm p95 OLTP tăng > 20% hoặc truy vấn báo cáo > 10 s | P7 | Một CSDL nữa, một pipeline nữa |
| Tách service | Module đó cần scale/deploy khác nhịp rõ rệt và không chia sẻ bảng với module khác và đội đủ người để trực nó | P8 | Distributed transaction, độ trễ mạng, debug xuyên service |
| GraphQL | Frontend thực sự đau vì over-fetching, đã đo | P6 | N+1 ẩn, phân quyền theo field, cache khó |
| Next.js headless | Blade/Livewire không đạt Core Web Vitals sau khi đã tối ưu | P6 | Hai codebase, hai pipeline, một tầng API phải giữ ổn định |
17. Rủi ro & phản biện thẳng thắn
17.1 Ba rủi ro có thể giết dự án
| # | Rủi ro | Dấu hiệu cảnh báo sớm | Đối sách |
|---|---|---|---|
| 1 | Over-engineering sớm — dựng Kafka/K8s/ES trước khi có đơn hàng đầu tiên | Hết tuần thứ 6 mà vẫn chưa ai đặt được đơn nào trên staging | Bám mục 16. Coi "đơn hàng thật đầu tiên chạy hết luồng" là mốc thiêng của P2. |
| 2 | Phạm vi phình vô hạn — e-commerce có đáy sâu vô tận (B2B, marketplace, đa kênh, subscription…) | Backlog dài ra nhanh hơn tốc độ hoàn thành | Đóng băng phạm vi từng pha. Ý tưởng mới vào "P9+", không chen ngang. |
| 3 | Nợ đúng đắn dữ liệu — chấp nhận "tạm thế đã" ở tồn kho/tiền | Có một bug oversell/lệch tiền được đánh nhãn "để sau" | Oversell và lệch sổ là P0-incident, sửa ngay, không thương lượng. Đây là lý do sổ kép và CHECK constraint nằm ở tầng CSDL. |
17.2 Những chỗ kế hoạch này còn yếu — cần bổ sung ở tài liệu sau
- Chi phí hạ tầng hằng tháng chưa ước tính. Cần làm trước khi chốt cloud. PostgreSQL managed + 2 app node + Valkey + S3 + CDN ở quy mô khởi đầu là con số cần biết.
- Chưa quyết ai làm frontend. Nếu bạn làm một mình, Blade + Livewire 4 là lựa chọn đúng và mục P6-Next.js nên bỏ.
- Chưa có kế hoạch di trú dữ liệu từ hệ thống cũ (nếu có).
- Chưa có kế hoạch nhân sự/on-call. "Ai trả lời lúc 3 giờ sáng khi sập" là câu hỏi phải trả lời trước khi mở bán, không phải sau.
- Chưa có A/B testing và attribution marketing — quan trọng cho tăng trưởng, nên xếp vào P6/P7.
- Nghị định 13/2023 và luật kế thừa cần rà soát pháp lý thật, tài liệu này chỉ nêu yêu cầu kỹ thuật.
- Danh mục đơn vị hành chính sau sáp nhập (mục 12.2) cần đối chiếu với API thực tế của từng hãng vận chuyển ở thời điểm tích hợp.
17.3 Đánh giá tổng thể
Kế hoạch này khả thi cho một người làm toàn thời gian trong khoảng 6–8 tháng đến hết P5, tức là có một sàn TMĐT vận hành thật, quan sát được, đủ tin cậy. P6–P8 là phần "công nghệ khó" mà bạn muốn — và đến lúc đó bạn sẽ có hệ thống thật với số liệu thật để biện minh cho từng lựa chọn, thay vì thêm chúng vì chúng nghe hay.
Chỗ dễ sa lầy nhất, theo thứ tự: (1) mô hình hoá sản phẩm/biến thể — làm sai thì mọi thứ phía sau lệch theo; (2) khuyến mãi — luôn phức tạp gấp 5 lần dự tính; (3) đối soát thanh toán và COD — nhàm chán nhưng là nơi mất tiền thật.
18. Tuần đầu tiên — việc cụ thể
Chạy trên PowerShell, Windows 11, Docker Desktop bật WSL2 backend.
Ngày 1 — khởi tạo sạch
# Đặt skeleton Laravel 10 cũ sang một bên (giữ lại để tham khảo, chưa xoá)
Rename-Item C:\ecommer\example-app C:\ecommer\_old-skeleton-l10
# Tạo dự án Laravel 13 mới
cd C:\ecommer
composer create-project laravel/laravel:^13.9 ecommerce
cd C:\ecommer\ecommerce
php artisan --version # kỳ vọng: Laravel Framework 13.x
git init
git add -A
git commit -m "chore: khoi tao Laravel 13 skeleton"
Ngày 1 — mang tài liệu sang
New-Item -ItemType Directory C:\ecommer\ecommerce\docs -Force
Copy-Item C:\ecommer\_old-skeleton-l10\docs\*.md C:\ecommer\ecommerce\docs\
Ngày 2 — hạ tầng local
Tạo compose.yaml với PostgreSQL 18 (pgvector/pgvector:pg18 hoặc postgres:18 + cài extension), Valkey 9, Mailpit, MinIO. Rồi:
docker compose up -d
docker compose exec postgres psql -U app -d ecommerce -c "SELECT version();"
docker compose exec postgres psql -U app -d ecommerce -c "CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS vector;"
docker compose exec postgres psql -U app -d ecommerce -c "SELECT uuidv7();" # xác nhận PG18
Cập nhật .env: DB_CONNECTION=pgsql, DB_PORT=5432, CACHE_STORE=redis, QUEUE_CONNECTION=redis, SESSION_DRIVER=redis, REDIS_HOST=127.0.0.1.
php artisan migrate
php artisan about # xác nhận PostgreSQL + Redis
Ngày 3 — công cụ chất lượng
composer require --dev pestphp/pest:^5.1 larastan/larastan:^3.10 rector/rector:^2.6 qossmic/deptrac
php artisan pest:install
Tạo phpstan.neon (level 6 + baseline), rector.php, deptrac.yaml (mục 7.4), pint.json. Viết arch test đầu tiên và cố tình vi phạm một lần để xác nhận nó bắt được.
Ngày 4 — bộ khung module
Tạo modules/Shared và modules/Catalog theo cấu trúc mục 7.1, khai báo path repository trong composer.json gốc, đăng ký service provider. Viết một use case xuyên suốt (tạo product) để xác nhận luồng Domain → Application → Infrastructure chạy được và deptrac xanh.
Ngày 5 — CI + tài liệu
New-Item -ItemType Directory C:\ecommer\ecommerce\.github\workflows -Force
Viết .github/workflows/ci.yml (job: lint / static / test với service container PostgreSQL + Valkey). Viết CLAUDE.md. Push lên remote, xác nhận CI xanh.
Điều kiện kết thúc tuần 1: CI xanh · php artisan about báo PostgreSQL 18 + Redis · deptrac + arch test đang cưỡng chế ranh giới thật · docs/01-master-plan.md đã được rà lại và chốt.
19. Danh mục tài liệu tiếp theo
| File | Nội dung | Viết ở pha |
|---|---|---|
01-master-plan.md |
Tài liệu này | P0 |
02-domain-model.md |
Chi tiết từng bounded context, aggregate, invariant, domain event | P0 |
03-database-schema.md |
DDL đầy đủ, ERD, chiến lược index, kế hoạch migration | P1 |
04-module-guide.md |
Hướng dẫn tạo module mới, quy ước đặt tên, mẫu code | P0 |
05-api-spec/ |
OpenAPI 3.1 (nguồn sự thật, sinh ra client + test) | P1 |
06-order-lifecycle.md |
Máy trạng thái đầy đủ, saga, kịch bản bù trừ | P2 |
07-payment-integration.md |
VNPay/MoMo/ZaloPay/COD, sổ kép, đối soát | P2 |
08-inventory-design.md |
Chống oversell, lô/HSD, đa kho, FEFO | P2 |
09-vietnam-compliance.md |
Hoá đơn điện tử, VAT, ND13, địa chỉ hành chính | P3 |
10-testing-strategy.md |
Chiến lược test, dữ liệu test, môi trường | P0 |
11-runbook.md |
Sự cố thường gặp, cách xử lý, liên hệ | P5 |
12-slo.md |
SLI/SLO, cảnh báo, chính sách error budget | P5 |
adr/NNNN-*.md |
Từng quyết định khó đảo, một file một quyết định | liên tục |
Quy tắc ADR: viết ADR khi quyết định thuộc loại "khó đảo" hoặc "gần như một chiều" (chọn CSDL, kiểu khoá chính, cách lưu tiền, hình thái kiến trúc). Không viết ADR cho quyết định dễ đảo — nó chỉ tạo nghi thức rỗng.
20. Các quyết định cần bạn xác nhận
Kế hoạch này đã tự quyết mọi thứ như bạn giao ("kiến trúc tuỳ chuyên gia chọn"). Bốn điểm dưới đây tôi đã chọn giúp, nhưng chúng phụ thuộc hoàn cảnh của bạn — nếu khác, nói để tôi điều chỉnh trước khi bắt tay vào P0:
| # | Điểm | Tôi đã chọn | Đổi nếu |
|---|---|---|---|
| 1 | Frontend | Blade + Livewire 4 + Filament 5 cho đến P6 | Bạn có người làm React/Next chuyên trách → tách headless ngay từ P1 |
| 2 | Quy mô đội | 1 người toàn thời gian (mọi ước lượng theo đó) | Đội đông hơn → rút ngắn và song song hoá được P1/P2 |
| 3 | Skeleton cũ | Tạo mới bằng laravel new, giữ example-app sang bên |
Bạn đã viết gì đó trong đó mà tôi chưa thấy |
| 4 | Mục tiêu thật | Sản phẩm chạy được thật, có học công nghệ khó theo lộ trình | Nếu mục tiêu chính là học công nghệ chứ không cần bán hàng → đảo thứ tự, đưa Kafka/K8s/ES lên sớm và chấp nhận không có sản phẩm |
Tài liệu #1 · Lập 13/08/2026 · Mọi số liệu phiên bản trong tài liệu đã được kiểm chứng bằng lệnh tại thời điểm lập.
All rights reserved