Django production: best practice theo evidence cho backend API
Với Django production, ưu tiên correctness và failure mode thật: database integrity → transaction → authorization → security → query behavior → migration → background jobs → observability. Những quy tắc như “phải có services.py”, “fat model/thin view” hay “luôn dùng ViewSet” chỉ nên xem là convention hoặc lựa chọn kiến trúc theo context.
Context áp dụng
Note này được chắt lọc từ research về best practice Django cho một backend API có stack gần với project hiện tại:
- Django 5.1.
- Django REST Framework.
- PostgreSQL.
- Redis.
- Celery.
- SimpleJWT.
- Docker.
- Cloudflare Tunnel/reverse proxy.
- Cloudflare R2 cho media/object storage.
Mức rủi ro cần quan tâm không chỉ là code style mà còn gồm:
- authorization sai dẫn tới lộ dữ liệu;
- race condition khi nhiều request cùng ghi;
- task chạy trước khi transaction commit;
- N+1 query;
- migration gây lock production;
- upload không được giới hạn;
- reverse proxy/HTTPS config sai;
- dependency đã hết security support.
Cách phân loại practice
- Mức: Requirement / baseline — Ý nghĩa: Không làm có failure mode rõ về security, correctness hoặc support.
- Mức: Established good practice — Ý nghĩa: Có maintainer guidance và rationale kỹ thuật mạnh; nên là mặc định.
- Mức: Context-dependent — Ý nghĩa: Tốt với workload/architecture phù hợp; áp dụng máy móc có thể làm hệ thống tệ hơn.
- Mức: Preference / convention — Ý nghĩa: Chủ yếu phục vụ consistency/readability; không đủ cơ sở để gọi là universal best practice.
Mental model quan trọng:
"popular" ≠ "best practice"
"vendor recommends" ≠ "best in every context"
"clean-looking architecture" ≠ "correct production system"Đặc biệt với performance, phải đo workload thật trước khi tối ưu. Một rule như “field nào filter cũng phải index” hoặc “luôn dùng .exists()” không đáng tin nếu tách khỏi query plan và usage pattern thực tế.
Decision về phiên bản Django
Research gốc được thực hiện ngày 2026-09-09. Audit lại ngày 2026-09-16 cho thấy:
- Django 5.1 đã hết support từ 2025-12-03.
- Django 5.2 LTS hiện ở 5.2.17 và nhận extended support đến tháng 4/2028.
- Django 6.1.1 vẫn là latest official release tại thời điểm audit.
- DRF 3.18.1 là patch release hiện tại của series 3.18; project công bố support Django 5.2, 6.0 và 6.1.
- SimpleJWT 5.5.1 là release hiện tại. Metadata của release trên PyPI chỉ classifier đến Django 5.2, trong khi
setup.pytrên branchmasterđã có classifier Django 6.0 nhưng chưa có 6.1. Sự thiếu classifier 6.1 không tự động chứng minh package không chạy trên 6.1, nhưng cũng không phải bằng chứng compatibility đã được release chính thức xác nhận.
Recommendation cho project hiện tại
Ưu tiên:
Django 5.1
↓
Django 5.2 LTSthay vì nhảy thẳng lên 6.1 chỉ vì đó là bản mới nhất.
Lý do:
- quay lại supported branch;
- giảm dependency compatibility risk;
- ít migration/churn hơn;
- LTS phù hợp production khi không cần feature mới của 6.x.
Decision này cần xem xét lại khi:
- project thực sự cần feature chỉ có ở Django 6.x;
- toàn bộ dependency đã xác nhận compatibility;
- test suite đủ mạnh để validate upgrade;
- có lý do vận hành đủ lớn để bỏ LTS.
Security baseline trước khi bàn về architecture
Các cấu hình sau là production baseline, không phải style preference:
DEBUG=False;SECRET_KEYkhông commit vào repository;ALLOWED_HOSTSgiới hạn đúng host;- HTTPS cho toàn bộ production traffic;
- không dùng
runserverlàm production server; - database và Redis không expose trực tiếp ra Internet;
- có logging/error monitoring;
- có backup database và phải biết restore;
- chạy deployment checks của Django.
Verification đáng giữ:
python manage.py check --deployLệnh này không chứng minh hệ thống hoàn toàn an toàn, nhưng giúp phát hiện nhiều deployment setting nguy hiểm hoặc thiếu sót.
Reverse proxy và SECURE_PROXY_SSL_HEADER
Config kiểu:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")không được copy-paste mù quáng.
Chỉ nên bật khi:
- request thực sự đi qua proxy đáng tin cậy;
- proxy tự thiết lập header HTTPS;
- proxy loại bỏ hoặc overwrite giá trị header do client tự gửi.
Nếu trust boundary này sai, Django có thể hiểu request HTTP là HTTPS.
Với Cloudflare Tunnel/reverse proxy, phải xác minh behavior của ingress/proxy trước khi coi dòng config trên là đúng.
HTTPS, cookie, CSRF và CORS
Khi production chỉ chạy HTTPS và sử dụng cookie/session:
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = Truelà security practice đáng áp dụng.
HSTS hữu ích nhưng cần triển khai thận trọng. Các option như include-subdomains hoặc preload có hậu quả dài hạn, không nên bật chỉ vì “security checklist bảo vậy”.
Không dùng @csrf_exempt để chữa triệu chứng
Anti-pattern:
@csrf_exemptchỉ để request hết báo lỗi CSRF.
Cần phân biệt:
CORS
= browser có được phép gọi origin khác hay không
CSRF
= server có đang chấp nhận request dùng credential của user
mà không có bằng chứng request được user chủ động tạo hay khôngFix CORS không đồng nghĩa fix CSRF và ngược lại.
CSRF_COOKIE_HTTPONLY=True không phải universal security rule
Research trước đó ghi nhận chính Django xem option này chỉ đem lại ít practical protection trong nhiều trường hợp. Có thể cần vì audit/policy cụ thể, nhưng không nên gọi nó là best practice bắt buộc.
Authentication không thay thế authorization
Trong DRF:
Authentication
→ Ai đang gửi request?
Permission / Authorization
→ Người đó có được phép làm việc này không?Một endpoint có:
permission_classes = [IsAuthenticated]vẫn có thể lộ dữ liệu nếu queryset không scope theo user/tenant.
Ví dụ cần audit:
Order.objects.filter(user=request.user)hoặc một cơ chế authorization tương đương.
Failure mode quan trọng: object-level permission và list endpoint
Object-level permission không có nghĩa mọi object trong một list queryset tự động được filter an toàn.
Do đó:
Queryset scope
+
permission checkphải được thiết kế cùng nhau.
Đây là một trong những điểm nên ưu tiên cao nhất khi audit API order/cart/profile hoặc bất kỳ dữ liệu user-owned nào.
JWT là lựa chọn kiến trúc, không phải “Django best practice”
JWT hợp lý khi backend phục vụ:
- mobile app;
- Zalo Mini App;
- API client;
- frontend/backend tách biệt;
- service-to-service.
Nhưng câu:
"REST API thì nên dùng JWT"không phải universal rule.
First-party browser app có thể đơn giản hơn với Django session auth vì session lifecycle, logout và revocation đã có semantics phù hợp.
Với Mini App → Django API, bearer token/JWT là lựa chọn hợp lý do architecture, không phải vì JWT “modern hơn”.
Database là lớp phòng thủ cuối cho invariant
Business validation ở serializer hoặc form rất hữu ích để tạo error message dễ hiểu, nhưng không đủ để đảm bảo correctness dưới concurrency.
Ví dụ invariant:
Một user chỉ được có một active cart.Code kiểu:
if not Cart.objects.filter(...).exists():
Cart.objects.create(...)vẫn có race condition khi hai request chạy đồng thời.
Nếu invariant có thể biểu diễn ở database, ưu tiên constraint như:
UniqueConstraint(...)
CheckConstraint(...)Mental model:
Serializer/form validation
→ UX và validation sớm
Database constraint
→ correctness cuối cùng, kể cả dưới concurrencyĐây là established good practice mạnh hơn việc cố giữ mọi rule trong Python.
Transaction boundary phải theo business operation
Một flow như:
create order
→ create order items
→ decrement stock
→ create payment recordnên có transaction boundary theo toàn bộ business operation.
Dùng explicit transaction:
from django.db import transaction
with transaction.atomic():
...thay vì wrap mọi request chỉ vì tiện.
Vì sao không mặc định ATOMIC_REQUESTS=True
ATOMIC_REQUESTS=True wrap mỗi request trong transaction.
Trade-off:
- dễ reasoning cho write request đơn giản;
- nhưng tạo transaction overhead cho cả request;
- giữ transaction mở lâu hơn;
- có thể giảm throughput khi traffic tăng.
Với backend này, ưu tiên explicit atomic() tại operation thực sự cần atomicity.
Không catch database error sai bên trong atomic()
Pattern cần tránh:
with transaction.atomic():
try:
save_something()
except IntegrityError:
...Sau database error, transaction có thể ở trạng thái broken cho tới rollback.
Thực tế nên đặt exception boundary phù hợp bên ngoài transaction hoặc dùng nested atomic() khi cần một savepoint riêng.
Điểm cần nhớ không phải syntax cụ thể mà là:
“Sau database error, đừng giả định transaction hiện tại vẫn dùng bình thường.
”
Concurrency: chọn đúng công cụ
F() cho atomic update đơn giản
Code dễ bị lost update:
product.stock -= 1
product.save()Hai worker cùng đọc stock=10 có thể cùng ghi 9, trong khi kết quả đúng phải là 8.
Với arithmetic update đơn giản:
from django.db.models import F
Product.objects.filter(...).update(
stock=F("stock") - 1
)cho database thực hiện update dựa trên giá trị hiện tại.
select_for_update() cho read → decision → write
Khi phải đọc row, kiểm tra state rồi mới quyết định write:
from django.db import transaction
with transaction.atomic():
order = (
Order.objects
.select_for_update()
.get(...)
)
...select_for_update() lock row tới khi transaction kết thúc.
Không nên dùng khắp nơi vì lock có cost:
- giảm concurrency;
- tăng waiting;
- có thể tạo deadlock/contention.
Chỉ dùng khi invariant thực sự đòi serialization.
Celery phải tôn trọng transaction boundary
Anti-pattern:
order = Order.objects.create(...)
send_order.delay(order.id)nếu code đang nằm trong một transaction chưa commit.
Worker có thể chạy trước commit và:
- không tìm thấy
Order; - đọc state cũ;
- thực hiện side effect dù transaction cuối cùng rollback.
Dùng:
from django.db import transaction
transaction.on_commit(
lambda: send_order.delay(order.id)
)Mental model:
DB transaction thành công
↓
commit
↓
enqueue Celery taskchứ không phải:
enqueue task
↓
hy vọng database commit kịpCelery task quan trọng phải idempotent
Background task có thể được redeliver hoặc chạy lại trong failure scenario.
Do đó task nên chịu được retry khi có thể.
Ví dụ tác động thấp:
send_email(order_id)có thể chấp nhận duplicate hoặc dedupe.
Ví dụ tác động cao:
charge_credit_card(order_id)không được phép charge hai lần.
Các operation có side effect tài chính hoặc state quan trọng cần idempotency strategy như:
- idempotency key;
- unique constraint;
- state transition kiểm tra rõ;
- deduplication record.
Không giả định queue delivery đồng nghĩa business operation chạy chính xác một lần.
Django Tasks không tự thay thế Celery
Django 6.x đã có Tasks framework trong core, nhưng nó cung cấp API/contract cho task chứ không tự biến Django thành production distributed queue.
Nếu project hiện tại đã dùng:
Redis + Celeryvà nó giải quyết đúng nhu cầu, không nên refactor chỉ vì Django có Tasks API mới.
Đây là ví dụ điển hình của:
new feature ≠ reason to rewrite working infrastructureQuery performance: ưu tiên N+1 trước micro-optimization
DRF serializer rất dễ tạo N+1 khi traverse relationship.
Ví dụ:
orders = Order.objects.all()
for order in orders:
print(order.customer.name)có thể thành:
1 query lấy orders
+
N query lấy customerCông cụ thường dùng:
select_related(...)
prefetch_related(...)Nhưng không nên add relation preload hàng loạt nếu endpoint không cần.
Workflow đúng:
đo query count
→ xác định N+1 thật
→ thêm select_related/prefetch_related
→ đo lạiDRF serializer không tự optimize queryset giúp mình.
Index phải dựa trên workload và query plan
Rule:
"field nào filter cũng index"là cargo cult nếu dùng như luật.
Index có cost:
- tăng disk;
- làm insert/update/delete tốn hơn;
- PostgreSQL phải maintain;
- query planner có thể không dùng.
Candidate thường đáng xem xét là field xuất hiện nhiều trong:
WHERE
JOIN
ORDER BYnhưng decision nên dựa vào query thật.
Dùng:
queryset.explain()để xem query plan khi cần.
Mental model:
profile
→ inspect query
→ inspect plan
→ change
→ measure again.exists() và .count() không phải lúc nào cũng “tối ưu”
Nếu chỉ cần boolean:
qs.exists()thường hợp lý.
Nhưng:
if qs.exists():
for item in qs:
...có thể tạo hai query thay vì chỉ evaluate queryset một lần.
Tương tự với .count().
Không học ORM optimization dưới dạng rule:
"always use exists()"mà phải nhìn vào lifecycle của queryset.
Pagination cho collection không bounded
Endpoint kiểu:
GET /orderskhông nên trả vô hạn khi dữ liệu tăng.
Failure mode:
- query time tăng;
- RAM tăng;
- serialization cost tăng;
- payload lớn;
- client render chậm.
Generic views/ViewSet của DRF hỗ trợ pagination mechanism, nhưng custom APIView không tự paginate nếu mình không implement.
DRF throttling không phải DDoS protection
DRF throttling phù hợp để:
- giới hạn usage;
- business policy;
- basic abuse control.
Không nên coi nó là lớp bảo vệ DDoS hoặc brute-force hoàn chỉnh.
Với architecture có Cloudflare:
Internet
↓
Cloudflare / edge rate limit / WAF
↓
Django / DRF throttle
↓
applicationEdge nên chịu phần lớn volumetric/abuse protection; DRF throttle là policy layer bổ sung.
Migration: local thành công không chứng minh production an toàn
Migration nhanh trên laptop không nói nhiều về behavior trên table lớn production.
Data migration phải dùng historical model
Trong migration, ưu tiên:
apps.get_model(...)thay vì:
from orders.models import OrderImport model hiện tại có thể khiến migration cũ hỏng sau khi model evolve.
Table lớn và index
PostgreSQL có concurrent index creation và Django hỗ trợ operation như:
AddIndexConcurrentlyđể giảm blocking write so với index creation thông thường.
Đây chỉ trở thành ưu tiên cao khi table đủ lớn và downtime/lock có ý nghĩa.
Data migration lớn
Có thể cần:
- batching;
- non-atomic strategy;
- tách schema migration và data backfill;
- verification theo từng bước.
Không áp dụng phức tạp này cho table nhỏ chỉ vì “zero-downtime best practice”.
Upload: giới hạn ở nhiều lớp
User upload là untrusted input.
Chỉ dựa vào Django upload settings không đủ cho request rất lớn.
Với stack dùng Cloudflare và R2, mental model tốt hơn:
Internet
↓
Cloudflare / proxy request-size limit
↓
Django authorization + validation
↓
R2 object storageKhông nên để media lớn sống trong container Django nếu object storage đã là architecture target.
Redis/cache: chỉ cache sau khi có lý do đo được
Redis có thể giúp nhưng cache thêm:
- stale data;
- invalidation logic;
- memory cost;
- dependency vận hành;
- nguy cơ leak dữ liệu giữa user/tenant nếu cache key sai.
Anti-pattern:
"Có Redis rồi thì cache API"Đặc biệt với response phụ thuộc user, cache key phải encode đủ authorization scope.
Caching là context-dependent optimization, không phải default correctness layer.
Signals: tránh giấu core workflow
Signals tạo loose coupling ở bề mặt nhưng dễ làm execution flow khó lần theo.
Core business flow kiểu:
Order created
→ decrement stock
→ create payment
→ enqueue receiptkhông nên bị giấu thành:
post_save
→ signal A
→ signal B
→ task CSignals hợp hơn với event thực sự cross-cutting hoặc extension-like, nhưng ngay cả vậy explicit call thường dễ debug hơn khi sender/receiver đều nằm trong cùng project.
Service layer là công cụ, không phải luật
Cấu trúc như:
services.py
selectors.pycó thể hữu ích, nhưng không phải requirement của Django.
Service/use-case layer đáng xuất hiện khi operation:
span nhiều model
+ transaction
+ external API
+ Celery
+ được gọi từ API/admin/taskVí dụ:
place_order(...)
cancel_order(...)
confirm_payment(...)thường có cohesion tốt ở use-case/service boundary.
Không cần bọc CRUD đơn giản:
get_product_by_id()
create_category()
update_customer_name()chỉ để project “clean”.
Architecture recommendation cho project này
DRF view
↓
HTTP concern + permission orchestration
Serializer
↓
parsing + API validation
Explicit service/use-case
↓
chỉ khi business operation đủ phức tạp
Models / QuerySets
↓
data behavior và query composition
PostgreSQL constraints
↓
final integrity boundary
transaction.on_commit()
↓
Celery
↓
slow/external side effectsKhông ép mọi request phải đi qua đủ mọi layer.
CRUD đơn giản có thể:
view/serializer
→ model/querysetWorkflow như đặt món, tồn kho, thanh toán hoặc hoàn tiền mới đáng có explicit use-case boundary.
“Fat models, thin views” chỉ là heuristic
Method như:
order.cancel()rất hợp nếu behavior chủ yếu thuộc Order.
Nhưng nếu cancel() phải:
refund payment provider
→ restore stock
→ release voucher
→ notify user
→ write audit eventthì model dễ biến thành god object.
Rule nên giữ:
“Đặt business logic ở nơi có cohesion tốt nhất và dependency rõ nhất, không theo khẩu hiệu “mọi logic phải ở model” hoặc “mọi logic phải ở service”.
”
ViewSet, generic view và APIView
Không có winner universal.
- Tool:
ModelViewSet— Phù hợp: CRUD resource khá chuẩn - Tool: Generic views — Phù hợp: CRUD nhưng muốn explicit hơn
- Tool:
APIView— Phù hợp: Workflow/custom endpoint - Tool: Function-based view — Phù hợp: Endpoint nhỏ và đơn giản
Đây là architecture/convention choice. Đừng refactor chỉ để thống nhất theo một trường phái nếu current behavior rõ và maintainable.
Serializer không phải domain layer
Serializer phù hợp với:
parse request
type validation
input validation
representation
API-specific errorNếu Serializer.create() dần chứa:
transaction
inventory
payment gateway
voucher
Celery
notification
analyticsthì use case đã vượt khỏi serialization concern.
Khi đó explicit domain/service operation giúp reuse và test tốt hơn.
Không cần extract serializer CRUD ngắn chỉ vì styleguide bảo vậy.
Custom User: quyết định phụ thuộc lifecycle
Cho project mới, custom user từ đầu thường đáng làm, thường bằng AbstractUser, vì đổi user model sau này phức tạp.
Nhưng với project đã production ổn bằng default User:
"đổi sang custom user để theo best practice"không phải lý do đủ mạnh.
Lifecycle quyết định recommendation.
null=True trên CharField không phải security rule
Tránh cả:
NULL
""cùng biểu diễn “không có dữ liệu” thường giúp model nhất quán.
Nhưng đây chủ yếu là data-model convention và có ngoại lệ hợp lệ. Không nên biến thành lint rule tuyệt đối.
Async Django: dùng khi workload thật sự cần
Async hữu ích với nhiều concurrent I/O như:
external API calls
streaming
long polling
websocket-related workloadNhưng:
async ≠ ORM tự nhanh hơnvà transaction-sensitive code vẫn cần cân nhắc sync boundary theo support của Django.
Đổi toàn bộ views sang async def chỉ vì “async nhanh hơn” là anti-pattern.
Testing: test failure mode, không chase coverage %
Ưu tiên test:
- business invariant;
- permission và ownership;
- transaction quan trọng;
- Celery idempotency;
- migration có rủi ro;
- query count/N+1 ở endpoint quan trọng.
Không có lý do kỹ thuật chung để ép 100% coverage.
Transaction/concurrency test
TestCase phù hợp phần lớn test vì nhanh.
Nhưng behavior như select_for_update() cần test đúng transaction semantics; TransactionTestCase phù hợp hơn cho các scenario này.
Với PostgreSQL-specific behavior như:
- locking;
- constraint;
- query planner;
- PostgreSQL migration operation;
test quan trọng nên chạy trên PostgreSQL thật thay vì giả định SQLite tương đương.
Observability là production requirement, structured logging chỉ là implementation choice
Production backend phải giúp trả lời:
request nào lỗi?
endpoint nào chậm?
query nào chậm?
Celery task nào fail?
deployment nào bắt đầu regression?Nên có khả năng correlate:
request ID
task ID
status
latency
important domain eventKhông log secret hoặc credential:
Authorization header
password
token
secret
raw sensitive payloadStructured logging là preference/implementation detail; khả năng debug production là established good practice.
Anti-pattern và advice lỗi thời cần tránh
- Advice: Mỗi model phải có service — Đánh giá: Cargo cult
- Advice: Mọi business logic phải ở model — Đánh giá: Heuristic bị áp dụng quá mức
- Advice: Mọi business logic phải ra service — Đánh giá: Heuristic bị áp dụng quá mức
- Advice: Luôn dùng ViewSet — Đánh giá: Convention
- Advice: Luôn dùng APIView — Đánh giá: Convention
- Advice: Phải có
selectors.py— Đánh giá: Convention - Advice: Luôn có repository layer trên Django ORM — Đánh giá: Context-dependent
- Advice: Settings bắt buộc phải tách
base/dev/prod.py— Đánh giá: Convention - Advice:
.envtự nó là secret-management best practice — Đánh giá: Sai abstraction - Advice: UUID luôn tốt hơn integer PK — Đánh giá: Context-dependent
- Advice: Soft delete luôn tốt hơn hard delete — Đánh giá: Domain-dependent
- Advice: Có Redis thì nên cache mọi thứ — Đánh giá: Cargo cult
- Advice: Async luôn nhanh hơn sync — Đánh giá: Sai
- Advice: Field nào filter cũng cần index — Đánh giá: Cargo cult
- Advice:
.exists()luôn nhanh hơn — Đánh giá: Context-dependent - Advice: Signals luôn giúp decouple — Đánh giá: Thường làm flow khó trace
- Advice:
ATOMIC_REQUESTS=Trueluôn tốt — Đánh giá: Context-dependent - Advice: JWT luôn tốt hơn session — Đánh giá: Architecture-dependent
- Advice: DRF throttle chống DDoS — Đánh giá: Sai
- Advice: Serializer validation đủ bảo vệ invariant — Đánh giá: Sai dưới concurrency
- Advice: Latest Django luôn là production choice tốt nhất — Đánh giá: Sai
Khi nào recommendation cần đổi
- Context: Internal CRUD tool ít user — Điều cần xem lại: Có thể đơn giản hóa migration, cache, observability
- Context: Table lớn / traffic cao — Điều cần xem lại: Online migration, index strategy, pooling trở nên quan trọng
- Context: First-party browser app — Điều cần xem lại: Session auth có thể tốt hơn JWT
- Context: Mobile/Mini App/API ecosystem — Điều cần xem lại: Bearer/JWT hợp lý hơn
- Context: Không có concurrent writes — Điều cần xem lại: Locking có thể không cần
- Context: Heavy external I/O — Điều cần xem lại: Async/ASGI đáng cân nhắc hơn
- Context: Project mới — Điều cần xem lại: Custom User từ đầu đáng làm
- Context: Project production lâu năm — Điều cần xem lại: Không đổi user architecture chỉ vì convention
- Context: Không có background job — Điều cần xem lại: Celery có thể là complexity không cần thiết
- Context: Upload file lớn — Điều cần xem lại: Direct-to-object-storage càng đáng cân nhắc
- Context: Cần feature Django 6.x — Điều cần xem lại: Upgrade sau dependency/test validation
- Context: Ưu tiên stability — Điều cần xem lại: Django 5.2 LTS là target hợp lý hơn cho project 5.1 hiện tại
Checklist audit thực tế cho backend hiện tại
Thứ tự này ưu tiên failure mode nghiêm trọng trước code cleanliness.
Version
- Nâng Django 5.1 → 5.2 LTS.
- Kiểm tra compatibility của DRF, SimpleJWT và package liên quan.
- Chạy full test và migration verification.
Production security
Kiểm tra:
DEBUG
SECRET_KEY
ALLOWED_HOSTS
HTTPS
proxy headers
CORS
CSRF
secure cookies
database exposure
Redis exposureSau đó:
python manage.py check --deployAuthorization
Audit mọi endpoint dữ liệu user-owned:
orders
cart
profile
address
voucher
payment-related objectXác minh queryset scope và object authorization, không chỉ nhìn IsAuthenticated.
Data integrity và concurrency
Tìm invariant hiện chỉ được giữ bằng Python.
Xem xét:
UniqueConstraint
CheckConstraint
F()
transaction.atomic()
select_for_update()chỉ ở nơi failure mode thật sự tồn tại.
Celery boundary
Xác minh:
task enqueue sau commit
task quan trọng idempotent
core workflow không bị giấu trong signal chainQuery behavior
Đo:
query count
N+1
slow query
query planrồi mới quyết định:
select_related
prefetch_related
index
cacheMigration và upload
Kiểm tra:
- data migration dùng historical model;
- migration trên table lớn có lock risk;
- request/upload size được giới hạn ở proxy lẫn app;
- media phù hợp được đưa ra object storage như R2.
Architecture refactor
Chỉ sau correctness/security/performance audit mới quyết định có cần:
service/use-case layer
selector
custom abstractionKhông refactor project để “trông giống styleguide” nếu chưa giải quyết failure mode thực tế.
Mental model cuối cùng
Có thể dùng thứ tự sau khi review một Django backend:
1. Version còn support không?
↓
2. Request có được authenticate + authorize đúng không?
↓
3. Database có giữ invariant khi concurrent không?
↓
4. Transaction boundary đúng business operation không?
↓
5. Side effect/task có chạy đúng sau commit và chịu retry không?
↓
6. Query có scale không?
↓
7. Migration/deploy có an toàn không?
↓
8. Có đủ observability để debug production không?
↓
9. Sau đó mới tối ưu architecture/style.Đây là thứ tự đáng giữ hơn bất kỳ “Django folder structure chuẩn” nào.
References
Các nguồn dưới đây đã được dùng trong research trước đó và đáng giữ để kiểm chứng lại theo version. Audit ngày 2026-09-16 cũng kiểm tra lại version/compatibility bằng các nguồn hiện hành sau: