Kiến trúc & các tầng (Layers)
Quy ước cấu trúc thư mục và trách nhiệm từng tầng trong ứng dụng Next.js App Router. Luồng dữ liệu và gọi hàm một chiều: UI → Actions → Services → Prisma/DB.
Tổng quan luồng
[UI / Route] → [Actions] → [Services] → [Prisma / DB]
↓
[Lib] (helper, handler, auth...)
- Route / Page / Component gọi Actions (hoặc hook gọi API rồi tới Server Action/Route Handler).
- Actions chỉ điều phối: validate input, gọi Services, format response. Không gọi Prisma trực tiếp.
- Services chứa logic nghiệp vụ và truy cập DB qua Prisma (query, transaction). Không gọi Actions.
- Lib cung cấp tiện ích dùng chung (exception handler, auth, format) cho Actions hoặc Services.
Cấu trúc thư mục gợi ý
src/
├── actions/ # Server Actions (theo domain hoặc resource)
│ ├── customer.ts
│ ├── vendor.ts
│ └── ...
├── services/ # Service layer (business logic + DB access)
│ ├── customer.ts
│ ├── vendor.ts
│ └── ...
├── utils/ # Shared utilities
│ ├── server.ts
│ ├── auth.ts
│ ├── client.ts
│ └── ...
├── hooks/ # Client-side hooks (SWR, state)
│ ├── useCustomers.ts
│ ├── ...
│ └── index.ts
├── types/ # Shared types, Strapi types
│ └── strapi.ts
└── app/ # App Router (routes, layouts, pages)
└── [locale]/...
Tên thư mục có thể điều chỉnh theo dự án (ví dụ server/actions, server/services) nhưng quan hệ giữa các tầng giữ nguyên.
Tầng Actions (src/actions hoặc tương đương)
Trách nhiệm
- Entry point từ phía client (form submit, button click, server component).
- Nhận input (form data, params); validate sơ bộ (format, required).
- Gọi Service (một hoặc nhiều) để thực hiện nghiệp vụ.
- Trả kết quả theo format chuẩn (ví dụ
{ data },{ error }) và xử lý lỗi tập trung (ví dụwithActionExceptionHandler).
Không làm
- Không gọi
prisma.*trực tiếp — mọi truy vấn DB qua Service. - Không chứa logic nghiệp vụ phức tạp (check duplicate, tính toán) — đưa vào Service.
Ví dụ
'use server';
import { withActionExceptionHandler } from '@/lib/action-handler';
import { createCustomerService } from '@/services/customer';
/**
* Create a new customer.
* @param input - Data required to create the customer.
* @returns Created customer wrapped in a standardized response.
*/
export const createCustomerAction = withActionExceptionHandler(async (input: CreateCustomerInput) => {
const customer = await createCustomerService(input);
return { data: customer };
});
Tầng Services (src/services hoặc tương đương)
Trách nhiệm
- Logic nghiệp vụ: validation theo domain (trùng email, exclusive constraint, …).
- Truy cập DB qua Prisma: query, create, update, delete.
- Transaction: khi cần nhiều thao tác atomic, dùng
prisma.$transactionvà gọi các service/hàm con trong callback (hoặc tách hàm nhậntx).
Không làm
- Không gọi Action hoặc Route Handler — Service chỉ được gọi từ Action (hoặc từ Service khác trong cùng transaction).
- Không phụ thuộc vào request/response (cookie, headers) — nếu cần context (user, org), nhận qua tham số từ Action.
Ví dụ
import { getPrisma } from '@/lib/db';
import type { Prisma } from '@prisma/client';
export async function createCustomerService(input: CreateCustomerInput) {
const prisma = getPrisma();
await checkDuplicateCustomerEmail(prisma, input.email);
return prisma.customer.create({ data: input });
}
export async function createOrderWithItemsService(
tx: Prisma.TransactionClient,
orderData: CreateOrderInput
) {
const order = await tx.order.create({ data: orderData });
await tx.orderItem.createMany({ data: orderData.items.map(...) });
return order;
}
Tầng Lib (src/utils hoặc src/lib)
Trách nhiệm
- Prisma client singleton (ví dụ
configs/prisma.ts) — tạo một instance dùng chung, tránh nhiều connection. - Action exception handler — bọc Server Action, log lỗi, map sang message user, format response.
- Auth / permission helper — withAuth, withOrg, getCurrentUser (dùng trong Action hoặc middleware).
- Tiện ích dùng chung: format date, parse query, constant.
Không làm
- Không chứa logic nghiệp vụ đặc thù một domain — đưa vào Service.
Tầng Hooks (src/hooks)
Trách nhiệm
- Data fetching phía client: custom hook dùng SWR (hoặc tương đương) gọi API / Server Action.
- Export hook qua barrel (
index.ts); nơi khác import từ@/hooks.
Quy tắc
- Hook chỉ chạy ở Client Component ('use client'). Không gọi Prisma; gọi API hoặc Action đã expose.
App Router (src/app)
Trách nhiệm
- Định nghĩa route, layout, page.
- Server Component (mặc định) có thể gọi trực tiếp Service hoặc Action tùy quy ước (khuyến nghị: gọi Action để thống nhất entry point và auth).
- Client Component gọi Action qua form action hoặc hook (hook gọi API → Route Handler hoặc Server Action).
Quy tắc
- Không import trực tiếp từ thư mục con sâu của
app; component dùng chung đặt ngoàiapp. Xem Modules.
Tóm tắt phụ thuộc
| Tầng | Có thể gọi | Không gọi |
|---|---|---|
| Actions | Services, Lib | Prisma trực tiếp, Hooks |
| Services | Prisma (lib/db), Lib, Service khác (trong tx) | Actions |
| Hooks | API, Server Action (qua fetch/form) | Prisma, Server services trực tiếp |
| Lib | Prisma (chỉ trong db.ts), thư viện ngoài | Actions, Services |
Tuân thủ cấu trúc này giúp code dễ test, tái sử dụng và bảo trì.