[Spring] Rest API

2024. 7. 6. 23:35BE/Spring

1. Rest란?

 

  • Representational State Transfer
  • 하나의 URI는 하나의 고유한 리소스를 대표하도록 설계된다는 개념.

 

URI + HTTP Method (GET/POST/PUT/DELETE)

 

 

HTTP URI를 통해 제어할 자원을 명시하고, 어떤 제어를 명령할지는 HTTP Method를 통해 설정하는 방식.

 

Method 설명
POST Create
GET Read
PUT, PATCH Update
DELETE Delete

 

 

서비스 유형 설명
기존의 서비스 요청 처리 후, 가공된 데이터를 이용해 특정 플랫폼에 적합한 형태의 View로 반환
Rest 서비스 데이터 처리만 하거나, 처리 후 반환할 데이터가 있다면 JSON이나 XML 형식으로 반환

 


2. Rest의 특징

 

Rest의 특징 설명
Stateless!!! 항상 결과가 무조건 동일해야 한다.
Uniform Interface 데이터가 표준 형식으로 전송될 수 있도록 구성 요소 간 통합 인터페이스를 사용한다.
Cacheable HTTP를 비롯한 네트워크 프로토콜에서 제공하는 캐싱 기능을 적용할 수 있어야 한다.
Self-descriptiveness API를 통해 전송되는 내용은 별도 문서 없이 쉽게 이해할 수 있도록 자체 표현 구조를 지녀야 한다.
Client-Server 클라이언트와 서버로 분리되어야하며 서로 의존성이 없어야 한다.
Layered System 요청된 정보를 검색하는데 있어 계층 구조로 분리되어야 한다.

 


3. Rest 관련 Annotation

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...

    @GetMapping(value = "/user")
    public ResponseEntity<?> userList() {
        logger.debug("userList call");
        try {
            List<MemberDto> list = memberService.listMember(null);
            if(list != null && !list.isEmpty()) {
                return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
            } else {
                return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return exceptionHandling(e);
        }
    }

 

 

@RequestBody를 사용하려면 Http Request Header의 Content-Type이 json이어야 한다.


4. ResponseEntity

 

 

ResponseEntity (Spring Framework 6.1.10 API)

Create a ResponseEntity with a body, headers, and a raw status code.

docs.spring.io

 

ResponseEntityHttpEntity클래스를 상속받아 구현한 클래스다.

 

HttpEntityHttpHeadersHttpBody를 가지고 있는데 여기에 HttpStatusCode status code를 추가하기 위해서 ResponseEntity를 사용한다.

 

@GetMapping(value = "/user")
public ResponseEntity<?> userList() {
    logger.debug("userList call");
    try {
        List<MemberDto> list = memberService.listMember(null);
        if(list != null && !list.isEmpty()) {
            return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
        } else {
            return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
        }
    } catch (Exception e) {
        return exceptionHandling(e);
    }
}
  • ? : 와일드카드. 무엇을 보낼지 미정.

 

public class CustomResult<T> {
    private int statusCode;
    private String message;
    private T body;
    ...
}

ResponseEntity를 사용해도 되지만 응답을 위한 별도의 class를 정의하기도 한다.

 

이때 Http Status는 무조건 200번으로 보내고 커스텀 응답코드를 사용해서 응답 결과를 표시하는 경우가 많다.

 


5. Jackson library

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...

    @GetMapping(value = "/user")
    public ResponseEntity<?> userList() {
        logger.debug("userList call");
        try {
            List<MemberDto> list = memberService.listMember(null);
            if(list != null && !list.isEmpty()) {
                return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
            } else {
                return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return exceptionHandling(e);
        }
    }

Jackson-data-binding이 알아서 JSON으로 변환해서 전송한다.

 

자바 객체를 반환해도 JSON으로 변환해서 반환한다.

 

(Jackson-data-binding이 없으면 toString() 결과를 전송한다.)

 

<!-- pom.xml -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson-databind-version}</version>
</dependency>

사용하기 위해서는 pom.xml에 추가해야 한다.

 

XML로 전송하기 하는 jackson-dataformat-xml도 있다.

 


6. 예시

 

가. GET

1) ajax

