북터디/스프링 부트 핵심 가이드

스프링 부트 핵심 가이드 5장 [API를 작성하는 다양한 방법]

vita12321 2023. 7. 31. 01:03
728x90
반응형

스프링 부트 핵심가이드 ( 부제 : 스프링 부트를 활용한 애플리케이션 개발 실무 ) - 장성우 지음

5장 API를 작성하는 다양한 방법

 

5.1 프로젝트 설정

  • 4장과 동일

5.2 GET API 만들기

  • GET API는 웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API이다.
  • 컨트롤러 클래스에 @RestController, @RequestMapping 설정
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
}

5.2.1 @RequestMapping으로 구현하기

  • @RequestMapping 어노테이션을 별다른 설정없이 선언하면 HTTP의 모든 요청을 받는다. 
  • GET 형식의 요청을 받기 위한 method 요소 값을 RequestMethod.GET 로 설정해 준다.
package com.springboot.api.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
    
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        return "Hello World!";
    }
    
}
  • 스프링 4.3 이후 버전은 @RequestMapping을 사용하지 않는다.
// 각 HTTP 메서드에 맞는 어노테이션 사용
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping

5.2.2 매개변수가 없는 GET 메서드 구현

  • 별도의 매개변수 없이 GET API를 구현하는 경우 아래와 같이 적용할 수 있다.
    // http://localhost:8080//api/v1/get-api/name
    @GetMapping("/name")
    public String getName() {
        return "Flature";
    }
}

5.2.3 @PathVariable을 활용한 GET 메서드 구현

  • 웹 통신의 기본 목적은 데이터를 주고 받는 것이기에 대부분 매개변수를 받는 메서드를 작성한다.
  • 매개변수를 받을 때 자주 쓰이는 방법 중 하나인 URL에 값을 담아 전달되는 요청을 처리하는 방법.
// http://localhost:8080/api/v1/get-api/variable1/{String 값}
    @GetMapping("/variable1/{variable}")
    public String getVariable(@PathVariable String variable) {
        return "variable";
    }
    
    
    
@PathVariable에 변수명을 매핑하는 방법
// http://localhost:8080/api/v1/get-api/variable2/{String 값}
    @GetMapping("/variable2/{variable}")
    public String getVariable2(@PathVariable ("variable") String var) {
        return var;
    }

5.2.4 @RequestParam을 활용한 GET 메서드 구현

  • 쿼리 형식으로 값을 전달할 때 사용한다.
  • URL 에서 ?를 기준으로 우측에 {키}={값} 형태로 구성된 요청을 전송하는 방법.
// http://localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3
@GetMapping(value = "/request1")
public String getRequestParam1(
 		@RequestParam String name,
		@RequestParam String email,
		@RequestParam String organization) {
 	return name + " " + email + " " + organization;
}
  • 1번줄의 주석을 보면 "?" 오른쪽에 쿼리스트링(query string)이 명시돼 있다.
  • 쿼리스트링에는 키(변수의 이름)가 모두 적혀 있기 때문에 이 값을 기준으로 메서드의 매개변수의 이름을 정하면, 자동으로 값을 가져올 수 있다.
  • 만약 쿼리스트링에 어떤 값이 들어올지 모른다면 아래과 같이 Map 객체를 활용.
// http://localhost:8080/api/v1/get-api/request2?key1=value1&key2=value2
@GetMapping(value = "/request2")
public String getRequestParam2(@RequestParam Map<String, String> param) {
 	StringBuilder sb = new StringBuilder();

	param.entrySet().forEach(map -> { 
		sb.append(map.getKey() + ":" + map.getValue() + "\n");
	});
	
	return sb.toString();
}
  • 위 형태로 코드를 작성하면 값에 상관없이 요청을 받을 수 있다.
  • 매개변수의 항목이 일정하지 않을 경우 Map 객체로 받는 것이 효율적이다.

5.2.5 DTO 객체를 활용한 GET 메서드 구현

 

5.2.5.1 DTO

  • Data Transfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용된다. 
  • 간략하게 설명 하자면 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체이다.
  • DTO에는 private 필드, Getter, Setter만 존재한다.
  • DTO와 VO의 역할은 서로 엄밀하게 구분하지 않고 사용할 때가 많다.

public class MemberDto {
    private String name;
    private String email;
    private String mobile;

    public String getName() {
        return name;
    }

    private void setName(String name) {
        this.name = name;
    }

    .
    .
    .

    @Override
    public String toString() {
        return "MemberDto { " +
                "name'" + name + '\'' +
                ", email='" + email + '\'' +
                ", mobile'" + mobile + '\'' +
                "}";
    }
}

