ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Springboot] 티켓팅 사이트 만들기 1
    백엔드/spring 2023. 1. 7. 16:12

     

     

     

    간단한 토이 프로젝트로 티켓팅 사이트를 주제로 잡았고 딱 필요한 페이지만 만들기로 함.

     

    우선 API로 구현할 것이기 때문에 Restful API에 대해서 공부해볼려고 한다.

     

     

     

     

    RESTful??   REST??


    RESTful API에서 RESTRepresentational State Transfer의 약자로 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미한다.

     

    REST 란

    1. HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시하고,
    2. HTTP Method(POST, GET, PUT, DELETE, PATCH 등)를 통해
    3. 해당 자원(URI)에 대한 CRUD Operation을 적용하는 것을 의미합니다.

    즉, 어떤 자원에 대한 CRUD 연산을 수행하기 위해 URIGET, POST 방식을 사용하여 요청을 보내고, 요청을 위한 자원은 JSON, XML과 같은 형태로 표현된다.

     

    REST에서 중요한 기본규칙

    • URI는 정보의 자원을 표현해야 한다.
    • 자원에 대한 행위는 HTTP Method(POST, GET, PUT, DELETE, PATCH 등)으로 표현한다.
      1. GET - Read : 정보 요청, URI가 가진 정보를 검색하기 위해 서버에 요청한다.
      2. POST - Create : 정보 입력, 클라이언트에서 서버로 전달하려는 정보를 보낸다.
      3. PUT - Update : 정보 업데이트, 주로 내용을 갱신하기 위해 사용한다. (데이터 전체를 바꿀 때)
      4. PATCH - Update : 정보 업데이트, 주로 내용을 갱신하기 위해 사용한다. (데이터 일부만 바꿀 때)
      5. DELETE - Delete : 정보 삭제, (안전성 문제로 대부분 서버에서 비활성화한다.)

     

    REST API 설계규칙

    1. 슬래시 구분자 ( / )는 계층 관계를 나타내는데 사용한다.
    2. URI 마지막 문자로 슬래시 ( / )를 포함하지 않는다.
    3. 하이픈 ( - )은 URI 가독성을 높이는데 사용한다.
    4. 밑줄 ( _ )은 URI에 사용하지 않는다.
    5.  URI 경로에는 소문자가 적합하다.
    6. 파일확장자는 URI에 포함하지 않는다.
      • REST API 에서는 메시지 바디 내용의 포맷을 나타내기 위한 파일 확장자를 URI 안에 포함시키지 않는다.
      • 대신 Accept Header 를 사용한다.
      • ex) GET: http://restapi.exam.com/orders/2/Accept: image/jpg
    7.  리소스 간에 연관 관계가 있는 경우
      • /리소스명/리소스ID/관계가 있는 다른 리소스 명
      • ex) GET: /users/2/orders (일반적으로 소유의 관계를 표현할 때 사용)
    8. HTTP 응답 상태 코드 사용
      • 클라이언트는 해당 요청에 대한 실패, 처리완료 또는 잘못된 요청 등에 대한 피드백을 받아야 한다.
      • 응답 상태 코드 참고: https://dev-coco.tistory.com/98

     

     

    결론, RESTful은 REST 설계 규칙을 잘 지켜서 설계된 API를  RESTful한 API라고 한다.

     

     

     

     

    참고:

    https://velog.io/@somday/RESTful-API-%EC%9D%B4%EB%9E%80 

    https://dev-coco.tistory.com/97

     


     

     

    API를 공부했으니 프로젝트에 적용을 해보자.

     

    API로 구현하기 위해 Spring에서 제공하는 ResponseEntity 클래스를 잠깐 보고 가자.

    ResponseEntity 클래스가 HttpEntity 클래스를 상속 받는데, HttpEntity 클래스는 HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader HttpBody를 포함하고 있다.

    public class HttpEntity<T>{
        private final HttpHeaders headers;
        private final T body;
    }
    public class ResponseEntity<T> extends HttpEntity<T> {
        private final Object status;
    }

    HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity 클래스이다. ResponseEntity는 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다. 따라서 HttpStatus, HttpHeaders, HttpBody를 포함한다. 

     

    public ResponseEntity(HttpStatus status) {
        this(null, null, status);
    }
    
    public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
        this(body, headers, (Object) status);
    }

    ResponseEntity 클래스에 생성자가 몇 개 더 있지만 모든 생성자가 this를 통해 매개변수가 3개인 생성자를 호출한다.

     

     

    ResponseEntity 클래스를 사용해서 클라이언트에게 응답을 보낼 수 있도록 구현해보자.

     

    Message 클래스를 만들어 body에 들어갈 상태코드, 메시지, 데이터를 담을 필드를 추가한다.

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter @Setter
    public class Message {
        private StatusEnum status;
        private String message;
        private Object data;
    
        public Message() {
            this.status = StatusEnum.BAD_REQUEST;
            this.data = null;
            this.message = null;
        }
    }

    StatusEnum 클래스에는 상태코드로 보낼 몇가지 응답코드를 작성했다.

    import lombok.AllArgsConstructor;
    
    @AllArgsConstructor
    public enum StatusEnum {
        OK(200, "SUCCESS"),
        BAD_REQUEST(400, "BAD_REQUEST"),
        NOT_FOUND(404, "NOT_FOUND"),
        INTERNAL_SERER_ERROR(500, "INTERNAL_SERER_ERROR");
    
        int statusCode;
        String code;
    }

     

    클라이언트에서 회원정보를 담은 요청을 보내오면 Controller에서 받아서 처리결과에 따른 응답코드를 아래와 같이 보낸다.

    package com.example.toy.controller;
    
    import com.example.toy.entity.Member;
    import com.example.toy.entity.response.Message;
    import com.example.toy.entity.response.StatusEnum;
    import com.example.toy.service.MemberService;
    import lombok.RequiredArgsConstructor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.PostConstruct;
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/api/v1")
    public class MemberController {
        private final Logger log = LoggerFactory.getLogger(this.getClass().getSimpleName());
    
        private final MemberService memberService;
    
        HttpHeaders responseHeaders;
    
        @PostConstruct
        public void init() {
            responseHeaders = new HttpHeaders();
            responseHeaders.add("Content-Type", "application/json; charset=UTF-8");
        }
    
        @PostMapping("/signup")
        @ResponseBody
        public ResponseEntity<Message> signup(@RequestBody HashMap<String, Object> param) {
            Member member = new Member();
            member.setId(param.get("id").toString());
            member.setPw(param.get("password").toString());
            member.setEmail(param.get("email").toString());
            member.setPhone(param.get("phone").toString());
    
            boolean check = memberService.save(member);
    
            Map<String,Boolean> map = new HashMap<>();
    
            if(check) {
                log.info("회원가입 성공");
    
                map.put("result", true);
    
                Message message = new Message();
                message.setMessage("Sign up Success !");
                message.setStatus(StatusEnum.OK);
                message.setData(map);
    
                return new ResponseEntity<>(message, responseHeaders, HttpStatus.OK);
            }
            else {
                log.info("회원가입 실패");
    
                map.put("result", false);
    
                Message message = new Message();
                message.setMessage("Sign up Fail !");
                message.setStatus(StatusEnum.OK);
                message.setData(map);
    
                return new ResponseEntity<>(message, responseHeaders, HttpStatus.OK);
            }
    
    
        }
    }

    위 코드의  @PostConstruct는 의존성 주입이 이루어진 후 초기화를 수행하는 메서드에 사용한다.

     

    클라이언트로부터 온 요청에 대한 응답이 아래와 같이 전달된다.

    {
        "status": "OK",
        "message": "Sign Up Success" / "Sign Up Fail",
        "data": {
            "result": true / false
        }
    }

     

     

    참고:

    https://devlog-wjdrbs96.tistory.com/182


     

     

     

Designed by Tistory.