MVVM 학습 중, Repository Pattern이 많이 쓰이기도 하고 처음 접해보아 정리해본다..

우선 Repository을 짧게 요약하자면, 데이터 접근 로직을 몰아 넣은 곳이다.
위 사진을 보면 도메인 계층(ViewModel)과 데이터 계층(Data)사이에서 Repository을 추가하여 상호작용 하게 한다. Repository를 데이터 접근만을 (비즈니스 로직X!) 다루므로 데이터 계층으로 볼 수도 있다.
Repository layer 덕분에 추후 단위 테스트를 작성할 때
실제 DB나 API에 의존하지 않고 모의 객체를 사용하여 테스트가 가능해진다고 한다!
또한, DB나 API와의 작업을 Repository에서 처리하여 비즈니스 로직(=데이터를 어떻게 사용할까?)에 더욱 집중할 수 있다.
데이터 접근만을 다루고, 비즈니스 로직은 서비스 계층에서 다루는게 바람직하다.
abstract class UserRepository {
Future<User> getUser(int id);
Future<void> saveUser(User user);
}
class UserRepositoryImpl implements UserRepository {
final Map<int, User> _userDatabase = {}; // 임시 로컬 데이터베이스
Future<User> getUser(int userId) async {
// 사용자 정보를 로컬 데이터베이스에서 찾아 반환
if (_userDatabase.containsKey(userId)) {
return _userDatabase[userId]!;
} else {
throw Exception('User not found');
}
}
Future<void> saveUser(User user) async {
// 사용자 정보를 로컬 데이터베이스에 저장
_userDatabase[user.id] = user;
}
}
위의 데이터 관련 로직을 통해 ViewModel과 View에 어떻게 사용되는지 알아보자
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
class UserViewModel {
final UserRepository userRepository;
// UserViewModel이 UserRepositoryImpl을 사용하도록 의존성 주입
UserViewModel(this.userRepository);
Future<User> fetchUser(int userId) async {
try {
// 사용자 ID가 유효한지 확인
if (userId <= 0) {
throw Exception('Invalid user ID');
}
// Repository를 통해 사용자 정보 가져오기
return await userRepository.getUser(userId);
} catch (e) {
throw Exception('Failed to fetch user: $e');
}
}
Future<void> saveUser(User user) async {
await userRepository.saveUser(user);
}
}
UserRepositoryImpl을 이용해 데이터를 관리하고, UI에 표시할 데이터를 준비하며, 데이터를 가져올 때 비즈니스 로직(사용자 ID 확인, 에러 핸들링)도 처리한다.
import 'package:flutter/material.dart';
class UserView extends StatelessWidget {
final UserViewModel userViewModel = UserViewModel(UserRepositoryImpl());
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User Information')),
body: Center(
child: FutureBuilder<User>(
future: userViewModel.fetchUser(1), // 사용자 ID 1로 데이터를 불러옴
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // 로딩 중 표시
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}'); // 에러 메시지
} else if (snapshot.hasData) {
final user = snapshot.data!;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('ID: ${user.id}'),
Text('Name: ${user.name}'),
Text('Email: ${user.email}'),
],
);
} else {
return Text('No data available');
}
},
),
),
);
}
}
이런식으로 디자인 패턴을 통해 layer를 나누는 것 만으로는 클래스 간의 결합도를 최소화하는데 완벽할 수 없다. 더 효과적으로 나아가, 의존성 주입과 같은 방법으로 클래스 간의 결합도 최소화를 실현할 수 있음!
https://f-lab.kr/insight/repository-pattern-20240626
https://medium.com/@pererikbergman/repository-design-pattern-e28c0f3e4a30
https://velog.io/@ilil1/Repository-Pattern-%EC%9D%B4%EB%9E%80