본문 바로가기

Programming/개인 프로젝트

[중고거래사이트 - 4] 제대로 만든다는 것은 무엇일까

반응형

개발 일정표에 따르면 로그인 기능을 구현했어야 했지만, 기존에 구현했던 회원가입 기능 보수 공사에 대부분의 시간을 할애했다. 보수 공사 내역은 다음과 같다.

 

  • Controller 클래스 URI 형식 수정(RESTful하게?)
  • ResponseEntity 클래스를 활용한 메소드 리스폰스 형태 수정
  • 커스텀 RuntimeException 클래스 생성 및 활용
  • 메소드 구현부에 있는 기존의 if-else 구문을 try-catch 구문으로 교체
  • 코드 변경에 따른 테스트 클래스 코드 수정 및 테스트

 

그래도 로그인 기능 구현을 시작했다는 조금의 위안을 얻기 위해 로그인 페이지 작성은 했다. 거기까지만.

 

사실 기존의 회원가입 기능에 사용된 코드도 문제없었다. 그러니까 일단 동작은 한다. 그리고 나는 그것이 마음에 들지 않았다. 실제로 서비스될 수 있는 것을 만들고 싶기 때문이다. 이 코드가 내가 할 수 있는 최선인지, 어떻게 하면 지속 가능한 코드가 될 수 있을지 고민했다. 그렇게 코드를 바라보니 수정 사항이 하나 둘 보이기 시작했다. 

 

Controller 클래스 URI 형식 수정

REST 방식의 구현을 지향하기 때문에 REST 방식에서 요구하는 URI 형태로 구현하는데 집중하고 있다. 문제는 REST 방식을 준수하는 것이 쉽지 않다는 것. 기존에 본인이 마음대로 작성하던 URI를 정해전 규격에 맞추어 작성하려니 힘들었다. URI 네가 뭔데 나를 이렇게 힘들게 하니..라는 생각이 들었지만, 이전과 비교하여 통일성과 간결함을 갖춘 URI를 바라보니 새삼 URI디자인의 중요성을 느낄 수 있었다. 

 

예를 들면 기존에 작성한 아이디 중복 체크 URI는 아래와 같다.

 

@GetMapping("/check-userId")
public ResponseEntity<Void> checkUserId(@RequestParam("userId") String userId) {...}

 

위의 코드를 아래처럼 수정했다.

 

@GetMapping("/id/{userId}")
public ResponseEntity<Void> checkUserId(@PathVariable("userId") String userId) {...}

 

@RequestMapping("/users")로 설정했기 때문에 최종 URI는 /users/id/[유저 아이디] 형식으로 클라이언트에게 노출될 것이다. 동사(Verb) 형태는 최대한 배제하고 명사(Noun) 형태로 구성했다. 그리고 @PathVariable로 URI에 쿼리 스트링을 추가하면 좀 더 의도가 명확하게 보일 수 있지 않을까 생각했다. 사실 무엇이 최선의 방법인지 아직도 잘 모르겠다. 좀 더 견문을 넓혀야겠다.

 

단순한 URI 수정이지만, 이 과정을 통해 개발이라는 작업이 언뜻 자유로워 보이지만 철저하게 효율과 규칙의 틀 안에서 움직인다고 생각했다. 어디 개발뿐이겠는가. 팔리는(비즈니스) 영역에서 활동하려면 효율과 규칙은 필수이지 않을까. 

 

ResponseEntity 클래스를 활용한 메소드 리스폰스 형태 수정

 

ResponseEntity<T> 클래스는 Http 요청 및 응답 표현을 담당하는 HttpEntity<T> 클래스를 상속한다. 추가로 200, 404과 같은 HttpStatus(Http 상태코드) 클래스도 포함하고 있어 Spring MVC 환경에서 메소드 리턴값으로 ResponseEntity<T> 클래스를 사용하면 요청에 대한 Http 응답을 전달할 수 있다. 참고로 ResponseEntity<T>의 T는 Http 응답 본문(Response Body) 타입이다. 개발자는 의도에 맞는 응답 데이터 형태를 설정해서 Http 상태 코드와 함께 응답을 전달할 수 있다.

 

