반응형
클린 아키텍처란?
클린 아키텍처는 소프트웨어 설계의 복잡성을 줄이고 유지보수성을 극대화하기 위해 로버트 C. 마틴(일명 "Uncle Bob")이 제안한 소프트웨어 아키텍처입니다. 클린 아키텍처의 핵심은 의존성을 내부에서 외부로 흐르도록 하여 핵심 비즈니스 로직을 외부 프레임워크나 도구들로부터 독립시키는 것입니다.
사용 목적
- 유지보수성 향상: 변경이 빈번한 외부 요소들과 비즈니스 로직을 분리하여 시스템의 수명을 연장합니다.
- 테스트 용이성: 독립적인 계층 구조로 인해 단위 테스트 작성이 쉬워집니다.
- 확장성 증가: 요구사항 변경에 유연하게 대응할 수 있습니다.
- 가독성 개선: 코드가 모듈화되어 있어 이해하기 쉽고, 팀 협업에 유리합니다.
클린 아키텍처의 핵심 원칙
- 의존성 역전 원칙 (Dependency Inversion Principle):
- 상위 계층은 하위 계층의 구체적인 구현에 의존하지 않습니다.
- 대신, 상위 계층과 하위 계층 모두 추상화된 인터페이스에 의존합니다.
- 이를 통해 비즈니스 로직은 외부 프레임워크, 라이브러리, 데이터베이스와 독립적으로 설계됩니다.
- 단일 책임 원칙 (Single Responsibility Principle):
- 각 계층은 하나의 역할과 책임만을 가집니다.
- 예를 들어, Use Case 계층은 비즈니스 로직을 다루고, Framework 계층은 외부 시스템과의 상호작용만을 담당합니다.
- 이는 코드의 가독성을 높이고, 유지보수를 쉽게 만들어 줍니다.
- 계층 간 독립성:
- 각 계층은 다른 계층의 변경에 영향을 받지 않아야 합니다.
- Entity 계층은 Use Case 계층의 변경에 독립적이며, Use Case 계층은 Framework 계층의 변경과 무관해야 합니다.
- 이 원칙은 시스템 확장성과 유연성을 보장합니다.
- 의존성 규칙 (Dependency Rule):
- 의존성은 항상 외부에서 내부로 향해야 합니다.
- Framework 계층은 Interface Adapters 계층에 의존하고, Interface Adapters 계층은 Use Case 계층에 의존하며, 마지막으로 Use Case 계층은 Entity 계층에 의존합니다.
- 이를 통해 핵심 비즈니스 로직이 외부의 변화로부터 보호됩니다.
클린 아키텍처의 샘플 구조 (코틀린)
클린 아키텍처는 보통 다음과 같은 계층으로 구성됩니다:
- Entity (엔티티)
- Use Case (유즈케이스)
- Interface Adapters (인터페이스 어댑터)
- Frameworks & Drivers (프레임워크 및 드라이버)
아래는 클린 아키텍처를 구현한 간단한 코틀린 샘플 구조입니다.
// Entity Layer
data class User(val id: Int, val name: String)
// Use Case Layer
class GetUserUseCase(private val userRepository: UserRepository) {
fun execute(userId: Int): User? {
return userRepository.getUserById(userId)
}
}
// Interface Adapters Layer
interface UserRepository {
fun getUserById(userId: Int): User?
}
class UserRepositoryImpl : UserRepository {
override fun getUserById(userId: Int): User? {
// 데이터베이스나 네트워크로부터 사용자 데이터 조회
return User(userId, "John Doe")
}
}
// Framework & Drivers Layer
class UserController(private val getUserUseCase: GetUserUseCase) {
fun getUser(userId: Int): String {
val user = getUserUseCase.execute(userId)
return user?.name ?: "User not found"
}
}
fun main() {
val userRepository = UserRepositoryImpl()
val getUserUseCase = GetUserUseCase(userRepository)
val userController = UserController(getUserUseCase)
println(userController.getUser(1)) // "John Doe" 출력
}
계층 간 관계
- Entity 계층은 시스템의 핵심 데이터와 규칙을 정의하며, 어떤 다른 계층에도 의존하지 않습니다.
- Use Case 계층은 Entity를 사용하여 비즈니스 규칙을 구현합니다.
- Interface Adapters 계층은 외부 입력을 Use Case로 변환하고, Use Case 출력을 외부 형식으로 변환합니다.
- Frameworks & Drivers 계층은 외부 라이브러리, 데이터베이스, 사용자 인터페이스와 상호작용하며, 다른 계층에 영향을 미치지 않습니다.
계층 간 의존성 흐름도
아래 이미지는 클린 아키텍처에서 계층 간 의존성의 흐름을 보여줍니다:
+---------------------------+
| Frameworks & Drivers |
+---------------------------+
|
v
+---------------------------+
| Interface Adapters |
+---------------------------+
|
v
+---------------------------+
| Use Cases |
+---------------------------+
|
v
+---------------------------+
| Entities |
+---------------------------+
의존성은 항상 바깥 계층에서 안쪽 계층으로 흐르며, 각 계층은 내부 계층에만 의존합니다.
클린 아키텍처의 장단점
장점
- 유지보수 용이: 의존성이 내부로 흐르기 때문에 비즈니스 로직이 외부 도구에 의존하지 않습니다.
- 테스트 편리성: 각 계층을 독립적으로 테스트할 수 있습니다.
- 확장 가능성: 계층 간 분리가 명확하여 새로운 요구사항 추가가 용이합니다.
- 가독성 증가: 모듈화된 구조로 코드 이해가 쉬워집니다.
단점
- 초기 설계 부담: 계층 분리에 따른 설계 작업이 추가됩니다.
- 복잡성 증가: 작은 프로젝트에서는 불필요하게 느껴질 수 있습니다.
- 성능 이슈: 계층 간 호출이 많아지면 성능이 저하될 가능성이 있습니다.
언제 클린 아키텍처를 사용해야 할까?
- 장기적으로 유지보수가 필요한 시스템: 지속적인 요구사항 변경이 예상되는 프로젝트.
- 대규모 애플리케이션: 여러 개발자 팀이 협업하는 경우 구조적 가이드가 필요합니다.
- 테스트가 중요한 프로젝트: 비즈니스 로직의 철저한 테스트가 필수인 경우.
- 다양한 플랫폼 지원: 동일한 비즈니스 로직을 웹, 모바일, 데스크톱 등 다양한 플랫폼에서 사용해야 하는 경우.
특징
- 의존성 역전 원칙(DIP): 비즈니스 로직이 외부 요소에 의존하지 않습니다.
- 계층화 구조: 계층 간 책임이 명확히 분리됩니다.
- 유연성: 외부 프레임워크 교체가 용이합니다.
각 계층별 설명
- Entity (엔티티)
- 시스템의 핵심 규칙과 데이터를 캡슐화합니다.
- 예: User 클래스.
- Use Case (유즈케이스)
- 특정 비즈니스 로직을 구현하며, 애플리케이션의 동작을 정의합니다.
- 예: GetUserUseCase 클래스.
- Interface Adapters (인터페이스 어댑터)
- 데이터 형식을 변환하여 상호작용을 용이하게 합니다.
- 예: UserRepository 인터페이스와 구현체.
- Frameworks & Drivers (프레임워크 및 드라이버)
- 외부 라이브러리나 API와의 상호작용을 담당합니다.
- 예: UserController 클래스.
마무리
클린 아키텍처는 대규모 시스템에서 특히 빛을 발하는 설계 방법론입니다. 비즈니스 로직을 외부 의존성으로부터 분리하고 유지보수성과 확장성을 높이는 데 강력한 도구가 됩니다. 이를 코틀린으로 구현하며 클린 아키텍처의 진정한 가치를 경험해보세요.
반응형
'어질어질 개발노트 > Android' 카테고리의 다른 글
[Android] 프로세스, 스레드, 코루틴의 차이점 완벽 정리 (0) | 2025.01.15 |
---|---|
[Android] 안드로이드 앱 품질 향상을 위한 Unit 및 UI 테스트 완벽 가이드 (0) | 2025.01.15 |
[Android] 안드로이드 개발에서 Private 함수 테스트: 실전 팁과 예제 (0) | 2025.01.15 |
[Android] Abstract Class vs Interface: 무엇을 선택해야 할까? (0) | 2025.01.14 |
[Android] Sealed Class와 Enum의 차이점과 적절한 사용법 - Kotlin 개발자를 위한 가이드 (0) | 2025.01.10 |
[Android] ListAdapter vs RecyclerViewAdapter: 무엇을 선택해야 할까? (0) | 2025.01.10 |
[Android] RxJava: flatMap vs switchMap - 차이를 명확히 이해하기 (0) | 2025.01.10 |
[Android] [Kotlin] Compose 기초 - 시작하기 (0) | 2024.08.27 |