Mô hình Module hóa trong phát triển phần mềm

1, Các phân chia source code theo mô hình MVC cơ bản và các vấn đề phát sinh

Trong phát triển phần mềm nói chung và phát triển web site trên nền tảng phần lớn các framework (Laravel là một ví dụ). MVC là một design pattern chủ chốt đóng vai trò là nền tảng của toàn bộ ứng dụng. Các Lớp (Class) và file theo mô hình MVC được chia làm 3 loại chính 

  • Model: Phục vụ việc thể hiện nội dung của các thực thể trong business và database
  • Controller: Phục vụ việc điều hướng người dùng, gọi các action liên quan đến business và đưa ra dữ liệu phục vụ cho view.
  • View: Phục vụ việc hiển thị dữ liệu và tương tác với người dùng

Tùy theo mục đích các file này thường được tổ chức và chia vào các thư mục riêng biệt Controller, Model, View

Dựa theo các phân chia này các thành phần khác của ứng dụng cũng được chia ra và lưu trữ trong các thư mục riêng dựa theo chức năng về mặt kỹ thuật của chúng trong ứng dụng vd như migrations, command, seeder. Vậy ưu điểm và nhược điểm của việc phân chia này như nào

  • Ưu điểm: Đơn giản, dễ tiếp cận , phát triển nhanh trong giai đoạn đầu
  • Nhược điểm: Khi ứng dụng phát triển lớn, kèm theo đó là sự thay đổi, thêm bớt liên tục của các thực thể , database dẫn đến sự tăng lên của của số lượng mã nguồn. Source code sẽ trở nên khó tiếp cận, khó quản lý .

VD: Làm sao để biết db liên quan đến 1 chức năng đã update những gì khi phải bơi trong vài trăm file migrations. 

Mô hình module hóa được sinh ra để giải quyết các bài toán này 

2, Sơ lược về mô hình module hóa 

Về cơ bản mô hình module hóa sẽ chia source code theo các thực thể và chức năng liên quan về mặt nghiệp vụ. Ví dụ trong thương mại điện tử ta có thể phân tích một số module chính như Catalog (Quản lý sản phẩm và danh mục), Sales/Order (Quản lý đơn hàng), Payment (Quản lý các phương thức thanh toán )….

Các module sẽ được chia để thỏa mãn điều kiện : 

  • Database của nó là tách biệt hoàn toàn hoặc chỉ phụ thuộc vào các module cấp thấp hơn.
  • Các module có thể bị tắt, bỏ hoặc thay thế mà không ảnh hưởng đến các module cấp thấp hơn.

VD: 

  • Module Catalog sẽ quản lý các thông tin về sản phẩm và danh mục, module này có database không liên quan đến các module khác.
  • Module Sales sẽ là module cấp cao hơn của Module Catalog vì đơn hàng cần dùng đến dữ liệu của sản phẩm.
  • Module Momo sẽ là module cấp cao hơn của Module Sale vì việc thanh toán qua Momo cần có đơn hàng. 
  • Chúng ta có thể xóa, ngắt hoàn toàn Module Momo ra khỏi hệ thống mà không ảnh hưởng đến tính đúng đắn của phần còn lại của hệ thống

Note: Một module phụ thuộc vào một module khác thí một module đó được gọi là module cấp cao hơn (Order phụ thuộc Catalog) .

Bên trong các Module source code sẽ được tổ chức bình thường theo mô hình MVC với các chức năng như Controller, Models, View 

Cấu trúc thư mục bên trong của mỗi module tương tự như mô hình MVC.

Khi chia các module như thể này. Database, Endpoint và Command của từng thành phần của hệ thống sẽ được tổ chức tập chung với nhau. Điều này giúp lập trình viên nhanh chóng kiểm soát được toàn bộ luồng làm việc của hệ thống với một thực thể nhất định.

3, Các design pattern thường được sử dụng trong mô hình Module hóa

Ở phần 2 ta đã biết được Tuy nhiên chia nhỏ thư mục chỉ là bước đầu trong quá đính module hóa. Để đạt được sự độc lập cần thiết giữa các module lập trình viên cần tuân thủ và áp dụng rất nhiều pattern . Phần 3 của bài viết sẽ trình bày một số pattern cơ bản hay được sử dụng khi module hóa.

