Clean Architecture và MVVM trên iOS (Swift) phần 1

Tram Ho

Tổng quan

Khi mà chúng ta phát triển phần mềm thì điều quan trọng là không những sử dụng design patterns, mà còn architectural pattern. Có rất nhiều architectural pattern trong kỹ thuật phần mềm. Trong mobile, sử dụng rộng rãi nhất là MVVM, Clean Architecture và Redux patterns.

Trong bài viết lần này, tôi sẽ show trên Working_Example_Project, cách mà architectural pattern MVVM & Clean Architecture ứng dụng vào iOS app.

Vì nội dung khá dài nên bài viết mình sẽ chia làm 2 phần. Ở phần này mình sẽ tập trung nói về Clean Architecture và phần tiếp theo sẽ nói về MVVM.

Nếu bạn cũng muốn tìm hiểu về Redux, hãy xem cuốn sách này: Advanced iOS App Architecture.

Xem thêm thông tin về Clean Architecture

image.png

Như bạn có thể thấy trên mô hình Clean Architecture, chúng ta có các layer khác nhau trên ứng dụng. Nguyên tắc chính là không có sự phụ thuộc từ các layer bên trong đến các layer bên ngoài. Các mũi tên chỉ từ ngoài vào trong là Dependency Rule (nguyên tắc độc lập). Chỉ có thể có các phụ thuộc từ layer ngoài vào trong.

Sau khi group các layer, chúng ta có 3 layer chính: Presentation, Domain and Data layers.

image.png

Domain Layer (Business logic) là layer bên trong cùng của mô hình (không phụ thuộc vào các layer khác, nó hoàn toàn độc lập). chúng bao gồm Entities(Business Models), Use Cases, and Repository Interfaces. Layer này có thể được sử dụng lại trong các dự án khác nhau. Sự phân tách này cho phép không sử dụng ứng dụng chủ trong mục tiêu kiểm thử vì không cần phụ thuộc (bao gồm cả phụ thuộc từ bên thứ ba). Điều này làm cho Domain Use Cases test chỉ mất vài giây.
Lưu ý: Domain layer không được bao gồm bất kỳ thứ gì từ các layer khác (ví dụ: Presentation — UIKit or SwiftUI or Data Layer — Mapping Codable) .

Lý do mà good architecture tập trung vào UseCases là để các developer có thể mô tả một cách an toàn các cấu trúc hỗ trợ các UseCases đó mà không import Framework, tools và environment. Nó được gọi là
Screaming Architecture.

Presentation Layer bao gồm UI (UIViewControllers or SwiftUI Views), Views được điều phối bởi ViewModels (Presenters) thực hiện một hay nhiều Usecase. Presentation Layer chỉ phụ thuộc vào Domain Layer.

Data Layer chứa các Responsitory Implementations và một hoặc nhiều Datasource. Responsitory chịu trách nhiệm điều phối data từ nhiều nguồn data khác nhau. Datasource có thể là remote hoặc local.Data Layer chỉ phụ thuộc vào Domain Layer. Trong layer này, chúng ta cũng có thể add thêm mapping của Network JSON Data (ví dụ: Decodable performance) tới Domain model.

Trên biểu đồ bên dưới, mọi thành phần từ mỗi layer được biểu diễn dọc theo Dependency Direction (hướng phụ thuộc) và Data Flow (Request/Response). Chúng ta có thể thấy Dependency Inversion nơi chúng ta sử dụng Repository interfaces(protocols). Phần giải thích của từng Layer sẽ dựa trên Project ví dụ được đề cập ở đầu bài viết.

image.png

Data Flow

  1. View(UI) calls method from ViewModel (Presenter).
  2. ViewModel thực thi Use Case.
  3. Use Case kết hợp data từ User và Repositories.
  4. Mỗi Repository returns data từ Remote Data (Network), Persistent DB Storage Source or In-memory Data (Remote or Cached).
  5. Information flows trở lại View(UI), nơi hiển thị list item.

Dependency Direction

  • Presentation Layer -> Domain Layer <- Data Repositories Layer
  • Presentation Layer (MVVM) = ViewModels(Presenters) + Views(UI)
  • Domain Layer = Entities + Use Cases + Repositories Interfaces
  • Data Repositories Layer = Repositories Implementations + API(Network) + Persistence DB

Example Project: “Movies App”

image.png

Domain Layer

Bên trong Example Project bạn có thể tìm thấy Domain layer. nó chứa Entities, SearchMoviesUseCase tìm kiếm phim và lưu trữ các query thành công gần đây. Ngoài ra, nó chứa Data Repositories Interfaces, nó cần thiết cho Dependency Inversion.

Note: Cách khác để tạo Use Cases là sử dụng UseCase protocol với start() function và tất cả UseCasse implementations sẽ conform với protocol này. Một trong những UseCase trong example project tiếp cận theo cách này: FetchRecentMovieQueriesUseCase. Use Cases còn được gọi là Interactors

Note: Một UseCase có thể phụ thuộc vào các UseCase khác.

Presentation Layer

