Home Spring Boot Guide - 4
Post
Cancel

Spring Boot Guide - 4

Spring Guide 시리즈는 스프링부트 핵심 가이드 - 스프링 부트를 활용한 애플리케이션 개발 실무 책을 통해 학습 및 정리한 내용을 담고 있습니다.

이번 포스트는 간단한 API를 개발해보면서 앞으로 어플리케이션을 작성하는데에 필요한 내용을 정리해보고자 한다.


시작에 앞서

Spring Initializr를 통해 프로젝트를 생성하거나, IntelliJ IDEA 에서 새 프로젝트를 직접 생성하여 프로젝트를 미리 세팅하고 시작한다. 참고하는 교안에서는 Maven을 사용하지만, 나는 좀 더 최근에 나오기도 했고 가독성 측면에서도 효율적인 Gradle을 사용할 예정이다.

Java 11, JDK 11 기반의 프로젝트를 생성하고 보일러 플레이트를 줄여주는 Lombok, 추후 .yml 파일을 편리하게 작성하게 도와줄 Spring Configuration Processor 그리고 Spring Web을 추가해주도록 하겠다.


DTO 활용

간단하게 POST, GET 메서드를 사용하는 컨트롤러를 작성해보는 챕터는 패스하도록 하겠다. 이 부분은 SpringBoot 공식문서 에서도 자세히 설명해주고 있어서 쉽게 다뤄볼 수 있다고 생각했다.

백엔드에서는 데이터를 주고 받는 일이 대부분이고 프로젝트를 진행할 때에도 DTO를 필수로 사용했던터라, 앞서 말한 기본적인 컨트롤러 작성법보다 DTO를 더 중요하게 짚고 넘어가는 것이 좋을 것 같다.

DTO를 알아보자.

Data Transfer Object의 약자이다. 직역하자면 데이터를 전송하는 객체이다. 우리가 전 포스트에서 보았던 계층들을 기억해보자. Controller 계층이나 Service 계층 등 여러 계층들 사이에서는 실제로 데이터를 주고 받는다. 또한 우리가 살펴봤던 어플리케이션 내부적인 계층을 넘어 인프라 관점에서의 또 다른 서버 아키텍처 계층을 의미하기도 한다. 중요한 것은 계층 사이에서 주고 받는 데이터들을 하나의 DTO로서 하나의 매개변수로 사용되게끔 묶어주는 것이 바로 DTO의 역할이라는 것이다.

아래처럼 CREATE /api/user로 name을 받아 새로운 유저를 생성해주는 createUser() 컨트롤러 메서드가 있다고 가정해보자.

1
2
3
4
5
6
7
8
    ...

    @PostMapping
    public String createUser(@RequestBody String name) {
        return name;
    }

    ...

현재는 name만을 받는 그저 귀여운 메서드이지만, 서비스가 무럭 무럭 성장하거나 조금 더 욕심부려 더 많은 정보를 받아 유저를 생성하도록 발전한다면 createUser()는 더 이상 귀엽지 않게 된다. 넘겨받는 파라미터의 수가 많아지면 코드의 가독성도 떨어질뿐더러 유지 보수할 때에도 꽤 복병일 것이다. 그럴 때, DTO를 적용해볼 수 있다.

어떤 인자가 요청으로 들어오는지 불확실하다면?

자바의 Map 객체를 이용해 받아오는 방법이 있다. 하지만 요구 사항에 맞게 클라이언트 파트와 소통하여 요청 인자를 미리 다 정하고 설계하는 것이 정신 건강에 이롭다.

DTO를 만들어보자.

DTO를 관리하기 위한 프로젝트 구조는 다 다를 수 있다. 우리는 스프링 부트를 가동하는 클래스가 존재하는 패키지 단에서 dto 라는 패키지를 만들고 그 내부에 DTO 클래스들을 만들기로 하자.

