반응형

이전 내용↓↓↓

2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (3)

이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2) [REST API] Spring Boot로 REST API CRUD 간단 구현 (2) 지난 시간, 간단한 model을 생성하고, 해당 model을 클래스

im-gonna.tistory.com

 

이전 포스팅에서 cloudVendor의 기본적인 CRUD가 동작하는 것을 확인하였다.

 

그런데 get mapping에서 cloud vendor가 존재하지 않을 때는 출력 시 오류가 발생하는 것을 확인하였다.

 

오늘은 이러한 오류가 발생했을 때, 즉 예외에 대해서는 어떻게 처리해야 하는지에 대해 알아볼 것이다.

 


✔ 웹 개발에 있어서 중요한 점

✅ 오류나 예외 상황 발생 시 사용자가 이해하기 쉽도록 상황에 대한 정보를 충분히 전달할 수 있어야 한다.

 

따라서 개발자는 로직 구현에 있어서 예외가 발생하는 부분을 찾아서, 예외처리를 해주어야 한다.

 

이제 프로젝트에서 발생한 오류를 가지고 예외처리를 해보자.

 

예외처리 전 cloud vendor get시 에러 발생

예외 처리를 하기 전에는 위와 같이 데이터베이스에 C3가 없을 때, C3를 get하는 mapping과정에서 반환 값이 없어서 에러가 발생하였다.

우리는 이를 CloudVendorNotFoundException이라고 할 것이다.

이 예외를 처리하기 위해서 다음 단계를 따른다.

 

 

1. 예외 처리를 위한 클래스들을 담은 exception 패키지를 하나 생성한다.

2. exception 패키지에 CloudVendorNotFoundException 클래스를 생성한다.

CloudVendorNotFoundException class

  • 이 클래스를 예외 클래스로 인식하기 위해서 RuntimeException 클래스를 extends한다.
  • 이 클래스에 대한 두 타입의 생성자를 자동생성해준다.
    - 인자로 message만 담은 생성자
    - 인자로 message와 Throwable 객체인 cause를 담은 생성자

*Throwable이 뭐야? (접은글을 확인하세요)

더보기

- Throwable은 java에서 오류나 예외를 처리하는 최상위 클래스로, exception과 error 클래스를 하위에 둔다.

- Throwable에는 두개의 메서드가 있는데,

  • getMessage(): 예외 또는 오류에 대한 상세한 메시지를 반환합니다.
  • printStackTrace(): 예외 또는 오류의 추적 정보를 출력합니다.

3. 클라이언트에게 예외 정보를 전달하기 위해서, 정보를 담는 CloudVendorException 클래스를 생성한다.

  • 위 3개의 속성을 추가해준다. 
  • 사용자에게 보여질 오류 메시지 = message
  • 예외 정보 = throwable
  • http 상태 정보 = httpStatus

  • 기본 생성자와, getter들을 자동 생성해준다.

 

4. 이제 예외 처리를 할 CloudVendorExceptionHandler 클래스를 생성해준다. 프론트와 직접 연결되는 컨트롤러라고 생각하면 된다.

  • 우리는 지금 CloudVendor를 찾을 수 없다는 예외에 대한 처리를 다루어야 하기 때문에, handleCloudVendorNotFoundException 메서드를 선언해주자.
  • rest api이기 때문에 반환타입은 ResponseEntity로 하고, Object유형으로 반환하도록 한다.
  • 그리고 예외 발생 시의 CloudVendorNotFoundException 객체를 인자로 받아서 이 메서드의 인수로 매핑되도록 한다.
  • 로직 내에서는 예외 정보를 받아서 적절히 처리 후 반환해주어야 하기 때문에, 예외 정보를 담아 보낼 CloudVendorException 객체를 하나 생성한다.
  • 인자로 받은 cloudVendorNotFoundException의 message와, cause, 그리고 HttpStatus를 not_found로 하여 생성되도록 한다.

  • return 시에는 ResponseEntity에 로직내에서 생성한 cloudVendorException 클래스와 HttpStatus를 인자로 넣어 반환한다.

  • 그리고 이 메서드가 어떤 예외를 처리할 지 알려줘야 하기 때문에, 메서드 위에 @ExceptionHandler라는 어노테이션을 추가해주고, 이는 메서드에 의해 처리될 예외의 목록을 value로 갖는다.
  • 즉 CloudVendorNotFoundException이 발생하면 value에 명시되어 있기 때문에, 해당 메서드의 인자로 매핑될 수 있는 것이다. 만약 여러개의 Exception을 한 메서드에서 처리하는 경우, value가 {} 리스트 형태이기 때문에, 쉼표를 하고 여러 개를 추가로 작성해주면 된다.

  • 마지막으로 CloudVedorExceptionHandler 클래스에 대해서 @ControllerAdvice 어노테이션을 달아준다
  • 이 컨트롤러는 이 프로젝트 전반에 걸쳐서 전역적으로 여러 예외처리를 해야 하기 때문이다.

 

