MVC design pattern và cách chúng ta hỗ trợ trong Mini-Web-Server – phần 2


MVC là gì?

Model-View-Controller là một mẫu thiết kế cho phép ta phân tách các thành phần theo 3 chức năng:

  • Các thực thể (entity) chứa dữ liệu, hay còn gọi là Model.
  • Các View có nhiệm vụ hiển thị dữ liệu.
  • Các Controller có nhiệm vụ điều phối Model đến View.

Việc phân tách này giúp chúng ta tách biệt các thành phần với các chức năng hoàn toàn độc lập, và mang lại những lợi ích sau:

  • Dễ thay đổi: Một thay đổi nào đó trên giao diện chỉ ảnh hưởng đến View. Giả sử chương trình của bạn cần cung cấp các trang hiển thị thông tin sản phẩm khác nhau: cho người dùng web, cho người dùng trên các thiết bị màn hình nhỏ, cho máy in… thì chỉ cần tạo ra các view khác nhau, tất cả những gì trong Model hoặc code tải thông tin sản phẩm trong Controller đều không cần thay đổi.
  • Dễ test: bạn hoàn toàn có thể viết các unit test riêng biệt cho Controller (chứa business logic) hoặc View (để test giao diện), một tester cũng có thể test các view thông qua các Fake/Mock object mà không cần chờ Controller hoàn thiện.

Nếu bạn vẫn chưa hiểu rõ, hãy để tôi thử giải thích theo một cách khác:

MVC có 3 cái:

  • Cái thứ nhất chỉ chứa dữ liệu, nó không cần quan tâm dữ liệu đến từ đâu, được tạo ra như thế nào, nó chỉ đơn thuần là nơi chứa dữ liệu, ta gọi nó là Model.
  • Cái thứ hai chỉ có nhiệm vụ hiển thị, nó không cần biết cái Model từ đâu ra, ai bỏ dữ liệu vào nó, chỉ cần đưa nó một Model, nó sẽ vẽ ra giao diện, vì nó cho phép chúng ta “xem” Model, nên ta gọi nó là View.
  • Cái còn lại là Controller có nhiệm vụ tạo ra Model, việc tạo ra như thế nào chỉ mình nó biết, rồi đem cái Model được tạo ra đó đưa cho View phù hợp để chúng ta có thể “xem”. Nó chỉ biết tạo ra Model chứ không biết cái Model đó sẽ được View hiển thị thế nào.

Nếu nhìn lên hình minh họa phía trên bạn sẽ thấy Controller chỉ mũi tên sang Model, rồi từ Model sang View, ý của tôi muốn nói là: Controller tạo ra Model, rồi đem cái Model đó đưa cho View. Tới đây hi vọng các bạn đã mường tượng ra được các thành phần trong MVC kết hợp với nhau như thế nào. Rất đơn giản phải không? Vậy tại sao khi các bạn học ASP.NET MVC hay Apache Struts lại cảm thấy chúng phức tạp vậy? Đó là vì các công nghệ này cung cấp thêm rất nhiều “đồ chơi” giúp các bạn viết ứng dụng MVC dễ dàng hơn. Những khái niệm như action, action filter, routing, parameter binding, interceptor, validator… đều là của các “công nghệ giúp bạn viết MVC dễ dàng hơn”, không phải bản thân mô hình MVC.

Một ví dụ về MVC

Hãy đến với ví dụ đơn giản nhất: môt trang xem Video trên Mytube (không phải Youtube nhé :D):

Một trang xem video trên Mytube sẽ có những gì?

  • Đường link đến video.
  • Mốc thời gian bắt đầu hiển thị.
  • Danh sách ID các video liên quan.

Để xem một video, người ta cần ID của video đó, và có thể là cả mốc thời gian bắt đầu, danh sách các video liên quan sẽ được lấy tự động, vậy ta sẽ có Model như sau:

Model {
    Link: string,
    Time: int,
    RelatedIds: List<int>
}

View sẽ là một class/script có nhiệm vụ hiển thị nội dung trang video, ví dụ:

VideoView {
    print(Model model) {
        print "<video link='{model.Link}' start='{model.Time}' />"
        foreach (int id in model.RelatedIds) {
            print "<video-thumb id='{id}' />";
        }
    }
}

Và Controller sẽ là:

Controller {
    execute() {
        int videoId = getParameter("id");
        Video video = videoService.findById(videoId);
        if (video == null) return View("VideoNotFound");
        int time = getParameter("t");
        if (time == null) time = 0;
        List<int> relatedIds = recomendationService.findRelatedVideos(videoId);

        Model model = {
            Link: video.Link,
            Time: time,
            RelatedIds: relatedIds
        };
        return View("VideoView", model);
    }
}

Ví dụ được viết bằng mã giả (made by tui) nhưng hi vọng các bạn vẫn có thể hiểu được. Ở đây ta có thể thấy Model, View và Controller chỉ làm đúng nhiệm vụ của nó. View chỉ phụ thuộc vào Model, controller có thay đổi gì cũng không ảnh hưởng tới nó, controller cũng chỉ phụ thuộc vào Model, thay đổi trong view không làm ta phải thay đổi code trong controller.

