본문 바로가기
개발/Java

[Java] 인수값 유효성 검증 - 표현 계층 vs 응용 계층

by Mingvel 2022. 10. 23.

서비스를 운영하는 환경에서는 Language와 Framework를 막론하고 Inbound 된 데이터에 대한 유효성 검증이 반드시 필요하다

 

유효성 검증에는 인자 값에 대한 검증, 인수 값에 대한 검증, 비즈니스 로직에 대한 검증과 같은 다양한 종류의 유효성 검증이 있다

 

인자 값에 대한 검증은 왠만한 프레임워크의 표현 계층에서 알아서 수행해주고

 

비즈니스 로직에 대한 검증은 응용 계층에서 수행하는 것이 지극히 일반적이다

 

하지만 특히 그 중에서도 인수 값에 대한 검증을 표현 계층에서 하느냐, 응용 계층에서 하느냐에 대한 의견이 다양해서 

 

이번 글에서는 각 계층에서 인수 값에 대한 검증을 가져갔을 때의 특징 및 장단점에 대해 비교해 보고자 한다

 

'이름'이라는 데이터를 생성할 때, 이름의 글자 수에 대한 유효성 검증하는 것을 예시로 사용할 것이다

 

표현 계층에서 인수 값에 대한 검증을 수행할 경우

@RestController
class NameController {
	@PostMapping
    	public HttpStatus create(@RequestBody NameCreateRequest request) {
    	    if (request.getName().length() > MAX_NAME_LENGTH) {
        	    return HttpStatus.BAD_REQUEST;
        	}
        	nameService.save(request.getName());
        	return HttpStatus.CREATED;
    	}
}
    
@Service
class NameService {
	public void save(String name) {
    	//save
    }
}

 

위와 같이 표현 계층에서 인수 값에 대한 유효성 검증을 수행할 경우 표현 계층인 Controller에서 모든 유효성을 검증하고 

 

검증된 결과만을 응용 계층(Service)으로 전달한다

 

위와 같은 구조는 1 : 1 구조(1 Controller : 1 Service)에서 자주 사용되곤 한다

 

하나의 api endpoint가 하나의 service method를 호출하는 구조를 사용하고,

인수 값에 대한 유효성 검증을 표현 계층에서 수행하게 되면

응용 계층에서는 정제된 데이터를 받는다는 보장이 있으니 코드가 간결해지고, 기능 수행에 대해서만 집중할 수 있다

 

반면에 응용 계층에서는 Name의 글자 수 제약사항을 알지 못하기 때문에

save() 기능의 비즈니스 응집도는 다소 떨어지는 모습을 보여준다

위에서 설명했던 1 : 1 구조 즉, 표현 영역과 응용 영역이 원팀으로 움직이는 것이 강제화 되는 현상이 바로 그것이다

 

또한 유효성 검증 요구사항이 증가할 수록 표현 영역이 불필요하게 지저분해지는 현상이 있는데

validation 관련 Library를 활용해 위 현상을 해결할 수 있다

 

 

validation 관련 Library 활용할 경우

public class NameCreateRequest {
    private static final int MAX_SIZE = 6;

    @Length(max = MAX_SIZE)
    private String name;
    ...
    //생성자, getter
}

@RestController
class NameController {
	@PostMapping
    	public HttpStatus create(@RequestBody @Valid NameCreateRequest request) {
    	   nameService.save(request.getName());
	        return HttpStatus.CREATED;
    	}
}
    
@Service
class NameService {
	public void save(String name) {
    	//save
    }
}

표현 영역에서 유효성 검증을 수행하는 것은 동일하지만, Validation 관련 라이브러리를 사용함으로써 유효성 검증에 대한 로직이 간결해졌다

 

필자가 관리하는 Production Code 도 위 구조를 채택하여 사용하고 있다

 

표현영역에서의 유효성 검증 관련 코드가 늘어나는 것을 방지했지만, 응용 계층의 비즈니스 응집도가 낮은 특징은 여전히 같다

 

그렇다면 응용 계층에서 유효성 검증을 수행하면 어떨까?

 

응용 계층에서 유효성 검증을 수행하는 경우

@RestController
class NameController {
	@PostMapping
    	public HttpStatus create(@RequestBody NameCreateRequest request) {
    	    try {
            	nameService.saveV2(request.getName());
	        } catch (IllegalArgumentException e) {
    	        return HttpStatus.BAD_REQUEST;
        	}
	        return HttpStatus.CREATED;
    	}
}
    
@Service
class NameService {
	public void saveV2(String name) {
        validNameLength(name);
        //save
    }

    private void validNameLength(String name) {
        if (name.length() > NAME_MAX_LENGTH) {
            throw new IllegalArgumentException();
        }
    }
}

 

표현 계층은 인자 값에 대한 검증이 끝나면, 인수 값에 대한 검증 없이 모든 데이터와 책임을 응용 계층에 위임한다

 

표현 계층에서 유효성 검증을 하는 구조에서는 Inboud 데이터의 인수 값에 대한 유효성 검증이 통과하지 못했을 경우에 응용 계층으로 유입조차 되지 않지만, 

 

위 구조는 인수 값의 유효성 여부와 상관없이 모든 케이스가 다 유입된다

 

또한

 

응용 계층에서 인수값에 대한 검증을 수행하는 경우 외부에서 어떤 데이터가 오던지 비즈니스에서 요구하는 요구사항을 응용 계층 스스로 검증하고 수행할 수 있기에

 

비즈니스 응집도가 높고, 다른 Service에서 NameService를 호출해야 할 경우 유효성 검증에 대한 불필요한 중복 코드 생성을 차단할 수 있어 재사용성이 뛰어난 특징을 가지고 있다

 

 

결론

 

인수 값에 대한 검증을 표현 계층에서 할 경우, Inbound 데이터에 대한 한 단계 여과를 수행하므로 데이터를 이어받은 응용 계층에서 본질적인 기능 수행에 집중할 수 있지만 응용 계층 재사용 시 중복 코드들이 생성될 수 있고, 비즈니스 응집도가 떨어진다

 

인수 값에 대한 검증을 응용 계층에서 할 경우, 비즈니스 응집도가 뛰어나 재사용이 가능하지만, 사실상 인자 값을 제외한 모든 유효성 검증 로직이 응용 계층에 있는 것이기에 코드가 조잡해 보일 수도 있다

 

위에서 비교한 두 가지 검증 방식은 무엇이 좋고, 무엇이 나쁘다고 표현할 수 있는 것이 아니다

 

각 방식의 장단점을 파악하고 내가 운영하는 환경에 어떤 방식의 도입이 더 적합한지 판단해서 사용해야 한다

 

 

반응형

댓글