Yêu cầu với lập trình viên
Chuẩn riêng cho Chat, Task và Bán hàng
Ba module của Alliance App có ba loại rủi ro khác nhau, nên có ba bộ luật khác nhau. Đây là phần bắt buộc khi làm việc trong từng module.
Nội dung bài
Vì sao mỗi module có luật riêng
Áp một bộ chuẩn chung cho cả ba module thì hoặc quá lỏng với module rủi ro cao, hoặc quá nặng với module rủi ro thấp. Nên ta tách:
| Module | Thứ dễ hỏng nhất | Người dùng cảm nhận lỗi thế nào |
|---|---|---|
| Chat | Thứ tự, trùng lặp, mất tin khi mạng chập chờn | Mất niềm tin ngay lập tức |
| Task | Phân quyền, trạng thái không nhất quán, thông báo sai | Làm sai việc, mất thời gian |
| Bán hàng | Sai tiền, đơn trùng, không truy được vết | Mất tiền thật, tranh chấp |
Mức độ nghiêm ngặt tăng dần từ trên xuống dưới.
Chat
Luật bắt buộc
- Mỗi tin nhắn có
clientMessageIddo client sinh ra trước khi gửi, và giữ nguyên qua mọi lần gửi lại. Server dùng nó để khử trùng. Không có nó thì mất mạng lúc gửi sẽ sinh tin nhắn đôi — lỗi này người dùng nhìn thấy ngay. - Gửi khi mất mạng phải vào hàng đợi cục bộ, hiển thị trạng thái đang gửi, tự gửi lại khi có mạng, và giữ đúng thứ tự trong cùng một cuộc hội thoại.
- Thứ tự hiển thị dựa trên thứ tự do server chốt, không dựa trên giờ của máy người dùng. Đồng hồ máy người dùng có thể sai hàng giờ.
- Gửi lại phải có giới hạn: số lần thử, và khoảng cách tăng dần. Gửi lại vô hạn sẽ làm nóng máy, tốn pin, và đánh sập server khi có sự cố mạng diện rộng.
- Phân trang lịch sử hội thoại theo con trỏ, không theo số trang. Dữ liệu chèn vào giữa liên tục nên phân trang theo số trang sẽ nhảy và lặp.
- Không log nội dung tin nhắn ở bất kỳ mức nào, kể cả khi debug. Chỉ log định danh và độ dài.
- Giải phóng kết nối thời gian thực khi app vào nền và kết nối lại khi quay ra, có xử lý trường hợp hệ điều hành đã thu hồi app.
Ngân sách hiệu năng
| Chỉ số | Ngưỡng |
|---|---|
| Tin nhắn hiện trên máy người nhận | Dưới 1 giây ở mạng bình thường |
| Mở một hội thoại có sẵn | Dưới 400 ms tới khi thấy tin cũ |
| Cuộn lịch sử 1000 tin | Không rớt khung hình thấy được |
Bắt buộc test trước khi mở PR
- Gửi tin khi bật chế độ máy bay, bật mạng lại — tin phải tới, đúng một lần, đúng thứ tự.
- Gửi liên tiếp 20 tin ở mạng chậm — không đảo thứ tự, không mất.
- Hai thiết bị cùng một tài khoản — trạng thái đã đọc đồng bộ đúng.
- Giết app giữa lúc đang gửi, mở lại — tin trong hàng đợi vẫn được gửi.
- Hội thoại có 5000 tin — cuộn lên đầu không treo, không ngốn bộ nhớ tăng vô hạn.
Task
Luật bắt buộc
- Phân quyền kiểm ở server, luôn luôn. Ẩn nút trên giao diện là trải nghiệm, không phải bảo mật. Mọi endpoint phải tự kiểm tra quyền của người gọi, không tin tham số từ client.
- Trạng thái task đi theo máy trạng thái có khai báo rõ. Chuyển trạng thái không hợp lệ phải bị từ chối ở server kèm mã lý do, không phải bị chặn bằng cách ẩn nút.
- Mọi thay đổi task ghi vào lịch sử thay đổi: ai, lúc nào, từ giá trị nào sang giá trị nào. Đây là thứ duy nhất giải quyết được tranh cãi "tôi đâu có đổi".
- Hạn công việc lưu theo UTC, hiển thị theo múi giờ người dùng. Không bao giờ lưu giờ địa phương. Đây là nguồn bug kinh điển của mọi hệ thống có deadline.
- Thông báo phải khử trùng. Một sự kiện sinh đúng một thông báo, kể cả khi có nhiều thiết bị, kể cả khi server gửi lại.
- Giao diện lạc quan phải rollback được. Nếu cập nhật ngay trên máy rồi server từ chối, giao diện phải quay về đúng trạng thái cũ và nói rõ vì sao — không được để hai bên lệch nhau âm thầm.
- Sửa đồng thời phải phát hiện được xung đột. Hai người sửa cùng một task thì người sau phải được báo, không được ghi đè im lặng.
Bắt buộc test trước khi mở PR
- Tài khoản không có quyền gọi thẳng endpoint — phải bị từ chối, không chỉ bị ẩn nút.
- Mọi chuyển trạng thái hợp lệ và ít nhất ba chuyển trạng thái không hợp lệ.
- Người dùng ở múi giờ khác xem cùng một hạn công việc — hiển thị đúng.
- Hai người cùng sửa một task — có cảnh báo xung đột, không mất dữ liệu.
- Thao tác khi mất mạng — giao diện lạc quan quay về đúng trạng thái cũ.
- Task có 200 subtask và 100 bình luận — màn hình vẫn mở được.
Bán hàng
Module này đụng tới tiền, nên luật ở đây là nghiêm ngặt nhất và không có ngoại lệ nào vì lý do tiến độ.
Luật bắt buộc
- Không dùng số thực (float, double) cho tiền. Không bao giờ. Lưu bằng số nguyên theo
đơn vị nhỏ nhất (đồng), hoặc kiểu decimal có độ chính xác cố định. Tên biến phải mang
đơn vị:
totalInVnd, không phảitotal. - Quy tắc làm tròn được khai báo ở một chỗ duy nhất và có test riêng. Làm tròn rải rác mỗi nơi một kiểu là cách chắc chắn nhất để lệch sổ.
- Mọi thao tác tạo đơn và thanh toán phải idempotent, dùng khoá idempotency do client sinh. Gửi lại cùng một khoá phải trả về cùng một kết quả, không tạo đơn thứ hai.
- Nút gửi bị vô hiệu hoá trong lúc chờ, và server vẫn phải tự chống trùng — giao diện là lớp phòng thủ thứ nhất, không phải lớp duy nhất.
- Tính tiền cuối cùng do server quyết. Client tính để hiển thị cho mượt, nhưng số tiền ghi vào đơn luôn là số server tính lại. Không bao giờ tin giá do client gửi lên.
- Không lưu dữ liệu thẻ thanh toán ở bất kỳ đâu trong hệ thống của ta — không trong database, không trong log, không trong bộ nhớ đệm, không trong báo cáo lỗi.
- Lịch sử giao dịch chỉ ghi thêm, không sửa, không xoá. Sai thì tạo bản ghi điều chỉnh mới trỏ về bản ghi cũ. Xoá một bản ghi tiền là phá dấu vết kiểm toán.
- Mọi thay đổi trạng thái đơn ghi lại đủ: ai, lúc nào, giá trị trước, giá trị sau, nguyên nhân. Giữ tối thiểu theo thời hạn kế toán yêu cầu.
- Luồng hoàn tiền và huỷ đơn phải được làm cùng lúc với luồng bán, không để lại sau. Bán được mà không hoàn được là lỗi nghiệp vụ nghiêm trọng, không phải tính năng thiếu.
- Thay đổi chạm tới tính tiền, thanh toán, hoặc hoàn tiền cần 2 approve, trong đó một người là chủ module Bán hàng.
Bắt buộc test trước khi mở PR
- Bảng test tính tiền có đủ: giá lẻ, nhiều mức giảm giá chồng nhau, thuế, số lượng lớn, và các ca làm tròn ở biên.
- Gửi lại cùng một khoá idempotency 5 lần — chỉ sinh một đơn.
- Bấm nút thanh toán hai lần thật nhanh — chỉ một giao dịch.
- Mất mạng sau khi server đã nhận nhưng trước khi client nhận phản hồi — mở lại app phải thấy đúng một đơn, đúng trạng thái.
- Hai người mua cùng lúc món cuối cùng trong kho — đúng một người thành công.
- Huỷ và hoàn tiền một đơn đã thanh toán — số dư và lịch sử khớp.
- Đối chiếu tổng doanh số từ danh sách đơn với báo cáo tổng hợp — phải khớp tuyệt đối.
Một quy tắc riêng cho tiền
Bất kỳ ai phát hiện một sai lệch tiền, dù nhỏ, báo ngay và dừng release liên quan. Sai lệch tiền không bao giờ là "sai số cho phép". Một đồng lệch không giải thích được nghĩa là mô hình tính đang sai ở đâu đó, và nó sẽ lệch lớn khi khối lượng tăng lên.
Phần dùng chung giữa các module
Code nằm ở tầng dùng chung chịu luật nghiêm ngặt nhất trong ba module, vì hỏng một chỗ là hỏng cả ba:
- Cần 2 approve, trong đó một người là chủ kỹ thuật.
- Bắt buộc có test cho mọi nhánh logic.
- Thay đổi phá vỡ tương thích phải có kế hoạch chuyển đổi, không đổi thẳng.
- Xem thêm ranh giới trách nhiệm ở phạm vi sản phẩm.

