플러터 애플리케이션의 구조화를 위해 널리 사용되는 MVC, MVVM, Clean Architecture의 핵심 개념과 차이점, 실무 적용 방법에 대해 설명하겠습니다. 각 아키텍처의 장단점과 활용 시나리오를 통해 프로젝트에 맞는 최적의 설계 방식을 선택할 수 있습니다.
MVC (Model-View-Controller)
구조
| Model | 데이터와 비즈니스 로직 관리 |
| View | UI 렌더링 및 사용자 인터랙션 수신 |
| Controller | Model-View 간 데이터 흐름 제어 |
Flutter 구현 예시
// Model: 데이터 클래스
class Product {
final String name;
final double price;
Product(this.name, this.price);
}
// View: UI 위젯
class ProductView extends StatelessWidget {
final Product product;
ProductView(this.product);
@override
Widget build(BuildContext context) {
return Column(
children: [Text(product.name), Text('\$${product.price}')],
);
}
}
// Controller: 상태 관리 (Provider 예시)
class ProductController extends ChangeNotifier {
Product _product = Product('Laptop', 1200);
void updatePrice(double newPrice) {
_product = Product(_product.name, newPrice);
notifyListeners();
}
}
장단점
- 장점: 단순한 구조로 소규모 프로젝트 적합
- 단점: 대규모 앱에서 Controller의 복잡도 증가 및 테스트 어려움
MVVM (Model-View-ViewModel)
구조
| Model | 데이터 소스 및 순수 비즈니스 로직 |
| View | UI 표시 및 사용자 입력 전달 |
| ViewModel | Model 데이터를 View에 맞게 변환, 상태 관리 |
Flutter 구현 예시 (Riverpod 사용)
// ViewModel
@riverpod
class ProductViewModel extends _$ProductViewModel {
@override
Product build() => Product('Laptop', 1200);
void updatePrice(double newPrice) {
state = Product(state.name, newPrice);
}
}
// View
class ProductView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final product = ref.watch(productViewModelProvider);
return Column(
children: [
Text(product.name),
Text('\$${product.price}'),
ElevatedButton(
onPressed: () => ref.read(productViewModelProvider.notifier).updatePrice(1000),
child: Text('할인 적용'),
),
],
);
}
}
장단점
- 장점: View-ViewModel 분리로 테스트 용이성 향상
- 단점: 복잡한 데이터 바인딩 구현 필요
Clean Architecture
계층 구조
| Presentation | UI 및 상태 관리 (MVVM/BLoC 적용) |
| Domain | 엔티티, 유스케이스, 인터페이스 정의 |
| Data | 데이터 소스 구현 (API, 로컬 DB) |
프로젝트 구조 예시
lib/
├── core/
├── features/
│ └── product/
│ ├── data/
│ │ ├── models/
│ │ └── repositories/
│ ├── domain/
│ │ ├── entities/
│ │ └── usecases/
│ └── presentation/
│ ├── providers/
│ └── screens/
└── main.dart
장단점
- 장점: 계층 간 의존성 역전으로 유지보수성 극대화
- 단점: 초기 설정 복잡도가 높아 소규모 프로젝트 비효율적
아키텍처 비교 및 선택 가이드
| 적합 규모 | 소규모 | 중규모 | 대규모 |
| 학습 곡선 | 낮음 | 중간 | 높음 |
| 테스트 용이성 | 제한적 | 우수 | 최상 |
| 유연성 | 낮음 | 중간 | 높음 |
| 도구 | Provider | Riverpod, BLoC | DDD + 상태 관리 |
실무 적용 전략
MVP/MVVM + Clean Architecture 조합
- Presentation 계층: MVVM 패턴 적용 (ViewModel ↔ View)
- Domain 계층: UseCase로 비즈니스 로직 캡슐화
- Data 계층: Repository 패턴으로 데이터 소스 추상화
// Domain Layer
abstract class ProductRepository {
Future getProductById(int id);
}
// Data Layer
class ProductRepositoryImpl implements ProductRepository {
final ProductRemoteDataSource remoteSource;
@override
Future getProductById(int id) async { ... }
}
// Presentation Layer (MVVM)
class ProductViewModel {
final ProductRepository repository;
ProductViewModel(this.repository);
Future loadProduct(int id) async { ... }
}
상태 관리 도구 선택
- Riverpod: MVVM 구현에 적합한 선언적 DI
- BLoC: 복잡한 비즈니스 로직 처리에 강점
- GetX: 빠른 프로토타이핑에 최적화
마무리
프로젝트 요구사항에 맞는 아키텍처 선택
- 소규모 앱: MVC 또는 MVVM 단독 사용
- 중규모 앱: MVVM + Clean Architecture 경량화 버전
- 대규모 엔터프라이즈 앱: Clean Architecture + DDD 철저 적용
각 아키텍처는 고유한 장단점을 가지고 있으며, 프로젝트의 규모와 복잡도, 팀의 경험 수준에 따라 적절한 선택이 필요합니다. 초기에는 단순한 구조로 시작하여 필요에 따라 점진적으로 복잡한 아키텍처로 발전시키는 것이 효과적인 전략입니다.