MVC nằm đâu trong kiến trúc phần mềm?

MVC là một design pattern cho tầng Presentation, tức thành phần đóng vai trò hiển thị dữ liệu. MVC tập trung vào cách chúng ta hiển thị nội dung của ứng dụng hơn là nói về việc chúng ta quản lý dữ liệu như thế nào.

Tất cả việc xử lý đều sẽ bắt đầu từ controller, controller có thể sẽ query trực tiếp đến database (client-server/2-tier architecture), cũng có thể gọi đến một COM+/EJB object/WebAPI (3-tier architecture), nếu các COM+/EJB object/WebAPI này được thiết kế độc lập hơn nữa, ta sẽ có microservice architecture.

Trong ví dụ trên, ta có videoService, recomendationService là hai service cung cấp các chức năng tương ứng, ta không biết và cũng không cần biết chúng được implement thế nào, có thể chúng lấy dữ liệu từ database, gọi lại một API nào đó hay thậm chí đọc từ một file txt (ai mà thèm quan tâm chứ?). Việc videoService, recomendationService được implement và deploy thế nào sẽ cho ta biết phần mềm này dùng kiến trúc nào. Còn riêng các thành phần Model, View và Controller sẽ luôn chạy cùng nhau, trong cùng một máy tính, cùng một tầng, cùng một process, không bao giờ có chuyện chúng chạy phân tán trên nhiều hệ thống khác nhau.

MVC và API

Thông thường ta hay gắn liền MVC với các ứng dụng web, tuy nhiên MVC cũng có có thể được dùng trong các hệ thống tương tác máy-máy, ví dụ như các web API. Bạn muốn tải về danh sách sản phẩm dạng file Excel, JSON, hay XML? Hãy dùng chung một Model, một Controller, và viết từng View riêng cho mỗi loại file bạn muốn tải về. Rất đơn giản phải không?

Sao tôi thấy người ta minh họa với nhiều mũi tên giữa các thành phần hơn?

Trước khi trả lời, tôi muốn hỏi lại bạn: Bạn đã hiểu mô hình với chỉ 2 mũi tên phía trên chưa? Nếu rồi thì vậy là ổn.

Trong một số hình minh họa khác, ví dụ như cái dưới đây:

Đây là một bức ảnh lấy từ: https://developer.mozilla.org/en-US/docs/Glossary/MVC. Bạn sẽ thấy “Sends input from user”, có lẽ ý tác giả là: “Sau khi người dùng xem View, họ sẽ thực hiện thao tác gì đó và gửi lệnh đến Controller”. Theo tôi vẽ một mũi tên từ View sang Controller như vậy sẽ gây khó hiểu, hình minh họa bên dưới có lẽ sẽ thể hiện đúng hơn ý này.

Trong hình minh họa này, Actor là một đối tượng bên ngoài hệ thống, họ sẽ xem nội dung hiển thị bởi View và gửi yêu cầu đến Controller. Tuy nhiên Actor không phải là một phần trong MVC, và tất nhiên các hành vi của Actor chỉ kích hoạt một chu trình, chứ không phải là một phần trong MVC.

Tương tự với “Sometimes updates directly”, tôi không nghĩ đây lại là một best practice trong MVC, thậm chí nó không phải là MVC (dù trong thực tế vẫn xảy ra).

Có một lý do cần nhắc đến là hầu hết các bạn sử dụng MVC dựa trên một framework nào đó, và các framework khác nhau có thể được thiết kế khác nhau, do vậy hình minh họa còn tùy thuộc vào ngữ cảnh của bài viết nữa. Nói chung là nếu các bạn đã hiểu được mô hình với 2 mũi tên (ở trên cùng) thì các bạn cũng sẽ hiểu mô hình với n mũi tên :D.

Model có phải là nơi chứa các lệnh query đến database không?

Không! Model chỉ chứa dữ liệu, nó không có bất kỳ liên hệ nào với database. Một lỗi thiết kế thường gặp là chúng ta sử dụng chung các lớp model, ví dụ ta dùng luôn entity class trong entity framework làm Model luôn (tiện tay quá mà!), và đến một ngày ta sửa lại cấu trúc một bảng nào đó, thế là việc thay đổi đó lan khắp cả hệ thống. Chẳng phải khi đó View của bạn không chỉ phụ thuộc vào Model, mà còn cả vào database?

Vậy nên, best practice là hãy định nghĩa các lớp Model riêng, trong controller bạn sẽ phải copy dữ liệu từ database object sang Model, bạn sẽ phải cập nhật cả Model lẫn database object nếu database thay đổi, mất công thật nhưng bạn phân tách được các thành phần và giúp tránh các lỗi bị lan truyền trong toàn hệ thống.

(Hết phần 2)

Leave a comment