ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - GET
    웹 어플리케이션 2019. 2. 21. 16:06

    지난번 포스트에서 To-Do 앱을 만들기 위해 일단 스프링 부트 어플리케이션을 만들어 실행 시키는 것 까지 해 보았다. 오늘은 서버 어플리케이션을 만들기 위해서 어떻게 디자인을 해야 하고, 어떤 어노테이션을 사용해 RESTful API을 만들 수 있는지 알아보도록 한다.

    예상 독자

    목표

    • RESTful API
    • 웹 어플리케이션 디자인
    • 모델 (Model)
    • 리퀘스트 (Request)
    • 리스폰스 (Response)
    • 서비스 (Service)
    • 컨트롤러 (Controller)

    RESTful API

     RESTful API(Respresentational State Trasfer)  HTTP 리퀘스트를 이용해 데이터를 GET, POST, PUT, DELETE 할 수 있도록 하는 API를 의미한다. GET, POST, PUT, DELETE를 리퀘스트 메서드라고 부른다. 예를들어서 HTTP 리퀘스트를 보낼 때, 얘는 GET메서드에요, 얘는 POST메서드에요 하고 메서드를 포함시켜서 리퀘스트를 보내면 서버 어플리케이션이 이를 인지하고 해석 할 수 있어야 한다.
    • GET - 데이터를 가져온다.
    • POST - 데이터를 생성하거나 업데이트한다.
    • PUT - 데이터를 업데이트한다.
    • DELETE - 데이터를 삭제한다.
    이 포스트에서는 네가지 오퍼레이션 중에서도 GET API를 구현하는데 중점을 둘 것이다. 구체적인 목표는 우리가 브라우저에 http://localhost:8080/todo/{id}/ 를 치고 들어가면 다음과 같은 JSON 오브젝트를 리턴하는 GET API를 만드는 것이다. (브라우저에 URL을 치고 엔터를 누르는게 GET API를 call하는 방법 중 하나이다.) 나중에 Frontend에서 다시 설명 할 것이니 익숙하지 않다면 RESTful API라는게 있구나 하고 넘어가자.
    {
    "data": {"id":null,"title":"Add an id validation","done":false},
    "errors":[] }

    웹 어플리케이션 디자인

    지난 포스트에서 3 tier 아키텍쳐에 대해 간단히 이야기 했다. 오늘 구현 할 부분은 3 -tier 아키텍쳐 중에서도 중간에 있는 어플리케이션 서버이다. 어플리케이션 가장 먼저 구현 하는 이유는 1) 비즈니스 로직이 있는 가장 핵심적인 부분이고, 2) 다른 티어들에 독립적으로 구현 할 수 있는 부분이기 때문이다. 이제 어플리케이션 서버 내부에 어떤 자바 클래스들이 있어야 하는지 알아보자.

    어플리케이션 서버를 확대 해 보자. 실제로는 이 서버 안팎으로 톰캣+스프링과 관련한 여러가지 일들이 일어나지만 지금 우리가 관심을 가져야 할 부분은 우리의 코드 부분 뿐이다. 어플리케이션내부의 자바 클래스들은 크게 두 가지 부류로 나뉜다.

    • 데이터 클래스- request, response, model이 이 데이터 클래스에 해당한다. 데이터 클래스는 아무 기능도 하지 않는 말 그대로 데이터 그 자체라고 생각하면 된다.

    • 데이터처리 클래스 - 데이터처리는 controller, service, jpa repository등을 포함한다. 데이터 클래스를 가지고 이것 저것 바꾸고, 더하고, 빼고 디비에 넣는 등 다양한 작업을 한다.

    이제 프로젝트의 구현을 보자.

    패키지

     com.fsoftwareengineer.MySpringApp.ApiResponse - Response를 위한 추상 클래스

     com.fsoftwareengineer.MySpringApp.ToDoItem - To Do Item에 관련 된 것을 구현하기 위한 모든 클래스

     com.fsoftwareengineer.MySpringApp - 스프링 어플리케이션 root 패키지


    위의 그림에서 말했던 모델(ToDoItem), 컨트롤러(ToDoItemController), 리퀘스트(ToDoItemRequest), 리스폰스(ToDoItemResponse), 서비(ToDoItemSrevice)등이 보인다. 나머지 클래스와 왜 JPARepository클래스가 없는지에 대해서는 아래에서 설명하도록 하겠다.


    모델 (Model) 


    실제 비즈니스 모델을 가지고 있다. 예를들어서, User, Person, Credential등등. 우리 앱에서는 모델이 무엇인가? 바로 To-Do Item이다. 이 모델들이 바로 데이터베이스에 저장될 데이터이다.
    To Do 리스트의 한 아이템을 표현하기 위해 다음과 같은 클래스를 만들었다.

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import lombok.*;

    @Data
    @Getter @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class ToDoItem {
    private String id;
    private String title;
    private boolean done;
    }

    Lombok을 이용해 필요한 Getter/Setter/Constructor/Builder등등을 추가했다. Lombok이 뭔지 잘 모르겠다면 직접 Getter/Setter/Builder/Constructor를 만들어도 상관없다. 아무튼 이 ToDoItem은 크게 2가지가 중요한데 첫번째는 title, 두번째는 done이다.

    이렇게 생긴 to-do리스트에서 우리는 to-do 리스트의 타이틀과, 완료 여부를 알아야 한다. 따라서 title, done을 만들었다. 그리고 같은 title이 있을 수 있기 때문에 id를 만들어 각각의 아이템이 고유하게 구별 될 수 있도록 해야 한다.

    리퀘스트 (Request) 

    위의 그림에서 http request에 포함되어 날라오는 데이터이다. 모델 자체를 브라우저로 넘기는 것은 비즈니스 로직을 공개하는 것이므로 지양하는것이 좋다. 또는 때로 리퀘스트로 받아온 정보가 모델과 똑같지 않은 경우도 많기 때문에,  리퀘스트 클래스를 따로 만드는 것이 바람직하다. 오늘 하는 튜토리얼에서는 사용하지 않을 것이므로 다음에 필요 할 때 설명하도록 하겠다.

    리스폰스 (Response) 


    http response에 포함되어 브라우저로 전달 될 모델이다. 리퀘스트와 마찬가지로 모델을 그대로 브라우저에 전달하는것은 비즈니스 로직을 공개하는 것이므로 리스폰스 클래스로 감싸는게 좋다. 또한 리스폰스에는 에러메시지등이 들어가야하므로 항상 모델과 같지 않다. 따라서 보통 리스폰스 클래스를 만들고 그 내부에 모델 클래스와 부가적으로 UI가 필요한 정보들을 더 넣어준다.

     이 정보들에 대해 잠깐 얘기해 보자. 예를들어 에러메시지 리스트는 모든 리스폰스들이 공통으로 가지고 있어야 한다. 무슨 말이냐 하면, 지금은 모델이 ToDoItem 하나밖에 없지만 나중에 예를들어 User 모델이 생긴다면 이 모델의 리스폰스인 UserResponse도 에러메시지 리스트를 가지고 있어야 한다. 따라서 각 모델에 대한 리스폰스를 만들 때 마다 에러메시지를 넣어주는 것은 비효율적이다.

    package com.fsoftwareengineer.MySpringApp.ApiResponse;

    import lombok.*;

    import java.util.List;

    @Getter @Setter
    @RequiredArgsConstructor
    public abstract class ApiResponse<T> {
    @NonNull private T data;
    private List<String> errors;
    }
    이렇게 하면 data에는 어떤 모델이든 들어 갈 수 있고, 각 리스폰스는 항상 errors라는 에러 리스트를 가지고 있으므로 리스폰스마다 List<String> errors를 추가 하지 않아도 된다. 이제 리스폰스 클래스를 보자.
    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import com.fsoftwareengineer.MySpringApp.ApiResponse.ApiResponse;
    import lombok.Builder;
    import java.util.List;

    public class ToDoItemResponse extends ApiResponse<ToDoItem> {

    @Builder
    public ToDoItemResponse(final ToDoItem toDoItem, final List<String> errors) {
    super(toDoItem);
    this.setErrors(errors);
    }
    }
    ApiResponse의 T자리에 ToDoItem이 들어갔다고 생각하면 된다. 그리고 이 클래스의 생성자에서 부모클래스인 ApiResponse의  data를 toDoItem으로 초기화 해 준다. 모델을 그대로 노출하는 것이 보안에 큰 문제가 될 경우에는 어쩔 수 없이 멤버 변수들을 복사 붙여넣기 해야된다. 우리는 연습 하는 것이므로 그냥 ToDoItem을 직접 넣었다.

    서비스 (Service)

    서비스란 컨트롤러와 리파지토리 중간에서 비즈니스 로직을 수행하는 클래스를 의미한다. 보통 컨트롤러는 리퀘스트를 받아서 서비스에 넘겨주고, 서비스가 리턴한 리스폰스를 다시 리턴하는 역할을 한다. 리파지토리는 마찬가지로 데이터베이스로 쿼리를 날리는 역할이다.

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;


    @Service
    public class ToDoItemService {

    public ToDoItem get(final String id) {
    // do id validation
    return ToDoItem.builder()
    .title("Add an id validation")
    .build();
    }

    }

    서비스는 스프링에서 @Service 어노테이션으로 지정 해 줄 수 있다. 이 서비스는 원래대로라면 id를 받아 JPA Repository 클래스를 이용해 데이터베이스에서 이 id를 검색해야한다. 하지만 우리는 지금 데이터베이스가 없다. 그래서 일단 임의로 ToDoItem를 만들어 ToDoItemResponse에 넣어 리턴 하려 한다. 

    컨트롤러(Controller)

    스프링 부트에서 컨트롤러가 하는 역할은 다양하다. 일단 URL Resolution이라는 것을 한다. 우리가 만들 GET API가 어떻게 생겼었는지 기억하는가?

    http://localhost:8080/todo/{id}/

    이렇게 생겼었다. 그러면 스프링이 경로가 todo이고 {id}에 숫자가 들어가야하고 이런건 어떻게 아는가? 이런 것들을 바로 Controller에서 정의한다.

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    import java.util.ArrayList;
    import java.util.List;

    @RestController
    @RequestMapping("/todo")
    public class ToDoItemController {

    @Autowired
    private ToDoItemService toDoItemService;

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    public @ResponseBody ToDoItemResponse get(@PathVariable(value="id") String id) {
    List<String> errors = new ArrayList<>();
    ToDoItem toDoItem = null;
    try {
    toDoItem = toDoItemService.get(id);
    } catch (final Exception e) {
    errors.add(e.getMessage());
    }
    return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
    }

    }

    • @RestController - "이 클래스틑 RESTful API를 위한 컨트롤러 클래스이다"라고 스프링에게 알려줌.
    • @RequestMapping - 구체적으로 어떤 경로인지 지정 해 준다.
    • @RequestMapping - 어떤 REST 메서를 사용 할 것인지, 어떤 경로인지, 또는 path variable을 받는 경우 path variable의 이름을 지정 해 줄 수 있다.
    • @PathVariable - 어떤 Path Variable이 어느 변수에 맵핑되어야 하는지 알려준다. "/{id}" -> value="id" 얘가 Integer id에 맵핑된다.
    • @ResponseBody - 이 메서드는 HTTP Response Body 부분을 JSON의 형태로 리턴 할 것임을 알려준다.
    • @Getter, @Setter - Lombok 어노테이션

    우리는 RESTful API를 만들 것이므로 @RestController 어노테이션을 이용해 이 컨트롤러가 RESTful API임 스프링 부트에게 알려주도록 한다. 또한 @RequestMapping 어노테이션을 이용해 이 컨트롤러의 URL을 정해준다. RequestMapping은 위 처럼 클래스 위에 하나, 메서드 위에 하나 정의 할 수 있는데 이 클래스 안의 모든 메서드들은 클래스의 RequestMapping을 공유한다. 따라서 http://localhost:8080/todo/{id}/ 를 보면 '/todo'는 클래스위에 '/{id}'는 메서드레벨에 정의 된 것을 확인 할 수 있다.

    이 컨트롤러는 ToDoItemService를 사용하므로 이를 추가해 줬다. @Getter, @Setter가 알아서 getter, setter를 만들어 준다.

    @RequestMapping에 method = RequestMethod.GET이 보이는가? 바로 이 부분에서 우리는 RESTful API의 네 가지 Method중 어떤 것을 이용 할 지 선택하는 것이다. 우리는 GET을 이용 할 것이므로 GET으로 선택하고 value에는 /{id}를 넣었다. 이 id는 아래의 @PathVariable의 value 값과 같은 값이어야 한다.


    이 때, 우리는 ToDoItem을 ToDoItemResponse로 바꿔주어야 하는데 그 작업을 해 주는게 바로 ToDoItemAdapter이다. 
    ToDoItemAdapter.java
    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import java.util.List;

    public class ToDoItemAdapter {

    public static ToDoItemResponse toToDoItemResponse(final ToDoItem toDoItem, final List<String> errors) {
    return ToDoItemResponse.builder().toDoItem(toDoItem).errors(errors).build();
    }
    }
    toToDoItemResponse라는 메서드가 toDoItem과 errors리스트를 받아 ToDoItemResponse를 만들어준다. 

    실행

    여기까지 복사 붙여넣기를 했으면 이제 실행해보자.

    IDE에서 Run을 누르거나, 터미널에서 gradle bootRun을 하면 실행된다.

    위의 ToDoItemService에서 리턴한 title과 같은지 확인 해 보자. 자 이제 질문이 생겼을 것이다. http://localhost:8080/todo/{id}/ 이 url에서 {id}는 어디가고 2가 나왔는가? {id}는 path variable이라고 해서, 이 값이  {id}로 넘어 오는 것이 아니라, 여기에 들어오는 값을 @PathVariable(value = "여기") 에서 정의한 인자로 넘겨 준다는 뜻이다. 다시말해 우리는 {id}대신 2를 넘겨 주었으므로 Controller의 파라미터인 id에는 2라는 값이 들어오게 된다.


    이제 GET함수가 불렸는지 확인 해보자. 각 브라우저에서 개발자 툴을 열어본다. 구글크롬에서는 마우스 오른쪽을 누른 후 Inspect를 누르면 된다.

    네트워크 탭으로 들어가 우리가 보낸 리퀘스트를 확인 해 보자. 아무것도 안뜬다면 브라우저를 새로고침해라. Request URL과 Request Method를 확인 해 보자. GET메서드를 이용해 리퀘스트를 보낸 것을 확인 할 수 있다

    이번 포스트를 이용해 기본적을 웹 어플리케이션 구조에 대해 알아보았다. 또, 주로 컨트롤러와 서비스가 어떻게 동작하는지 설명했다. 이 포스트에서는 데이터베이스에 대한 얘기를 안했다. 심지어 리파지토리 클래스도 만들지 않았다. 포스트가 너무 길어지고 핵심적인 부분의 이해가 힘들어 질 수 있기 때문이다. 다음 포스트에서는 JPA Repository 클래스를 만들고, In-Memory 데이터베이스를 이용해 POST메서드를 구현 해 보도록 할 것이다.


    다음 포스트: [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - POST

    댓글

f.software engineer @ All Right Reserved