modular_architecture_on_ios

Kiến trúc Mô-đun

Mô-đun – tính từ: sử dụng hoặc liên quan đến một mô-đun hoặc nhiều mô-đun làm nền tảng cho thiết kế hoặc xây dựng: “các đơn vị nhà ở dạng mô-đun”.

Trong phần giới thiệu, tôi đã đề cập ngắn gọn đến động lực xây dựng dự án theo hướng mô-đun. Tóm lại, kiến trúc mô-đun sẽ mang lại cho chúng ta nhiều tự do hơn trong việc đưa ra các quyết định sản phẩm – những quyết định sẽ ảnh hưởng trực tiếp đến toàn bộ kỹ thuật của ứng dụng. Điều này bao gồm việc xây dựng một ứng dụng khác cho cùng một công ty, mã nguồn mở một phần mã hiện có, mở rộng quy mô đội ngũ lập trình viên, v.v. Với nền tảng di động đã được thiết lập, toàn bộ quy trình phát triển sẽ diễn ra nhanh hơn và gọn gàng hơn.

Tuy nhiên, để công bằng mà nói, việc duy trì một nền tảng phần mềm như vậy trong công ty cũng có thể rất khó khăn. Ở đây, “duy trì” có nghĩa là: quản lý CI/CD (Tích hợp liên tục / Triển khai liên tục), duy trì các dự án cũ được xây dựng dựa trên nền tảng đã được tái cấu trúc mạnh mẽ, xử lý mã legacy, cập nhật nền tảng với các công cụ phát triển mới nhất, v.v. Rõ ràng, với một dự án quy mô lớn, việc này có thể là trách nhiệm của cả một đội ngũ riêng biệt.

Cuốn sách này mô tả cách xây dựng một kiến trúc quy mô lớn, có khả năng mở rộng, dựa trên Domain-Driven Design (thiết kế theo miền), thông qua các ví dụ cụ thể – chẳng hạn như nền tảng phần mềm cho Trạm Vũ trụ Quốc tế (ISS).

Trong phạm vi cuốn sách này, một mô-đun là một “hộp riêng biệt”, hoặc trên thực tế là một dự án Xcode độc lập chứa các framework, test bundle, v.v.

Trích dẫn từ Apple:

Framework là một thư mục có cấu trúc phân cấp, bao gồm các tài nguyên dùng chung như thư viện liên kết động (dynamic shared library), tệp nib, tệp hình ảnh, chuỗi đã được bản địa hóa, tệp tiêu đề (header files), và tài liệu tham khảo – tất cả gói gọn trong một gói duy nhất.
Nhiều ứng dụng có thể sử dụng các tài nguyên này cùng lúc. Hệ thống sẽ nạp chúng vào bộ nhớ khi cần và chia sẻ một bản duy nhất của tài nguyên cho tất cả các ứng dụng bất cứ khi nào có thể.

Thiết kế

Trong cuốn sách này, tôi lựa chọn sử dụng một kiến trúc mà tôi cho là linh hoạt nhất. Đó là kiến trúc gồm năm lớp (layer) như sau:

  • Application (Ứng dụng)
  • Domain (Miền nghiệp vụ)
  • Service (Dịch vụ)
  • Core (Lõi)
  • Shared (Chia sẻ)

Mỗi lớp sẽ được giải thích chi tiết trong phần sau.

Tuy nhiên, các nguyên tắc được trình bày ở đây cũng có thể áp dụng cho các kiểu kiến trúc khác. Ví dụ, một kiến trúc đơn giản hơn theo hướng chức năng (feature-oriented) có thể được chia thành:

  • Application
  • Feature
  • Core

Toàn bộ hệ thống phân lớp và mô-đun này, trong các phần sau của cuốn sách, sẽ được gọi chung là Application Framework (Khung ứng dụng).


Các lớp (Layers)

Giờ hãy cùng xem xét từng lớp một và vai trò của nó. Sau đó, chúng ta sẽ đi sâu vào các mô-đun cụ thể trong từng lớp và cấu trúc nội bộ của chúng.

Lớp Ứng Dụng (Application Layer)

Lớp Application bao gồm các sản phẩm cuối cùng hướng đến người dùng: chính là các ứng dụng. Ứng dụng sẽ tập hợp tất cả các phần cần thiết từ Application Framework, liên kết các domain, service, v.v. Ngoài ra, ứng dụng sẽ khởi tạo ngăn xếp UI (UI stack), chủ yếu là các domain coordinator (nếu có sử dụng mẫu thiết kế này), và các đối tượng như NetworkService, CashierService, v.v. Ứng dụng cũng sẽ có cấu hình riêng, chứa các thông tin như: loại ứng dụng (flavour), biến thể ứng dụng (variant), các tính năng được bật (feature toggles), cấu hình keychain, v.v. Các mẫu thiết kế giúp hiện thực những yêu cầu này sẽ được trình bày ở phần sau.

