이번에 기획서에 따라 NovelService 의 소설 생성, 업데이트, 완결 상태 업데이트, 삭제(Soft Delete) 기능을 다시 구현하게 되었다. 그런데 소설 등록 기능을 구현하던 중 한가지 문제가 발생하였다.
소설을 등록하려면 Request DTO → Entity 변환을 한 후 Entity 를 저장해야 하는데, 이 변환 과정에서 이전에 말한 것 처럼 strip() 을 사용해 제목의 앞뒤 공백을 제거해야 했다. 하지만 Service 레이어에서 이 작업을 수행하려고 보니 실제 변환 로직은 RequestMapper 가 수행하고 있었고, 이때문에 strip() 작업을 Mapper 에서 수행해야 하는 상황이 벌어졌다.
물론 Mapper 에서 해도 아직은 큰 문제는 없을 수 있지만 Mapper 는 순수하게 한 쪽 레이어의 DTO를 다른 쪽 레이어에서 사용할 수 있는 객체로 변환하는 책임만 갖는 경우가 많아 문자열을 정규화 하는 등의 값 가공 로직이 들어가면 그 로직이 잘 드러나지 않는다고 생각하였다.
strip() 같은 작업들은 간단한 작업이긴 하지만 데이터의 무결성을 보장하는 중요한 작업이다. 그런데 Mapper 에 해당 로직을 두게 된다면 일반적인 Mapper 의 책임을 벗어나는 일이라서 나중에 어느곳에서 값을 정규화 하고 있는 지 찾기가 힘들어지고 처음 봤을 때도 값을 정규화 하고 있다는 부분이 잘 드러나지 않아 중요한 부분을 놓칠 수 있다.
그렇다고 Mapper 을 아예 사용하지 않고 Service 에서 직접 변환을 수행하는 것은 좋은 방법은 아니라고 판단하였다. 왜냐하면 Mapper 는 단점보다 장점이 더 많기 때문이다. 대표적으로 아래와 같은 장단점이 있다.
장점
단점
이런 장점과 단점이 있고, 개인적으로는 Mapper 를 따로 구현하는게 Service 클래스의 코드도 더 깔끔해져서 Mapper 는 거의 항상 사용하고 있다.
그래서 결국 strip 같은 값 가공 작업을 Service 에서는 수행할 수 없고, Mapper 나 Request DTO 에서 수행해야 하는 상황이었다.
물론 Mapper 에서 따로 제목을 추가 인자로 받고, Service에서 정규화 된 제목을 Mapper에 직접 넘길수도 있다. 하지만 이는 두 클래스 사이에 결합도가 높아지게 되고, 나중에 추가로 정규화가 필요한 필드가 또 생기거나 새로운 필드가 Request DTO 에 추가된다면 계속해서 두 클래스를 수정해야 하므로 Mapper 는 단순히 Request DTO 자체를 직접 받아 알아서 변환 하는 방식이 좋은 방식이라고 생각하였다.
그렇다고 Request DTO 에 값 가공 로직을 넣게 되면 이전 글에서 말한 것 처럼 Request DTO 의 책임이 흐려지고, 사용자의 값을 가공하게 되면 검증에서 부작용이 발생할 수도 있기 때문에 이 방식은 옳지 않다고 판단하였다.
결론적으로 현재 구조에서는 그 어떤 곳에서도 값 가공 로직을 수행할 수 없었다. 그래서 새 계층을 도입하는 것이 제일 나은 방법이겠다고 생각하였고, 이전부터 여러 곳에서 권장해오던 Command/Query 객체를 떠올리게 되었다.