# Lesson 5 - Mô hình MVC, middleware

## Khái niệm, cách thức hoạt động

**MVC** là viết tắt của cụm từ “**Model-View-Controller**“. Đây là mô hình thiết kế được sử dụng trong kỹ thuật phần mềm. MVC là một **mẫu kiến trúc phần mềm** để tạo lập giao diện người dùng trên máy tính. MVC chia thành ba phần được kết nối với nhau và mỗi thành phần đều có một **nhiệm vụ riêng** của nó và **độc lập** với các thành phần khác.

1. **Model (M)**:
   * Model đại diện cho dữ liệu và logic ứng dụng. Nó là nơi bạn định nghĩa cách dữ liệu được lưu trữ, truy xuất và xử lý.
   * Model cung cấp các phương thức để truy cập và cập nhật dữ liệu. Nó không liên quan trực tiếp đến giao diện người dùng.
   * Khi có sự thay đổi trong dữ liệu hoặc logic, Model thông báo cho Controller để cập nhật giao diện người dùng.
2. **View (V)**:
   * View chịu trách nhiệm hiển thị thông tin cho người dùng. Nó là phần giao diện người dùng của ứng dụng.
   * View không nên chứa logic kinh doanh. Nó chỉ thể hiện dữ liệu mà Model cung cấp và trả về sự tương tác của người dùng cho Controller.
   * Các thay đổi trong dữ liệu hoặc từ người dùng được hiển thị trong View và được gửi đến Controller để xử lý.
3. **Controller (C)**:
   * Controller là trung tâm điều khiển của kiến trúc MVC. Nó xử lý các yêu cầu từ người dùng và quyết định cách tương tác với Model và View.
   * Khi người dùng tương tác với ứng dụng (ví dụ: bấm nút, gửi biểu mẫu), Controller nhận yêu cầu và thực hiện các hành động tương ứng.
   * Controller có thể truy cập Model để trích xuất hoặc cập nhật dữ liệu và sau đó cập nhật View để hiển thị dữ liệu mới cho người dùng.

### Ưu điểm

**- Tải nhanh, tối ưu lượng băng thông:** MVC không sử dụng viewstate nên sẽ giúp tiết kiệm băng thông cho website. Người dùng có thể sử dụng ứng dụng trên web cần tương tác để gửi và nhận dữ liệu một cách liên tục. Điều này cũng giúp giảm băng thông cho website hoạt động tốt cũng như ổn định hơn.

**- Kiểm tra dễ dàng hơn:** Với MVC, bạn có thể dễ dàng thực hiện các công việc như kiểm tra, rà soát lỗi phần mềm trước khi đưa tới tay người tiêu dùng. Việc này đảm bảo chất lượng và độ uy tín sản phẩm cao hơn.

**- View và size:** View là nơi dùng để lưu trữ dữ liệu. Khi có yêu cầu được thực hiện càng lớn thì càng kích thích tệp lớn hơn. Khi đó, tốc độ đường truyền mạnh cũng bị giảm. Việc sử dụng mô hình MVC sẽ giúp bạn tiết kiệm được diện tích băng thông một cách tối ưu.

**- Chức năng SOC (Separation of Concern):** Chức năng này cho phép bạn có thể phân tách rõ ràng các thành phần như model, data, giao diện, nghiệp vụ.

**- Kết hợp:** Mô hình MVC cho phép bạn code thoải mái trên nền tảng web. Điều này giúp máy chủ giảm tải được khá nhiều.

**- Đơn giản:** Đây là mô hình có kết cấu tương tối đơn giản. Bạn không cần có chuyên môn cao vẫn có thể sử dụng được.

### Nhược điểm

Ngoài các ưu điểm nổi bật bên trên, mô hình MVC cũng có những hạn chế nhất định, cụ thể như:

\- Cần nhiều thời gian để tìm hiểu nếu muốn áp dụng đúng đắn.

\- Tốn kém thời gian và công sức để quản lý tổ chức file.

\- Xây dựng quy trình tương đối phức tạp, bởi vậy không cần thiết áp dụng mô hình này cho các dự án nhỏ

## Cấu trúc MVC

Trong khoá học, ta đã tách phía client (frontend) và server (backend) thành 2 phần và 2 thành phần này, tương ứng với mô hình MVC, ta có:

* View: phía client (frontend) - reactjs.
* Model, controller: phía server (backend) - express server + mongodb.

### Model

Về model, trong lesson 4, khi học về mongodb, ta đã sử dụng kết hợp mongoose nhằm tạo ra một model, ví dụ: UsersModel, PostsModel, CommentsModel. đó cũng chính là `Model` trong mô hình MVC.

Ví dụ:

<figure><img src="https://content.gitbook.com/content/bApz6rKK01BWUpp5sM0n/blobs/KgyWe52EW7kMygXKyFlk/image.png" alt=""><figcaption><p>Khởi tạo model</p></figcaption></figure>

### Controller

