Home Spring Boot Guide - 5
Post
Cancel

Spring Boot Guide - 5

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


REST API 명세서를 만들어보자

명세서는 뭐고, 명세서를 왜 만들어야 할까? Swagger?

우리가 개발한 API들은 인터페이스이다. 이를 외부에 공개하게 된다면 외부에서는 이 API가 대체 어떤 로직을 수행할 수 있는지에 대해 곧바로 이해할 수 없다. 또한 어떤 값을 요청 값에 포함해야하는지 등의 사용법도 알 수가 없다. 또 요청을 보낸다한들, 어떤 응답이 내려오는지도 예측할 수 없다. 이러한 점들을 정리하여 외부에서 우리가 만든 API를 쉽게 이해할 수 있도록 하는 것이 명세의 역할이다. 그리고 이 명세를 잘 정리해둔 문서를 명세서라고 하는 것이다.

컨트롤러는 계속해서 진화하거나, 요구 사항에 따라 계속해서 변화가 일어난다. 앞 글에서도 보았듯 요청 값이 바뀌기도 하고, 응답 값이 바뀌기도 한다는 것이다. 만약 명세서를 단순 워드나 최근에 자주 사용하는 노션 등으로 정리를 한다면, 우리는 API가 변경될 때마다 번거롭게 직접 수정해야 한다. 또 직접 타이핑 해야 하기 때문에 시간도 오래걸리고 손가락도 아플 것이다. 이를 해결하기 위해 등장한 것이 바로 Swagger이다.

Swagger

Swagger는 오픈소스 프로젝트이다. 모든 명세를 직접 타이핑해서 만드는 것이 아니라, 코드 위에 간단한 어노테이션들을 붙여 명세서를 자동적으로 만들어주는 매우 좋은 도구이다.

Swagger 세팅

Swagger를 세팅하기 위해 의존할 라이브러리가 두 가지가 있는데, 이 두 가지 중에서 하나만 선택하면 된다.

  • Spring-Fox
  • Spring-Doc

교재에서는 Spring-Fox를 사용했지만, 가장 중요한 업데이트가 잘 이루어지지 않는 것 같아서 나는 그냥 Spring-Doc을 사용하기로 결정했다. Spring-Fox를 사용해도 문제 없다.

버전은 MVN Repository 에서 확인해볼 수 있다. 글을 쓰는 시점에서는 1.7.0 버전이 새롭게 출시되어 이를 적용해보고자 한다.

Spring-Doc 업데이트가 꽤 꾸준함

build.gradle에 한 줄을 추가하고, 의존 라이브러리 설치를 완료한다.

1
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

그 뒤엔 Spring에 Swagger를 사용한다고 알려줘야하기 때문에, Configuration을 하나 생성해준다. 최상단 패키지에 config라는 패키지를 만들고 SwaggerConfig 클래스를 아래와 같이 만들어주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI testOpenApi() {
        Info info = new Info()
                .title("Swagger 테스트")
                .description("Swagger 테스트를 위한 명세서입니다.")
                .version("v1.0.0");

        return new OpenAPI().info(info);
    }
}

자세하게 이해할 필요는 없다고 생각한다. OpenAPI에서 Swagger 명세서에 보여지는 제목이나 설명 같은 부분들을 커스텀하고 있다고 이해하면 될 것 같다.

그 뒤에는 application.yml에 아래의 코드를 삽입해준다. 파일 확장자를 바꾸지 않았다면 기본적으로 application.properties 일텐데 코드 포맷만 다를뿐이지 비슷하게 세팅해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
springdoc:
  packages-to-scan: com.sehoon.example
  default-consumes-media-type: application/json;charset=UTF-8
  default-produces-media-type: application/json;charset=UTF-8
  swagger-ui:
    path: doc.html
    tags-sorter: alpha
    operations-sorter: alpha
    api-docs:
      path: /docs/json
      groups:
        enabled: true
    cache:
      disabled: true

spring-doc은 API 명세를 정렬해주는 기능을 포함하고 있다. tags-sorter, operations-sorteralpha로 세팅하면 사전순으로 정렬, method로 세팅하면 HTTP 메소드를 기준으로 정렬한다. 여기까지 세팅을 마쳤다면, 서버를 재가동하고 swagger-ui.path에 지정해둔 doc.html로 접속하면 Swagger 페이지가 나온다. ( localhost:8080/doc.html )

Swagger

Swagger 사용해보기

현재는 추가적인 세팅을 해주지 않았기 때문에, API에 대한 명세가 명확하지 않다. 컨트롤러를 좀 더 수정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/api/user")
@Tag(name = "User", description = "User 관련 API")
public class UserController {

    @GetMapping()
    @Operation(summary = "유저 조회", description = "User를 조회합니다.")
    public String printUser() {
        return "I'm User";
    }

    @PostMapping
    @Operation(summary = "유저 생성", description = "name과 age를 받아 User를 생성합니다.")
    public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(userDto);
    }
}

nameage에 대한 제한 사항 같은 부분이 있다면, UserDto 안에도 명세를 추가해줄 수 있다.

UserDto를 수정해보자. 현재는 description만 추가했지만, 더 많은 프로퍼티가 존재하니 직접 찾아서 추가해볼 수 있겠다.

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDto {
    @Schema(description = "유저 이름")
    private String name;

    @Schema(description = "유저 나이")
    private Integer age;

    public String getName() {
        return name;
    }
    ...
}

다시 스웨거에 접속해보면 아래와 같이 적용된 모습을 확인할 수 있다.

Swagger 변경 후 1

Swagger 변경 후 2

로깅을 적용하자

로깅이 뭐고, 왜 해야하지?

