ABOUT ME

-

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

    지난 포스트에서는 스프링 자바 RESTful API를 설계하고 관련 패키지와 클래스를 만들었다. 그 후 가장 간단한 API인 GET을 테스트 해 보았다. 이 포스트에서는 임베디드 몽고디비를 이용해서 실제로 데이터를 생성하는 POST API를 만들어 보도록 한다. 들어가기 앞서, 이전 포스트에 있는 에러와 버그를 수정했으니 전 포스트와 조금 달라도 당황하지 말고 이 포스트를 따라 수정하면 된다.

    예상 독자

    목표

    • 몽고디비 설정
    • 리파지토리 (JPA Repository)
    • 서비스에서 리파지토리 이용하기 (Service)
    • 컨트롤러 메서드 만들기 (Controller)

    몽고디비 설정

    build.gradle

    임베디드 몽고디비를 사용하기 위해서는 build.gradle에 디펜덴시를 추가해야한다. 추가해야 할 디펜던시는 다음과 같다.

    compile('org.springframework.boot:spring-boot-starter-data-mongodb')
    compile('cz.jirutka.spring:embedmongo-spring:1.3.1')
    compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')

    참고를 위해 전체 build.gradle을 첨부한다.

    plugins {
    id 'org.springframework.boot' version '2.1.3.RELEASE'
    id 'java'
    }

    apply plugin: 'io.spring.dependency-management'

    group = 'com.fsoftwareengineer'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8'

    repositories {
    mavenCentral()
    }

    dependencies {
    //implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok:1.18.4'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // mongo db related
    compile('org.springframework.boot:spring-boot-starter-data-mongodb')
    compile('cz.jirutka.spring:embedmongo-spring:1.3.1')
    compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')
    }

    참고로 spring-boot-starter-data-jpa는 대신 spring-boot-starter-data-mongodb를 사용해야해 주석처리했다.

    MongoConfig.java

    몽고디비를 이용하기위해 몽고디비 Config 클래스를 만들어야한다. 이를 위해 Config라는 패키지를 추가하고 그 아래 MongoConfig.java를 추가한다.

    package com.fsoftwareengineer.MySpringApp.Config;

    import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.Profile;


    @Profile(value = "mongo")
    @Configuration
    @Import(EmbeddedMongoAutoConfiguration.class)
    public class MongoConfig {

    }

    @Profile : 프로파일을 이용해 데이터베이스를 구분 할 수 있다. 예를들어 Application.properties에 spring.profiles.active = mongo로 하면 현재 Config를 사용 할 것이고 다른 값을 준다면 다른 값을 사용 할 것이다. 이부분은 아직 내가 확인을 안해봤으니 넘어가도록 하겠다.
    @Configuration : 이 어노테이션을 이용하면 Spring이 알아서 이 클래스는 환경설정을 위한 클래스 인식하고 알아서 그 내용을 불러온다.
    @Import : 이 어노테이션으로 티폴트 설정인 EmbeddedMongoAutoConfiguration을 불러온다. 이 어노테이션이 없으면 우리가 직접 configuration을 작성해야 하는 셈이다.
    여기까지 했으면 실행 해 보자. 아플리케이션이 정상적으로 실행한다면 제대로 환경설정이 된 것이다.

    리파지토리 (JPA Repository)

    몽고디비를 설정한 어플리케이션이 잘 돌아간다면 이제 리파지토리를 만들 차례이다. 마찬가지로 ToDoItem패키지에 ToDoItemRepository를 만들어 보도록 하자.

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import org.springframework.data.mongodb.repository.MongoRepository;

    public interface ToDoItemRepository extends MongoRepository<ToDoItem, String> {
    }

    JPA리파지토리가 뭔지 간단히 설명하도록 하겠다. JPA는 간단히 말하면 자바 메서드의 형태로 SQL을 실행하고 결과값을 가져 올 수 있게 하는 API를 말한다. 리파지토리는 말 그대로 데이터베이스를 의미한다. 웹 어플리케이션에서 자주 사용되는 DB 오퍼레이션들이 몇 가지 있다. 대표적으로 create, update, findById, findByName등등이 있다. 그래서 옛날에는 개발자들이 어플리케이션을 만들 때 디비와 연결하기 위해서는 항상 몇 가지 데이터베이스 SQL을 직접 짜고 파싱 해야 했었다. 세월이 흘러 데이터베이스 ORM(Object Relational Mapping)이라고 데이터베이스 스키마를 만들어주고, 또 SQL의 결과의 파싱을 대신 해주는 툴들이 생겼다. Hibernate이 대표적인 ORM 툴이다. 여기서 더 나아가 자바에서는 Java Persistent API라는 것을 만들었다. 즉 데이터베이스의 제공자(여기서는 Mongo DB)가 개발자들이 쉽게 이용 할 수 있도록 자바 API를 만든 것이다. 따라서 우리는 이렇게 interface를 만들고 Repository를 extends~하면 JPA를 바로 이용 할 수 있다. 

    여기서 <ToDoItem, String>에 대해 잠깐 설명하겠다. <Model, ID>로 첫번째에는 이 리파지토리가 표현해야하는 모델, 여기서는 ToDoItem이 들어가고, ID부분에는 이 ToDoItem을 구별 할 수 있는 ID의 타입이 들어간다. 그렇다면 ToDoItem.java를 보자. 이와 관련해 수정해야 하는 것이 있다.

    ToDoItem.java

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

    import lombok.*;
    import org.springframework.data.annotation.Id;

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

    id가 몽고 디비에서의 ID임을 나타내기 위해  @Id 어노테이션을 추가하자. 이 ID는 몽고 디비가 알아서 만들어 줌으로 우리가 앞으로 고유한 값을 생성 할 필요가 없어진다.

    서비스에서 리파지토리 이용하기 (Service)

    지난번의 get 메서드에 이어서 이번엔 create메서드와 getAll메서드를 작성 해 보았다.

    ToDoItemService.java

    package com.fsoftwareengineer.MySpringApp.ToDoItem;

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

    import java.util.List;

    @Service
    public class ToDoItemService {
    @Autowired
    private ToDoItemRepository toDoItemRepository;

    public ToDoItem get(final String id) {
    // do id validation
    return toDoItemRepository.findById(id).orElse(null);
    }

    public ToDoItem create(final ToDoItem toDoItem) {
    if(toDoItem == null) {
    throw new NullPointerException("To Do Item cannot be null.");
    }
    return toDoItemRepository.insert(toDoItem);
    }

    public List<ToDoItem> getAll() {
    return toDoItemRepository.findAll();
    }
    }

    서비스에 위에서 생성한 ToDoItemRespository를 서비스에 추가 해 주고 @Getter @Setter를 만들어 줬다. 그리고 create메서드를 만들어 toToItemRepository에 insert하는 메서드를 짜본다. 또한 이전에 아무렇게나 리턴했었던 get을 toDoItemRepository를 이용해 수정 해 주고 getAll메서드도 만들어 주었다. 

    눈치 챘을 지 모르겠지만 toToItemRepository하고 .을 누르면 사용 할 수 있는 여러가지 메서드들이 나오는 것을 볼 수 있다. 이게 바로 위에서 말했던 JPA 라는 것이다. 우리는 insert, count, delete같은 함수의 내부를 구현하지 않았지만 extends MongoRepository가 구현한 JPA를 이용하면 이와 같은함수들을 사용 할 수 있다.

    컨트롤러 메서드 만들기 (Controller)

    ToDoItemController에 다음과 같이 두 메서드를 추가했다.

    @RequestMapping(method = RequestMethod.GET)
    public @ResponseBody List<ToDoItemResponse> getAll() {
    List<String> errors = new ArrayList<>();
    List<ToDoItem> toDoItems = toDoItemService.getAll();
    List<ToDoItemResponse> toDoItemResponses = new ArrayList<>();
    toDoItems.stream().forEach(toDoItem -> {
    toDoItemResponses.add(ToDoItemAdapter.toToDoItemResponse(toDoItem, errors));
    });
    return toDoItemResponses;
    }

    @RequestMapping(method = RequestMethod.POST)
    public @ResponseBody ToDoItemResponse create(@RequestBody final ToDoItemRequest toDoItemRequest) {
    List<String> errors = new ArrayList<>();
    ToDoItem toDoItem = ToDoItemAdapter.toToDoItem(toDoItemRequest);
    try {
    toDoItem = toDoItemService.create(toDoItem);
    } catch (final Exception e) {
    errors.add(e.getMessage());
    }
    return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
    }

    ToDoItemResponse와 마찬가지로 ToDoItemRequest를 ToDoItem으로 변환 해 줄 아답터가 필요하다.
    ToDoItemAdapter.java에 다음과 같은 메서드를 추가 해 줬다.

    public static ToDoItem toToDoItem(final ToDoItemRequest toDoItemRequest) {
    if(toDoItemRequest == null) {
    return null;
    }
    return ToDoItem.builder()
    .title(toDoItemRequest.getTitle())
    .done(toDoItemRequest.isDone())
    .build();
    }

    참고를 위해 전체 ToDoItemController와 ToDoItemAdapter를 첨부한다.
    ToDoItemController.java
    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);
    }

    @RequestMapping(method = RequestMethod.GET)
    public @ResponseBody List<ToDoItemResponse> getAll() {
    List<String> errors = new ArrayList<>();
    List<ToDoItem> toDoItems = toDoItemService.getAll();
    List<ToDoItemResponse> toDoItemResponses = new ArrayList<>();
    toDoItems.stream().forEach(toDoItem -> {
    toDoItemResponses.add(ToDoItemAdapter.toToDoItemResponse(toDoItem, errors));
    });
    return toDoItemResponses;
    }

    @RequestMapping(method = RequestMethod.POST)
    public @ResponseBody ToDoItemResponse create(@RequestBody final ToDoItemRequest toDoItemRequest) {
    List<String> errors = new ArrayList<>();
    ToDoItem toDoItem = ToDoItemAdapter.toToDoItem(toDoItemRequest);
    System.out.println(toDoItemRequest.getTitle());
    try {
    toDoItem = toDoItemService.create(toDoItem);
    } catch (final Exception e) {
    errors.add(e.getMessage());
    e.printStackTrace();
    }
    return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
    }

    }
    ToDoItemAdapter.java
    package com.fsoftwareengineer.MySpringApp.ToDoItem;

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

    public class ToDoItemAdapter {

    public static ToDoItem toToDoItem(final ToDoItemRequest toDoItemRequest) {
    if(toDoItemRequest == null) {
    return null;
    }
    return ToDoItem.builder()
    .title(toDoItemRequest.getTitle())
    .done(toDoItemRequest.isDone())
    .build();
    }

    public static ToDoItemResponse toToDoItemResponse(final ToDoItem toDoItem, final List<String> errors) {
    return ToDoItemResponse.builder()
    .toDoItem(toDoItem)
    .errors(Optional.ofNullable(errors).orElse(new ArrayList<>()))
    .build();
    }
    }
    실행 및 테스팅
    실행이 됐다면 Postman을 이용해 API 테스팅을 해 보자. Postman을 이용 해 API Test를 하는 법은 포스트맨(Postman)을 이용한 API 테스트를 참고한다.
    create 테스트
    create 을 이용해 To Do Item을 2개정도 만들어 보자.

    리턴 값은 다음과 같다. id가 몽고디비에 의해 자동으로 생성 된 것을 확인 할 수 있다.


    {
    "data": {
    "id": "5c72015ea1bd3074ba588b5d",
    "title": "test create method 2",
    "done": false
    },
    "errors": []

    }

    이제 http://localhost:8080/todo를 이용해 getAll을 불러보자. 여러분이 생성한 두 개의 To Do Item들이 리턴 될 것이다.

    리턴값은 다음과 같다. 생성된 아이템들이 리턴 된 것을 확인 할 수 있다.

    [
    {
    "data": {
    "id": "5c72011ea1bd3074ba588b5c",
    "title": "test create method",
    "done": false
    },
    "errors": []
    },
    {
    "data": {
    "id": "5c72015ea1bd3074ba588b5d",
    "title": "test create method 2",
    "done": false
    },
    "errors": []
    }
    ]

    이번 포스트에서는 임베디드 몽고 디비를 설정하고 POST메서드를 이용해 ToDoItem을 데이터베이스에 생성하는 서비스를 만들어 보았다. 다음 포스트 부터는 node.js와 vue.js를 이용해 드디어 Frontend의 개발환경을 설정 해 보도록 하겠다. 


    이 코드는 ToDoTutorial Github에서 확인 할 수 있다.


    음포스트 : [To-Do 앱]Vue.js 와 Node.js를 이용해 웹 앱 만들기

    댓글

f.software engineer @ All Right Reserved