5. 이제 이러한 예외가 발생할 수 있는 위치로 돌아가서 어떻게 했을 때 예외가 발생하는 지 확인해야 한다.

  • 서비스 레이어의 get 메서드에서 오류가 발행하였는데, 로직을 보면 return에서 findId를 호출하도록 되어 있다.
  • findId를 통해 반환할 내용이 있다면, cloudVendor에 대한 정보가 출력되겠지만, 찾을 수 없다면 예외를 발생시키고 일반 내부 서버 오류 메시지가 표시된다.
  • 따라서 예외 처리 기능을 추가함으로써 출력되는 메시지를 사용자가 오류에 대해 이해하기 쉽게 더 나은 메시지로 표시하고자 한다.
  • 예외 처리 기능을 추가해준다.

  • if문을 추가하여, findId 호출의 return이 비어있다면 예외를 표시하도록 한다.
    CloudVendorNotFoundException클래스를 throw 문법을 통해 발생시키고, 이때의 message는 오류에 대한 상황 설명을 친숙한 메시지로 제공해준다.
    => "requested cloud vendor does not exist"

6. 실행 결과

  • 클라이언트가 제대로 수신한 적절한 오류 응답 404 not found와 함께 적절한 오류 메시지인 cloud vendor를 찾을 수 없다는 메시지가 출력된 것을 확인할 수 있다.

 

위 과정이 바로 Spring boot rest api 애플리케이션으로 예외 처리를 하는 방식이다.

= 사용자 정의 예외 처리

 

 

다음 내용↓↓↓

2023.12.15 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

이전 내용↓↓↓ 2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception 이전 내용↓

im-gonna.tistory.com

 

반응형
반응형

이전 내용↓↓↓

2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

지난 시간, 간단한 model을 생성하고, 해당 model을 클래스 내에서 직접 생성하고, 클라인언트로부터 받은 정보를 통해 CRUD를 구현해 보았다. ↓↓↓ 2023.11.08 - [Web Application/Backend] - [REST API] Spring Boot

im-gonna.tistory.com

 

DB연동에 이어서, db에 있는 정보를 CRUD 처리해보자.

 

💡 Model을 entity로 설정
  • model인 CloudVendor를 entity로써 하나의 테이블로 선언해주기 위해, CloudVendor 클래스를 수정해준다.

  • CloudVendor를 Entity로 선언해주고, Table 이름은 "cloud_vendor_info"로 설정하여 생성 시 이 이름으로 table이 생성된다.
  • 이 테이블의 primary key를 vendorId로 설정해주기 위해, vendorId위에 @Id 를 추가해준다.

 

💡 CloudVendor Repository 생성하기
  • CloudVendor Entity에 대한 데이터베이스 작업인 CREATE, READ, UPDATE, DELETE를 수행하기 위해서 JpaRepository 인터페이스를 상속받아(extends) 사용한다.

  • 일단 repository 폴더를 생성해주고, 인터페이스로 CloudVendorRepository 파일을 추가한다.

  • CloudVendorRepository를 생성할 때, JpaRepository에 Entity이름과, Entity의 pk 데이터타입을 이용해서 define할 수 있다.

 

💡 CloudVendorService 생성하기
  • Service 단에서는 Repository에 접근하여 동작을 수행하도록 한다.

  • Service 폴더를 생성하고, CloudVendorService 라는 이름의 인터페이스를 먼저 생성한다.
  • 인터페이스를 implements하여 사용한 것이 CloudVendorServiceImpl이다.
  • 이렇게 하는 이유는, 인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮추기 위함이다.
public interface CloudVendorService {//인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮춤
    public String createCloudVendor(CloudVendor cloudVendor);
    public String updateCloudVendor(CloudVendor cloudVendor);
    public String deleteCloudVendor(String cloudVendorId);
    public CloudVendor getCloudVendor(String cloudVendorId);
    public List<CloudVendor> getAllCloudVendors();
}
  • CloudVendorService 인터페이스이다.
  • 구체적인 동작은 없고, 메서드 정의만 해두었다.
  • CloudVendor model을 CRUD하는 각각의 메서드이다.
    createCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 생성하는 메서드이다.
    updateCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 수정하는 메서드이다.
    deleteCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 삭제하는 메서드이다.
    getCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 읽어오는 메서드이다.
    getAllCloudVendor :  모든 CloudVendor 정보를 읽어오는 메서드로, List 형태로 가져온다.
@Service
public class CloudVendorServiceImpl implements CloudVendorService{
    CloudVendorRepository cloudVendorRepository;

    public CloudVendorServiceImpl(CloudVendorRepository cloudVendorRepository) {
        this.cloudVendorRepository = cloudVendorRepository;
    }

    @Override
    public String createCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);
        return "Success";
    }

    @Override
    public String updateCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);//jpa의 save 메서드의 경우, 이미 있는 값에 대해서는 변경된 내용만 추적해서 수정합니다
        return "Success";
    }

    @Override
    public String deleteCloudVendor(String cloudVendorId) {
        //more Business logic
        cloudVendorRepository.deleteById(cloudVendorId);
        return "Success";
    }

    @Override
    public CloudVendor getCloudVendor(String cloudVendorId) {
        //more Business logic
        return cloudVendorRepository.findById(cloudVendorId).get();
    }

    @Override
    public List<CloudVendor> getAllCloudVendors() {
        //more Business logic
        return cloudVendorRepository.findAll();
    }//이 안에 있는 모든 메서드를 implements 하기 전에는 빨간줄이 떠있음

}
  • CloudService를 implements한 것이 CloudVendorServiceImpl이다.
  • CloudVendorRepository 객체를 하나 생성하고, repository메서드를 이용해 각 메서드를 동작하도록 한다.
  • 처음에 모든 메서드를 implements하기 전에는 빨간 줄이 떠있게 된다. 빨간 표시를 누르면 자동으로 override할 수 있는 틀을 가져오는 버튼이 있다. 이 버튼을 누르면 override해야 할 메서드를 모두 가져와 준다.
  • 각 메서드에 맞는 repository의 메서드를 호출하여 데이터베이스 작업이 진행되도록 한다.
  • repository의 save() 메서드는 입력받은 Entity 정보를 삽입, 수정한다.
    해당하는 pk가 없었다면 삽입, 이미 있는 pk에 일부 정보만 바뀌었다면 수정 하도록 한다.
  • repository의 deleteById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 삭제한다.
  • repository의  findById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 가져온다.
  • repository의 findAll() 메서드는 모든 Entity 정보를 가져온다.

 

💡 CloudVendor Controller 수정하기
  • Controller의 CloudVendor Controller를 CloudVendorController로 클래스 이름을 수정한다.
  • Controller에서는 서비스 단의 메서드를 호출하여 CRUD를 수행하도록 한다.
public class CloudVendorController {
    CloudVendorService cloudVendorService;

    public CloudVendorController(CloudVendorService cloudVendorService) {
        this.cloudVendorService = cloudVendorService;
    }

