본문 바로가기

안드로이드

[안드로이드] Kotlin을 활용한 Clean Architecture(+ Hilt 활용한 간단한 예제)

728x90
반응형

 

 

 

 

Clean Architecture 란?

 

 

 

 

Clean Architecture는 소프트웨어 설계 원칙을 기반으로 한 아키텍쳐 스타일입니다.

로버트 C.마틴이 제안한 것으로, 코드의 각 계층을 분리하여 특정 계층이 다른 계층에 영향을 미치지 않도록 하는 것이 목표입니다.

이를 통해, 코드의 유지보수성과 확장성을 높이고 의존성을 최소화하여 안정적인 애플리케이션 개발을 가능하게 합니다.

 

 

핵심 개념은 의존성 규칙(Dependency Rule)입니다. 이 규칙에 따르면, 의존성은 반드시 바깥쪽 계층에서 안쪽 계층으로 향해야 합니다.

이를 통해 비즈니스 로직이나 애플리케이션의 핵심 기능이 외부의 UI, DB, 프레임워크 등과 분리되어 독립적으로 존재할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

Android 개발에서 Clean Architecture의 필요성

 

애플리케이션을 개발하며, 다양한 기능이 추가되고 코드가 복잡해져 유지보수가 어려워질 수 있습니다.

하나의 Acticity나 Fragment에서 UI, 비즈니스 로직, 데이터 로직 등이 모두 섞여있을 때 작은 수정에도 큰 영향을 줄 수 있습니다.

 

Android에서는 외부 라이브러리나 API, DB 등과 같은 다양한 외부 의존성을 가집니다.

Clean Architecture는 의존성을 관리하는 데 있어 명확한 원칙을 제공하기 때문에, 새로운 코드를 추가하거나 수정할 때 영향을 주지 않고 시스템을 확장할 수 있습니다.

 

 

 

최신 안드로이드 앱 아키텍처는 유지보수성과 확장성을 높이고, 복잡한 UI를 효율적으로 관리하며, 사용자 경험을 향상시키기 위해 여러가지 설계 원칙과 기법을 통합하고 있습니다.

 

1. 반응형 및 계층형 아키텍처

2. 단방향 데이터 흐름(UDF)

3. 상태 홀더가 있는 UI 레이어로 UI 복잡성 관리

4. 코루틴 및 Flow

5. DI 권장

 

 

 

 

 

 

 

 

 

 

소프트웨어 설계 원칙과 Clean Architecture

Clean Architecture는 SOLID 원칙과 같은 소프트웨어 설계 원칙을 기반으로 합니다.

 

- SOLID 원칙

   1. 단일 책임 원칙(SRP: Single Responsibility Principle)

   2. 개방-폐쇄 원칙(OCP: Open/Closed Principle)

   3. 리스코프 치환 원칙(LSP: Liskov Substitution Principle)

   4. 인터페이스 분리 원칙(ISP: Interface Segregation Principle)

   5. 의존성 역전 원칙(DIP: Dependency Inversion Principle)

 

이 중에서 DIP가 클린 아키텍쳐에서 가장 중요한 원칙입니다.

DIP는 고수준 모듈(비즈니스 로직 등)이 저수준 모듈(데이터베이스, 네트워크 등)에 의존해서는 안되고, 추상화(인터페이스)에 의존해야 하기 때문에, Clean Architecture의 핵심 철학과 잘 맞닿아 있습니다.

 

 

 

 

 

 

 

Clean Architecture의 레이어 구조

 

주요 레이어는 Presentation Layer, Domain Layer, Data Layer입니다. 

 

1. Presentation Layer

- 사용자 인터페이스(UI)와 직접 상호작용하는 레이어입니다. 

- Activity, ViewModel, Controller, Presenters 등이 해당됩니다.

 

2. Domain Layer

- 애플리케이션의 핵심 비즈니스 로직을 처리하는 레이어입니다. 

- Use Case, Entities를 통해 구체적인 비즈니스 로직을 구현합니다.

 

3. Data Layer

- 애플리케이션이 사용하는 데이터 소스를 관리하는 역할을 합니다. 외부 리소스로부터 데이터를 가져오고 저장하는 레이어입니다.

- Room DB, DAO, Repository 등 데이터 관리 및 접근 로직을 구현합니다.

 

 

 

 

 

 

 

 

 

 

 

Clean Architecture를 적용한 간단한 Compose 예제 (+ Hilt)

크게 data, domain, ui(presentation)로 폴더를 나누었습니다.

 

 

 

data 폴더 -> datasource, repository 

domain 폴더 -> model, repository, usecase

ui(presentation) -> viewmodel

 

이렇게 나누었습니다.

 

 

 

 

 

 

Presentation Layer - UI

 

 

MainActivity.kt

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import com.example.cleanarchitecture.ui.viewmodel.UserViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    private val viewModel : UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Toast.makeText(this, "My name is : ${viewModel.getUserName().name}", Toast.LENGTH_LONG).show()

        setContent {

        }
    }
}

 

- 토스트로 간단히 띄우겠습니다.

 

 

 

 

위처럼 하드코딩 된 이름을 잘 가져오는 것을 볼 수 있습니다.

 

 

UserViewModel.kt

import androidx.lifecycle.ViewModel
import com.example.cleanarchitecture.domain.model.User
import com.example.cleanarchitecture.domain.usecase.UserUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class UserViewModel @Inject constructor(private val useCase: UserUseCase): ViewModel() {

    fun getUserName(): User {
        return useCase.getUserName()
    }
}

 

 

 

 

 

Domain Layer

 

User.kt

data class User(
    val name: String
)

 

 

UserRepository.kt

interface UserRepository {

    fun getUserName(): User
}

 

 

UserUseCase.kt

import com.example.cleanarchitecture.domain.model.User
import com.example.cleanarchitecture.domain.repository.UserRepository
import javax.inject.Inject

class UserUseCase @Inject constructor(private val repository: UserRepository) {

    fun getUserName(): User {
        return repository.getUserName()
    }

 

 

 

 

 

Data Layer

 

UserDataSource.kt

import com.example.cleanarchitecture.domain.model.User
import javax.inject.Inject

class UserDataSource @Inject constructor() {

    fun getUserName(): User {
        return User("mimisongsong")
    }
}

 

 

UserRepositoryImpl.kt

import com.example.cleanarchitecture.data.datasource.UserDataSource
import com.example.cleanarchitecture.domain.model.User
import com.example.cleanarchitecture.domain.repository.UserRepository
import javax.inject.Inject

class UserRepositoryImpl @Inject constructor(private val dataSource: UserDataSource) :
    UserRepository {
    override fun getUserName(): User {
        return dataSource.getUserName()
    }
}

 

 

 

 

 

 

 

이렇게 간단한 예제까지 알아봤습니다.

감사합니다 !!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참고

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

 

Clean Coder Blog

The Clean Architecture 13 August 2012 Over the last several years we’ve seen a whole range of ideas regarding the architecture of systems. These include: Though these architectures all vary somewhat in their details, they are very similar. They all have

blog.cleancoder.com

 

https://developer.android.com/topic/architecture?hl=ko

 

앱 아키텍처 가이드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 아키텍처 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에는 고품질의 강력한

developer.android.com

 

728x90
반응형