Flutter

[Flutter] Clean Architecture: 개념부터 예제까지

devyong 2025. 6. 13. 20:22

Flutter 프로젝트가 점점 커질수록 코드 유지보수가 어려워지고, 테스트도 까다로워지며, 기능 추가 시 부작용(bug side effects)이 발생하기 쉬워집니다. 이런 문제를 구조적으로 해결할 수 있는 방법이 바로 Clean Architecture입니다.

 

Clean Architecture는 의존성 규칙(Dependency Rule)에 따라 소프트웨어를 계층적으로 분리하여, 변경에 강하고 테스트하기 쉬운 구조를 만드는 설계 패턴입니다. 원래는 백엔드에서 시작됐지만, Flutter와 같은 프론트엔드에서도 적용 가능합니다.

Clean Architecture는 크게 다음과 같은 4계층으로 나눌 수 있습니다.

  • Presentation Layer  : UI
  • Application Layer : UseCase
  • Domain Layer : Entity, Repository Interface
  • Data Layer : API, DB, Repository 구현

의존성 흐름

Clean Architecture의 핵심은 의존성 방향이 단방향이라는 것입니다.

  • Presentation → Application → Domain 방향으로만 의존성이 존재합니다.
  • Data Layer는 Domain Layer의 인터페이스만 구현하고, 직접 참조하지 않습니다.
  • 이런 구조 덕분에 내부 로직은 외부 변경(API, DB, UI 변화)에 영향을 받지 않게 됩니다.

 각 계층 설명

1. Presentation Layer (UI)

  • Flutter의 Widget들이 위치하는 계층입니다.
  • 상태 관리 (Riverpod, Bloc, Provider 등)를 여기서 사용합니다.
  • 유저의 인터랙션을 받아 UseCase를 호출합니다.
  • 외부 계층 중 하나이기 때문에 내부 계층의 구체 구현을 몰라도 됩니다.
class ProductPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(productViewModelProvider);
// UI 렌더링
}
}

2. Application Layer (UseCase)

  • 앱의 비즈니스 흐름을 제어하는 역할입니다.
  • UseCase는 한 가지 기능을 수행하는 단일 책임 클래스입니다.
  • Repository Interface를 호출해 데이터를 처리하고, 결과를 Presentation에 반환합니다.
class GetProductsUseCase {
final ProductRepository repository;

GetProductsUseCase(this.repository);

Future<List<Product>> call() => repository.getProducts();
}

3. Domain Layer (Entity + Repository Interface)

  • 앱의 핵심 비즈니스 모델이 존재하는 가장 순수한 계층입니다.
  • 외부 프레임워크나 라이브러리에 전혀 의존하지 않습니다.
  • Repository Interface만 정의되며, 실제 구현은 Data Layer에서 처리합니다.
class Product {
final String id;
final String name;
Product({required this.id, required this.name});
}

abstract class ProductRepository {
Future<List<Product>> getProducts();
}

4. Data Layer (Repository 구현, API/DB)

  • 실제 데이터 소스와의 통신(API, DB 등)을 처리합니다.
  • Domain Layer의 인터페이스를 구현합니다.
  • 외부 라이브러리와 의존성이 생기는 계층입니다.
class ProductRepositoryImpl implements ProductRepository {
final ProductRemoteDataSource dataSource;

ProductRepositoryImpl(this.dataSource);

@override
Future<List<Product>> getProducts() async {
final result = await dataSource.fetchProducts();
return result.map((e) => Product(id: e['id'], name: e['name'])).toList();
}
}

 

예시 프로젝트 템플릿

실제 프로젝트에서 사용할 수 있는 디렉토리 구조 예시는 다음과 같습니다.

 

lib/
├── main.dart
├── core/                    # 공통 유틸, 상수, 예외 처리
├── di/                      # 의존성 주입(get_it)
├── data/
│   ├── datasources/        # API, DB 구현
│   └── repositories/       # Repository 구현체
├── domain/
│   ├── entities/           # 도메인 모델
│   ├── repositories/       # 추상 Repository
│   └── usecases/           # 유즈케이스
├── application/            # ViewModel (상태 관리)
├── presentation/
│   ├── pages/              # Flutter 화면
│   └── viewmodels/         # 상태 관리 로직

 

 

Flutter에서 Clean Architecture는 앱의 복잡도를 관리하고 유지보수를 용이하게 만드는 강력한 도구입니다. 처음엔 다소 복잡해 보일 수 있지만, 실제 프로젝트에서 도입해 보면 기능 추가나 리팩토링 시 얼마나 도움이 되는지 체감하게 될 것입니다.