Ngoài ra, ứng dụng cũng có thể chứa các phần triển khai bắt buộc như: nhận thông báo đẩy (push notification), xử lý deep link, yêu cầu quyền truy cập (permissions), và nhiều thứ khác.

Trong Application Framework, ứng dụng chỉ đơn thuần là một container để kết nối các thành phần lại với nhau.

Ví dụ, trong một ứng dụng thương mại điện tử (e-commerce), bạn có thể có:

  • The Shop: dành cho khách hàng mua hàng online.
  • Cashier: dành cho nhân viên của công ty.

Lớp Miền (Domain Layer)

Lớp Domain liên kết các service và các mô-đun từ các lớp bên dưới, và sử dụng chúng để triển khai các nhu cầu nghiệp vụ của công ty hoặc dự án.

Các domain sẽ chứa, ví dụ như: luồng người dùng (user flow) trong từng phần chức năng cụ thể của ứng dụng. Ngoài ra, domain còn chứa các thành phần cần thiết cho luồng đó như:

  • Coordinator
  • View Controller
  • View
  • Model
  • ViewModel

Tất nhiên, việc sử dụng mô hình nào để xây dựng các màn hình phụ thuộc vào kinh nghiệm kỹ thuật và sở thích của nhóm phát triển. Cá nhân tôi thích mô hình reactive MVVM+C, nhưng sẽ bàn thêm về điều đó sau.

Tiếp tục với ví dụ thương mại điện tử:

  • Một domain có thể là Checkout hoặc Store Items.
  • Một shared domain có thể là User, với luồng hiển thị khác nhau tùy thuộc vào người dùng là nhân viên hay khách hàng.

Lớp Dịch Vụ (Service Layer)

Service là các mô-đun hỗ trợ cho các domain. Mỗi domain có thể liên kết với nhiều service để đạt được kết quả mong muốn. Các service này thường sẽ giao tiếp với hệ thống backend, lấy dữ liệu từ đó, lưu trữ dữ liệu vào bộ nhớ riêng, và cung cấp lại dữ liệu cho các domain.

Ví dụ trong ứng dụng thương mại điện tử lý thuyết của chúng ta, một service có thể là Checkout Service. Service này sẽ xử lý toàn bộ việc giao tiếp cần thiết với backend để thực hiện thanh toán qua thẻ tín dụng, v.v.


Lớp Lõi (Core Layer)

Core layer là lớp “nền tảng” cho toàn bộ ứng dụng. Các service sẽ liên kết với những mô-đun cần thiết từ lớp này, ví dụ như để giao tiếp với backend hoặc cung cấp các tầng trừu tượng chung để lưu trữ dữ liệu. Các domain cũng sẽ liên kết với lớp core, ví dụ để sử dụng các thành phần UI giúp việc tạo giao diện trở nên dễ dàng hơn.

Ví dụ trong ứng dụng thương mại điện tử, các mô-đun trong lớp core có thể là:

  • Network (mạng)
  • UIComponents (thành phần giao diện người dùng)

Lớp Chia Sẻ (Shared Layer)

Shared layer là lớp hỗ trợ cho toàn bộ framework. Trong một số trường hợp, lớp này có thể không cần thiết, vì vậy không phải sơ đồ nào cũng bao gồm lớp này. Tuy nhiên, ví dụ điển hình cho shared layer là cơ chế ghi log (logging). Ngay cả các mô-đun trong lớp core cũng có thể cần ghi log, và nếu không có lớp chia sẻ, có thể xảy ra trùng lặp mã. Việc trùng lặp này có thể được giải quyết bằng shared layer hoặc bằng cách áp dụng các nguyên tắc của clean architecture – tuy nhiên, ta sẽ nói kỹ hơn về vấn đề này ở phần sau.

Ví dụ, trong một ứng dụng thương mại điện tử, các mô-đun shared có thể là:

  • Logging
  • AppAnalytics (phân tích dữ liệu ứng dụng)

Ví dụ: Trạm Vũ trụ Quốc tế (International Space Station)

Trong ví dụ này, chúng ta sẽ xem xét cách mà kiến trúc năm lớp có thể được áp dụng cho một hệ thống như Trạm Vũ trụ Quốc tế. Sơ đồ bên dưới minh họa kiến trúc gồm năm lớp với các mô-đun và các mối liên kết giữa chúng. Cấu trúc này từ đây về sau sẽ được gọi là Application Framework (Khung Ứng Dụng) trong suốt cuốn sách.

Mặc dù chương này mang tính lý thuyết, nhưng trong các chương tiếp theo, mọi thứ sẽ được giải thích và trình bày bằng các ví dụ thực tế.