로깅(Logging)은 우리가 작성한 어플리케이션이 동작하는 중에 생기는 여러 시스템 상태, 동작에 대한 자세한 정보들을 시간순으로 쭉 기록하는 것을 의미한다. 이것이 과연 우리가 어떤 서비스를 하는데에 클라이언트에게 전달해줘야 할 정보일까? 아니다. 당연히 클라이언트는 우리 시스템 정보를 알 필요도 없고 알고 싶지도 않다. 로깅은 개발자가 디버그를 할 때나 서비스 중 생기는 문제들을 트래킹하기 위해 필요한 정보를 제공하는 것이다. 한마디로, 유지보수에 로그는 꼭 필요하다는 것이다. 간혹 코드를 작성하다가 sout (System.out.println())을 하나 하나 적어가며 에러를 찾아가 본 경험이 있을 것이라고 생각한다. 로깅 프레임워크를 적용하면 그런 불편함도 줄일 수 있다.

로깅 프레임워크

자바 진영에서 로깅을 위해 가장 자주 사용되는 것은 Logback이나 log4j2이다. 기존에 log4j를 사용했는데, 보안상 이슈도 있고 성능상의 문제도 있었다. 이후에 slf4j 기반으로 Logback이 등장했다. log4j2도 이전에 log4j를 더욱 개선하여 나온 프레임워크이다. Logbackspring-boot-stater-web 라이브러리가 자동으로 끌어왔기 때문에 별다른 의존성 추가가 필요없다. 교재에서도 Logback을 사용하고 있으나, 나는 가장 최근에 나오기도 했고 성능상 가장 괜찮은 log4j2를 사용하기로 결정했다. 성능 비교는 여기에서 차트를 통해 확인해볼 수 있다.

등장 순으로 정렬하자면 log4j > Logback > log4j2 이다.

log4j2 사용하기

의존성을 추가해줘야 하는데, 그 전에 Spring에 기본으로 탑재되어 있는 Logback과의 충돌을 막기 위해 아래의 설정을 추가해줘야 한다. application.yml을 수정해주자.

1
2
3
4
5
6
7
8
9
10
...
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    all { # 충돌을 방지하기 위해 spring-boot-starter-logging exclude 처리
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
    }
}
...

그리고 의존성을 추가한다.

1
implementation 'org.springframework.boot:spring-boot-starter-log4j2'

이후에 resourceslog4j2.xml 파일을 생성하고 아래의 xml 코드를 그대로 넣어준다. log4j2에 대한 설정을 해주는 것으로 이해하면 된다. 각각의 태그가 어떤 역할을 하는지 정확히 이해하는 것보다, 이런 단순한 로거 세팅은 필요한 부분만 찾아서 고치고 나머지는 템플릿을 그대로 사용하는 것이 정신 건강에 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <property name="LOGS_PATH">logs</property>
    </Properties>

    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %-5level %logger{35} - %msg%n" />
        </Console>

        <RollingFile name ="RollingFile">
            <FileName>${LOGS_PATH}/log4j2.log</FileName>
            <FilePattern>${LOGS_PATH}/log4j2.%d{yyyy-MM-dd}.%i.log.gz</FilePattern>
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss} %5p [%c] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="100MB"/>
                <TimeBasedTriggeringPolicy interval = "1" modulate = "true"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <logger name="com.test.api.controller" level="INFO" additivity="false">
            <AppenderRef ref="console" />
            <AppenderRef ref="RollingFile" />
        </logger>

        <logger name="com.test.api.service" level="INFO" additivity="false">
            <AppenderRef ref="console" />
            <AppenderRef ref="RollingFile" />
        </logger>

        <!-- FATAL, ERROR, WARN, INFO, DEBUG, TRACE -->
        <Root level="INFO">
            <AppenderRef ref="console" />
            <AppenderRef ref="RollingFile" />
        </Root>
    </Loggers>
</Configuration>

그럼 이제 우리의 컨트롤러에서 로그를 남기도록 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Log4j2
@RestController
@RequestMapping("/api/user")
@Tag(name = "User", description = "User 관련 API")
public class UserController {

    @GetMapping()
    @Operation(summary = "유저 조회", description = "User를 조회합니다.")
    public String printUser() {
        return "I'm User";
    }

    @PostMapping
    @Operation(summary = "유저 생성", description = "name과 age를 받아 User를 생성합니다.")
    public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
        log.info("유저가 생성됩니다.");
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(userDto);
    }
}

컨트롤러 상단에 @Log4j2 어노테이션을 붙여주고, createUser() 안에 log.info()를 통해 info 레벨의 로그를 남겨볼 수 있다.

로그 레벨은 크게 6개로 나뉜다.

FATAL, ERROR, WARN, INFO, DEBUG, TRACE

해당 API를 호출해보자.

로그

콘솔을 보면 단순 String 형태의 로그가 아니라, 레벨 및 자세한 시간 등 추가 정보가 포함된 로그가 출력되고 있는 것을 확인할 수 있다. 또한, 프로젝트 디렉토리를 확인해보면 logs 라는 폴더가 생겼고 그 안에 log4j2.log 파일이 생김을 확인할 수 있다. 이는 우리가 출력하는 것을 넘어서 로컬에 어떤 로그들이 남겨졌었는지 파일 형태로 저장해둘 수 있다는 것이다.

정리

Swagger와 log4j2에 대해서 알아봤다. Swagger에는 다양한 기능을 제공하고 있기 때문에 클라이언트와의 원활한 소통을 위해 이것 저것 실험해보며 적용해보는 연습이 필요할 것 같다. 추가로 어떤 정보를 어떠한 레벨로 정리하여 로그로 기록해야 좋을지도 고민하여 어플리케이션의 유지보수도 신경쓰는 것이 좋을 것 같다.

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

Spring MVC

Spring Boot Guide - 6