5.3 POST API 만들기

  • POST API는 웹 애플리케이션을 통해 데이터베이스에 리소스를 저장할 때 사용되는 API 이다.
  • POST API에서는 저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달한다.
  • URI 가 GET API에 비해 간단하다.

5.3.1 @RequestMapping으로 구현하기

@RequestMapping(value = "/domain", method = RequestMethod.POST)
    public String postExample() {
        return "Hello Post API";
    }
}

5.3.2 @RequestBody를 활용한 POST 메서드 구현

 

일반적으로 POST 형식의 요청은 클라이언트가 서버에 리소스를 저장하는 데 사용한다. 그러므로 클라이언트 의 요청 트래픽에 값이 포함되어 있다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송한다.

  • Body 영역에 작성되는 값은 일정한 형태를 취기에 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송된다.
// http://localhost:8080/api/v1/post-api/member
@PostMapping("member")
public String postMember(
            @RequestBody Map<String, String> postData
) {
    StringBuilder sb = new StringBuilder();
    postData.entrySet().forEach(
            map -> {
                sb.append(map.getKey() + " : " + map.getValue() + "\n");
            }
    );
    return sb.toString();
}
  • @PostMapping을 사용시 method 요소를 정의하지 않아도 된다.
  • @RequestBody는 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할.

5.4 PUT API 만들기

 

PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용한다. POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정(서비스 로직)에서 차이가 있지만 컨트롤러 클래스를 구현하는 방법은 POST API와 거의 동일하다. 리소스를 서버에 전달하기 위해 HTTP Body를 활용해야 하기 때문이다.


5.4.1 @RequestBody를 활용한 PUT 메서드 구현

// http://localhost:8080/api/v1/put-api/member
@PutMapping(value = "/member")
public String postMember(@RequestBody Map<String, Object> putData) {
	StringBuilder sb = new StringBuilder();
	
	putData.entrySet().forEach (map -> {
		sb.append(map.getKey() + " : " + map.getValue() + "\n");
	});

	return sb.toString();
}
  • 만약 서버에 들어오는 요청에 담겨 있는 값이 정해져 있는 경우 DTO 객체를  활용해서 구현해야한다.
  • String으로 json을 반환하는 방법도 있지만, DTO 자체를 반환하는 경우도 있다.
DTO 자체를 반환하는 경우
// http://localhost:8080/api/v1/put-api/member1
@PutMapping(value = "/member1")
public String postMemberDto1 (@RequestBody MemberDto memberDto) {
	return memberDto.toString();
}

// http://localhost:8080/api/v1/put-api/member2
@PutMapping(value="/member2")
public MemberDto postMemberDto2 (@RequestBody MemberDto memberDto) {
	return memberDto;
}

String 리턴 시

 

memberDto 리턴 시


5.4.2 ResponseEntity를 활용한 PUT 메서드 구현

 

HttpEntity는 다음과 같이 헤더(Header)와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행한다.

  • RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스이다.
  • ResponseEntity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달
// http://localhost:8080/api/v1/put-api/member3
@PutMapping(value = "/member3")
public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
	return ResponseEntity
		.status(HttpStatus.ACCEPTED)
		.body (memberDto);
}
  • ResponseEntity 는 HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현.
  • 이 클래스를 활용하면 응답 코드 변경은 물론 Header와 Body를 더욱 쉽게 구성할 수 있다.
  • 예제의 3번 줄에서는 메서드의 리턴 타입을 Response Entity로 설정하고 4~6번줄에서 리턴 값을 만든다.

5.5 DELETE API 만들기

 

  • DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용한다.
  • 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행한다.

5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현

 

  • @PathVariable을 활용한 DELETE 메서드 구현
// http://localhost:8080/api/v1/delete-api/{String 값}
@DeleteMapping(value = "/{variable}")
public String DeleteVariable(@PathVariable String variable) {
 	return variable;
}
  • @RequestParam을 활용한 DELETE 메서드 구현
// http://localhost:8080/api/v1/delete-api/request1?email=value
@DeleteMapping (value = "/request1")
public String getRequestParam1 (@RequestParam String email) {
	return "e-mail : + email;
}

5.6 [ 한걸음 더 ] REST API 명세를 문서화하는 방법 – Swagger

 

API를 개발하면 명세를 관리해야 한다.
명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직 을 수행하기 위해 어떤 값을 요청하며,
이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료이다.

 