Ví dụ này có ba ứng dụng:

  • Overview: ứng dụng hiển thị cho các phi hành gia trạng thái tổng thể của trạm vũ trụ.
  • Cosmonaut: ứng dụng cho phép phi hành gia điều khiển bộ đồ không gian của mình, cũng như quản lý nhu yếu phẩm và thông tin cá nhân.
  • Laboratory: ứng dụng dùng để điều khiển các phòng thí nghiệm trên trạm vũ trụ.

Như đã mô tả ở trên, tất cả các ứng dụng đều liên kết với mô-đun Scaffold, mô-đun này đảm nhận việc khởi tạo ban đầu cho ứng dụng, trong khi bản thân ứng dụng chỉ hoạt động như một container (thùng chứa các thành phần).

Sơ đồ ở trên mô tả cách liên kết cụ thể giữa các mô-đun trong ứng dụng. Hãy cùng phân tích chi tiết hơn.

Ứng dụng Overview liên kết với domain Peripheries, domain này triển khai logic và giao diện cho các thiết bị ngoại vi của trạm vũ trụ.

Domain Peripheries liên kết với các service Heat Radiator, Solar ArrayDocking Port, từ đó thu thập dữ liệu về các thiết bị ngoại vi đó. Đồng thời, nó cũng liên kết với UIComponents để khởi tạo việc phát triển giao diện người dùng.

Các service được liên kết sẽ sử dụng các mô-đun core NetworkRadio. Đây là các nền tảng cung cấp khả năng giao tiếp với các hệ thống khác thông qua các giao thức mạng. Trong trường hợp này, mô-đun Radio có thể triển khai một kênh giao tiếp sử dụng BLE (Bluetooth Low Energy) hoặc công nghệ khác để kết nối với hệ thống tấm pin mặt trời hoặc bộ tản nhiệt.

Ngoài ra, UIComponents được dùng để khởi tạo phần thiết kế giao diện và Persistence được dùng cho các thao tác với cơ sở dữ liệu.

Cosmonaut

Ứng dụng Cosmonaut liên kết với các domain SpacesuitCosmonaut. Giống như các domain khác, mỗi domain chịu trách nhiệm cho các màn hình và luồng người dùng tương ứng trong phần ứng dụng đó.

Domain SpacesuitCosmonaut liên kết với các service tương ứng là Spacesuit ServiceCosmonaut Service, đây là các dịch vụ cung cấp dữ liệu cho các màn hình đặc thù của domain. UIComponents cung cấp các thành phần giao diện người dùng.

Spacesuit Service sử dụng Radio để giao tiếp với bộ đồ không gian của phi hành gia thông qua BLE (Bluetooth Low Energy) hoặc một loại công nghệ vô tuyến khác. Cosmonaut Service sử dụng Network để cập nhật thông tin cho trung tâm điều khiển ở Houston về trạng thái hiện tại của phi hành gia, và sử dụng Persistence để lưu trữ dữ liệu của phi hành gia để có thể sử dụng khi không có kết nối mạng.

Laboratory
Phần này được để dành cho người đọc tự khám phá.

Kết luận

Như bạn có thể hình dung, việc mở rộng kiến trúc như đã mô tả ở trên sẽ không phải là vấn đề. Khi cần mở rộng ứng dụng Overview để hỗ trợ thêm một thiết bị ngoại vi khác của trạm ISS, ví dụ, bạn có thể dễ dàng thêm một domain module mới cùng với các service module cần thiết.

Khi có yêu cầu tạo một ứng dụng mới, chẳng hạn dành cho các phi hành gia, ứng dụng mới hoàn toàn có thể liên kết với module Cosmonaut đã được kiểm chứng và sử dụng thực tế, cùng với các module khác cần thiết. Việc phát triển ứng dụng mới nhờ đó sẽ trở nên dễ dàng hơn rất nhiều.

Việc toàn bộ kiến thức phần mềm được lưu trữ trong cùng một repository, nơi các lập trình viên đều có thể truy cập và học hỏi lẫn nhau, cũng mang lại rất nhiều lợi ích.

Tuy nhiên, tất nhiên cũng tồn tại một số nhược điểm. Ví dụ, việc hướng dẫn các lập trình viên mới làm quen với kiến trúc này có thể mất kha khá thời gian, đặc biệt nếu dự án đã có một lượng mã nguồn lớn từ trước. Trong trường hợp đó, lập trình đôi (pair programming), quy trình giới thiệu dự án bài bản, tài liệu kiến trúc phần mềm, và tài liệu chi tiết cho từng module sẽ đóng vai trò quan trọng trong việc giúp người mới nhanh chóng bắt kịp tiến độ.

Code Toàn Bug

Code nhiều bug nhưng biết cách giấu!

Leave a Reply

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