Giới thiệu
Clean Architecture là một kiến trúc lập trình được Robert C. Martin đề cập tại blog của ông vào năm 2012, đó là sự kiện quan trọng trong tư tưởng thiết kế phần mềm ảnh hưởng cho đến bây giờ.
Clean Architecture có thể được sử dụng trong bất kỳ ứng dụng và nền tảng nào. Bài viết này mình xin được tập trung vào kiến trúc sạch trong phát triển ứng dụng android.
Tổng quan kiến trúc sạch
Có rất nhiều kiến trúc khác nhau trong lập trình nhưng chúng đều có một mục tiêu chung là tách biệt các mối quan tâm. Để đạt được sự tách biệt này cách chia phần mềm thành các layer đảm nhận các nghiệp vụ khác nhau.
Một vài đặc điểm:
- Dễ dàng test, các bussiness logic có thể test mà không có giao diện người dùng, cơ sở dữ liệu, bất kỳ yếu tố bên ngoài nào khác.
- Giao diện người dùng có thể dễ dàng thay đổi mà không làm thay đổi phần còn lại của hệ thống.
- Độc lập với các dữ liệu, ta có thể chuyển đổi các loại dữ liệu khác nhau mà không ảnh hưởng đến các layer khác.
- Độc lập với các framework, điều này cho phép sử dụng các framework như các công cụ, thay vì phải nhồi nhét hệ thống của bạn phụ thuộc vào chúng.
- Một layer chỉ biết layer phía dưới nó, layer trong cùng sẽ không biết gì về các layer khác(isolated) => tách biệt giữa các layer, layer business logic bên trong sẽ không phụ thuộc vào các layer khác.
Mộ- Các vòng tròn đồng tâm càng ở trong càng cấp cao, module cấp cao không phụ thuộc vào module cấp thấp(Dependency Inversion principle). Ta sẽ rõ hơn trong chi tiết từng layer khi xây dựng ứng dụng android. - Áp dụng nguyên tắc SOLID.
Clean Architecture trong ứng dụng Android
Tùy thuộc vào đặc thù, quy mô dự án mà ta phân chia thành các layer khác nhau. Trong dự án android ta thường chia thành ba layer chính:
- Domain
- Data
- Presenter
Mỗi layer là một module trong Android Studio, giờ ta sẽ đi vào chi tiết từng layer.
Domain layer
- Domain layer là lớp chứa tất cả model và toàn bộ bussiness logic của ứng dụng, có thể coi đây là nơi chứa các policy còn các layer khác là nơi chứa các cơ chế.
- Domain layer nằm trong cùng do đó sẽ không biết bất kỳ layer nào khác bên ngoài.
Đây là module cấp cao, không phụ thuộc vào bất kỳ implementation của module cấp thấp nào mà chỉ phụ thuộc thông qua abstraction - Mỗi usecase đảm nhiệm một nhiệm vụ duy nhất (Single responsibility principle), ví dụ:
GetData()
,AddData()
, …
Tạo module domain với lib java or kotlin, do domain layer không phụ thuộc vào bất kỳ android framwork nào. Do domain layer chỉ chứa bussiness logic của app.
Ta có thể thấy file gradle của module domain không chứa dependencies của android framework.
Ở domain layer chỉ định nghĩa interface Repository, data layer chứa implementation mà domain đã định nghĩa.
Presenter layer phụ thuộc usecase.
Usecase tương tự như trong UML chứa một chức năng duy nhất, usecase chỉ phụ thuộc vào interface SchoolRepository (Nguyên tắc DI trong SOLID) mà không phụ thuộc trực tiếp repository tại data layer.
class GetAllSchoolUseCase @Inject constructor(
private val schoolRepository: SchoolRepository
) : IUseCase<Flow<List<School>>> {
override fun invoke(): Flow<List<School>> = schoolRepository.getAllSchool()
}
Data layer
Lớp này cung cấp cách thức để truy cập các nguồn dữ liệu trong room database hoặc internet. Các triển khai này sử dụng Repository pattern.
Data layer được tạo là module library android.
Tại data layer sẽ tạo class triển khai interface repository mà định nghĩa trong domain layer.
<code>class SchoolRepositoryImpl @Inject constructor(
@DispatcherIO private val dispatcher: CoroutineDispatcher,
private val schoolDao: SchoolDao,
private val studentDao: StudentDao
) : SchoolRepository {
...
}
Để chuyển đổi qua lại giữa model và entity giữa domain layer và data layer ta dùng các function mapper.
fun SchoolWithStudents.toSchool(): School {
return this.run {
School(
schoolEntity.schoolID,
schoolEntity.schoolName,
schoolEntity.address,
students.toStudents()
)
}
}
fun School.fromSchool(): SchoolEntity {
return this.run {
SchoolEntity(schoolID, schoolName, address)
}
}
Presenter layer
Tại đây chứa các code liên quan đến giao diện người dùng, layer này sẽ dependent các module khác.
Do sử dụng một vài dependencies tại module data nên mình sử dụng api project(":data")
.
Sử dụng MVVM
- View: chịu trách nhiệm vẽ UI người dùng (Activity, Fragment, …). Lắng nghe hành động từ người dùng và gọi ViewModel sử lý, observe LiveData trong ViewModel.
- Model: Chứa các bussiness login và data
- ViewModel: Cầu nối giữa data và UI, ViewModel sẽ sử dụng các UseCase tại domain layer để thực hiện các nhiệm vụ.
Mỗi màn hình(feature) sẽ là một subpackage trong package ui.
ViewModel sử dụng các usecase để thực hiện các task khác nhau.
@HiltViewModel
class SchoolListViewModel @Inject constructor(
private val getAllSchoolUseCase: GetAllSchoolUseCase,
private val deleteSchoolUseCase: DeleteSchoolUseCase,
private val deleteStudentUseCase: DeleteStudentUseCase,
private val updateSchoolUseCase: UpdateSchoolUseCase,
private val updateStudentUseCase: UpdateStudentUseCase
) : BaseViewModel<SchoolsViewState>() {
...
}
Kết luận
Clean Architecture rất phù hợp với những dự án lớn phức tạp, có nhiều bussiness logic, giúp cho việc phát triển các tính năng và maintain dễ dàng hơn. Tuy nhiên với những dự án nhỏ và đơn giản hơn thì việc sử dụng clean architecture không mang lại hiệu quả và mất thời gian cho việc xây dựng ban đầu.
Do chưa có kinh nghiệm nên bài viết này không thể tránh khỏi sai sót, rất mong nhận được góp ý từ các Anh.
Author: CuongNV83