3.1 Các pattern phục vụ việc giao tiếp giữa các module.

Các module được thiết kế để hoạt động độc lập và có thể thay thế. Việc có chuẩn giao tiếp giữa các module là vô cùng quan trọng. Để thực hiện điều này ta có thể dùng 2 design pattern phổ biến là Repository và Service Contract 

3.1.1 Repository Pattern 

Repository Pattern là 1 pattern nằm giữa tầng business và data access. Implement của pattern sẽ giao tiếp với tầng data access để đưa ra interface cho tầng business. Đồng thời triển khai việc lưu dữ liệu liên quan đến Repository thông qua 1 data interface. 

Thông thường với mỗi entity trong một module chúng ta sẽ viết một class Repository tương ứng 

VD: Catalog module sẽ có ProductRepositoryCategoryRepository.

Hãy nhớ: Khi muốn truy vấn hoặc lưu thông tin một entity của một module khác hãy luôn thông qua Repository Interface tương ứng .

Lưu ý: 

  • Repository không chỉ gắn với database, khi tương tác với một hệ thống bên ngoài lập trình viên hoàn toàn có thể viết repository bọc các logic liên quan đến api, sdk hoặc bất kỳ cách tương tác nào. 
  • Repository chỉ nên đảm nhận các công việc liên quan đến truy vấn dữ liệu hoặc lưu dữ liệu, Không nên viết các triển khai logic phức tạp trong repository,
3.1.2 Service Contract 

Service Contracts là một quy tắc giao tiếp giữa cách thành phần thuộc các Module/ Service khác nhau. Về cơ bản  sử dụng Service Contracts thể hiện qua việc các Module không sử dụng trực tiếp function của nhau mà sử dụng qua các API, Interface định trước. 

Việc định nghĩa interface cho từng service được định nghĩa từ đầu và không bao giờ thay đổi. Sự thay đổi chỉ được diễn ra trong nội bộ module.

VD: Việc triển khai cơ bản của Service Contract cho module Catalog có thể được thực hiện như sau:

  • Data Interface: Ta định nghĩa ProductDataInterface, CategoryDataInterface . Trong module Catalog.
  • ProductRepositoryCategoryRepository không trả ra Model trực tiếp mà trả ra instance của ProductDataInterfaceCategoryDataInterface. Đồng thời các function lưu dữ liệu cũng chỉ nhận các Data Interface trên làm input.
  •  ProductServiceCategoryServicce phục vụ tầng Business liên quan để Product và Category sẽ nhận ProductDataInterface, CategoryDataInterface là đầu vào. Tương tự với kết quả trả về.

Với cách triển khai như trên với mỗi Module ta sẽ có Repository phục vụ cho việc lấy và tìm kiếm dữ liệu. Các Class Service phục vụ các logic về mặt bussiness. Việc giao tiếp giữa Module này với tầng kiến trúc khác hoặc module khác hoàn toàn thông qua Interface. Các thay đổi có thể xảy ra trong nội bộ Module mà hoàn toàn không ảnh hưởng đến phần còn lại của hệ thống miễn các Interface trả ra đều giữ nguyên. Dưới đây là ví dụ về mô hình cơ bản của service contract trong các Module của Magento một platform TMĐT lớn.

3.2 Các pattern phục vụ việc can thiệp vào quá trình thực thi của ứng dụng.

Ở phần trên với sự trợ giúp của Repository và Service Contract việc giao tiếp giữa các Module đã được thực hiện để đảm bảo tính độc lập của các Module. Tuy nhiên còn một vấn đề khác cũng quan trọng không kém của việc Module hóa là việc can thiệp và mở rộng những thành phần cũ của hệ thống. Nếu cứ có một kiểu sản phẩm mới thuộc về module Catalog ta lại phải tiến vào Module Order để chỉnh sửa cho phù hợp với kiểu sản phẩm mới hoặc module Report phải tiến vào tất cả các Module Order, Withdraw để thêm các bản ghi cần thiết. Khi ấy các module này sẽ không còn độc lập. Ở phần này chúng ta sẽ điểm qua một số pattern phục vụ việc mở rộng nghiệp vụ mà không cần sửa các module khác.

3.2.1 Observer Pattern 

Observer Pattern là một design pattern phổ biến. Trong đó các sự kiện (event) sẽ được định nghĩa trước và được thực thi (dispatch) trong quá trình ứng dụng chạy.

Observer là một danh sách các hành động sẽ được thực hiện khi một sự kiện được thực thi. Các Observer của 1 event có thể được đăng ký bên ngoài module.

Trong Laravel pattern này được gọi với tên Event Listener. https://laravel.com/docs/8.x/events

Trong mô hình Module hóa. Các event đóng vai trò xương sống trong toàn bộ ứng dụng. Trong ví dụ về Order, WithDraw và Report ở trên ta có thể làm như sau.

  • Module Order dispatch event liên quan đến trạng thái của order 
  • Module Withdraw dispatch event liên quan đến việc KOL/ Publisher rút tiền
  • Module Report lắng nghe các sự kiện liên quan đến Order để tạo các bản ghi thống kê liên quan đến lượng Commission của các đơn hàng . Đồng thời lắng nghe các sự kiện trong module Withdraw để thống kê các hoạt động rút tiền của KOL/PUBLISHER tự đó tổng hợp các báo cáo

Với cách tổ chức như trên ta thấy:

  • Việc mở rộng các module khác liên quan đến order không còn đòi hỏi sự sửa đổi trực tiếp trong các function của Module Order
  • Các logic tổng hợp của module Report được tập hợp trong module, Module này có thể dễ dàng thay đổi, tắt, sửa mà không ảnh hưởng đến các thành phần khác của hệ thống.
3.2.2 Plugin và Hook 

PluginHook là 2 cơ chế hay được sử dụng trong các Framework / Platform hỗ trợ Module hóa. Về cơ chế, cách can thiệp, ý tưởng của các pattern này tương tự như Observer đó là chèn một hành động vào trong quá trình thực thi của ứng dụng. Điểm khác nhau cơ bản giữa 2 phương thức này là :

  • Observer: Event sẽ do các Module chủ động bắn (dispatch) ra để các module khác extends. Sử dụng observer để xử lý.
  • Plugin & Hook: Không phải do các module chủ động bắn ra mà là 1 cơ chế trong core của Framework/Platform. Các function thường sẽ được chèn vào trước hoặc sau 1 function nào đó.

Với observer:

  • Phần lớn các observer sẽ được giữ nguyên sau các sự thay đổi hoặc upgrade vì các event này thường được thiết kế dựa trên khung của nghiệp vụ
  • Plugin & Hook có thể không hoạt động sau các sửa đổi trong nội bộ module đặc biệt đổi với các function không thuộc tập của Service Contract 

Kết luận: Nếu dùng được event thì dùng event trước nhé ^^ 

4, Kết luận và sử dụng trong Laravel 

Như vậy bài viết đã giới thiệu với mọi người một kiểu tổ chức project khác so với các thường dùng mà mọi người hay sử dụng trong rất nhiều Framework. Cách triển khai này có các điểm mạnh như.

  • Dễ dàng mở rộng
  • Logic được phân chia và triển khai rõ ràng

Tuy nhiên một số vấn đề sẽ phát sinh trong quá trình phát triển

  • Cần thời gian nhất định để tiếp cận và quen với các concept mới
  • Kiến trúc phức tạp hơn so với phát triển MVC thông thường.
  • Trong quá trình phát triển cả team cần chia sẻ chung một mindset về module hóa và hệ thống không rất dễ dẫn tới việc Module hóa nửa vời, gây kho khăn cho việc bảo trì.

Trong Laravel chúng ta có thể Module hóa một cách đơn giản với việc chia các thành phần thành các Package hoặc sử dụng 1 open source hỗ trợ việc này ví dụ như Laravel Module  https://nwidart.com/laravel-modules/v6/introduction 

Lưu ý: Trong quá trình triển khai cần cân bằng giữa nguồn lực, thời gian và chức năng cần phát triển. Một số phần nêu trên có thể dẫn đến việc tiêu tốn thêm một phần tài nguyên của team phát triển. Tác giả không chịu trách nhiệm cho việc chậm deadline khi phát triển nếu có. Hãy cân bằng giữa các nguyên tắc phát triển và deadline hehe.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *