Building REST services with Spring #1

kdkdhoho·2022년 7월 30일
0

[https://spring.io/guides/tutorials/rest/] 를 처음부터 따라하며 실습해보는 것을 정리하는 글입니다. 의역이나 오역이 있을 수 있으니 정확한 것은 원문을 참고해주세요.

개요

왜 RESTful API를 사용할까?

  • 우리가 웹을 사용함에 있어서, 웹의 이점을 모두 수용할 수 있는 방법이기 때문이다.

그렇다면 웹의 이점은?

  • 적절한 actions(GET, POST, PUT, DELTE, ...)
  • 캐싱
  • Redirection / Forwarding
  • 보안 (암호화, 인증)
    등 ..

하지만 깨달아야 할 중요한 점은, REST가 표준 그 자체가 아니라 웹 기반 시스템을 구축하는 데 도움이 되는 아키텍처의 접근 방식, 스타일, 제약 조건이라는 것이다.

시작하기

Spring Initializer를 이용해 dependencies로 Web, JPA, H2를 넣어주고, 프로젝트 명을 Payroll로 하여 프로젝트를 생성하자.

Gradle로 사용해도 되지만 해당 문서에서는 maven 기반의 프로젝트로 실행한다.
난 Gradle이 익숙하기에 Gradle로 생성

What u build

회사의 직원을 관리하는 간단한 급여 시스템을 만들어 볼 것이다.

처음에는 REST를 사용하지 않고 개발할 것이며, 결과적으로 차이를 보기 위해 점차 RESTful 하게 코드를 수정해나갈 것이다.

Start

  • 직원 엔티티: Lombok을 이용했습니다.
package com.example.Payroll;

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Employee {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String role;

}
  • 직원 레포지토리: JPA Repository를 이용하여 구현하였습니다.
package com.example.Payroll;

import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
  • 미리 데이터를 넣어두고 시작: @Configuration에 의해 application context가 load 될 때 자동 실행된다. log를 통해 저장된 결과를 출력한다.
package com.example.Payroll;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoadDatabase {

    private static final Logger log = LoggerFactory.getLogger(LoadDatabase.class);

    @Bean
    CommandLineRunner initDatabase(EmployeeRepository repository) {
        return args -> {
            log.info("Preloading " + repository.save(new Employee("Kim", "Student")));
            log.info("Preloading " + repository.save(new Employee("Lee", "Teacher")));
        };
    }
}
  • 컨트롤러 개발
package com.example.Payroll;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeRepository repository;

    @GetMapping("/employees")
    public List<Employee> getAll() {
        return repository.findAll();
    }

    @PostMapping("/employees/{id}")
    public Employee enrollEmployee(@RequestBody Employee newEmployee) {
        return repository.save(newEmployee);
    }

    @GetMapping("/employees/{id}")
    public Employee getOne(@PathVariable Long id) {
        Optional<Employee> employee = repository.findById(id);
        return employee.orElseThrow(() -> new EmployeeNotFoundException(id));
    }

    @PutMapping("/employees/{id}")
    public Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
        Optional<Employee> findEmployee = repository.findById(id);

        return findEmployee.map(employee -> {
            employee.setName(newEmployee.getName());
            employee.setRole(newEmployee.getRole());
            return repository.save(employee);
        }).orElseGet(() -> {
            newEmployee.setId(id);
            return repository.save(newEmployee);
        });
    }

    @DeleteMapping("/employees/{id}")
    public void deleteEmployee(@PathVariable Long id) {
        repository.deleteById(id);
    }
}

@RestController: 모든 응답은 HTTP Response Body에 담아 응답.
@RequestBody: body에 있는 정보들을 읽음
@PathVariable: 경로에 있는 정보를 읽음
EmployeeNotFoundException(Long id): 커스텀 exception

EmployeeNotFoundException.java

package com.example.Payroll;

public class EmployeeNotFoundException extends RuntimeException {

    EmployeeNotFoundException(Long id) {
        super("" + id + "번의 직원을 찾을 수 없습니다.");
    }
}

위의 예외가 터지면, HTTP 404 에러를 렌더링하는 @ControllerAdvice:

package com.example.Payroll;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class EmployeeNotFoundAdvice {

    @ResponseBody
    @ExceptionHandler(EmployeeNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String employeeNotFoundHandler(EmployeeNotFoundException exception) {
        return exception.getMessage();
    }
}

Test

이제 main 함수를 실행시키고 http://localhost:8080/employees로 접근하면,

[{"id":1,"name":"Kim","role":"Student"},{"id":2,"name":"Lee","role":"Teacher"}]

이 뜰 것이다.

이제 저장되어 있지 않은 id 값을 통해 접근해보자.

http://localhost:8080/employees/3 으로 접근하면, 3번의 직원을 찾을 수 없습니다. 라고 뜰 것이다. 정상적으로 예외처리가 된 것을 확인할 수 있다.

그렇다면 이제는 POSTMAN을 사용하여 직원 등록을 해보자.

이제는 방금 등록한 직원의 role을 수정해보자.

마지막으로 3번 직원을 delete 해보자.

이상 무!!

줄이며

여기까지 Spring MVC를 이용하여 직원을 GET, POST, PUT, DELETE 해보았다.
저장되어 있지 않는 직원을 조회할 때에는 적절하게 예외 처리로 오류 페이지까지 띄어보았다.

다음 시간부터는 이 코드들을 수정하여 RESTful스럽게 수정해보겠다.

profile
newBlog == https://kdkdhoho.github.io

0개의 댓글