DEV Community

EME GUG
EME GUG

Posted on

Cách mình tổ chức code trong dự án lớn

Dự án nhỏ thì cấu trúc không quan trọng. Nhưng khi team 5+ người, 50+ files, bạn cần organize code cho dễ maintain.

Cấu trúc theo Feature (recommended)

src/
├── features/
│   ├── auth/
│   │   ├── auth.controller.js
│   │   ├── auth.service.js
│   │   ├── auth.repository.js
│   │   ├── auth.middleware.js
│   │   ├── auth.validator.js
│   │   └── auth.test.js
│   ├── orders/
│   │   ├── orders.controller.js
│   │   ├── orders.service.js
│   │   ├── orders.repository.js
│   │   └── orders.test.js
│   └── users/
│       ├── users.controller.js
│       ├── users.service.js
│       └── users.test.js
├── shared/
│   ├── database.js
│   ├── logger.js
│   ├── errors.js
│   └── middleware/
│       ├── error-handler.js
│       └── rate-limit.js
├── config/
│   └── index.js
└── app.js
Enter fullscreen mode Exit fullscreen mode

Tại sao không theo Layer?

# Cấu trúc theo Layer (KHÔNG recommend)
src/
├── controllers/
│   ├── authController.js
│   ├── orderController.js
│   └── userController.js
├── services/
│   ├── authService.js
│   ├── orderService.js
│   └── userService.js
├── models/
│   ├── User.js
│   ├── Order.js
│   └── Token.js
Enter fullscreen mode Exit fullscreen mode

Vấn đề: Muốn hiểu feature "orders" phải nhảy qua 4-5 thư mục. Feature-based thì mọi thứ liên quan nằm cạnh nhau.

3 Layers trong mỗi Feature

Controller → Service → Repository
(HTTP)      (Logic)   (Database)
Enter fullscreen mode Exit fullscreen mode

Controller: Nhận request, trả response

// orders.controller.js
const router = express.Router();

router.post('/', authenticate, async (req, res, next) => {
    try {
        const order = await orderService.create(req.user.id, req.body);
        res.status(201).json(order);
    } catch (err) {
        next(err);
    }
});
Enter fullscreen mode Exit fullscreen mode

Service: Business logic

// orders.service.js
async function create(userId, data) {
    const items = await validateItems(data.items);
    const total = calculateTotal(items);

    if (total > 10000) {
        throw new AppError('ORDER_LIMIT', 'Order exceeds maximum amount');
    }

    const order = await orderRepo.create({
        userId,
        items,
        total,
        status: 'pending'
    });

    await notificationService.send(userId, 'Order created');
    return order;
}
Enter fullscreen mode Exit fullscreen mode

Repository: Database access

// orders.repository.js
async function create(data) {
    return db('orders').insert(data).returning('*');
}

async function findByUserId(userId, { limit = 20, offset = 0 } = {}) {
    return db('orders')
        .where({ user_id: userId })
        .orderBy('created_at', 'desc')
        .limit(limit)
        .offset(offset);
}
Enter fullscreen mode Exit fullscreen mode

Rules

  1. Controller không chứa business logic — chỉ parse request, gọi service, format response
  2. Service không biết HTTP — không dùng req, res, status codes
  3. Repository không biết business rules — chỉ CRUD
  4. Feature không import từ feature khác trực tiếp — dùng shared hoặc events

Khi nào cần refactor?

  • File > 300 dòng → tách
  • Folder > 10 files → tách sub-features
  • Import cycles → sai architecture
  • Test khó viết → coupling quá chặt

Bạn tổ chức code thế nào? Có pattern nào hay share nhé!

Top comments (0)