Presentation Layer chứa MoviesListViewModel với các item được quan sát từ MoviesListView. MoviesListViewModel không được import UIKit. Bởi vì việc giữ cho ViewModel clean khỏi bất kỳ UI frameworks nào như UIKit, SwiftUI hoặc WatchKit sẽ cho phép tái cấu trúc và tái sử dụng dễ dàng. Ví dụ: trong tương lai, việc tái cấu trúc Views từ UIKit sang SwiftUI sẽ dễ dàng hơn nhiều vì ViewModel sẽ không cần phải thay đổi.

Chúng ta sử dụng interfaces MoviesListViewModelInput và MoviesListViewModelOutput để MoviesListViewController có thể test được bằng cách mocking ViewModel một cách dễ dàng (example).. Ngoài ra, chúng ta có MoviesListViewModelActions closures, nó nói với MoviesSearchFlowCoordinator lúc nào prensent tới các view khác. Khi action closure được gọi, coordinator sẽ present màn hình movie detail. Chúng ta sử dụng một struct để nhóm các action vì sau này chúng ta có thể dễ dàng thêm nhiều action hơn nếu cần.

Presentation layer cũng chứa MoviesListViewController, nó ràng buộc với data (items) của MoviesListViewModel.

UI không thể truy cập vào business logic hoặc application logic (Business Models và UseCases), chỉ ViewModel mới có thể làm được điều đó. nó được gọi là separation of concerns.. Chúng ta không thể chuyển trực tiếp business logic sang View (UI). Đó là lý do tại sao chúng ta mapping Business model bên trong của ViewModel và đưa chúng đến View.

Chúng ta cũng có thể call một sự kiện search từ View đến ViewModel để bắt đầu search movies.

Chúng ta quan sát các item và reload khi mà chúng có sự thay đổi. Chúng tôi sử dụng observerble, nó được giải thích trong phần nội dung MVVM bên dưới đây:

Chúng ta chỉ định function showMovieDetails(movie:) cho action của MoviesListViewModel bên trong MovieSearchFlowCoordinator để present lên Movie Details Screen từ Coordinator flow:

Note: Chúng tả sử dụng Flow Coordinator cho presentation logic để giảm tải kích thước và trách nhiệm của viewcontroller. Chúng ta có strong reference đến Flow (action closures, self functions) để giữ cho Flow không bị hủy trong khi cần thiết.

Với cách tiếp cận này, chúng ta có thể dễ dàng sử dụng các view khác nhau với cùng ViewModel mà không cần sửa chúng. Chúng ta chỉ có thể kiểm tra if iOS 13.0 is available và sau đó tạo một SwiftUI View thay vì UIKit và liên kết nó với cùng một ViewModel, nếu không chúng ta tạo UIKit View. Trong example_project chúng ta tạo SwiftUI example cho MoviesQueriesSuggestionsList. Cần ít nhất Xcode 11 Beta.

Data Layer

Data Layer bao gồm DefaultMoviesRepository, nó phù hợp với các interface định nghĩa bên trong Domain Layer (Dependency Inversion). Chúng tôi cũng add vào đó mapping của JSON data (Decodable Confirmance)CoreData Entities cho Domain Model.

Note: Đối với Data Transfer Object (DTO) được sử dụng làm đối tượng trung gian để ánh xạ từ response của JSON vào Domain. Ngoài ra nếu chúng ta muốn cache EndPoint response, chúng ta có thể lưu Data Transfer Objects trong bộ lưu trữ liên tục bằng cách mapping chúng thành các đối tượng liên tục (ví dụ: DTO -> NSManagedObject).

Nói chung Data Repositories có thể được truyền vào với API Data ServicePersistent Data Storage. Data Repository làm việc với 2 dependencies để trả về data. Quy tắc trước tiên là yêu cầu lưu trữ liên tục cho đầu ra dữ liệu được lưu trong bộ nhớ cache (NSManagedObject được map vào Domain bằng DTO object, và được truy xuất trong cached data closure). Sau đó, để call API Data Service nó sẽ trả về data mới nhất. Sau khi Persistent Storage được updated data mới nhất (DTOs được map thành Persistent Objects lưu lại). Và sau đó DTO được map thành Domain và truy xuất trong updated data/completion closure. Đó là cách user sẽ thấy data ngay lập trức. Ngay cả khi không có kết nối internet, người dùng vẫn sẽ thấy dữ liệu mới nhất từ Persistent Storage. example

Bộ lưu trữ và API có thể được thay thế bằng các triển khai hoàn toàn khác (ví dụ thay đổi từ CoreData thành Realm). Mặc dù tất cả các lớp còn lại của ứng dụng sẽ không bị ảnh hưởng bởi thay đổi này, nhưng điều này là do DB là một chi tiết.

Infrastructure Layer (Network)

Nó bao bọc xung quanh Network Framework, có thể là Alamofire (hoặc framework khác), Nó có thể được cấu hình với các tham số khác (ví dụ như BaseURL). Nó cũng hỗ trợ định nghĩa các EndPoint và chứa các phương thức map data. (sử dụng Decodable)

Note: Bạn có thể đọc nhiều hơn ở đây.

Hết Phần 1

link gốc của bài viết (tác giả Oleh Kudinov) : https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
Link liên quan: https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo