티스토리 뷰

item49는 매개변수가 유효한지 검사하라 말한다

메서드나 생성자에 적용해야 하는 규칙으로써 이를 지키지 않으면 실패 원자성을 깨부술 수 있기 때문이다

실패 원자성이란 쉽게 생각해서 잘못 됐을 때 바로 터져야 함을 의미한다

매개변수가 유효하지 않음으로 인해 바로 그 순간에 예외가 터지는게 아닌

로직을 타고 흘러 흘러 어딘지도 모를 엉뚱한 곳에서 예외가 터진다면 실패 원자성이 깨진 상태다

 

웹 개발을 하는 경우 입력 유효성을 하는 대표적인 상황은 Controller에서 매개변수를 받는 상황일 것이다

이 역시 여러 방법으로 처리가 가능한데 @Valid @RequestBody를 이용해 객체를 매핑 받아 사용하고

바인딩이 실패하는 경우, 실패 원인을 담고 있는 BindingResult도 받아서 추가 처리도 가능하다

@GetMapping
public void 어쩌구(@Valid @RequestBody 저쩌구객체 저쩌구, BindingResult bindingResult) {
  if (bindingResult.hasErrors()) {
    bindingResult 지지고 볶기
  }
  
  // 로직
}

 

난 Controller에 BindingResult가 침투하는 모양새가 싫어 @ExceptionHandler에서 공통 처리를 하곤 한다

실무에서는 BindingResult를 쓰고 있는데 어떤 게 더 좋은 형태인지는 아직 잘 모르겠다

다만 모든 예외 처리가 비슷하게 런타임 에러 던지고 끝낸다면 덜 명시적이더라도 공통으로 빼버리는 것이 낫지 않을까 한다

 

책에서는 requireNonNull(), assert 단언문 등을 추천하는데 직접 작성해보면 알겠지만

이 놈들이 신성한 비지니스 로직에 깔짝 대면 피가 거꾸로 솟는 느낌이다

그렇다고 유효성 검사를 안 할수는 없으니 울며 겨자먹기로 써야한다, 그래도 가능한 한 예쁘게 써보자

public class 어쩌구 {

  private String v1;
  private String v2;
  private String v3;
  
  // 이런 코드 노노
  public 어쩌구(String v1, String v2, String v3) {
    assert v1 != null;
    assert v2 != null;
    assert v3 != null;

    this.v1 = v1;
    this.v2 = v2;
    this.v3 = v3;
  }
  
  // 이런 코드 예스
  public 어쩌구(String v1, String v2, String v3) {
    validateParams(v1, v2, v3);
    
    this.v1 = v1;
    this.v2 = v2;
    this.v3 = v3;
  }
  
  private void validateParams(String v1, String v2, String v3) {
    assert v1 != null;
    assert v2 != null;
    assert v3 != null;
  }
}

 

유효성 검사가 필요한 녀석들을 로직에서 뺄 순 없으니 메서드 분리를 통해 처리하자

최근 '만들면서 배우는 클린 아키텍처'를 재빠르게 읽어봤는데 해당 책에도 입력 유효성 검사에 대한 얘기가 등장한다

더 고려해 봐야할 점으로 Controller에서 매개변수를 받는 상황이 입력 유효성 검사가 필요한 유일한 경우가 아니라는 점이다

컨트롤러에서 입력 객체를 받고 서비스로 넘기고 비지니스 로직이나 도메인 로직에서 처리가 될 텐데

상남자식 코딩으로 Entity를 직접 받는게 아니면 DTO를 Entity로 변환 해야 한다, 이 과정에도 유효성 검사가 들어가야 한다

요즘 @NotNull, @NotBlank, @Size 등의 애노테이션 기반 입력 유효성 검사 하면 쉬운데

굳이 로직을 만들어서 수행해야 하는가? 하는 의문이 들었지만

왜인고 하니 DTO에서 Entity의 모든 필드를 커버하지 않는 경우가 있기 때문이다

이로 인해 앞서 언급한 실패 원자성이 깨질 수 있기도 하고 신성한 비지니스 로직에 들어가기 전 발 한번 털고 들어가자는 의미다

 

잠깐 번외로 DTO <-> Entity 변환은 어느 Layer에서 해야하는 지에 대한 영감을 얻을 수 있는 글을 소개한다

- https://www.slipp.net/questions/93

- https://tecoble.techcourse.co.kr/post/2021-04-25-dto-layer-scope/

 

만들면서 배우는 클린 아키텍처 책에서는 Layer 간 입력 & 출력 모델을 따로 만들면 좋다고 하는데

그렇다고 급발진해서 진짜 다 나눠버리면 DTO 지옥에 빠질 수가 있다

전통적인 계층형 스타일로 만들면 Controller, Service, Repository에서 하나의 엔티티에 대한 모델이 6개 생기는 것이다

 

그럼 결국 어찌해야 할까?

클린 아키텍처는 실무적 접근이라기보다 엄근진 접근이라고 생각한다

물론 변경에 유연하게 대응할 수 있는 열려 있는 설계가 좋은 것을 누가 모를까 싶지만

어떤 프로젝트던지 과연 핵심 비지니스 로직 계층인 Service를 바꿔 낄 일이 있을까?

또한 MyBatis -> JPA, 혹은 그 반대로 DB 접근 기술을 바꿀 일이 있을까?, 요런 고민이 필요한 것 같다

게다가 입출력 모델이 늘어날 수록 변환 과정이 필요하고 입력 유효성 검사가 추가되어야 한다

 

이펙티브 자바에 자주 등장하는 API도 공개 되는 스펙이라는 점과 실무적인 접근법을 짬뽕 해보자면

Controller에서는 무조건 개별적인 입출력 모델을 사용해야 하고 (공개되는 스펙이므로)

Service 계층은 프로젝트가 빠개지기 전까지는 바뀔 일이 없을 것 같고

Repository 계층 역시 바뀔 일이 거의 없을 거라 생각한다

그러니까 대규모 실무 프로젝트나 내가 만든 프로젝트나 비슷한 형태로 만들어지는 것 아닐까?

DDD에 대한 내공이 쌓이면 생각이 바뀌려나? 생각이 바뀌면 추가 정리 해봐야겠다

'Java > Effective Java' 카테고리의 다른 글

[Item51] 설계를 신중히  (0) 2022.05.01
[Item50] 방어적 복사본과 불변 클래스  (0) 2022.04.24
[Item48] 병렬화 주의  (0) 2022.04.16
[Item47] 스트림은 언제 반환하는가  (0) 2022.04.13
[Item46] Stream과 Side-Effect  (0) 2022.04.07
댓글
링크
글 보관함
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Total
Today
Yesterday