Tìm hiểu cách các framework backend ảnh hưởng đến cấu trúc thư mục, ranh giới, kiểm thử và quy trình đội — để đội có thể phát hành nhanh hơn với mã nhất quán và dễ bảo trì.

Một framework backend không chỉ là một tập hợp thư viện. Thư viện giúp bạn làm việc cụ thể (routing, validation, ORM, logging). Một framework bổ sung một “cách làm” có quan điểm: cấu trúc dự án mặc định, pattern chung, công cụ tích hợp và quy tắc về cách các phần kết nối với nhau.
Khi một framework được áp dụng, nó dẫn dắt hàng trăm lựa chọn nhỏ:
Đây là lý do hai đội xây “cùng API” vẫn có thể có codebase rất khác nhau — dù dùng cùng ngôn ngữ và DB. Quy ước của framework trở thành câu trả lời mặc định cho “chúng ta làm thế nào ở đây?”.
Framework thường đánh đổi tính linh hoạt lấy cấu trúc dễ đoán. Ưu điểm là onboarding nhanh hơn, ít tranh luận hơn, và pattern tái sử dụng giảm độ phức tạp vô tình. Nhược điểm là quy ước framework có thể cảm thấy hạn chế khi sản phẩm của bạn cần luồng công việc khác thường, tuning hiệu năng, hoặc kiến trúc không chuẩn.
Quyết định tốt không phải là “dùng framework hay không”, mà là muốn có bao nhiêu quy ước—và đội có sẵn sàng trả chi phí tuỳ chỉnh theo thời gian không.
Hầu hết đội không bắt đầu với thư mục trống — họ bắt đầu với layout “khuyến nghị” của framework. Những mặc định đó quyết định nơi đặt mã, cách đặt tên và cái gì cảm thấy “bình thường” trong review.
Một số framework thúc đẩy cấu trúc theo lớp cổ điển: controllers / services / models. Nó dễ học và map rõ ràng cho xử lý request:
/src
/controllers
/services
/models
/repositories
Những framework khác nghiêng về module theo tính năng: gom mọi thứ của một tính năng lại với nhau (HTTP handlers, domain rules, persistence). Cách này khuyến khích suy nghĩ cục bộ — khi bạn làm việc trên “Billing”, bạn mở một thư mục:
/src
/modules
/billing
/http
/domain
/data
Không loại nào tự động tốt hơn, nhưng mỗi kiểu định hình thói quen. Cấu trúc theo lớp giúp chuẩn hóa các tiêu chuẩn cross-cutting (logging, validation, error handling). Cấu trúc theo module giảm việc phải “cuộn ngang” khắp codebase khi nó lớn lên.
CLI generators (scaffolding) có độ bám dính cao. Nếu generator tạo controller + service cho mỗi endpoint, mọi người sẽ tiếp tục làm như vậy — dù một hàm đơn giản hơn có thể đủ. Nếu nó sinh module với ranh giới rõ ràng, đội sẽ có xu hướng tôn trọng ranh giới đó khi deadline căng.
Hiệu ứng tương tự xuất hiện trong workflow “vibe-coding”: nếu mặc định nền tảng tạo layout dự đoán và ranh giới module rõ, đội thường giữ codebase mạch lạc khi nó phình to. Ví dụ, Koder.ai sinh app full-stack từ prompt chat, và lợi ích thực tế (ngoài tốc độ) là đội có thể chuẩn hóa cấu trúc và pattern sớm — rồi lặp trên chúng như bất kỳ codebase nào khác (bao gồm xuất mã nguồn khi bạn muốn toàn quyền kiểm soát).
Frameworks làm controllers nổi bật có thể cám dỗ đội nhét logic nghiệp vụ vào request handlers. Quy tắc hữu ích: controllers chỉ dịch HTTP → cuộc gọi ứng dụng, và không hơn. Đưa logic nghiệp vụ vào service/use-case (hoặc domain layer của module) để có thể kiểm thử mà không cần HTTP và tái dùng cho background jobs hoặc CLI tasks.
Nếu bạn không trả lời được “Logic giá cả nằm ở đâu?” trong một câu, mặc định framework có thể đang chống lại domain của bạn. Điều chỉnh sớm — thư mục dễ thay đổi; thói quen thì không.
Một framework backend cung cấp một cách có quan điểm để xây dựng ứng dụng: cấu trúc dự án mặc định, quy ước vòng đời request (routing → middleware → controllers/handlers), công cụ tích hợp sẵn, và các pattern được “chọn sẵn”. Các thư viện thường giải quyết các vấn đề rời rạc (routing, validation, ORM) nhưng không ép buộc cách mà những phần đó kết hợp lại trong toàn đội.
Các quy ước của framework trở thành câu trả lời mặc định cho các câu hỏi hàng ngày: mã nên đặt ở đâu, request chảy như thế nào, lỗi được định dạng ra sao, và các dependency được wired thế nào. Sự nhất quán đó giúp onboarding nhanh hơn và giảm tranh luận trong review, nhưng cũng gây “khóa” vào một số pattern mà sau này khó uốn nắn.
Chọn cách tổ chức theo lớp khi bạn muốn tách rõ các mối quan tâm kỹ thuật và dễ tập trung hóa các hành vi cros-cutting (auth, validation, logging).
Chọn feature modules khi bạn muốn các đội làm việc “cục bộ” theo năng lực kinh doanh (ví dụ Billing) mà không phải nhảy qua nhiều thư mục.
Dù chọn gì, hãy ghi lại quy tắc và thực thi trong review để cấu trúc không bị xộc xệch khi codebase lớn lên.
Dùng generator để tạo shell nhất quán (routes/controllers, DTOs, test stubs), rồi coi đầu ra là điểm bắt đầu — không phải kiến trúc cuối cùng.
Nếu scaffolding luôn sinh controller+service+repo cho mọi thứ, nó có thể tạo thủ tục thừa cho những endpoint đơn giản. Thỉnh thoảng hãy rà soát mẫu sinh và cập nhật template để khớp với cách bạn thực sự xây tính năng.
Giữ controllers tập trung vào dịch HTTP:
Đưa logic nghiệp vụ vào application/service hoặc domain layer để nó có thể tái sử dụng (jobs/CLI) và dễ kiểm thử mà không cần khởi động web stack.
Middleware nên làm giàu hoặc bảo vệ request, không nên thực thi quy tắc sản phẩm.
Ví dụ phù hợp:
Quyết định nghiệp vụ (pricing, eligibility, workflow branching) nên nằm trong services/use-cases để test và tái dùng được.
DI cải thiện khả năng test và làm cho việc thay thế triển khai dễ hơn (ví dụ đổi nhà cung cấp thanh toán hoặc dùng fake trong test) bằng cách khai báo dependencies rõ ràng.
Giữ DI dễ hiểu bằng cách:
Nếu gặp circular dependencies, thường đó là dấu hiệu ranh giới không rõ — chứ không phải lỗi của DI.
Đối xử request/response như hợp đồng:
code, message, details, traceId)Dùng DTOs/view models để tránh lộ các trường nội bộ/ORM và để client không bị coupling với schema DB của bạn.
Để tooling framework chỉ dẫn những gì dễ làm, nhưng giữ sự phân vùng rõ ràng:
Ưu tiên override binding DI hoặc dùng in-memory adapters thay vì monkey-patch, và giữ CI nhanh bằng cách giảm thiểu việc boot framework và setup DB lặp lại.
Những dấu hiệu sớm cho thấy sẽ cần rewrite:
Giảm rủi ro rewrite bằng cách tạo seam: