Skip to main content

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.$transaction và gọi các service/hàm con trong callback (hoặc tách hàm nhận tx).

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ài app. Xem Modules.

Tóm tắt phụ thuộc​

TầngCó thể gọiKhông gọi
ActionsServices, LibPrisma trực tiếp, Hooks
ServicesPrisma (lib/db), Lib, Service khác (trong tx)Actions
HooksAPI, Server Action (qua fetch/form)Prisma, Server services trực tiếp
LibPrisma (chỉ trong db.ts), thư viện ngoàiActions, Services

Tuân thủ cấu trúc này giúp code dễ test, tái sử dụng và bảo trì.