0

# 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: static property, 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 Request vào constructor → giữ mãi request đầu tiên.
  • Str::macro, Blade::directive đăng ký trong boot() 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):

  1. 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.
  2. Người thắng mới đi tiếp vào luồng reserve trên PostgreSQL (lúc này chỉ còn đúng bằng số suất, không còn tranh chấp).
  3. Đố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.
  4. 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_redemptionsUNIQUE(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_units với valid_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 orderssnapshot 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_rates phả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:

  1. 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.
  2. Khách đã đăng nhập → áp mã giảm giá → COD → đơn được tạo với đúng số tiền.
  3. 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.
  4. Đơn hàng → tạo vận đơn GHN → webhook giao thành công → đơn completed → điểm loyalty được cộng.
  5. 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):

  • readonly class + clone with → Value Object bất biến trong Domain/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/pennant 1.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ánh main được bảo vệ
  • [ ] Tạo mới bằng laravel new thay 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 .env sang pgsql, redis cho cache/queue/session
  • [ ] Dựng bộ khung modules/ + composer path repository + Shared kernel
  • [ ] 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:example-app chưa có một dòng code nghiệp vụ nào, tạo mới bằng laravel new là 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òn app/Http/Kernel.phpapp/Console/Kernel.php (thay bằng bootstrap/app.php), thư mục config/ 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_rates có 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/pennant 1.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/scout 11.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-sourcing 7.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ồi Notification)
  • [ ] 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 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ừ 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 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 không chia sẻ bảng với module khác độ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/Sharedmodules/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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí