Clean Architecture — multi-layer na arkitektura na iminungkahi ni Robert Martin (Uncle Bob) noong 2012, na hinahati ang aplikasyon sa mga independiyenteng layer: Domain (Entities, Use Cases), Data (Repositories, DataSources) at Presentation (ViewModels, Views). Ang pangunahing prinsipyo — Dependency Rule: ang mga dependency ay nakatuon sa loob, ang mga panlabas na layer ay nakadepende sa panloob, ngunit hindi kabaligtaran. Ang Clean Architecture ay inilalapat sa mobile development para sa mga proyektong may mataas na komplikasyon ng lohika ng negosyo. Higit pa — sa aklat na The Clean Architecture.
Mga Pangunahing Punto
Clean Architecture — pattern ng arkitektura na binuo ni Robert Martin (Uncle Bob) noong 2012. Ang pangunahing ideya — paghahati ng aplikasyon sa mga layer na may mahigpit na patakaran ng dependency: ang kodigo sa loob ng layer ay hindi alam ang tungkol sa kodigo sa labas. Ang mga panlabas na layer (UI, framework, database) — mga detalye ng implementasyon. Ang mga panloob na layer (lohika ng negosyo, mga patakaran ng negosyo) — ang esensya ng aplikasyon.
Mga layer ng Clean Architecture sa mobile development: 1) Domain — Entities (mga bagay ng negosyo) at Use Cases (mga kaso ng paggamit); 2) Data — RepositoryImpl (implementasyon ng mga repository), DataSources (network, database, cache); 3) Presentation — ViewModels, Views (Compose/SwiftUI). Domain — ang pinakaloob na layer, walang mga dependency. Data ay nakadepende sa Domain (nagpapatupad ng mga interface ng repository). Presentation ay nakadepende sa Domain (tumatawag sa Use Cases, nag-subscribe sa resulta).
| Layer | Naglalaman | Mga Dependency |
|---|---|---|
| Domain | Entities, Use Cases, Repository Interfaces | Wala (purong Kotlin/Swift) |
| Data | RepositoryImpl, DataSources (API, DB, Cache) | Domain, Retrofit, Room, Ktor |
| Presentation | ViewModels, Views, Composables | Domain, Jetpack, SwiftUI |
Dependency Rule — ang tanging mahigpit na patakaran ng Clean Architecture. Ang source code ay maaari lamang sumangguni sa layer sa loob mismo o sa layer sa ibaba (mas malapit sa gitna). Ang Presentation ay nag-iimport ng Domain. Ang Domain HINDI nag-iimport ng Data o Presentation. Ito ay nakakamit sa pamamagitan ng pagbabaligtad ng dependency (Dependency Inversion Principle): Tinutukoy ng Domain ang Repository interface, Data ang nagpapatupad nito. Presentation ay nakadepende sa abstraksiyon ng UseCase, hindi sa konkretong repository.
Domain — ang pinaka-matatag na layer ng aplikasyon. Entities — mga bagay ng negosyo na hindi nakadepende sa mga framework: User, Product, Order. Use Cases — mga klase na may isang metodo na invoke (o operator fun invoke sa Kotlin), nagpapatupad ng isang senaryo: GetUserUseCase, PlaceOrderUseCase, CalculateTotalUseCase. Repository Interfaces — mga abstraksiyon ng pag-access sa datos, tinukoy sa Domain, ipinatupad sa Data. Ang Domain ay hindi naglalaman ng Android SDK, iOS UIKit, Retrofit, Room — purong Kotlin o Swift lamang.
// Entity — bagay ng negosyo (Domain)
struct User: Equatable {
let id: Int
let name: String
let email: String
}
// Repository Interface — abstraksiyon ng datos (Domain)
protocol UserRepository {
func getUser(id: Int) async throws -> User
func getUsers() async throws -> [User]
}
// Use Case — isang senaryo (Domain)
final class GetUserUseCase {
private let repository: UserRepository
init(repository: UserRepository) {
self.repository = repository
}
func execute(id: Int) async throws -> User {
return try await repository.getUser(id: id)
}
}
Use Case — «klase na may isang metodo» — hindi dogma, kundi praktikal na rekomendasyon. Kapag naging mas kumplikado ang Use Case (validation + pag-log + pagtawag ng repository), ang mga metodo nito ay pinag-grupo ayon sa kahulugan: UserUseCase.getUser, UserUseCase.searchUsers, UserUseCase.deleteUser. Mahalaga — hindi dapat malaman ng Use Case kung saan nanggaling ang datos (network, database, cache) at kung sino ang nagpapakita nito (Compose, SwiftUI). Sa IT Sectr naglalaan kami ng Use Case para sa bawat operasyon na may patakaran ng negosyo, validation o pagsasama ng datos mula sa dalawang source.
Kadalisayan ng Domain ay nakakamit sa pamamagitan ng DTO mapping sa mga hangganan ng layer. Ang Data layer ay tumatanggap ng mga modelo ng JSON (DTO), mina-map ang mga ito sa Domain Entity. Presentation ay tumatanggap ng Domain Entity, mina-map sa ViewModel (DisplayItem). Ang Domain Entity ay hindi kailanman naglalaman ng mga anotasyon ng Retrofit, Room, Codable — ito ay ginagarantiyahan na ang layer ay hindi kailangang baguhin kapag nagpalit ng database mula Room patungo Realm o kapag pinalitan ang Retrofit ng Ktor.
Data Layer — implementasyon ng mga interface na tinukoy sa Domain. Naglalaman ng RepositoryImpl (mga klase na nagpapatupad ng UserRepository) at DataSources (RemoteDataSource — API, LocalDataSource — database, CacheDataSource — SharedPreferences/NSUserDefaults). Ang Data layer ay nakadepende sa Domain (nag-iimport ng mga interface ng repository at Entities) at sa mga framework (Retrofit, Room, Ktor, CoreData). Itinatago ng RepositoryImpl ang pinagmulan ng datos mula sa Domain — hindi alam ng Use Case kung ang datos ay nanggaling sa network o cache.
// DTO — modelo para sa network (Data)
data class UserDto(
@SerializedName("id") val id: Int,
@SerializedName("first_name") val firstName: String,
@SerializedName("last_name") val lastName: String,
@SerializedName("email") val email: String
)
// RepositoryImpl — implementasyon (Data)
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
) : UserRepository {
override suspend fun getUser(id: Int): User {
// Sinusubukang kunin mula sa cache
localDataSource.getUser(id)?.let { return it.toDomain() }
// Kung wala — naglo-load mula sa network
val dto = remoteDataSource.fetchUser(id)
val user = dto.toDomain()
localDataSource.saveUser(user)
return user
}
override suspend fun getUsers(): List<User> {
return remoteDataSource.fetchAllUsers().map { it.toDomain() }
}
}
// Mapper — conversion DTO ↔ Domain
fun UserDto.toDomain() = User(
id = id,
name = "$firstName $lastName",
email = email
)
Estratehiya ng caching sa Data Layer: Una munang sinusuri ng RepositoryImpl ang lokal na imbakan, kung walang datos — naglo-load mula sa network at nag-save nang lokal. Kung ang network ay hindi available — nagbabalik ng lumang datos na may markang isStale. Ang Use Case sa Domain ay hindi alam ang tungkol sa estratehiya — natatanggap ang User sa pamamagitan ng Repository.getUser(id). Ang pagbabago ng estratehiya (halimbawa, pag-invalidate ng cache bawat 15 minuto) ay hindi nakakaapekto sa Domain at Presentation.
Modularidad sa Android — pinapayagan ng Kotlin Multiplatform na ilipat ang Domain sa isang hiwalay na KMP module na walang mga dependency sa Android SDK. Data — hiwalay na module na may dependency sa Domain. Presentation — Android module na may dependency sa Domain. Mga dependency ng Gradle: domain (pure Kotlin), data (domain + Retrofit + Room), app (domain + presentation + Hilt). Ang ganitong modularidad ay sapilitan para sa malalaking proyekto — CI ay nagbu-build ng Domain nang hiwalay, ang unit test ng Domain ay hindi nangangailangan ng Android emulator.
Presentation Layer — ang pinakapanlabas na layer ng Clean Architecture. Naglalaman ng ViewModels (Android) / ObservableObject (iOS) at Views (Compose/SwiftUI). Tumatawag ang ViewModel ng Use Case, natatanggap ang resulta at binabago ito sa estado ng UI (State). Ang View ay nag-subscribe sa State at nagpapakita. Ang Presentation ay nakadepende sa Domain — nag-iimport ng Use Cases at Entities. Ang Presentation ay hindi nag-iimport ng Data Layer — ang datos ay dumarating sa pamamagitan ng Use Case, na sa loob ay gumagamit ng Repository.
ViewModel sa Clean Architecture ay hindi naglalaman ng lohika ng negosyo — tumatawag ng Use Case. Kung ang Use Case ay nagbabalik ng User, binabago ito ng ViewModel sa UserDisplayItem (name, emailFormatted, avatarUrl) — isang purong modelo ng presentasyon. Hindi alam ng Use Case ang DisplayItem — nagbabalik ng Entity. Ang paghihiwalay na ito ay nagpapahintulot sa pag-test ng Use Case nang walang UI at ViewModel nang walang UseCase (sa pamamagitan ng mock). Sa IT Sectr mahigpit naming sinusunod: Use Case — lohika ng negosyo, ViewModel — presentasyon lamang, View — pagpapakita lamang.
// Use Case (Domain) — purong lohika ng negosyo
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: Int): User {
return repository.getUser(id)
}
}
// ViewModel (Presentation) — presentasyon lamang
class UserViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
private val _state = MutableStateFlow<UserScreenState>(UserScreenState.Loading)
val state: StateFlow<UserScreenState> = _state.asStateFlow()
fun loadUser(id: Int) {
viewModelScope.launch {
_state.value = UserScreenState.Loading
val user = getUserUseCase(id)
val displayItem = UserDisplayItem(
name = user.name,
email = user.email,
initials = user.name.split(" ").joinToString("") { it.first().toString() }
)
_state.value = UserScreenState.Success(displayItem)
}
}
}
data class UserDisplayItem(
val name: String,
val email: String,
val initials: String
)
sealed interface UserScreenState {
data object Loading : UserScreenState
data class Success(val displayItem: UserDisplayItem) : UserScreenState
data class Error(val message: String) : UserScreenState
}
Navigation sa Presentation layer — bahagi ng panlabas na singsing. Hindi itinakda ng Clean Architecture ang mekanismo ng nabigasyon — maaaring NavController (Compose), NavigationStack (SwiftUI), Coordinator (UIKit) o Router (VIPER). Mahalaga: ang desisyon ng nabigasyon ay ginagawa ng Presentation, ngunit ang nabigasyon ay hindi dapat pumasok sa Use Case. Ang Use Case ay nagbabalik ng resulta, ang ViewModel ay nagpapasya kung saang screen pupunta. Sa Clean Architecture ang nabigasyon ay isang detalye na maaaring palitan nang hindi binabago ang Domain.
Clean Architecture sa Android ay ipinatutupad sa pamamagitan ng mga module ng Gradle: domain (pure Kotlin), data (domain + Retrofit + Room), presentation (domain + Compose). Estruktura ng folder: domain/user/User.kt, GetUserUseCase.kt, UserRepository.kt; data/remote/UserRemoteDataSource.kt, local/UserDao.kt, repository/UserRepositoryImpl.kt; presentation/ui/user/UserViewModel.kt, UserScreen.kt. DI (Hilt) ay nag-uugnay ng mga layer: ang UserRepositoryImpl ay naka-bound sa UserRepository interface sa domain module.
Clean Architecture sa iOS ay gumagamit ng SPM o mga grupo ng Xcode nang walang hiwalay na modules (dahil sa mga limitasyon ng Xcode). Domain — folder na may mga file na hindi nag-iimport ng UIKit o SwiftUI. Data — folder na may APIClient, CoreDataStack, RepositoryImpl. Presentation — folder na may ViewModels at SwiftUI Views. DI sa pamamagitan ng konstruktor o assembly sa App. Ang pangunahing tawag — async/await sa pamamagitan ng UseCase.execute() na may pagsusuri ng MainActor para sa mga update ng UI.
// Data Layer: Remote DataSource (iOS)
final class UserRemoteDataSource {
private let apiClient: APIClient
func fetchUser(id: Int) async throws -> UserDTO {
return try await apiClient.get("/users/\(id)")
}
}
// Repository Implementation (Data)
final class UserRepositoryImpl: UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
func getUser(id: Int) async throws -> User {
if let cached = try await local.getUser(id) {
return cached
}
let dto = try await remote.fetchUser(id)
let user = dto.toDomain()
try await local.saveUser(user)
return user
}
}
// Presentation: ViewModel + SwiftUI View
@MainActor
final class UserViewModel: ObservableObject {
@Published private(set) var state: UserScreenState = .loading
private let getUserUseCase: GetUserUseCase
func loadUser(id: Int) {
Task {
state = .loading
if let user = try? await getUserUseCase.execute(id: id) {
state = .success(user)
} else {
state = .error("Failed to load")
}
}
}
}
Clean Architecture sa mga proyekto ng IT Sectr — ang aming pamantayan para sa mga proyekto mula 30 araw. Ginagamit namin ang tatlong-layer na arkitektura na may Kotlin Multiplatform para sa Android/iOS mula noong 2022. Domain — pinagsamang KMP module, Data — mga module ng platform (Retrofit sa Android, URLSession sa iOS), Presentation — native UI. Ito ay nagbibigay ng 60–80% na pinagsamang kodigo ng lohika ng negosyo sa pagitan ng iOS at Android, na nagbabawas ng oras ng pag-develop ng 30–40% kumpara sa dalawang magkahiwalay na implementasyon.
Mga Madalas Itanong
Minimum tatlo: Domain, Data, Presentation. Para sa malalaking proyekto idinadagdag ang Framework (mga dependency ng Android SDK/iOS UIKit) at Device (GPS, kamera, sensor). Ang bilang ng mga layer — hindi mahigpit na patakaran, isyu ng kaginhawahan. Ang mahalaga ay sundin ang Dependency Rule: ang mga dependency ay nakatuon sa loob, patungo sa Domain. Maaaring magsimula sa tatlo at magdagdag ng mga layer habang lumalaki ang proyekto.
Oo — 30–50% kumpara sa MVVM dahil sa paghihiwalay ng mga interface ng repository, Use Cases at mapper. Para sa simpleng CRUD application ito ay sobra. Ang Clean Architecture ay makatwiran para sa mga proyektong may komplikadong lohika ng negosyo, kung saan ang testability at isolasyon ng layer ay mas mahalaga kaysa sa bilis ng pag-develop. Para sa MVP o prototype gamitin ang MVVM — pababagalin ng Clean Architecture ang paglulunsad.
Oo, ito ay karaniwang gawain. Ang Use Cases ay nananatili sa Domain at ang Presentation ay gumagamit ng siklo ng MVI (Intent → Reducer → State). Data Layer — pareho, Domain — pareho. Ang MVI sa Presentation ay nagbibigay ng predictable na estado ng screen, Clean Architecture — isolasyon ng lohika ng negosyo. Ang kombinasyong ito ay ginagamit sa malalaking proyekto na may sampu-sampung developer.
Ang Use Case ay kinakailangan kapag ang operasyon ay may kasamang patakaran ng negosyo: validation, pagsasama ng datos mula sa dalawang source, pagkalkula, pag-log, pagsusuri ng karapatan sa pag-access. Ang simpleng kahilingang getUser(id) na walang karagdagang lohika ay maaaring tumawag ng Repository nang direkta mula sa ViewModel. Gayunpaman para sa pagkakapareho ng arkitektura, maraming koponan ang gumagawa ng Use Case para sa bawat pampublikong metodo ng Repository — ito ay nagdaragdag ng 5–10% na kodigo ngunit pinapasimple ang pagbasa.
Domain: unit test ng Use Cases na may mock Repository — purong Kotlin/Swift na walang Android SDK. Data: integration test ng RepositoryImpl na may mock/fake DataSource. Presentation: test ng ViewModel na may mock UseCase. Dahil sa Dependency Rule bawat layer ay tine-test nang hiwalay. Sa IT Sectr ang coverage ng Domain ay umaabot sa 95%, Data — 70–80%, Presentation — 60–70%.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din