본문 바로가기

기타 개발 스킬

나는 어떤 응답을 만들었는가(부제 : 그놈의 '기초', '기본'은 무엇인가)

반응형

어떤 Http Response를 줄 것 인가?

글에서 다룰 주제

이번 글에서 다룰 내용은 기술적인 내용이나 경험(?)이 아닌 개발자라면 한 번쯤 생각해볼만 한 것? 정도 된다.

최근 들어 개발자 연봉 상승 소식도 많이 들리고, 개발자 수요도 많다하고, 비교적 진입 장벽이 높지 않다는 희망적인 소식 덕분에 많은 사람들이 소프트웨어 업계로 많이 오고 있다.

전직 또는 진로 변경을 하려는 사람들은 이미 현업에 있는 사람들에게 업계 질문을 많이 할 것이다.

그리고 현업에 있는 사람들의 조언을 잘 들어보면 거기에는 "기초만 잘 다지면..." , "기본적인 것만 할줄 알면..." 이런 말이 있을 것이다. 주의해야한다.

필자도 만 3년정도 경력이 있지만 그 기초가 뭔지 기본적인 것은 뭔지 명쾌하게 말할 수 없었다.

그래서 이번에 기초에 대해서 생각해보고 내린 결론에 대해서 다루려고 한다.

결론부터 말하자면, 내가 내린 결론은 "결국 어떤 요청(Request)에 대해서 어떤 응답(Response)을 줄 것 인가?"를 이해하는 것이 기초이자 기본기라고 생각한다.

이런 생각을 갖게된 배경? 근거?에 대해서 아래에서 열거해볼 것이다.

→ 경력과 상관없이 많은 분들께서 이 글을 보고 의견을 제시해주시기를 희망하고 있다.

아무튼 다룰 궁극적인 주제는 개발자에게 기본, 구체적으로 서버 개발자의 기본 이다.

배경

소프트웨어 업계에 계신 분들이라면 기초, 기본기라는 단어를 굉장히 많이 들었을 것 같다. (심지어 지금 보고 있는 이 블로그의 제목도 '기본기를 쌓는 정아마추어 블로그'다...)

  • 추구하는 개발자 유형 중 '기초가 탄탄한 개발자'
  • 채용 공고에서 'CS 기본 지식 보유하신 분'
  • 컴퓨터공학 기본기 4대장 (자료구조/알고리즘, 네트워크, 운영체제, 데이터베이스)
  • XXX를 배워야 될까요? 에 대한 답변 중 '기본적인 것 만 할 줄 아시면 돼요'
  • ...

위에서 간단한 예시를 작성해봤다.

굉장히 자주 언급되는 단어라는 것에 공감하지 않을까 싶다.

근데 여기서 드는 의문이 바로 "무엇이 기본인가?", "어디까지해야 기초인가?" 같은 것이다.

스프링 MVC를 학습해서 사용 가능하면 기초인가?

OSI 7 layer, 3 way handshake등의 네트워크 지식을 이해하고 기초인가?

CPU 스케줄링, 메모리 관리 방법 등 OS지식을 이해하고 개발하면 기초인가?

뭔가 어느정도는 맞는 말 같으면서도 어느정도는 틀린 말 같다.

물론 어떤 서비스를 개발하느냐, 어떤 사양이 요구되느냐 등 기준이 다양하기 때문에 그 기본이 다를 수 있다.

하지만 개발을 하면서 굉장히 자주 만나는 기본이라는 글자를 한 번은 명확하게 정의내려야 성장할 수 있을 것만 같다.

어디까지가 기본인가?🤔

먼저 가끔씩 포스팅의 자료로 참조되는 스프링 관련 오픈 채팅방에서 우연히 토비님께서 방향성(?)을 찾을 수 있는 말씀을 해주셨다.

그 내용은 다음 캡쳐와 같다.

(혹시라도 토비님께서 대화 내용 캡쳐가 불편하시다면 언제든 그림은 삭제될 수 있음을 알려드립니다.)

토비님의 의도를 명확하게 파악하진 못 했으나 개인적으로 방향성을 찾았다.

요점을 정리하면 Web Application을 개발하는 사람이라면 그 근본Web에 있다는 것으로 이해했다.

어떤 저장소(Repository)에서 데이터를 가져올지, 어떤 데이터를 어디에 캐시할지, 비동기로 처리해야할 부분이 어딘지와 같은 것들은 부가적인 것일 뿐이라고 하신다.