Controller, theo định nghĩa, ta biết đến nó như là một nơi dùng để xử lý dữ liệu từ **Model** dựa trên các request được gửi lên từ **View** (**client**).

Theo dõi đoạn code phía dưới và phân tích:

```javascript
app.post('/api/v1/users', async (req, res) => {
    try {
        const { userName, email } = req.body;
        if (!userName) throw new Error('userName is required!');
        if (!email) throw new Error('email is required!');
    
        const createdUser = await UsersModel.create({
            userName,
            email
        });
        res.status(201).send({
            data: createdUser,
            message: 'Register successful!',
            success: true
        });
    } catch (error) {
        res.status(403).send({
            message: error.message,
            data: null,
            success: false
        });
    }
});
```

Khi **View (Client)** gửi request lên với API nào đó tương ứng, lập tức, ta có một logic là một handler function là các logic để xử lý dữ liệu với với **Model,** trả về kết quả cho **View (Client)** đây được gọi là **Controller.**

Vậy, khi thực thi đúng mô hình MVC, ta sẽ cần quản lý được các **Controller**, giống như **Model,** một cách đơn giản, ta sẽ tạo ra một folder tên là controllers, thực hiện tách tất cả các logic liên quan về controller và đưa vào file, export ra để các API sử dụng.

Tạo folder `controllers`, file `users.js` (có thể là users.controller.js, ...tuỳ thuộc vào rule được sử dụng trong dự án)

<figure><img src="https://content.gitbook.com/content/bApz6rKK01BWUpp5sM0n/blobs/wq6FgV5Fi8l96NyYVQko/image.png" alt=""><figcaption><p>Tạo file controller</p></figcaption></figure>

API sẽ có dạng như sau:

File index.js:

```javascript
import express from 'express';
import mongoose from 'mongoose';
import usersController from './controllers/users.js';

mongoose.connect('mongodb://localhost:27017/mindx-fullstack');
const app = express();
app.use(express.json());

// thực hiện gán handler bằng phương thức usersController.createNewUser
app.post('/api/v1/users', usersController.createNewUser);

app.listen(8080, () => {
    console.log('Server is running!');
});
```

Ta có thể thấy, bằng cách tách ra, file index.js (server) của chúng ta đã gọn gàng hơn rất nhiều và có thể quản lý các logic theo các file khoa học hơn.

Nếu để ý, ví dụ các API như sau:

/api/v1/users, /api/v1/posts, /api/v1/comments, ... đều chung đoạn /api/v1, tương lai sau này, có thể trùng nhiều hơn phía sau, vậy ta cũng sẽ có cách có thể làm gọn lại chúng nữa và đảm bảo được đúng theo định nghĩa "*Một đường đi có nhiều ngã rẽ, mỗi ngã rẽ lại có nhiều đường đi*". Để tìm hiểu được cách thức xử lý, trước tiên, ta cần phải hiểu về khái niệm middleware

## Middleware

Middleware là một khái niệm chỉ một lớp trung gian, giữa các phần của ứng dụng, đóng vai trò quan trọng trong việc phát triển ứng dụng, nhất là đối với ứng dụng server, thường được sử dụng thực hiện các chức năng như xác thực, xử lý dữ liệu, ... trước khi tới được controller. Hoàn toàn có thể kiểm soát được việc cho phép sử dụng controller hoặc không.

1. **Builtin Middleware**: Express đi kèm với một số middleware tích hợp sẵn hoặc các thư viện cung cấp. Để sử dụng chúng, bạn cần chỉ định chúng bằng cách sử dụng `app.use()` hoặc bằng cách gắn chúng với một route cụ thể.

   Ví dụ: Sử dụng middleware `express.json()` để phân tích JSON trong các yêu cầu:

   ```javascript
   import express from 'express';
   const app = express();

   app.use(express.json());

   app.post('/api/posts', (req, res) => {
     // Xử lý yêu cầu và truy cập dữ liệu JSON qua req.body
   });
   ```
2. **Custom Middleware**: Bạn có thể tạo middleware tùy chỉnh bằng cách định nghĩa một hàm và sử dụng nó bằng `app.use()`.

   Ví dụ: Đây là một middleware tùy chỉnh đơn giản để ghi log mỗi lần có yêu cầu:

   ```javascript
   function myLogger(req, res, next) {
     console.log(`Received request for: ${req.url}`);
     next(); // Để middleware tiếp theo hoặc xử lý route chính tiếp tục được gọi
   }

   app.use(myLogger);
   ```
3. **Middleware Stacks**: Bạn có thể sử dụng nhiều middleware liên tiếp nhau bằng cách đặt chúng trong một dãy và sử dụng `app.use()` cho dãy đó.

   Ví dụ: Sử dụng nhiều middleware theo thứ tự:

   ```javascript
   app.use(myLogger); // Middleware ghi log
   app.use(express.json()); // Middleware phân tích JSON
   ```