앞서 말했듯, DTO는 단순 데이터를 교환하는 데에만 쓰일 객체이기 때문에 별도의 로직은 추가하지 않고 생성해야 한다. 비즈니스 요구 사항에서 새로운 유저를 생성할 때 name, age를 받도록 정해졌다면 아래와 같이 DTO를 작성해볼 수 있겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UserDto {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "UserDto{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

private 접근제어자를 이용해 UserDto의 프로퍼티를 설정하고, 그 두 개의 프로퍼티 각각의 gettersetter 메서드를 선언해주었다. 추가로 이 DTO를 출력해보기 위한 toString() 메서드도 오버라이드 해주었다.

그럼 진짜 적용해보자!

현재 createUser() 컨트롤러에는 @RequestBody String name로 요청 바디를 받고 있다. 여기서 잠시 알아야 할 부분이 있는데, 요청 바디는 어떠한 형태가 지정되어 있고 그 형태를 따라서 모습을 갖추고 있다. 거의 대부분 JSON 형식을 사용하고 있어서 JSON을 사용하지 않을 이유가 없다. 문자 기반 데이터 포맷이라 파싱하기에도 좋다.

다시 본론으로 돌아오자. 이제 createUser()는 요구 사항이 변경됨에 따라 name 뿐만 아니라 age 값도 받아야 한다. 우리는 해당 컨트롤러로 어떤 인자가 요청 바디로 들어올지 결정된 상태라 UserDTO를 생성했다. 이제는 UserDTO 객체를 매개변수로 삼아 작성해보자.

1
2
3
4
5
6
    ...
    @PostMapping
    public String createUser(@RequestBody UserDto userDto) {
        return userDto.toString();
    }
    ...

간단히 이렇게만 적용하면 Map 객체를 사용하지 않더라도 요청 메시지의 키와 매핑하여 값을 가져오고 userDto에 담긴다. UserDto안에 toString() 메서드를 선언해뒀으니, 해당 메서드를 통해 text/plain 형태로 받아볼 수 있다.

1
UserDto{name='kwonsehoon', age=10}

반대로, UserDto 객체를 그대로 리턴한다면 아래와 같이 application/json 형태의 응답이 내려오게 된다.

1
2
3
4
{
  "name": "kwonsehoon",
  "age": 10
}

ResponseEntity를 활용해 발전시키기

스프링 프레임워크에는 HttpEntity 클래스를 상속받은 ResponseEntity 라는 클래스가 존재한다. Header, Body로 구성된 HTTP의 요청, 응답을 구성하는 역할을 수행하는데 이 클래스를 활용한다면 앞에서 실습했던 컨트롤러를 조금 더 발전시킬 수 있다.

1
2
3
4
5
6
    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(userDto);
    }

.status() 체이닝을 통해 응답에 대한 구체적인 Status Code도 설정할 수 있다. HttpStatus에는 OK (200)뿐만 아니라 다양한 응답 코드가 존재한다. 응답은 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 05 Jun 2023 01:12:15 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "name": "kwonsehoon",
  "age": 10
}

마무리

글에서는 PostMapping을 이용한 POST 형식의 컨트롤러만 작성했는데, 스프링 프레임워크에서는 GET, DELETE, PUT, PATCH 등 여러 HTTP 메서드들을 손쉽게 이용할 수 있도록 도와주고 있다. 여기에 DTO 객체를 만들어 요청을 보다 편안하게 컨트롤할 수 있도록 했고, 스프링에서 제공하는 ResponseEntity를 적용하여 컨트롤러를 더 발전시켜봤다. 다음 글에서는 이 컨트롤러를 더욱 발전시켜 명세로서의 역할을 해줄 Swagger를 적용해보고 나아가 유지보수를 효과적으로 할 수 있도록 Logger도 세팅해볼 생각이다.

This post is licensed under CC BY 4.0 by the author.
Contents

Spring Boot Guide - 3

Spring MVC