핵심이자 기초는 웹(Web)을 지탱하는 기술(HTTP, HTML, URI)에 있다.

그 중 제일 중요한 것이 HTTP 에 대한 이해고, 구체적으로 HTTP Request, Response에 대한 것이다.

서버 개발자 입장에서는 들어오는 요청(Request)에 대해서 명확하게 이해하고, 적절한 응답(Response)을 만들어서 제공해주는 것이 해야할 이다.

(여기서 적절한 응답과 RESTful 같은 것과는 관계가 없다. 오로지 HttpHeader와 HttpBody, Status를 구성하는 일과 관련있다.)

그렇기 때문에 필자는 적절한 응답(Response)을 만들 수 있으면 기초가 있다고 보고 누군가 기초가 무엇이냐 묻거든 HTTP에 대한 이해라고 해줄 것이다.

그 근본적인 HTTP Response를 만들면서 발생하는 문제 또는 해결해야하는 문제를 어떤 기술로 해결할지는 기초 이상의 것이다.

토비님의 의견만 근거가 아니다. 추가적으로 Web, HTTP 가 근본인 근거로 개발자들에게 익숙한 자료인 '백엔드 개발자 로드맵'이 있다.

기본기, 기초, 기술 학습의 뿌리가 뭔지를 한 번 봐보자. 시작부분인 가장 최상단 부분만 캡쳐했다.

그렇다. 모든 뿌리, 근본은 여기서 출발했다. "인터넷=웹" 이것이 기초다.

나는 어떤 응답을 만들었는가?

깨달은 것과 주장에 대해서는 앞에서 얘기를 했다.

살짝 다른 얘기를 해보려고한다.

앞에서 캡쳐한 내용도 사실 오래된 말씀이었다. 나중에 작성해서 얘기좀 나눠봐야지 했다.

그렇게 게으른 생활을 이어가던 중 이 포스트를 작성해야겠다고 마음 먹게 된 계기이자 시험(?)이 찾아왔다.

같은 팀에 있는 선배 개발자분께서 이런 질문을 하셨다.

"요청이 왔을 때 서버에서 어떤 조건에 따라 분기 처리(JSON 응답을 주거나, 특정 페이지로 리다이렉트(Redirect)해야 하거나)를 하려고 하는데 스프링에서 어떻게 할 수 있는가?"에 대한 질문이었다. (TMI - 선배 개발자님은 프론트 개발자로 자바스크립트를 이용한 개발에 능통하시다. 자바, 스프링을 이용한 개발도 가능하시지만 특별히 흥미가 있으시진 않다. 그래서 필자에게 이런 질문을 해주셨다.)

처음에는 그렇게 컨트롤러(엄밀하게는 응답)를 구성해본 적이 없어서 '그게 가능한 가?' 하는 생각이 잠시 들었다.

그러나 곧장 가능할 것 같은데?하는 생각이 들었다.

왜냐하면 브라우저가 HTTP Response에서 Status가 3XX 대 즉, Redirect임을 암시하고 HTTP Header에 Location 속성의 값으로 URI가 제공되면, 브라우저는 해당 경로(Location)로 리다이렉트를 시켜주는 것으로 알고 있었기 때문이었다. (다행히 나에게도 기초라는게 있었다.)

예제 구현은 다양하게 할 수 있었다.

조건에 따라 ResponseEntity 객체를 달리 만들어도 되고, 리다이렉트 해야하는 상황을 예외로 정의하고 ExceptionHandler 를 구현하여 리다이렉트 하도록 할 수도 있었다. filter에서 구현하든 interceptor에서 구현하든 뭐든 자유였다.

그러나 구현을 어떻게 할 것 인가? 이것 역시도 부가적인 것이었다.

핵심은 어떤 응답을 만들었는가다.

그냥 @Controller 애노테이션을 갖는 컨트롤러 클래스 하나 만들고 메서드에 @RequestMapping 해서 return 문에 return redirect:/ 이런 것을 만들어서 리다이렉트를 구현해봤느냐는 기초가 아니었고 어떤 응답을 해야 리다이렉트가 되는지를 아는게 중요했고, 그것이 기초였다.

더 이상은 똑같은 내용을 길게 늘어쓰는 것 같아서 이만 줄이겠다.