4. **Middleware route**: Bạn cũng có thể sử dụng middleware chỉ cho một số route cụ thể.

   Ví dụ: Sử dụng middleware cho một route cụ thể:

   ```javascript
   app.get('/user/:id', function (req, res, next) {
     if(!req.params.id){
         res.status(403).send({
             message:'Missing id!'
           });
     } else next()
   }, function (req, res, next) {
     res.end(req.params.id)
   })
   ```

Lưu ý rằng thứ tự sử dụng middleware trong Express quan trọng, vì nó ảnh hưởng đến cách chúng được thực thi trong chuỗi xử lý yêu cầu. Middleware có thể chuyển điều khiển cho middleware tiếp theo bằng cách gọi `next()`.

### Route Middleware

Route middleware sẽ giúp chúng ta apply các Route lạị với nhau, giống việc chia nhỏ và ghép chúng lại. Cách này sẽ giúp tách biệt, tổ chức mã nguồn ra các thành phần riêng lẻ dựa trên route hoặc chức năng.

1. **Tạo một thư mục cho routes**: Bắt đầu bằng cách tạo một thư mục cho các route handlers của bạn. Ví dụ, bạn tạo một thư mục có tên "routes" trong thư mục gốc của ứng dụng.
2. **Tạo các tệp route handler**: Trong thư mục "routes," tạo các tệp JavaScript để định nghĩa các route handlers. Ví dụ, tạo một tệp "users.js" để xử lý các route liên quan đến người dùng:

   ```javascript
   // users.js
   import {Router} from 'express';
   const router = express.Router();

   // Xử lý route GET /users
   router.get('/', (req, res) => {
     res.send('List of users');
   });

   // Xử lý route GET /users/:id
   router.get('/:id', (req, res) => {
     const userId = req.params.id;
     res.send(`User ID: ${userId}`);
   });

   export default router;
   ```
3. **Kết hợp các route handlers với Express**: Trong tệp chính của ứng dụng, cần kết hợp các route handlers này bằng cách sử dụng middleware. Sử dụng `app.use()` để gắn route handler vào ứng dụng.

   ```javascript
   import express from 'express';
   import userRouters from './routes/users.js';
   const app = express();

   // Sử dụng middleware để gắn các route handler vào ứng dụng
   app.use('/users', userRoutes); // Sử dụng '/users' làm tiền tố cho tất cả các route trong userRoutes

   app.listen(8080, () => {
     console.log('Server is running on port 8080');
   });
   ```
4. **Sử dụng các route tách biệt**: Khi bạn đã kết hợp các route handlers bằng cách sử dụng middleware, bạn có thể truy cập chúng bằng các route tương ứng. Ví dụ, các route trong "user.js" sẽ sẵn sàng tại '/users' và '/users/:id' trong ứng dụng của bạn.

Với cách tiếp cận này, mã nguồn của bạn được tổ chức một cách rõ ràng và dễ quản lý, và bạn có thể tách biệt chức năng của từng route thành các tệp riêng lẻ.

Hoàn toàn có thể tiếp tục apply các route lại với nhau, Ví dụ:

<pre class="language-javascript"><code class="lang-javascript"><strong>// routes/index.js
</strong><strong>import {Router} from 'express';
</strong><strong>import userRouter from './users.js';
</strong><strong>
</strong><strong>const rootRouter = Router();
</strong><strong>
</strong><strong>rootRouter.use('/users', userRouter);
</strong><strong>
</strong><strong>export default rootRouter;
</strong></code></pre>

```javascript
// index.js
import express from 'express';
import rootRouter from './routers/index.js';

app.use('/api/v1', rootRouter);

app.listen(8080, () => {
  console.log('Server is running on port 8080');
});
```

Vậy với cách làm này, tiếp tục dự án của chúng ta được phân tách các logic xử lý và dễ dàng quản trị hơn rất nhiều.

## Thực hành

Với các kiến thức trong bài học này, hay sử dụng, thực hiện cấu trúc lại dự án cho bài thực hành của bài tập số 4 đã được làm.

## Kết

Qua bài học, ta được học về một mô hình MVC, một trong các mô hình kiến trúc được thiết kế để tạo lập nên giao diện người dùng trên máy tính, phổ biến rộng rãi về tính hiệu quả, cũng như tính chất đơn giản.

Ngoài mô hình này ra, còn có thêm các mô hình khác như MVT, MVVM, MVP, ... Mỗi mô hình này có ưu điểm và ứng dụng khác nhau tùy thuộc vào loại ứng dụng và ngôn ngữ lập trình bạn sử dụng.

Lựa chọn mô hình phù hợp với dự án của bạn có thể giúp bạn quản lý mã nguồn một cách hiệu quả và tạo ứng dụng dễ bảo trì và mở rộng.

Qua các bài học tới giờ, về kiến trúc của hệ thống, ta đã được tìm hiểu một cách cơ bản nhất và đầy đủ nhất về các kiến thức cần được áp dụng trong việc xây dựng một server. Trong các bài học tiếp theo, ta sẽ tìm hiểu thêm các kỹ thuật, lối tư duy khi triển khai ứng dụng server.
