꽤 최근에 토이 프로젝트의 controller 코드가 조금 단순화 되었음을 볼 수 있습니다. 아직 리팩토링이 다 끝났다고 말 하기도 그렇지만. 사실 어제 리뷰를 받고 valid 어노테이션을 적용한 것이 조금 컸습니다. 변경 내역은 이 링크에서 보실 수 있습니다.

 


 사실 문제의 발단은 사소한 테스트에서 출발하였습니다. 유저가 중복된 경우에 대해서 왜 내 프로젝트는 500을 떨어트릴까? 예외 처리가 느슨해 보였습니다. 그런데 마침 카톡방에서 리뷰어 분들이 제 프로젝트를 보시고, Bad request 부분을 처리하는 로직이 비대해 보인다면서, Valid 어노테이션을 써 보는 게 어떻겠냐는 조언을 주셨습니다.

 

 

 먼저 dependencies에 Validation을 추가합니다.

 

 

 pom.xml에는 spring-boot-starter-validation을 넣어주시면 됩니다.

 

 

 대략적인 프로젝트 구조는 위와 같습니다. model에는 Item과 ItemList가 있습니다. GET /item api는 item들의 List들을 json으로 받게 되는데요. List로 받는 item들은 비어 있으면 안 되고, 각각의 아이템은 reqLevel이 1 이상이여야 하고, item 이름이 공백으로만 이루어져 있거나 비어 있으면 안 된다고 해 보겠습니다.

 

 입력 값이 valid 한지 판단하기 위해서, 예전에는 아래와 같이 처리했습니다.

 

 

 먼저, itemList 요소가 없으면 안 되겠죠? 없으면 바로 BAD_REQUEST를 떨어트립니다.

 

 

 아이템 리스트가 있다면, RegLevel이 null이거나 0이면 또 400을 떨어트리고, itemName이 null이거나, 공백으로만 이루어져 있으면 400을 떨어트립니다. 아. 미처 몰랐는데, itemList가 비어 있는 경우 400을 떨어트리는 로직은 까먹고 적지 않았네요. 사실, 21번째 logic부터 28번째 logic은 Item 클래스에 checkValid하는 메서드를 넣어서 단순화 시킬 수도 있기는 하지만 뭔가 복잡해 보입니다.

 

 list가 있는지 없는지 검사하고 순회하는 작업이 그리 간단해 보이지도 않고요.

 


 Valid 어노테이션을 적용해 보겠습니다. 먼저, ItemList는 아이템들의 리스트를 들고 있습니다. 이것은 비어 있으면 안 되고, Null 값이면 안 됩니다. 비어 있으면 안 된다는 이야기는 최소한 Size는 1 이상이여야 한다는 소리입니다.

 

 

 따라서, NotNull 어노테이션과 Size 어노테이션을 itemList 위에 붙입니다. 그런데, size는 min이 1이여야 하므로, size 어노테이션의 옵션 값에 min = 1을 넣어 줍니다. 다음에 itemList 위에 Valid 어노테이션을 붙였는데요. 이는 itemList 안에 있는 item 들에 대해서도 valid를 해 주기 위해서입니다. 이 답변에서 언급하고 있는 문서에 해당 부분이 잘 나와 있습니다. Collection-valued로 시작하는 문단을 보시면 됩니다.

 

 2번째 문단을 보시면 class X가 있고, 필드에 Y가 있고, 이 위에 Valid 어노테이션이 붙은 경우에, X가 validate 될 때 Y도 validate 된다고 나와 있어요. itemList 위에 Valid 어노테이션이 붙었기 때문에, itemList가 validate 된다면, itemList 또한 valid한지 검사하게 됩니다. 그러면 List를 Valid한지 어떻게 판정할까가 문제인데요. 3번째 문단에서 주목해야 할 부분은 the contents of iterator to be validated. 이 부분입니다. 이터레이터의 내용들?

 

 

 예를 들어, List에 요런 식으로 저장이 되어 있다면, Lv이 3이고 Name이 'c'인 것도, Lv이 1이고 Name이 'd'인 것도 valid 한지 검사하게 됩니다.

 

 

 Item 안에 내용을 보면, regLevel은 NotNull이면서 Min(1)을 가져야 합니다. 즉 1레벨 이상이여야 합니다. 다음에, itemName은 널 값이 아니면서, Blank로만 이루어지지 않아야 합니다.

 

 

 다음에, RequestBody로 들어오는 itemList에 대해서 벨리데이션 한다고 했으므로, itemList 앞에 @Valid을 붙이시면 됩니다. 이거도 문서에 나와있는 듯 하네요. 어찌 되었던 controller 내에서 인풋에 대해서 400 에러에 대해서 처리하는 부분이 싹 없어졌음을 알 수 있어요.

 

 


 이제 테스트 해 봅시다. 사실, test 코드를 만들어야 하는데 json에 대해서 익힐 겸 postman으로 테스트 해 보겠습니다. ItemList에는 List가 있어요. json에서 리스트는 []로 표현합니다. 그러면 "itemList" : [] 이런 식으로 표현하면 될 겁니다. 그런데, 이것은 무슨 배열이래요? regLevel과 itemName이 있는 객체 배열입니다. 따라서, 아래와 같이 작성하시면 됩니다.

 

 

 어렵지 않죠? 객체니까 {로 열고 }로 닫아주었어요. 오! 간단합니다. 요래 보내면 200 OK를 떨굽니다. 인풋에 아무런 문제가 없어 보이니까 당연합니다.

 

 

 그런데 요래 보내면 400을 떨궈냅니다. 왜? 1번째 요소의 itemName이 blank로만 이루어졌기 때문입니다. 실제로 공백과 탭으로만 이루어져 있습니다.

 

 

 이 경우도, 400을 보냅니다. 왜? 화염의 카타나의 regLevel이 0이기 때문입니다. item은 regLevel이 무조건 1 이상이여야 한다고 했으니까, 400일 수 밖에 없어요.

 

 

 이건 itemName이 없는 게 있네요. 400을 떨어트릴 겁니다.

 

 itemList가 빈 경우는 어떨까요? 400을 떨굽니다. 왜냐하면, itemList가 1 이상이여야 한다는 조건을 걸어놓았기 때문입니다.

 

 

 서버에 뿌려진 로그 메세지를 보면, validation이 왜 실패했는지 알려줍니다.