    //Read Specific Cloud Vendor Details
    @GetMapping("{vendorId}")
    public CloudVendor getCloudVendorDetails(@PathVariable("vendorId") String vendorId)//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getCloudVendor(vendorId);
    }

    //Read All Cloud Vendor Details
    @GetMapping
    public List<CloudVendor> getCloudVendorDetails()//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getAllCloudVendors();
    }

    //Create Cloud Vendor
    @PostMapping
    public String postCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.createCloudVendor(cloudVendor);
        return "Cloud Vendor Created Successfully";
    }

    //Update Cloud Vendor
    @PutMapping//수정이라 하더라도 특정 id 안받아도됨-> jpa의 save 메서드가 이미 있는 값에 대해서는 변경을 하고, 없으면 삽입을 하는 두가지 역할을 수행하기 때문
    public String updateCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.updateCloudVendor(cloudVendor);
        return "Cloud Vendor Updated Successfully";
    }

    @DeleteMapping("{VendorId}")
    public String deleteCloudVendorDetails(@PathVariable("VendorId") String vendorId)
    {
        cloudVendorService.deleteCloudVendor(vendorId);
        return "Cloud Vendor Deleted Successfully";
    }
}
  • CloudVendorService 객체를 하나 생성한다.
  • @PathVariable은 url로 받는 값을 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • @RequestBody는 프론트 단에서 입력한 값을 가져와 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • 각각의 CRUD 메서드에 맞는 Service 단의 메서드를 호출하여 수행한다.

 

💡 실행 결과
  • 첫 실행 후에는, 데이터 베이스에 Cloud Vendor Entity에 대한 테이블이 처음 생성되므로, 해당 테이블의 레코드는 Empty set 상태임을 확인할 수 있다.
  • POST MAN을 사용하여 정보를 입력한다.
  • post 모드에서 C1-C5  총 5개의 레코드를 삽입한다.

  • 실행 결과, 잘 삽입되었다는 메시지가 출력된다
Cloud Vendor Created Successfully

 

  • GET 모드에서 C3에 대한 값을 가져온다.

  •  실행 결과 C3에 대한 모델 값이 잘 반환된다.

  • GET 모드에서 모든 모델 값을 가져온다.

  • 실행 결과 모든 모델 값이 잘 반환된다.
  • PUT 모드에서 C4에 대한 모델 값을 address를 London으로 변경해본다.

  • 실행 결과 잘 update되었다는 메시지가 출력된다.
Cloud Vendor Updated Successfully
  • 그리고 GET 모드를 통해 검색한 결과, 변경된 값이 잘 반영된 것을 확인할 수 있다.

  • DELETE 모드를 통해 C3를 삭제해본다.

  • 실행 결과 잘 삭제되었다는 메시지가 출력된다.
Cloud Vendor Deleted Successfully

 

  • GET 모드에서 C3를 읽어들이려고 하니, 예외처리를 하지 않아 에러가 발생한다. 없는 값을 읽으려고 해서 발생하는 오류이다.

  • 이로써 모든 CRUD가 잘 동작하는 것을 확인하였다.
  • 실시간으로 powershell을 통해 db 상태를 확인하며 잘 동작하는 것도 확인하였다.

 

다음시간에는 예외처리를 위한 코드를 추가할 예정이다.

 

 

다음 내용↓↓↓

2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

이전 내용↓↓↓ 2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) 이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] S

im-gonna.tistory.com

 

반응형
반응형
API ?

"API 만들어 본 적 있어?"

친구의 질문으로 시작된 본격 API 파헤치기.. 

여기서 API는 [ Application Programming Interface ]의 약자로, 서로 다른 애플리케이션 사이에서 데이터를 주고 받을 수 있게 해주는 중간 다리 역할 이라고 생각하면 된다.

 

API에는 RESTful API, SOAP API, JPA... 등등 여러 종류가 있다.

 

지금껏 내가 해왔던 프로젝트는 클라이언트와 서버간의 상호작용에 있어서는 전통적인 자바 애플리케이션 @controller를 사용했다. 즉, HTTP 메서드인 GET, POST, PUT, DELETE (소위 CRUD라고 함)를 활용해서 조작하지 않고, @Getmapping, @Postmapping으로 url을 잡고 사용해왔다.

