Repository Pattern từ A đến Z
Admin · Cập nhật 19/07/2026
Repository Pattern là gì, giải quyết vấn đề gì và cách triển khai với Next.js + Prisma: interface, implementation, use case, khi nào nên và không nên dùng.
Chuyên mục: Software Architecture
Đối tượng: Backend Developer, Full-stack Developer
Mục lục
- Repository Pattern là gì?
- Vấn đề Repository Pattern giải quyết
- Kiến trúc tổng quan
- Thành phần chính
- Ví dụ với Next.js + Prisma
- Repository Interface
- Repository Implementation
- Use Case sử dụng Repository
- Kiểm thử với In-Memory Repository
- So sánh gọi Prisma trực tiếp và dùng Repository
- Khi nào nên sử dụng
- Khi nào không nên sử dụng
- Ưu điểm
- Nhược điểm
- Best Practices
- Anti-Patterns
- Sai lầm thường gặp
- Checklist
- FAQ
Repository Pattern là gì?
Repository Pattern là một mẫu thiết kế (Design Pattern) giúp tách biệt logic truy cập dữ liệu khỏi business logic.
Thay vì để Use Case hoặc Service làm việc trực tiếp với Prisma, Entity Framework hoặc SQL, chúng chỉ giao tiếp thông qua một Repository Interface.
Điều này giúp hệ thống không phụ thuộc vào công nghệ lưu trữ dữ liệu.
Bài toán
Không nên:
const user = await prisma.user.findUnique({
where: { id }
});
được gọi trực tiếp trong business logic.
Nên:
const user = await userRepository.findById(id);
Use Case không cần biết dữ liệu đến từ Prisma, MongoDB, Redis hay API.
Kiến trúc
Presentation
│
▼
Application (Use Case)
│
▼
Repository Interface
│
▼
Infrastructure
│
▼
Prisma / SQL / MongoDB
Repository Interface
export interface UserRepository {
findById(id: string): Promise<User | null>;
create(user: User): Promise<void>;
update(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
Prisma Implementation
Đây là nơi duy nhất trong hệ thống được phép "biết" Prisma. Một implementation đầy đủ sẽ hiện thực hóa toàn bộ phương thức trong interface:
import { PrismaClient } from "@prisma/client";
import type { UserRepository } from "@/application/ports/user-repository";
import type { User } from "@/domain/entities/user";
export class PrismaUserRepository implements UserRepository {
constructor(private prisma: PrismaClient) {}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async create(user: User): Promise<void> {
await this.prisma.user.create({
data: { id: user.id, email: user.email },
});
}
async update(user: User): Promise<void> {
await this.prisma.user.update({
where: { id: user.id },
data: { email: user.email },
});
}
async delete(id: string): Promise<void> {
await this.prisma.user.delete({ where: { id } });
}
}
Nếu sau này cần đổi sang một công nghệ khác, bạn chỉ việc tạo lớp mới (ví dụ MongoUserRepository) hiện thực hóa cùng interface — toàn bộ Use Case phía trên không đổi một dòng nào.
Use Case
export class GetUserUseCase {
constructor(
private repository: UserRepository
){}
execute(id:string){
return this.repository.findById(id);
}
}
Kiểm thử với In-Memory Repository
Giá trị lớn nhất của Repository Pattern lộ rõ khi viết test. Vì Use Case chỉ phụ thuộc interface, ta có thể thay Prisma bằng một repository chạy trong bộ nhớ — test nhanh, không cần database, không cần Docker:
// test/in-memory-user-repository.ts
export class InMemoryUserRepository implements UserRepository {
private store = new Map<string, User>();
async findById(id: string) {
return this.store.get(id) ?? null;
}
async create(user: User) { this.store.set(user.id, user); }
async update(user: User) { this.store.set(user.id, user); }
async delete(id: string) { this.store.delete(id); }
}
// Trong test
const repo = new InMemoryUserRepository();
await repo.create({ id: "1", email: "a@demo.com" } as User);
const useCase = new GetUserUseCase(repo);
const user = await useCase.execute("1");
// expect(user?.email).toBe("a@demo.com")
So sánh: gọi Prisma trực tiếp và dùng Repository
| Tiêu chí | Gọi Prisma trực tiếp trong Use Case | Qua Repository |
|---|---|---|
| Phụ thuộc ORM | Cao, dính chặt Prisma | Thấp, chỉ phụ thuộc interface |
| Unit Test | Cần DB thật hoặc mock Prisma phức tạp | Dùng in-memory repo, nhanh gọn |
| Đổi công nghệ lưu trữ | Sửa khắp nơi | Viết một implementation mới |
| Tập trung truy vấn | Query rải rác toàn dự án | Gom về một chỗ, dễ tối ưu |
| Tốc độ viết ban đầu | Nhanh | Chậm hơn chút |
| Phù hợp dự án | Nhỏ, CRUD đơn giản | Vừa và lớn, có nghiệp vụ |
Ưu điểm
- Tách business logic khỏi database.
- Dễ Unit Test.
- Có thể thay Prisma bằng ORM khác.
- Giảm coupling.
Nhược điểm
- Thêm nhiều lớp.
- Không phù hợp CRUD rất nhỏ.
- Dễ bị "Repository quá lớn".
Best Practices
- Một Aggregate Root nên có một Repository.
- Không đưa Business Logic vào Repository.
- Repository chỉ thao tác dữ liệu.
- Transaction nên quản lý ở Unit of Work hoặc Service.
Anti-Patterns
❌ Repository gọi API bên ngoài.
❌ Repository chứa validate nghiệp vụ.
❌ Repository trả DTO thay vì Domain Entity (nếu theo DDD).
Sai lầm thường gặp
- Repository "thần thánh" (God Repository). Một
UserRepositoryvới 40 phương thức truy vấn đủ mọi trường hợp. Hãy tách theo Aggregate Root và cân nhắc tách phần đọc phức tạp sang query riêng (theo tinh thần CQRS). - Nhét business logic vào Repository. Ví dụ tính điểm thưởng, kiểm tra quyền ngay trong hàm truy vấn. Nghiệp vụ phải nằm ở Domain/Use Case; Repository chỉ đọc/ghi dữ liệu.
- Trả thẳng Prisma model ra ngoài. Khi đó kiểu dữ liệu của Prisma lan khắp hệ thống, và bạn mất luôn lợi ích tách tầng. Hãy map sang Domain Entity trước khi trả về.
- Tạo Repository chung chung
IRepository<T>cho mọi entity. Nghe có vẻ DRY nhưng thực tế mỗi Aggregate có nhu cầu truy vấn rất khác nhau; interface tổng quát thường thừa phương thức không dùng và thiếu phương thức cần. - Quản lý transaction sai chỗ. Gọi hai Repository trong một nghiệp vụ nhưng mỗi cái tự commit riêng, dẫn tới dữ liệu không nhất quán. Transaction bao nhiều Repository nên do Unit of Work hoặc Service điều phối.
- Leaky abstraction — để lộ chi tiết ORM qua tham số. Ví dụ cho phép truyền
Prisma.UserWhereInputvào phương thức Repository. Làm vậy thì đổi ORM lại phải sửa cả interface, đúng thứ mà pattern này muốn tránh.
Khi nào nên dùng?
✔ Hệ thống lớn
✔ Clean Architecture
✔ Domain Driven Design
✔ Có Unit Test
Khi nào không nên dùng?
- MVP nhỏ.
- CRUD nội bộ.
- Script dùng một lần.
Checklist
- Có Repository Interface.
- Use Case chỉ dùng Interface.
- Infrastructure triển khai Interface.
- Không import Prisma trong Domain.
- Repository không chứa Business Logic.
FAQ
Repository có bắt buộc trong Prisma không?
Không. Prisma có API rất mạnh. Tuy nhiên trong các dự án lớn hoặc theo Clean Architecture, Repository vẫn giúp giảm sự phụ thuộc vào ORM.
Repository có thay thế Service không?
Không. Repository chịu trách nhiệm truy cập dữ liệu, còn Service/Use Case xử lý nghiệp vụ.
Repository và DAO khác nhau thế nào?
DAO (Data Access Object) thường gắn với một bảng và tư duy theo hàng dữ liệu (CRUD từng bảng). Repository trừu tượng ở mức cao hơn: nó làm việc với Aggregate/Domain Entity và che giấu hoàn toàn nguồn dữ liệu, nên hợp với DDD hơn. Trong nhiều dự án nhỏ ranh giới này khá mờ.
Nên trả về Domain Entity hay DTO từ Repository?
Nếu theo DDD nghiêm ngặt, Repository nên trả về Domain Entity để Use Case làm việc với nghiệp vụ. DTO thường được dùng ở tầng Presentation để định hình dữ liệu trả cho client. Đừng để Repository trả DTO, vì như vậy nghiệp vụ sẽ thiếu hành vi của Entity.
Quản lý transaction qua nhiều Repository như thế nào?
Dùng Unit of Work: mở một transaction, chia sẻ cùng một PrismaClient (hoặc tx trong prisma.$transaction) cho các Repository liên quan, rồi commit/rollback một lần. Không nên để mỗi Repository tự mở transaction riêng.
Prisma đã có sẵn quan hệ và query mạnh, Repository có thừa không?
Với dự án nhỏ hoặc prototype thì có thể thừa — gọi Prisma trực tiếp sẽ nhanh hơn. Nhưng khi hệ thống lớn, cần Unit Test không phụ thuộc DB, hoặc muốn giữ khả năng đổi công nghệ, Repository giúp cô lập Prisma và giảm rủi ro khi thay đổi. Hãy quyết định dựa trên quy mô, không theo cảm tính.
Kết luận
Repository Pattern là nền tảng quan trọng trong các dự án sử dụng Clean Architecture và DDD. Hãy áp dụng khi dự án có quy mô vừa và lớn, cần khả năng kiểm thử, mở rộng và thay thế công nghệ lưu trữ dữ liệu trong tương lai.