fetch(`${root}/admin/user`)
    .then((response_ => response.json())
    .then((data) => makeList(data));

2) RestController

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...

    @GetMapping(value = "/user")
    public ResponseEntity<?> userList() {
        try {
            List<MemberDto> list = memberService.listMember(null);
            if(list != null && !list.isEmpty()) {
                return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
            } else {
                return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
            }
        } catch (Exception e) {
            return exceptionHandling(e);
        }
    }
  • @GetMapping

 


나. POST

1) ajax

let registerinfo = {
    userName: document.querySelector("#username").value,
    userId: document.querySelector("#userid").value,
    userPwd: document.querySelector("#userpwd").value,
    emailId: document.querySelector("#emailid").value,
    emailDomain: document.querySelector("#emaildomain").value,
};
let config = {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        },
        body: JSON.stringify(registerinfo),
};
fetch(`${root}/admin/user`, config)
    .then((response) => response.json())
    .then((data) => makeList(data));

2) RestController

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...
    @PostMapping(value = "/user")
    public ResponseEntity<?> userRegister(@RequestBody MemberDto memberDto) {
        try {
            memberService.joinMember(memberDto)
            List<MemberDto> list = memberService.listMember(null);
            return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
        } catch (Exception e) {
            return exceptionHandling(e);
        }
    }
  • @PostMapping
  • @RequestBody : registerinfo가 JSON로 주어졌기 때문에 @RequestBody가 필요하다.

 


다. PUT

1) ajax

let modifyinfo = {
    ...
};
let config = {
    method: "PUT",
    headers: {
        "Content-Type": "application/json",
        },
        body: JSON.stringify(modifyinfo),
};
fetch(`${root}/admin/user`, config)
    .then((response) => response.json())
    .then((data) => makeList(data));

2) RestController

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...
    @PutMapping(value = "/user")
    public ResponseEntity<?> userModify(@RequestBody MemberDto memberDto) {
        logger.debug("userModify memberDto : {}", memberDto);
        try {
            memberService.updateMember(memberDto);
            List<MemberDto> list = memberService.listMember(null);
            return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
        } catch (Exception e) {
            return exceptionHandling(e);
        }
  • @PutMapping

 


라. DELETE

1) ajax

let id = ... ;
let config = {
    method: "DELETE",
    headers: {
        "Content-Type": "application/json",
        },
};
fetch(`${root}/admin/user/${id}`, config)
    .then((response) => response.json())
    .then((data) => makeList(data));

2) RestController

@RestController
@RequestMapping("/admin")
@CrossOrigin("*")
public class AdminUserController {
    ...
    @DeleteMapping(value = "/user/{userid}")
    public ResponseEntity<?> userDelete(@PathVariable("userid") String userId) {
        try {
            memberService.deleteMember(userId);
            List<MemberDto> list = memberService.listMember(null);
            return new ResponseEntity<List<MemberDto>>(list, HttpStatus.OK);
        } catch (Exception e) {
            return exceptionHandling(e);
        }
    }

 


6. 암묵적인 규칙

 

명확하게 정해진 표준은 없지만 암묵적인 규칙은 있다.

  • URI는 명사를 사용한다.
  • 하이픈(-)은 사용하지만 언더바(_)는 사용하지 않는다.
  • 특별한 경우를 제외하고 대문자를 사용하지 않는다.
  • URI 마지막에 슬래시(/)를 사용하지 않는다.
  • 확장자를 포함된 파일 이름을 직접 포함하지 않는다.
  • 반드시 실행 결과를 반환한다. 예외가 발생해도 관련된 정보를 반환해야 한다.

 


'BE > Spring' 카테고리의 다른 글

[Spring] SOP & CORS  (0) 2024.07.06
[Spring] SpringBoot  (0) 2024.07.06
[Spring] 비동기 통신  (0) 2024.07.06
[Spring] MyBatis-Spring module  (0) 2024.07.06
[MyBatis] 동적 SQL  (0) 2024.07.06