API는 개발 과정에서 계속 변경되므로 작성한 명세 문서도 주기적인 업데이트가 필요하다. 
또한 명세 작업은 번거롭고 시간 또한 오래 걸리기에 이 같은 문제를 해결하기 위해 등장한 것이 바로 Swagger' 라는 오픈소스 프로젝트이다.

swagger 적용 예시

  • @ApiOperation : 대상 API의 설명을 작성하기 위한 어노테이션. 
  • @ApiParam : 매개변수에 대한 설명 및 설정을 위한 어노테이션.
@ApiOperation("Gets all diary data for the selected date")
@GetMapping("/read/diary")
List<Diary> readDiary(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 
   					  @ApiParam(value = "dtae : yyyy-mm-dd", example = "2023-07-30") LocalDate date) {
	return diaryService.readDiary(date);
}

실제 사용 예시


5.7 [ 한걸음 더 ] 로깅 라이브러리 – Logback

 

  • 로깅(logging)이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것을 의미한다.
  • 로깅은 디버깅하거나 개발 이후 발생한 문제를 해결할 때 원인을 분석하는 데 꼭 필요한 요소이다.
  • slf4j는 스프링 부트의 spring-boot-starter-web 라이브러리 내부에 내장돼 있어 별도의 의존성을 추가하지 않아도 사용할 수 있다.
  • 크게 5개의 로그 레벨(TRACE, DEBUG, INFO, WARN, ERROR)을 설정할 수 있다.
    • ERROR : 로직 수행 중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미한다.
    • WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨을 의미한다.
    • INFO : 애플리케이션의 상태 변경과 같은 정보 전달을 위해 사용된다.
    • DEBUG : 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미한다.
    • TRACE : DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨을 의미한다.

5.7.1 Logback 설정

 

  • resource 폴더 안에 생성 logback-spring.xml 파일 참조하도록 생성해야 한다. 
// Logback 설정 logback-spring.xml 파일 예시 코드 
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATH" value="./logs"/>
    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <file>${LOG_PATH}/info.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info_%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>
    <!-- TRACE < DEBUG < INFO < WARN < ERROR < OFF -->
    <!-- Root Logger -->
    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </root>
</configuration>

5.7.1.1 Appender 영역

 

  • 어펜더 영역은 로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳이다. 
  • 어펜더 자체는 하나의 인터페이스를 의미하며 하위에 여러 구현체가 존재한다.

Appender 구조

  • Consolekppender: 콘승에 로그를 출력
  • FileAppender: 파일에 로그를 저장
  • RollingFileAppender: 여러 개의 파일을 순회하면서 로그를 저장
  • SNTPAppender: 메일로 로그를 전송DBAppender: 데이터베이스에 로그를 저장

5.7.1.2  패턴(pattern) 정의

 

encoder 요소를 통해 로그의 표현 형식을 패턴(pattern)으로 정의한다.

 

  • %Logger {length} - 로거의 이름 
  • %-5level - 로그 레벨, -5는 출력 고정폭의 값
  •  %msg(%message) - 로그 메세지%d : 로그 기록 시간 
  • %p : 로깅 레벨 
  • %F : 로깅이 발생한 애플리케이션 파일명 
  • %M : 로깅이 발생한 메서드 이름 
  • %I : 로깅이 발생한 호출지의 정보 
  • %thread : 현재 스레드명 
  • %t : 로깅이 발생한 스레드명
  • %c : 로깅이 발생한 카테고리
  • %C : 로깅이 발생한 클래스명
  • %m : 로그 메시지
  • %n : 줄바꿈
  • %r : 애플리케이션 실행 후 로깅이 발생한 시점까지의 시간
  • %L : 로깅이 발생하는 호출 지점의 라인 수
// 로그 형식 예시
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>

5.7.1.3  Root 영역

 

Root Logger는 로깅 설정에서 가장 핵심적인 요소이다. 

Root Logger의 설정은 <root> 태그를 통해 이루어진디.

<root level="INFO">
    <appender-ref ref="console" />
    <appender-ref ref="INFO_LOG"/>
</root>

5.7.2 Logback 적용하기

 

  • Logback 적용 예시
public class DiaryService {

    private static final Logger logger = LoggerFactory.getLogger(WeatherApplication.class);

    @Transactional
    @Scheduled(cron = "0 0 1 * * *")
    public void saveWeatherDate() {
        logger.info("오늘도 날씨 데이터 잘 가져옴");
        dateWeatherRepository.save(getWeatherFromApi());
    }
  • 메서드 요청시 logs 폴더 생성, log 기록이 남는다.

 

728x90
반응형