[Java] 사용자 입력의 검증과 분석은 어떤 계층에서 수행해야 할까

이상현·2023년 11월 23일
0

Java

목록 보기
12/21
post-thumbnail

데이터 유효성 검증

Java로 프로그램을 만들다 보면 어떤 데이터의 유효성 검증을 어디에서 어떻게 수행하는게 좋을지 고민을 하게 된다.

특히 사용자의 입력값은 어떻게 들어올 지 모르는 데이터이기 때문에 유효성 검증을 어디에서 할 지 정하는게 중요하다.

검증, 분석 단계

사용자 입력의 유효성 검증 및 파싱 단계는 다음과 같다.

  1. 기본 입력 및 입력 형식 검증 : 빈 문자열이 아닌지, 숫자면 숫자가 맞는지, 입력 형식에 맞는지 검사
    ex) 바비큐립-1,초코케이크-2,제로콜라-1

  2. 필요 데이터 추출 : 요구사항에 맞게 필요한 데이터로 변환
    ex) 바비큐립, 1 초코케이크, 2 제로콜라, 1

  3. 비즈니스 로직 유효성 검증 : 비즈니스 로직에 올바른 데이터인지 검증
    ex) 개수 총합이 20 이하, 이름에 맞지 않는 메뉴 등

  4. 로직에 적용 : 최종적으로, 검증된 데이터를 로직에 활용
    ex) Order 객체 생성

이 단계들은 각각 알맞은 계층의 알맞은 클래스에서 수행되어야 코드 유지보수가 용이해지고, 오류 가능성도 줄어들고, 테스트와 디버깅이 편해진다.


MVC 패턴의 수행 위치

MVC 패턴을 기준으로, 위 단계들은 View, Controller, Model 계층에서 수행된다.

  1. 기본 입력 검증 및 입력 형식 검증
  • Controller: 일반적

  • View: 빠른 기본 유효성 확인

    기본적인 유효성 검증은 일반적으로 컨트롤러에서 수행되지만 사용자가 입력한 정보가 기본적인 요구사항을 충족하는지 빠르게 확인하기 위해 View 에서 수행해도 된다. (ex 이메일 형식 검증)

  1. 필요 데이터 추출
  • Controller: 대부분의 경우

    뷰에서 전달받은 데이터를 기반으로 정보를 추출하고, 모델이 이해할 수 있는 형태로 변환한다. 복잡한 비즈니스 로직을 표함할 경우 다른 레이어에서 수행도 고려할 수도 있다.

  1. 비즈니스 로직 유효성 검증
  • Model

    비즈니스 규칙 유효성 검증은 모델(도메인) 레이어에서 주로 수행한다. 모델 레이어는 비즈니스 로직과 관련된 데이터의 무결성을 유지하는 데 중요한 역할을 한다.

  1. 로직에 적용
  • Service

    검증된 데이터를 사용하여 최종 비즈니스 로직을 수행한다.
    (ex 도메인 인스턴스 생성, 로직 계산)


예외 처리

각 단계에서의 객체들은 본인이 알아야할 정보를 검증하며 올바르지 않을 경우 예외를 발생시켜야 한다.

즉, 사용자의 입력이 올바르지 않을 경우, 예외를 발생시키고 처리 (ex 재입력 요구) 를 해야 하는데, 검사 단계가 나누어져 있다 보니 이것을 한번에 처리하기 번거롭다.

이것을 수행하려면, 사용자의 입력을 받고, 기본적인 검증을 하고, 비즈니스 로직 유효성 검증까지 모두 한 구간에서 수행한 후, 예외가 발생하면 그 구간의 처음으로 돌아가서 다시 사용자의 입력을 받으면 된다.

아래는 그것의 예시 코드이다.
사용자 입력으로 Order 객체를 만드는 과정을 수행한다.

  • 객체간의 메세지 전달을 돕는 GameController
public class GameController {
    public void run() {
        Order order = requestOrder();
    }

    private Order requestOrder() {
        return executeWithRetry(() -> {
            String inputOrder = inputView.readOrder(); // 1. 기본 입력 검증 및 입력 형식 검증
            Map<String, Integer> parsedOrder = parseOrder(inputOrder); // 2. 필요 데이터 추출
            return new Order(parsedOrder); // 4. 로직에 적용
        });
    }
    
    // 재시작 로직을 재사용 하기 위한 메소드
    public <T> T executeWithRetry(Supplier<T> action) {
        while (true) {
            try {
                return action.get();
            } catch (Exception e) {
                outputView.printErrorMessage(e);
            }
        }
    }
    
	// 필요 데이터 추출 하는 메소드 parseOrder..
}
  • 사용자 입력과 기본 검증을 수행하는 InputView 클래스
public class InputView {
	public String readOrder() {
        outputView.printReadOrderMessage(); // 입력 안내 출력
        String input = Console.readLine()
		
        // 1. 기본 입력 검증 및 입력 형식 검증
        if (isInvalidOrder(input)) {
            throw new OrderException(); // 사용자 정의 예외 발생
        }
        return input;
    }
    
    // 기본 입력 검증, 형식 검증 메소드 isInvalidOrder ..
}
  • 도메인 로직 클래스 Order
public class Order {
    private final Map<String, Integer> menuQuantities;

    public Order(Map<String, Integer> parsedOrder) {
    	// 3. 비즈니스 로직 유효성 검증
        validateOrder(parsedOrder);
        this.menuQuantities = new HashMap(parsedOrder);
    }
    
    // 유효성 검증 메소드..
}

결론

프로그램의 크기가 크지 않다는 전제하에

├── controller
│   ├── GameController.java
│   └── InputParser.java : 입력 문자열을 모델이 알아듣는 형식으로 변환
├── model
│   ├── Order.java : 클래스에 알맞은 비즈니스 로직 유효성 검증
│   └── OrderService.java : 비즈니스 로직 수행
└── view
    └── InputView.java : 기본 입력 및 형식 검증

이런 식으로 구성하면 문제 없다.

만약 InputParser가 비즈니스 로직을 깊게 사용한다면 Service 패키지로,
비즈니스 로직을 사용하지는 않지만 여러 부분에서 재사용 된다면 util 패키지로 이동하면 된다.

0개의 댓글