이런 방식은 사실 상 서버 사이드 렌더링을 통해서 웹 페이지를 생성하고 제공하는데에 사용되기 때문에, 서버랑 클라이언트가 강력하게 결합되어 있어서 서버-클라이언트 간의 독립성은 떨어진다. (한 마디로 정말 간단한 웹 서비스 개발..)

[서버사이드 렌더링이 뭐고] ↴

더보기

서버 사이드 렌더링이 무슨말이냐구요?

 

서버 사이드 렌더링(Server-Side Rendering, SSR)은 웹 애플리케이션의 사용자 인터페이스(UI)를 서버에서 생성하고 초기 로드 시에 클라이언트에게 완전한 HTML 페이지를 제공하는 웹 개발 기술입니다. 이것은 클라이언트 사이드 렌더링(Client-Side Rendering, CSR)과 대조적입니다.

SSR의 작동 방식은 다음과 같습니다:

  1. 클라이언트에서 웹 페이지 요청을 서버로 보냅니다.
  2. 서버는 요청을 받아 해당 요청에 필요한 데이터를 데이터베이스에서 가져오거나 다른 외부 소스로부터 데이터를 가져옵니다.
  3. 서버는 서버 사이드 렌더링 엔진을 사용하여 사용자 인터페이스(UI) 템플릿을 렌더링하고, 데이터를 포함한 HTML 페이지를 생성합니다.
  4. 서버는 완전한 HTML 페이지를 클라이언트에게 반환합니다.
  5. 클라이언트는 받은 HTML을 렌더링하고 페이지를 화면에 표시합니다.

하지만 요즘 대부분의 서비스는 여러 형태의 클라이언트 플랫폼과 서버간의 통신이 이루어지는 방식이기 때문에, 서버와 클라이언트가 독립적이면서도 잘 통신할 수 있도록 하는 방식이 선호된다.

 

그런 방식이 대표적으로 RESTful API라고 할 수 있다.

 

그럼 RESTful API는 뭐야???

 

RESTful API?

RESTfult API는 [ Representational State Transferful Application Programming Interface ]의 약자로, URI에 자원의 정보를 나타내도록 하고, HTTP 메서드( GET, POST, PUT, DELETE  )를 사용해서 자원을 조작하는 것이다.

 

자원?? URI??? 조작???

 

한가지 예를 들어보자.

회원(회원정보)이 있고, 상품(상품정보)이 있다고 가정하자. 각각은 자원이라고 부른다. 

회원 정보에 대해서 조회(GET)를 할수 있고, 새로운 회원 정보를 생성(POST)할 수 있고, 회원 정보를 수정(PUT)할 수 있고, 회원 정보를 삭제(DELETE)할 수 있다.

mapping할 때마다 우리는 URI를 적었다. 그 URI를 자원에 따라서 패턴화 하고, 적절한 HTTP 메서드를 사용함으로써 동작을 정의할 수있다.

  

즉 이와 같이 쓸 수 있다.

  • GET /members: 모든 회원 목록 조회.
  • GET /members/{id}: 특정 회원 조회.
  • POST /members: 새로운 회원 생성.
  • PUT /members/{id}: 특정 회원 수정.
  • DELETE /members/{id}: 특정 회원 삭제.
  • GET /products: 모든 상품 목록 조회.
  • GET /products /{id}: 특정 상품 조회.
  • POST /products : 새로운 상품 생성.
  • PUT /products /{id}: 특정 상품 수정.
  • DELETE /products /{id}: 특정 상품 삭제.

 이렇게 한 자원에 대해서 '/자원'으로 패턴화 하는 과정을 거치고, 그 안의 세부 동작이나 자원에 대해서는 /이하에 붙여서 URI를 통해 자원의 상태를 나타내어 가독성을 높혀주는 것이다.

 

특징을 보면 자원이 모두 복수명사 형태로 표현된 것을 확인 할 수 있다.

