Contents

Controller <-> Service 간 DTO 도입

문제 상황

Controller 테스트 코드 작성시에 성공과 실패 코드 짜는 중에 서비스 레이어에서 단순히 String 타입으로 리턴하기 때문에 비정상적인 결과를 표기할 수 없었다.

테스트 코드

@Test  
public void testUpload() throws Exception{  
    MockMultipartFile file = new MockMultipartFile(  
            "file",  
            "hello.txt",  
            MediaType.TEXT_PLAIN_VALUE,  
            "Hello, World!".getBytes()  
    );  
  
    when(metaDataService.uploadFile(file, "test")).thenReturn("File uploaded successfully");  
  
    mockMvc.perform(  
            multipart("/file/upload").file(file)  
            .param("user", "test")  
            ).andExpect(status().isOk())  
            .andExpect(content().string("File uploaded successfully"));  
}

이전 Controller 코드

    if (file.isEmpty()) {  
        return ResponseEntity.badRequest().body("Please select a file to upload");  
    }  
    String body =  metaDataService.uploadFile(file, username);  
    return ResponseEntity.ok(body);  
}

현재 구조로는 실패해도 status code를 제대로 보내지 못한다. 이를 해결하기 위해서 Controller <-> Service 간 데이터 교환을 할 객체가 필요했다. 레이어들 간 데이터 교환 객체를 여러 용어로 부르지만 이 문제 상황에서 DTO라고 부르도록 하겠다.

이전 Service 코드

서비스 레이어에서 String으로 반환하기 때문에 서비스 함수 성공 여부 전달할 수 없다. String 안에 문자를 다르게 할 수도 있겠지만 짜친다.. 아래 코드를 보다시피 단순 String 타입으로 리턴하면 표현의 한계가 존재한다.

public String uploadFile(MultipartFile file, String username){  
  
    try {  
        // Get the file and save it somewhere  
        byte[] bytes = file.getBytes();  
        Path path = Paths.get(UPLOAD_DIR + file.getOriginalFilename());  
        Files.write(path, bytes);  
  
        MetaData metaData = initMetaData(file, username);  
  
        // Save metadata to database  
  
    } catch (IOException e) {  
        e.printStackTrace();  
        return  "Failed to upload file: " + e.getMessage();  
    }  
    return  "File uploaded successfully";  
}

해결 방법 DTO로 도입하자

최종본 DTO를 넣은 코드

public FileUploadResponse uploadFile(MultipartFile file, String username){  
  
    try {  
        // Get the file and save it somewhere  
        byte[] bytes = file.getBytes();  
        Path path = Paths.get(UPLOAD_DIR + file.getOriginalFilename());  
        Files.write(path, bytes);  
  
        MetaData metaData = initMetaData(file, username);  
  
        // Save metadata to database  
  
    } catch (IOException e) {  
        e.printStackTrace();  
        return new FileUploadResponse(false, "Failed to upload file: " + e.getMessage());  
    }  
    return new FileUploadResponse(true, "File uploaded successfully");  
}

위 코드를 보다시피 DTO 객체에 ‘성공여부’, ‘메세지’ 라는 두 필드를 정의했다. try-catch 문을 통해서 실패한 경우 false로 리턴하게 하였다.

느낀점

많은 책들에서 테스트코드를 많이 짜길 권했다. 가장 대표적인 이유로 테스트코드를 짜다보면 더 나은 코드를 짤 수 있다고 말했기 때문이다. 이번에 테스트 코드를 짜다보니 여러 상황에 적합한 코드를 생각해 볼 수 있었고 자연스럽게 DTO 도입으로 이어졌다. 테스트 코드의 중요성을 깨닫고 테스트 코드를 제대로 공부해보고 싶었다.

추후 개선점

현재 IOException 만 에러 처러하였는데. 추후에 코드가 추가되면서 다른 예외처리가 필요할 것으로 보인다. 또한, 현재 DTO 필드는 2개인데. 다른 정보를 컨트롤러에 추가해야 할지 고민해봐야 한다.