refactor: Repository 패턴 적용 및 Clean Architecture 완성
## 주요 변경사항 ### 🏗️ Architecture - Repository 패턴 전면 도입 (인터페이스/구현체 분리) - Domain Layer에 Repository 인터페이스 정의 - Data Layer에 Repository 구현체 배치 - UseCase 의존성을 Service에서 Repository로 전환 ### 📦 Dependency Injection - GetIt 기반 DI Container 재구성 (lib/injection_container.dart) - Repository 인터페이스와 구현체 등록 - Service와 Repository 공존 (마이그레이션 기간) ### 🔄 Migration Status 완료: - License 모듈 (6개 UseCase) - Warehouse Location 모듈 (5개 UseCase) 진행중: - Auth 모듈 (2/5 UseCase) - Company 모듈 (1/6 UseCase) 대기: - User 모듈 (7개 UseCase) - Equipment 모듈 (4개 UseCase) ### 🎯 Controller 통합 - 중복 Controller 제거 (with_usecase 버전) - 단일 Controller로 통합 - UseCase 패턴 직접 적용 ### 🧹 코드 정리 - 임시 파일 제거 (test_*.md, task.md) - Node.js 아티팩트 제거 (package.json) - 불필요한 테스트 파일 정리 ### ✅ 테스트 개선 - Real API 중심 테스트 구조 - Mock 제거, 실제 API 엔드포인트 사용 - 통합 테스트 프레임워크 강화 ## 기술적 영향 - 의존성 역전 원칙 적용 - 레이어 간 결합도 감소 - 테스트 용이성 향상 - 확장성 및 유지보수성 개선 ## 다음 단계 1. User/Equipment 모듈 Repository 마이그레이션 2. Service Layer 점진적 제거 3. 캐싱 전략 구현 4. 성능 최적화
This commit is contained in:
@@ -33,7 +33,7 @@ class _CompanyListState extends State<CompanyList> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = CompanyListController();
|
||||
_controller.initializeWithPageSize(10); // 페이지 크기 설정
|
||||
_controller.initialize(pageSize: 10); // 통일된 초기화 방식
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -430,18 +430,13 @@ class _CompanyListState extends State<CompanyList> {
|
||||
],
|
||||
),
|
||||
|
||||
// 페이지네이션 (Controller 상태 사용)
|
||||
// 페이지네이션 (BaseListController의 goToPage 사용)
|
||||
pagination: Pagination(
|
||||
totalCount: controller.total,
|
||||
currentPage: controller.currentPage,
|
||||
pageSize: controller.pageSize,
|
||||
onPageChanged: (page) {
|
||||
// 다음 페이지 로드
|
||||
if (page > controller.currentPage) {
|
||||
controller.loadNextPage();
|
||||
} else if (page == 1) {
|
||||
controller.refresh();
|
||||
}
|
||||
controller.goToPage(page);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -32,15 +32,9 @@ class CompanyListController extends BaseListController<Company> {
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 데이터 로드
|
||||
Future<void> initialize() async {
|
||||
await loadData(isRefresh: true);
|
||||
}
|
||||
|
||||
// 페이지 크기를 지정하여 초기화
|
||||
// 기존 initializeWithPageSize를 사용하는 코드와의 호환성 유지
|
||||
Future<void> initializeWithPageSize(int newPageSize) async {
|
||||
pageSize = newPageSize;
|
||||
await loadData(isRefresh: true);
|
||||
await initialize(pageSize: newPageSize);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -48,8 +42,8 @@ class CompanyListController extends BaseListController<Company> {
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// API 호출 - 회사 목록 조회 (이제 PaginatedResponse 반환)
|
||||
final response = await ErrorHandler.handleApiCall<dynamic>(
|
||||
// API 호출 - 회사 목록 조회 (PaginatedResponse 반환)
|
||||
final response = await ErrorHandler.handleApiCall(
|
||||
() => _companyService.getCompanies(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
@@ -61,6 +55,20 @@ class CompanyListController extends BaseListController<Company> {
|
||||
},
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
return PagedResult(
|
||||
items: [],
|
||||
meta: PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// PaginatedResponse를 PagedResult로 변환
|
||||
final meta = PaginationMeta(
|
||||
currentPage: response.page,
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/controllers/base_list_controller.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../../domain/usecases/base_usecase.dart';
|
||||
import '../../../domain/usecases/company/company_usecases.dart';
|
||||
import '../../../models/company_model.dart';
|
||||
import '../../../services/company_service.dart';
|
||||
import '../../../di/injection_container.dart';
|
||||
import '../../../data/models/common/pagination_params.dart';
|
||||
|
||||
/// UseCase를 활용한 회사 목록 관리 컨트롤러
|
||||
/// BaseListController를 상속받아 공통 기능 재사용
|
||||
class CompanyListControllerWithUseCase extends BaseListController<Company> {
|
||||
// UseCases
|
||||
late final GetCompaniesUseCase _getCompaniesUseCase;
|
||||
late final CreateCompanyUseCase _createCompanyUseCase;
|
||||
late final UpdateCompanyUseCase _updateCompanyUseCase;
|
||||
late final DeleteCompanyUseCase _deleteCompanyUseCase;
|
||||
late final GetCompanyDetailUseCase _getCompanyDetailUseCase;
|
||||
late final ToggleCompanyStatusUseCase _toggleCompanyStatusUseCase;
|
||||
|
||||
// 필터 상태
|
||||
String? selectedType;
|
||||
bool? isActive;
|
||||
|
||||
// 선택된 회사들
|
||||
final Set<int> _selectedCompanyIds = {};
|
||||
Set<int> get selectedCompanyIds => _selectedCompanyIds;
|
||||
bool get hasSelection => _selectedCompanyIds.isNotEmpty;
|
||||
|
||||
CompanyListControllerWithUseCase() {
|
||||
// UseCase 초기화
|
||||
final companyService = inject<CompanyService>();
|
||||
_getCompaniesUseCase = GetCompaniesUseCase(companyService);
|
||||
_createCompanyUseCase = CreateCompanyUseCase(companyService);
|
||||
_updateCompanyUseCase = UpdateCompanyUseCase(companyService);
|
||||
_deleteCompanyUseCase = DeleteCompanyUseCase(companyService);
|
||||
_getCompanyDetailUseCase = GetCompanyDetailUseCase(companyService);
|
||||
_toggleCompanyStatusUseCase = ToggleCompanyStatusUseCase(companyService);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PagedResult<Company>> fetchData({
|
||||
required PaginationParams params,
|
||||
Map<String, dynamic>? additionalFilters,
|
||||
}) async {
|
||||
// UseCase를 통한 데이터 조회
|
||||
final usecaseParams = GetCompaniesParams(
|
||||
page: params.page,
|
||||
perPage: params.perPage,
|
||||
search: params.search,
|
||||
isActive: isActive,
|
||||
);
|
||||
|
||||
final result = await _getCompaniesUseCase(usecaseParams);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
throw Exception(failure.message);
|
||||
},
|
||||
(companies) {
|
||||
// PagedResult로 래핑하여 반환 (임시로 메타데이터 생성)
|
||||
final meta = PaginationMeta(
|
||||
currentPage: params.page,
|
||||
perPage: params.perPage,
|
||||
total: companies.length, // 실제로는 서버에서 받아와야 함
|
||||
totalPages: (companies.length / params.perPage).ceil(),
|
||||
hasNext: companies.length >= params.perPage,
|
||||
hasPrevious: params.page > 1,
|
||||
);
|
||||
return PagedResult(items: companies, meta: meta);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 생성
|
||||
Future<bool> createCompany(Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = CreateCompanyParams(company: company);
|
||||
final result = await _createCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(newCompany) {
|
||||
// 로컬 리스트에 추가
|
||||
addItemLocally(newCompany);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 수정
|
||||
Future<bool> updateCompany(int id, Company company) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = UpdateCompanyParams(id: id, company: company);
|
||||
final result = await _updateCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
|
||||
// ValidationFailure의 경우 상세 에러 표시
|
||||
if (failure is ValidationFailure && failure.errors != null) {
|
||||
final errorMessages = failure.errors!.entries
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join('\n');
|
||||
errorState = errorMessages;
|
||||
}
|
||||
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(updatedCompany) {
|
||||
// 로컬 리스트 업데이트
|
||||
updateItemLocally(updatedCompany, (item) => item.id == id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 삭제
|
||||
Future<bool> deleteCompany(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 제거
|
||||
removeItemLocally((item) => item.id == id);
|
||||
_selectedCompanyIds.remove(id);
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상세 조회
|
||||
Future<Company?> getCompanyDetail(int id, {bool includeBranches = false}) async {
|
||||
final params = GetCompanyDetailParams(
|
||||
id: id,
|
||||
includeBranches: includeBranches,
|
||||
);
|
||||
|
||||
final result = await _getCompanyDetailUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
notifyListeners();
|
||||
return null;
|
||||
},
|
||||
(company) => company,
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 상태 토글 (활성화/비활성화)
|
||||
Future<bool> toggleCompanyStatus(int id) async {
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
// 현재 회사 상태를 확인하여 토글 (기본값 true로 가정)
|
||||
final params = ToggleCompanyStatusParams(
|
||||
id: id,
|
||||
isActive: false, // 임시로 false로 설정 (실제로는 현재 상태를 API로 확인해야 함)
|
||||
);
|
||||
final result = await _toggleCompanyStatusUseCase(params);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
errorState = failure.message;
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(_) {
|
||||
// 로컬 리스트에서 상태 업데이트 (실제로는 API에서 업데이트된 Company 객체를 받아와야 함)
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회사 선택/해제
|
||||
void toggleSelection(int companyId) {
|
||||
if (_selectedCompanyIds.contains(companyId)) {
|
||||
_selectedCompanyIds.remove(companyId);
|
||||
} else {
|
||||
_selectedCompanyIds.add(companyId);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 전체 선택/해제
|
||||
void toggleSelectAll() {
|
||||
if (_selectedCompanyIds.length == items.length) {
|
||||
_selectedCompanyIds.clear();
|
||||
} else {
|
||||
_selectedCompanyIds.clear();
|
||||
_selectedCompanyIds.addAll(items.where((c) => c.id != null).map((c) => c.id!));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 선택 초기화
|
||||
void clearSelection() {
|
||||
_selectedCompanyIds.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 필터 적용
|
||||
void applyFilters({String? type, bool? active}) {
|
||||
selectedType = type;
|
||||
isActive = active;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 필터 초기화
|
||||
void clearFilters() {
|
||||
selectedType = null;
|
||||
isActive = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// 선택된 회사들 일괄 삭제
|
||||
Future<bool> deleteSelectedCompanies() async {
|
||||
if (_selectedCompanyIds.isEmpty) return false;
|
||||
|
||||
isLoadingState = true;
|
||||
notifyListeners();
|
||||
|
||||
bool allSuccess = true;
|
||||
final failedIds = <int>[];
|
||||
|
||||
for (final id in _selectedCompanyIds.toList()) {
|
||||
final params = DeleteCompanyParams(id: id);
|
||||
final result = await _deleteCompanyUseCase(params);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
allSuccess = false;
|
||||
failedIds.add(id);
|
||||
debugPrint('회사 $id 삭제 실패: ${failure.message}');
|
||||
},
|
||||
(_) {
|
||||
removeItemLocally((item) => item.id == id);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (failedIds.isNotEmpty) {
|
||||
errorState = '일부 회사 삭제 실패: ${failedIds.join(', ')}';
|
||||
}
|
||||
|
||||
_selectedCompanyIds.clear();
|
||||
isLoadingState = false;
|
||||
notifyListeners();
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user