@RequestMapping("/{userId})
public ResponseEntity<List<UserVO>> getUser(@PathVariable("userId") String userId) {

List<UserVO> result = new ArrayList<>();
result = service.getUser(userId);

return ResponseEntity<>(result, HttpStatus.OK);
}

 

위의 코드는 설명을 위해 임의로 작성된 코드이다. 추가로 예외 처리 작업을 통해 안전성을 더하면 좋을 것 같다.

 

이론 설명은 여기까지 하고, 내가 하고 싶은 말이 무엇이냐면, ResponseEntity<T> 클래스를 통한 응답 형태로 메소드를 수정했다는 것이다. 기존 형태는 아래와 같았다. 

 

이 메소드는 회원가입 처리를 담당하고 있다.

 

@PostMapping("/signup")
public String signUpUser(@RequestBody UserVO user) {
    service.signUpUser(user);
    return "/";
}

 

REST 방식으로 개발하고 있는데, 그렇다면 리턴 값으로 전달될 "/" 는 경로가 아닌 "/" 이라는 String 형태의 데이터가 전달된다. 그래서 ajax 응답 형식을 아래와 같이 구현했다. 경로 값을 담은 String 데이터를 받아서 location.href으로 연결하여 페이지 이동 처리를 했다.

 

success: function(data) {
	alert("회원가입 완료");
	location.href = data;
},

 

그렇다. 어쨌든 작동은 된다. 

 

 

 

문제는 이렇게 구현할 거면 굳이 REST 방식을 고집할 필요가 있냐는 것이다. 차라리 @Controller 어노테이션 사용 방식으로 돌아가서 컨트롤러에서 View 이동 처리를 하는 것이 더 간결하지 않을까? 만약 View가 아닌 JSON 데이터 반환을 처리하려면 @ResponseBody 어노테이션을 추가하면 되고.

 

내가 이해한 대로 말하자면, REST 방식을 통해 구현하는 이유는, 순수하고 간결한 형태의 독립된 데이터 처리 API를 만들기 위한 것이다. REST 형태로 개발된 API는 하나의 서비스에 종속되지 않는다. 그리고 간단한 이식 과정을 통해 다른 서비스에서도 손쉽게 사용할 수 있다. 그렇게 구현하려면 간결한 URI 구조와 데이터 처리 방식을 가지고 있어야 한다. 

 

그렇다면 경로 값을 String 형태로 전달하는 것이 REST 방식에 맞는 개발일까? 아닌 것 같다. 경로 값은 서비스에 따라 다양한 형태로 존재한다. 따라서 서비스 구조의 변경이 발생할 때마다 매번 신경 써야 한다. 

 

그래서 아래와 같이 ResponseEntity<T> 클래스를 통해 Http 상태 코드 전송 형태로 변경했다. 

 

@PostMapping("")
public ResponseEntity<Void> signUpUser(@RequestBody UserVO user) {
    service.signUpUser(user);
    return HttpResponse.RESPONSE_CREATED;
}

 

ResponseEntity<Void> 형태로 전송하도록 했다. 이유는 굳이 "success"와 같은 리스폰스 값을 전달할 필요가 없다고 판단했기 때문이다. 코드에서 알 수 있듯이 리턴 값을 201 status code로 전달하는데, 동작 확인 결과 201 status code에 대한 별다른 처리 코드가 없어도 success로 이동되는 것을 확인했다. 구글링 해보니 200, 201, 404 등의 세부적인 status code에 대한 처리도 할 수 있다. 참고해야겠다. 

 

https://stackoverflow.com/questions/12410051/how-to-handle-ajax-201

 

How to handle ajax 201

When making a ajax call see example below success does gets a 201 status retuned. How do you handle these better i.e. 200, 201 within the success function? $.ajax({ type: "POST", dataType:...

stackoverflow.com

 

엉성하고, 그리고 자그마한 리팩토링 과정이었지만 이 과정을 통해 "그냥 좋을 것 같아서" 특정 기능을 사용하는 것의 치명적인 단점을 느낄 수 있었다. 처음 REST 방식을 사용하려고 생각했을 때는 사실 별다른 이유 없이 편해 보여서 사용했다. 그래서 리턴값을 페이지 경로를 담은 데이터로 넘겨 버리는 이상한(?) 형태의 코드를 완성했다. 왜 사용하는 것에 대한 명확한 개념을 알지 못했기 때문이다. 지금은 이해했냐고? 아직도 잘 모르겠다. 그래서 계속 공부하고 의심하면서 조금씩 발전된 코드로 수정해야겠다는 생각 뿐이다. 태도가 능력보다 중요하다고 알리바바의 창업자 마윈은 말했다. 이번 리팩토링 과정을 시작하게 만든 태도를 계속 유지하고, 더욱 발전시켜야겠다.

 

참고

https://sanghaklee.tistory.com/61

 

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code)

<!DOCTYPE html> REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code) REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code) TOC Introduction HTTP 와 REST HTTP Status Code 2XX Success 4...

sanghaklee.tistory.com

https://nhj12311.tistory.com/204

 

RuntimeException란 무엇인가?

Java를 한다면 RuntimeException을 수도 없이 만나게 된다. 물론 직접적인 RuntimeException 을 만나진 않을 것이다. 단지 아래와 같은 RuntimeException을 상속 받는 아주 많은 RuntimeException 을 만난다는 뜻..

nhj12311.tistory.com

https://stevenschwenke.de/ReturningAnEmptyResponseEntityInSpringMVC

 

Returning an empty ResponseEntity in Spring MVC | stevenschwenke.de

Returning an empty ResponseEntity in Spring MVC Posted by Steven Spring MVC is a great way of creating REST interfaces. Many convenience classes and methods are provided, such as the Response Entity object for returning data: @GetMapping("/")public Respons

stevenschwenke.de

 

반응형