아래 부터는 이번에 이런 일을 겪고서 다양한 Http Response를 만들어보기 위해서 학습한 ResponseEntity 에 대한 글이다.

이미 알고 있어서 볼게 없다든지(?) 특별히 관심 없는 분은 여기서 그만 읽어도 좋다. (대신 댓글로 앞선 기초에 대한 의견 주시면 매우 감사할 것 같다...)


ResponseEntity란?

HttpEntity 를 확장한 클래스로 완전한 HTTP Response를 제공할 수 있는 객체다.

HttpEntity 에서는 HttpHeader와 HttpBody에 해당하는 값을 넣어 응답을 만들 수 있었는데 HttpStatus에 대한 값을 넣어 응답을 할 수 없었다.

그래서 HttpEntity를 상속하여 HttpBody와 HttpHeader를 이용할 수 있으면서도 HttpStatus를 추가해 완전한 응답을 만들 수 있는 클래스가 바로 ResponseEntity 다.

ResponseEntity는 body변수 탑이 제네릭이기 때문에 모든 클래스로 응답을 할 수 있다.

@GetMapping("/test")
ResponseEntity<String> test() {
    return new ResponseEntity<>("String type", HttpStatus.OK);
}

위와 같이 ResponseEntity 생성자를 이용해서 객체를 만들어주면 적절한 응답이 만들어진다.

분기 처리하여 다른 응답을 만들고자 할때는 아래와 같이 하면 된다.

@GetMapping("/posts")
ResponseEntity<PostsDTO> getPosts(@RequestParam("page") int page) throws URISyntaxException {
    if (page <= 0) {
        HttpHeaders headers = new HttpHeaders();
        URI naver = new URI("http://www.naver.com");
        headers.setLocation(naver);
        return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
    }
    //... get posts 
    return new ResponseEntity<>(posts, HttpStatus.OK);
}

HttpHeaders에 있는 setLocation 메서드를 이용해서 넣지 않고 add 메서드를 이용해 그냥 커스텀으로 헤더를 넣을 수도 있다.

HttpHeaders headers = new HttpHeaders();
headers.add("customHeader", "helloworld");

생성자를 이용해서 응답을 생성하는 방법 말고도 내장된 빌더를 이용하여 ResponseEntity를 만들 수도 있다.

ResponseEntity.ok(...);
ResponseEntity.badRequest().body(...);
ResponseEntity.status(HttpStatus.OK).body(...);
ResponseEntity.ok()
        .header("myHeader", "jdk")
        .body(...);

굳이 ResponseEntity를 쓰지 않고 서블릿 스펙에서 쓰이는 HttpServletRequest, HttpServletResponse 를 컨트롤러 메서드의 아규먼트(arguments)로 받으면 자동으로 넣어주는 데 이것을 사용하면 되지 않을까? 생각해볼 수 있다.

@GetMapping("/servlet")
void useServlet(HttpServletResponse response) throws IOException {
    response.setHeader("Custom-Header", "foo");
    response.setStatus(200);
    response.getWriter().println("Hello World!");
}

그러나 권장하지 않는 방법이다.

약간의 회고

그 동안 사실 아무 생각없이 @ResponseStatus 이런거를 썼었고, @RestController 컨트롤러 만들고 리턴 타입도 그냥 DTO로 리턴했었다.

틀린 건 아니지만 더 나은 응답을 만드려는 노력을 하지 않았다.

자연스럽게 스프링이 DTO를 JSON으로 만들어 주니까... 심지어 컬렉션타입(ex List<>)로도 리턴을 했었다... 썩 좋은 패턴은 아니다.

컬렉션을 리턴값으로 하는 것이 안 좋은 이유는 확장에 유연하지 못하기 때문이다.

body 부분을 보면 시작부터 대괄호 [ 로 시작한다.

그러면 이 컬렉션의 개수(count) 같은 정보를 추가해서 줘야한다고 할 때, Root element가 배열이기 때문에 어디에 count를 넣어야할지 모르는 반면, Root element가 컬렉션이 아닌 단일 클래스라면 클래스에 프로퍼티(property)로 count를 쉽게 추가할 수 있기 때문이다.

이런 내용도 모르고 그냥 코딩했던 지난 날들이 참 아쉽다.

끝으로 이 글을 본 사람들 모두 적절한 Http Response를 만들며 좋은 개발자가 되길 바란다.

반응형