리소스 명은 동사보다는 명사를 사용하도록 하는 것이 바람직한 표현 방식이다.

 

따라서 명심하자.

 

GET /members/delete/1  //잘못된 표현

이와 같이 동작을 나타내는 delete를 리소스에 작성하는 것이 아니다.

 

DELETE /members/1  //옳게 수정된 표현

 자원은 명사로만 두고, 동작은 HTTP 메서드로 표현하는 것이다.

 

(URI 설계의 자세한 내용은 아래의 블로그를 참고하자.)

 

 

개발 초보를 위한 RESTful API 설계 가이드

초보자를 위한 RESTful API 설계 가이드를 작성해보았습니다.

velog.io

 

스프링부트에서 코드 상 어떻게 쓰이는지 살펴보자.

@RestController
@RequestMapping("/api/members")
public class MemberRestController {
    // 멤버 API 정의
    
    @GetMapping
    public ResponseEntity<List<MemberDTO>> getAllMembers() {
        // 모든 멤버 목록 조회 로직
        List<MemberDTO> members = memberService.getAllMembers();
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<MemberDTO> getMember(@PathVariable Long id) {
        // 특정 멤버 조회 로직
        MemberDTO member = memberService.getMemberById(id);
        return new ResponseEntity<>(member, HttpStatus.OK);
    }

    @PostMapping
    public ResponseEntity<MemberDTO> createMember(@RequestBody MemberDTO memberDTO) {
        // 새로운 멤버 생성 로직
        MemberDTO createdMember = memberService.createMember(memberDTO);
        return new ResponseEntity<>(createdMember, HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<MemberDTO> updateMember(@PathVariable Long id, @RequestBody MemberDTO memberDTO) {
        // 특정 멤버 수정 로직
        MemberDTO updatedMember = memberService.updateMember(id, memberDTO);
        return new ResponseEntity<>(updatedMember, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteMember(@PathVariable Long id) {
        // 특정 멤버 삭제 로직
        memberService.deleteMember(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

각 객체의 CRUD를 위해서 하나의 RESTController로 정의되는 것을 확인할 수 있다.

어노테이션에 주목하자.

@RestController
@RequestMapping("/api/members")

@RestController를 통해서 RESTful API를 사용하겠다는 것을 나타내고 있다.

@RequestMapping을 통해서 URI 패턴을 정의하기 시작했다. 회원에 대해서는 /api/members가 기본 URI로 동작할 것이다.

그리고 각각의 CRUD 동작에 맞추어서 @Getmapping @Postmapping @Putmapping @Deletemapping을 사용하고 있는 것을 확인할 수 있다.

 

여기서 또 중요한 점!!!

 

기존 프로젝트에서는 CRUD결과 html 페이지(main.html 이런 파일 자체)를 반환했었는데에 반해(그래서 더욱 결합이 된 것일지도..), RESTful API 방식에서는 ResponseEntity를 반환하고 있다!!!??

 

ResponseEntity ?

ResponseEntity는 Spring Framework에서 제공하는 클래스로, HTTP 응답을 나타내는 객체이다. 이 클래스를 사용하여 클라이언트에게 HTTP 응답을 구성하고 전달할 수 있다.

 

왜 이 객체를 반환하는 것일까?

 

그건 바로 RESTful API 방식에서의 HTTP 응답 형식은 JSON이나 XML같은 데이터 형식을 사용함으로써 클라이언트에 응답하기 때문이다. 따라서 반드시 반환 형태를 JSON 또는 XML형태의 데이터로 주어야 한다.

 

ResponseEntity는 다음과 같은 기능을 제공한다. 

  1. HTTP 응답 상태 코드 설정
  2. HTTP 응답 헤더 설정
  3. 응답 본문 데이터 설정
  4. 응답 타입 설정

 

이것도 코드를 통해서 쓰임을 살펴보자.

 

@GetMapping
    public ResponseEntity<List<MemberDTO>> getAllMembers() {
        // 모든 멤버 목록 조회 로직
        List<MemberDTO> members = memberService.getAllMembers();
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

 매핑 결과 ResponseEntity 객체를 new로 생성해서 members의 정보와 함께 HttpStatus.OK라는 것을 담아 반환하고 있다.

members는 조회 결과를 담은 데이터를 반환해 준 것일테고, HttpStatus.OK가 조금 생소하다.

 

HttpStatus.OK가 바로 ResponseEntity의 기능 1번인 HTTP 응답 상태 코드를 나타내는데, 의미는 200 OK 상태를 나타내는 코드이다.=말 그대로 OK= 요청이 성공적으로 처리되었음

HttpStatus.NOT_FOUND라는 코드는 404 not found 상태를 반환하라는 의미이다.= 요청한 리소스를 찾을 수 없음

(상태별로 코드가 정리된 표도 위 참고 블로그에 있으니 참고하자.)

 

상태 정보를 넘기는 이유가 뭘까?

상태코드 ?

상태 코드는 서버가 클라이언트에게 요청을 처리한 결과를 전달하는 수단이다. 클라이언트는 상태 코드를 통해 요청이 성공했는지, 실패했는지, 어떤 종류의 오류가 발생했는지 등을 파악할 수 있다.

 

클라이언트는 상태 코드를 기반으로 다음 단계를 결정할 수 있다. 예를 들어, 성공적인 응답(예: 200 OK)일 경우 데이터를 표시하고, 오류 응답(예: 404 Not Found)일 경우 오류 메시지를 표시하거나 다른 조치를 취할 수 있다.

 

이 외에도 다양한 이유가 있지만, 서버와 클라이언트 간의 정확하고 원활한 상호작용을 위해서가 중점이라고 할 수 있겠다.

 

 

마지막으로 한가지 더 잡고 넘어가야 할 부분이 있다.

 

바로 '세션' 문제인데, 기존의 프로젝트 코드(전통적인 웹 애플리케이션 방식)에서는 @HttpSession을 통해서 로그인 상태를 세션을 통해 유지할 수 있었다.

근데 RESTful API방식은 상태를 관리하지 않는 stateless 방식을 따른다. 각 요청이 독립적이고, 서버는 클라이언트의 상태를 유지하지 않기 때문에, 클라이언트는 요청을 할 때 요청 정보를 함께 제공해 주는 과정이 필요하다!!

 

예를 들어서 인증 정보는 요청 헤더에 토큰 또는 인증 정보를 포함하여 전송할 수 있다. 토큰 기반 인증을 사용해 사용자 인증을 하고 상태 관리를 할 수 있는 것이다.

 

RESTful API에서의 토큰 기반 인증:

  1. 사용자가 서버에 로그인하면 서버는 사용자에게 액세스 토큰(access token)을 발급합니다.
  2. 클라이언트는 액세스 토큰을 안전한 방식으로 저장하고 각 요청에 포함시켜 서버로 보냅니다. 일반적으로 요청의 헤더에 포함됩니다.
  3. 서버는 액세스 토큰을 검증하고, 유효한 토큰인 경우 해당 사용자를 식별하고 요청을 처리합니다.

요청의 헤더가 어디일까..~

 

일반적으로 사용되는 토큰 기반 인증 방식 중에는 OAuth 2.0 및 JWT(Json Web Token)가 있습니다. 이러한 인증 방식을 사용하여 RESTful API에서 사용자 인증 및 세션 관리를 구현할 수 있습니다.

 

OAuth...!!! 이번 정처기 실기에 나왔던 개념인데, 이걸 먼저 공부했더라면 맞힐 수 있었을텐데..

 

토큰 관련해서는 좀 더 공부가 필요할 것으로 보인다. 과거에 express랑 node.js로 개발했을 때 로그인에서 한번 쓴 것 같기도 한데(토큰 유효 시간 설정하고 그랬었음), 기억이 잘 나지 않은 것 보니.. RESTful API로 한번 프로젝트 파서 직접 해봐야겠다!!!

 

할거 +1됨.

 

 

 

반응형

+ Recent posts