오랜시간 고민한 결과 내가 생각하는 예외 처리에 적합한 Layer 는..
‘모든 Layer’ 인것같다.
이유인 즉, 각 Layer 의 목적과 관심사에 맞게 예외 처리를 해줘야 했다.
< 다른 좋은 의견 있으신분들은 댓글로 남겨주세요~ >
<Clean Architecture 에 관한 글은 다른 글로 자세히 찾아뵙겠습니다!>
이해를 돕기위해 간단한 카운터 예제를 준비해봤다.
Dart 언어를 사용했으며, 중요한 부분만 의미 전달을 위해 작성했다.
Domain Layer 에서 전달받은 예외를 사용자에게 적절한 내용으로 전달해준다.
UseCase 를 통해 [decrease()] 함수를 실행하고, 반환받은 [Either] 객체를 통해,
함수가 정상적으로 실행된 경우, 예외가 발생한 경우를 알수있고,
그에따라 View 에 맞게 사용자에게 내용을 전달해줄 수 있다.
( 일반적으로 UseCase 를 직접 사용하지 않고, ViewModel 이나 Controller 를 통해 View Logic 을 따로 관리해준다 )
// View.dart
class CountView {
final useCase = CountUseCase(CountImplement());
int get currentCount => useCase.count;
Widget minusButton() {
return AwesomeButton(
onPressed: () {
final either = useCase.decrease();
either.fold(
(exception) {
//handle Exception Alert
},
(count) {
// updateState.
currentCount = count;
},
);
},
child: Text("Minus Button"),
);
}
}
UseCase 에서는 Repository 를 의존하며, Repository 를 통해 Data Layer 와 소통하고,
Data Layer 에서 혹은 비지니스 로직에 의해 발생한 예외를
try-catch
문을 통해 catch
후, [Either] 객체에 담아 반환한다.
// UseCase.dart
class CountUseCase {
CountUseCase(this.repository);
final CountRepository repository;
int get count => repository.loadCount();
Either<CustomException, int> increase() {
try {
final value = count + 1;
repository.updateCount(value);
return Right(value);
} catch (e) {
return Left(CustomException.fromException(e));
}
}
Either<CustomException, int> decrease() {
try {
final value = count - 1;
// Custom Bussiness Logic
if (value < 0) {
return Left(CustomException.nagetiveNumber());
}
repository.updateCount(value);
return Right(value);
} catch (e) {
return Left(CustomException.fromException(e));
}
}
}
데이터, 네트워크, 파일 시스템등과의 상호작용에서 일어나는 애러들을 처리한다.
아래 예제는 [getValue()] 함수를 통해 값을 불러온후, int 타입으로 parse 해 반환해준다.
parse 과정중 Exception 이 발생할시, DB 값을 0 으로 초기화 해주고, 0을 반환해준다.
DB 값을 0으로 초기화 해주고, 0을 반환 해준다
이 부분이 개발자가 판단하에,
상황에 따라, 기획 의도에 따라 바뀔수 있는 비지니스 로직에 해당된다면 UseCase 에 작성해주는게 적합하다.
// Repository.dart
abstract class CountRepository {
int loadCount();
void updateCount(int value);
}
// Implement.dart
class CountImplement extends CountRepository {
// When occur Error throw [DataBaseException]
final _db = CountDataBase();
int loadCount() {
try{
final value = _db.getValue();
if(value==null){
return 0;
}
return int.parse(_db.getValue());
}on FormatException catch(e){
// if occur Format Exception, value reset 0 and return 0;
updateCount(0);
return 0;
}
}
void updateCount(int value) {
_db.update(value);
}
}
기존의 수많은 Error Handling 관련된 글들은 단순히 try-catch
문의 사용법만 설명해놨다.
그런 기본적인 사용법이 아닌, 한단계 더 나아가서 소프트웨어의 구조적 관점에서 사용법에 대해 고민해봤다.
몇몇 분들에겐 부질없어 보일 수도 있겠지만, 이 기회를 통해 각 Layer 의 역할을 분명히 이해 할 수 있게 되었고,
보다 안정적인 소프트웨어를 만들게 될 수 있어 소중한 시간이었다.
나는 가끔 개발이라는 자체를 망치로 무언가를 만드는것
과 같다고 생각한다.
망치질만 잘한다고 무언가를 만들 수 있는것은 아니다.
망치질을 하는건 쉽다. 아무 생각없이 손잡이를 잡고, 휘두르면 된다.
하지만 우리의 목표는 망치질을 하는것이 아닌 무언가를 만드는 것이다.
만들려고 하는 객체를 이해하고, 설계하고, 분석해야 한다.
그렇지 않고 망치를 마구자비로 휘두르게되면, 물건을 만들기는 커녕 망가트리고 있을것이다.
개발도 마찬가지인것 같다. 코드만 쓴다고 프로그램이 만들어지는게 아니다.
기획 의도를 이해하고, 설계하고, 분석한후, 코드로 구체화 시키는게 개발의 묘미가 아닐까 생각해 본다.