반응형

지난 시간, 간단한 model을 생성하고, 해당 model을 클래스 내에서 직접 생성하고, 클라인언트로부터 받은 정보를 통해 CRUD를 구현해 보았다.
↓↓↓

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

 

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

💡 REST API를 사용하여 간단한 CRUD 동작을 구현하기 ✅ 프로젝트 세팅 ✔ Spring Initializer를 이용하여 프로젝트 파일 생성 ✔ Gradle 선택 ✔ 스프링 버전 2.7.x ✔ 자바 버전 11 ❗ 참고로 스프링 버전 3

im-gonna.tistory.com

 

이번에는 직접 MYSQL db를 생성하고 db와 연동해보자.

 

💡 MYSQL, JPA 종속성 추가하기

 

MYSQL과 JPA를 사용하려면 먼저 종속성을 추가해 주어야 한다.

 

여기서 잠깐, JPA란?

> JPA는 "Java Persistence API"의 약자로, Java에서 데이터베이스를 관리하는 표준 인터페이스를 제공하는 API입니다. JPA는 객체와 관계형 데이터베이스 간의 매핑을 처리하고, 객체 지향 프로그래밍(OOP) 언어인 Java와 관계형 데이터베이스 간의 상호 작용을 간소화하는 목적으로 만들어졌습니다.

= 즉, db의 데이터를 가져올 수 있도록 하는 매개체 역할이다.

= 자바의 객체와, 데이터베이스 간의 데이터를 매칭해준다.

 

  • build.gradle 파일에 들어가서 dependencies 부분에 아래와 같이 코드를 추가해준다.

원래는 spring_web이랑 test 밖에 없었으나, 가운데 두 종속성을 추가해주었다.

위는 jpa, 아래는 mysql에 대한 종속성이다.

 

 

💡 MYSQL 연동을 위한 정보를 가진 yml 파일 생성하기

 

yml 파일은 애플리케이션의 설정 정보를 가진다.

resource 디렉토리 하에 application.yml 파일을 생성하고 데이터베이스 연결 정보를 추가해준다.

# database 연동 설정
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #    각자 PC에 만들어놓은 Database이름을 써야 합니다.
    url: jdbc:mysql://localhost:3306/cloud_vendor?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    #    mysql에 생성한 사용자 계정 정보를 써야 합니다.
    username: 자신의 db username
    password: 자신의 db pw
  thymeleaf:
    cache: false

  # spring data jpa 설정
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    open-in-view: false
    show-sql: true
    hibernate:
      ddl-auto: create //초기 설정 시 create로 하고 나중에 db가 완전해지면, update로 바꿔서 사용
#      use-new-id-generator-mappings: false

 

위는 yml에 들어가는 코드이다.

  • datesource의 url에서 cloud_vendor는 이 프로젝트에서 사용하는 db 이름이고, 내 db에 미리 만들어져 있어야 한다.
  • 아래 username과 password는 자신의 db에 맞게 넣어주면 된다.
  • 아래 jpa에서 ddl-auto는 create로 두면 entity로 둔 table이 생성되고, update로 되면 변경사항만 변경된다.
  • 초기에는 보통 create로 둔다.

 

💡 application controller 정상 실행되는지 확인하기

 

build.gradle을 수정하고 난 뒤에는 코끼리 모양을 눌러 다시 build해주어야 변경 사항이 적용된다.

yml 파일을 추가했으므로, 한번 application controller를 실행해 보아서 문제가 없는지 확인한다.

문제가 없다면 잘 진행된 것!

 

다음 회차↓↓↓

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

 

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

DB연동에 이어서, db에 있는 정보를 CRUD 처리해보자. 💡 Model을 entity로 설정 model인 CloudVendor를 entity로써 하나의 테이블로 선언해주기 위해, CloudVendor 클래스를 수정해준다. CloudVendor를 Entity로 선언

im-gonna.tistory.com

  

반응형
반응형
💡 REST API를 사용하여 간단한 CRUD 동작을 구현하기

 

✅ 프로젝트 세팅

✔ Spring Initializer를 이용하여 프로젝트 파일 생성
✔ Gradle 선택
✔ 스프링 버전 2.7.x
✔ 자바 버전 11
❗ 참고로 스프링 버전 3.x.x를 선택했다면 자바는 17버전 이상만 가능
✔ Spring Web 종속성 추가
✔ Generate 클릭 후 다운로드 받은 압축파일을 적절한 위치에 풀어준다.

✔ IntelliJ를 열고 프로젝트 파일을 불러온다
✔ File의 setting에서 프로젝트 언어를 gradle > IntelliJ IDEA로 모두 변경하고, 자바 11버전을 사용하도록 설정

 

  • Cloud Vendor(클라우드 공급업체)라는 객체가 있다고 가정한다.
  • 이 객체는 id, name, address, phone number를 정보로 갖는다.
  • 이 객체의 정보를 불러오고, 새로운 정보를 입력(새로운 객체를 생성)하고, 정보를 수정하고, 객체를 삭제하는 CRUD 동작을 rest api를 통해 구현할 것이다.

1. java 디렉토리 하에 아래와 같이 model 디렉토리를 생성한다.

  • model은 객체의 정보를 담는 역할을 한다.
  • model 디렉토리 아래에 CloudVendor라는 클래스를 정의해준다.
  • CloudVendor는 다음 네가지 속성을 갖는다.
    << private >>
    vendorId : String
    vendorName : String
    vendorAddress : String
    vendorPhoneNumber : String
  • 그리고 생성자와 getter/setter 메서드를 갖는다.
    이는 클래스 이름을 오른쪽 마우스로 누르면 generator 기능을 통해 자동 생성 가능하다.

2. java 디렉토리 하에 아래와 같이 controller 디렉토리를 생성한다.

  • 서버가 프론트 단과 정보를 주고 받게 되는 부분이다. 즉, 프론트 단과의 연결 역할을 한다.
  • CloudVendorAPIService라는 클래스를 만들어서 프론트로부터 받은 정보나 서버로부터 전달하고자 하는 정보를 CRUD 처리 하도록 할 것이다.
  • 이 클래스는 REST API로써 동작할 것이기 때문에, class에 @RestController를 붙여 rest api로 만든다.
  • rest api의 특징은 경로가 특정 이름으로 패턴을 가진다는 점이있다. 따라서 @RequestMapping("/cloudvendor")를 통해 8080이하 경로에서 /cloudvendor로 시작하는 경로에서는 이 클래스가 동작하도록 한다.
@RestController
@RequestMapping("/cloudvendor")
public class CloudVendorAPIService {

 

  • 이 클래스에서 사용할 CloudVendor 객체를 하나 생성한다.
CloudVendor cloudVendor;

 

3. Get

  • 특정 id에 해당하는 정보를 읽어오는  READ 동작을 Get 메서드를 통해 구현하자.
  • @GetMapping을 통해서 {}안에 들어가는 vendorId에 해당하는 vendor 정보를 가져오도록 한다.
@GetMapping("{vendorId}")
  • rest api는 json("이름":"값")형식을 반환하기 때문에, json형식으로 값을 return 해야 한다.
public CloudVendor getCloudVendorDetails(String vendorId)
{
    return cloudVendor;
}
  • 현재 코드는 단순히 동작만을 확인하기 위함이므로, cloudVendor에 있는 vendorId랑 들어온 Id가 같은지 비교하는 과정은 생략할 것이다. 

4. POST

  • 새로운 vendor 정보를 생성하는 CREATE 동작을 post 메서드를 통해 구현하자.
  • 함수에 @PostMapping을 추가한다
@PostMapping
  • 프론트로부터 입력받은 CloudVendor를 인자로 받고, 이 클래스에 있는 cloudVendor 객체에 입력받은 값을 저장해준다.
public String postCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
{
    this.cloudVendor = cloudVendor;
    return "Cloud Vendor Created Successfully";
}
  • 프론트로부터 받을 때 @RequestBody를 통해 받아온다.
  • 잘 create되었으면 string 타입의 메시지를 출력한다.

 

5. PUT

  • 기존의 객체의 정보를 수정하는 UPDATE 동작을 put 메서드를 통해 구현하자.
  • 함수에 @PutMapping을 추가한다.
@PutMapping("{VendorId}")
  • 프론트로부터 입력받은 CloudVendor를 인자로 받고, 이 클래스에 있는 cloudVendor 객체에 입력받은 값을 저장해준다.
public String updateCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
{
    this.cloudVendor = cloudVendor;
    return "Cloud Vendor Updated Successfully";
}
  • 잘 update되었으면 string 타입의 메시지를 출력한다.

 

6. DELETE

  • 특정 id에 해당하는 객체 정보를 삭제하는 DELETE 동작을 Delete 메서드를 통해 구현하자.
  • 함수에 @DeleteMapping을 추가한다.
  • 특정 id에 대한 정보를 담은 경로를 요청받았을 경우, 해당 id의 객체를 삭제한다.
@DeleteMapping("{VendorId}")
  • 경로로 들어온 id는 인자에서 사용하고, 요청한 id의 객체를 삭제하기 위해, this. cloudVendor가 null을 가리키도록 한다.
public String deleteCloudVendorDetails(String vendorId)
{
    this.cloudVendor = null;
    return "Cloud Vendor Deleted Successfully";
}
  • 잘 delete되었으면 string 타입의 메시지를 출력한다.

 


프론트엔드를 구성하지 않았기 때문에, 실제로 값을 읽어오거나 할 수 없다. 따라서 POSTMAN 프로그램을 이용해서 직접 값을 json형태로 넣어주면서 CRUD가 잘 동작하는지 확인하였다.

 

일단 GET 메서드를 사용해보았다. 우리가 web에서 주소창에 입력하는 것처럼 똑같이 입력해주면 된다.

http://localhost:8080/cloudvendor/C1 경로를 통해서..

id가 C1인 객체에 대해서 조회해보려고 하였다.

 

아직 cloudvendor에 아무 값도 넣어 준적이 없기 때문에, 당연히 return 결과는 null이므로, 출력창에 아무것도 출력되지 않는 것을 확인할 수 있다.

그러나 잘 동작하였음은 200OK인 상태 코드를 통해 확인할 수 있다.

 

이제, POST 메서드를 사용하여, 값을 입력해보자.

 

실제로 프론트 단에서 값을 읽어올 수 없기 때문에, 위와 같이 요청 Body에 json형태로 vendor 정보를 입력해주었다.

json 형식은 {"이름":"값"} 쌍의 형태인 것을 잊지 말자.

여러 개의 쌍이 있을 경우 ,로 구분해 주어야 한다.

 

위와 같이 값을 입력하고 실행해보니, 아래 잘 create 되었다는 메시지가 출력되고, 200ok 상태임을 확인할 수 있다.

POST도 잘 동작한다.

 

이제 PUT 메서드를 이용해서, 프론트로부터 수정된 값을 받아서 vendor 정보를 수정해보자.

PUT의 경우 POST와 비슷한데, 특정 Id의 정보를 수정하기 위함이므로, url에 id 정보를 추가해주었다.

역시 JSON 형태로 받아올 것이고, 이전 정보에서 Address에 updated를 추가해 주었다.

실행 한 결과 잘 update되었다는 메시지가 출력되고, 200OK 상태인 것으로 보아 put 메서드도 잘 동작하는 것을 확인할 수 있다.

 

잘 수정되었는지 확인하려면 역시 get 메서드를 통해 반환된 값을 확인하면 된다.

 

잘 수정되었다!

 

이제 마지막으로 DELETE 메서드를 이용해 정보를 삭제해보자.

이 역시 특정 Id에 대한 정보를 삭제하기 위함이므로 url에 id정보를 함께 넘겨준다.

c3를 삭제한다. 

실행 결과 잘 delete되었다는 메시지가 출력되고, 200OK상태인 것을 통해, delete 메서드가 잘 동작했음을 확인할 수 있다.

 

잘 삭제 되었는지 get을 통해 확인해보자.

c3가 삭제되었기 때문에, get을 통해 불러도 아무것도 반환하지 않는 것을 확인할 수 있다.

 

이로써 간단한 CRUD를 구현해 보았고, 잘 동작하는 것을 확인하였다.

 

다음 회차↓↓↓

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

 

반응형
반응형

인텔리제이에서 스프링부트 프로젝트 실행은 잘 되는데, 중지 버튼만 누르면 이런 오류 메시지가 뜨나요?

Execution failed for task ':RestDemoApplication.main()'.
> Build cancelled while executing task ':RestDemoApplication.main()'

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.4/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 32s
3 actionable tasks: 1 executed, 2 up-to-date

 

대충 읽어보면 gradle 버전이 호환이 되지 않는다는 내용입니다.

 

실행은 잘 되는데, 멈추기만 하면 이런 오류메시지가 뜹니다.

 

해당 문제는 프로젝트 설정에서 gradle로 실행되게 했기 때문입니다.

 

다음 절차를 따라주세요.

 

1.file > project structure로 들어가세요

 

그런 다음

 

project의 SDK가 java 11로 되어있는지 확인합니다.

 

2. 변경한 설정을 완료한 후 다시 File > Settings를 들어가세요

 그런다음

 

돋보기에 gradle을 검색합니다.

그럼 위와 같은 창이 뜹니다.

튜브가 위치한 곳의 언어를 IntelliJ IDEA로 변경해주세요

아마 처음에는 gradle로 초기값이 설정되어 있을거에요!

그리고 아래 gradle JVM역시 자바 11버전으로 설정해주세요

 

다 하신 후에는..

3. file > open > 현재 프로젝트의 build.gradle을 누르세요

 

그런다음 Open as Project로 열어주신 후 실행하시면 됩니다.!

 

 

그 결과...

 

 

 

실행을 멈추어도 오류 메시지가 뜨지 않고 finish 되었네요!

반응형
반응형

유튜브 '삼평동 연구소'의 영상을 보고 인터페이스의 개념을 조금이나마 이해할 수 있었다.

https://youtu.be/a6F7rIKaxzo 

 


인터페이스가 뭔지는 아는데, 설명해보라고 하면 애매한 개념..
그래서 정리해봤다!!

 

인터페이스??

 

인터페이스는 하나의 약속이다.

무슨 약속이냐?

A라는 것을 하면 B라는 결과가 나온다는 약속을 정의한 것이다.

즉, "A→(약속=인터페이스)→B" 와 같은 중간다리 역할을 한다.

이것만으로는 감이 잘 오지 않을 수 있어, 예를 준비했다.

 

실생활에는 생각보다 인터페이스가 많다.

예를 들면, 리모컨 전원 버튼이 있다.

TV를 켜고 싶다. 그러면? 우리는 리모컨을 개발한 사람이 만든 "리모컨 전원 버튼"을 눌러서 TV를 켠다.

따라서 "리모컨 전원 버튼"은 인터페이스가 된다.

또 다른 예를 들어보자.

화장실에서 볼일을 보고, 물을 내리고 싶다. 그러면? 우리는 변기의 "레버"를 눌러서 물을 내리게 된다.

따라서 "변기 레버"는 인터페이스가 된다.

 

프로그래밍으로도 예시를 들어보자.

LampOn()이라는 함수가 있다고 하자.

램프를 켜기위해서 LampOn()함수를 호출했더니, 램프가 켜진다.

따라서 "LampOn()"함수는 인터페이스가 된다.

함수도 일종의 인터페이스라고 할 수 있다.

 

이처럼 개발자가 만들어 놓은 환경에서 어떠한 입력을 했을 때, 어떠한 출력이 나오도록 중간에서 구현한 것을 인터페이스라고 한다.

 

동영상에서는 "고기집 인터페이스"를 통해 개념을 더욱 쉽게 설명하고 있다.

설명이 아주 맘에 들어서 정리해보겠다.

 

 

고기집 인터페이스

 

고기집이 있다.

고기집에 들어가서 "저기요~" 하고 부르니, 네~하고 종업원이 와서 주문을 받는다.

그런데 요즘은 "호출벨"을 누르면,  종업원이 화면에 뜬 테이블 번호를 확인하고, 해당 테이블에 와서 주문을 받는다.

 

여기서 "저기요~"랑 "호출벨"은 인터페이스가 된다. 

앞에서 설명한 것처럼, "저기요~" 하거나, "호출벨"을 누르면, 종업원이 온다는 결과가 있기 때문이다.

 

호출벨이 개발되기 전에는 직접 사람이 "저기요~"하고 불렀는데, 가게가 바쁘면, 어디서 누가 부르는지 모를 수 있고, 시끄러워서 못듣는 문제가 발생할 수 있다.

이러한 구버전 방식, 즉 구버전 인터페이스의 문제점을 보완한 신버전 인터페이스인 "호출벨"이 개발된 것이다.

 

"호출벨"을 누르면 내부 신호를 통해 화면에 해당 테이블의 번호가 찍히고, 띵동~하는 소리도 울린다. 그럼 종업원은 화면 속 테이블 번호를 확인하여 주문을 받을 수 있으므로, 누가 부르는지 모르는 등의 문제를 보완할 수 있다.

 

정리하자면,

고기집에서 손님이 주문을 하기위해 "저기요~" 하고 부르는 구버전 인터페이스 또는, "호출벨"을 눌러서 부르는 신버전 인터페이스를 통해 종업원이 온다.

 

이제 인터페이스의 개념은 확실히 잡혔을 것이다.

 

 

그렇다면, 백엔드 개발에서 많이 언급되는 API는?

 

API??

 

API(Application Programming Interface)는 어플리케이션 층에서의 인터페이스라고 할 수 있다.

즉 일종의 거대한 인터페이스이다.

 

API를 잘 만들기 위해서는 아래 4가지를 따라야 한다.

 

1. 직관적인 입출력

인터페이스의 입력과 출력이 굉장히 직관적이어야 한다.

무슨 의미냐하면..

고기집을 예시로 들겠다.

"호출벨"을 한번 누르면(입력), 종업원이 온다(출력). 이렇게 인터페이스가 설계되어 있다면, 베스트 케이스이다.

정말 심플하고 명확한 입력에, 명확한 출력이기 때문이다.

 그런데, "호출벨"을 세번 연달아 눌러야, 종업원이 온다. 라고 인터페이스를 설계했다던지, "호출벨"을 모스부호 처럼 눌러야, 종업원이 온다. 라고 인터페이스를 설계했다면, 사용자 입장에서 굉장히 모호하고, 입력이 애매하다.

따라서 Simple is Best이다. 인터페이스의 입출력은 정말 직관적이고 심플하게 구현되어야 한다.

현재 나의 인터페이스 혹은 함수도 직관적으로 설계되어 있는지 들여다 볼 필요가 있다.!!

 

2. 성능 요구사항

성능 요구사항은, 출력에 대한 명확성이 있어야 한다.

그러니까 어떤 입력이 들어오더라도, 결과는 어떠한 형태로든 출력이 되어서 사용자에게 정보를 잘 전달해주어야 한다.

어떤 입력이나, 내부 시스템으로 인해 문제가 발생했다고 하자. 그럼 발생한 문제에 대한 에러 메시지를 출력한다던지, 잘 동작했으면 올바른 결과값을 출력한다던지, 반응이 꼭 있어야 한다는 것이다.

그 반응은, 메시지, 문서, 효과음 어떤 것이든 상관없다.

 

3. 하위호환지원

하위호환지원은, 신버전 인터페이스가 있음에도, 구버전 인터페이스 또한 지원하는 것이다.

또 고기집으로 예를 들어보겠다.

"호출벨"이 설치된 고기집이 있다. 이 고기집에 가서 주문을 하기 위해 "저기요~"라고 불렀다. 그런데 아무리 불러도 종업원이 반응이 없다. "호출벨"의 기능을 안써보거나 모르는 손님은, 신버전 인터페이스인 호출벨만으로 주문을 받는 식당에게 화만 나고, 더이상 이 식당을 이용하지 않을 것이다.

이런 융통성 없는 식당 같은 상황이 실제로 개발에서도 이루어진다.

따라서 손님이 "저기요~"라고 불러도 종업원이 와야하고, "호출벨"을 눌러도 종업원이 올 수 있도록 두가지 모두 지원하는 것이 하위호환지원이다.

But!!

실제로는 하위호환지원이 쉽지 않다. 유지 관리가 어렵기 때문에, 개발자들 입장에서 두가지 버전을 다 지원하는 것이 쉬운 일은 아니다. 왜냐하면, 구버전의 문제점이나 성능을 보완하기 위해 신버전을 개발하였는데, 동시에 두 버전을 모두 유지하기위해서는 신버전의 기술을 미처 사용할 수 없는 상황이 오는 등 걸림돌이 될 수도 있다.

 

따라서 현실적으로 신버전 인터페이스만 제공하게 되는 경우가 대부분이라고 한다.

(마이크로소프트사가 window의 여러버전을 하위호환하는 데에는 최고라고 한다.)

 

4. 쉬운 접근성과 대중성

인터페이스는 누구나 알수 있게 만들어야 한다.

만약 인터페이스를 개발했는데, 사용자입장에서 사용하기 너무 어렵고, 뭘 하는 건지 이해하지 못한다면, 안쓰게 된다.

안쓴다는 것은 개발을 안한 것이나 다름없다.

따라서 인터페이스를 개발했다면, 사용자에게 적극적으로 알려야 한다.

고기집을 예로 들면..

"호출벨"이 나오기 전까지, 고기집에서는 "저기요~"라고 부르기만 하면 됐다.

그런데, 아무런 통지나 알림도 없이, 고기집에서 "호출벨"을 테이블에 붙혀 놓았다.

사람들은 처음 보는 "호출벨"이 뭔지도 모르고, 심지어는 눈에 안띌수도 있다.

따라서 있어도 모르니까 "호출벨"을 안쓰게 된다.

"저기요~"하고 부르면, 종업원은 와서 주문을 받고 "다음부터는 "호출벨"을 눌러주세요~"라고 손님들에게 지속적으로 알려주어야 한다. 그래야 손님들은 기억을 하고 학습하여 "호출벨"을 누를 수 있게 된다.

이렇게 인터페이스를 알려주어야 하며, 사용하기 쉽게 해주어야 한다.

 


인터페이스에 대한 개념은 이쯤에서 정리하고, 추가적인 내용들이 생기면 다음 편에 이어서 쓰도록 하겠다.

개인적으로 고기집 인터페이스 설명으로 아주 쉽게 이해할 수 있어 좋은 것 같다. 

반응형
반응형
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됨.

 

 

 

반응형
반응형
💡 WHY?
  • 기존의 스프링부트 프로젝트는 로그인을 하지 않더라도 페이지의 주소만 알면 접근이 가능했던(물론 로그인 정보는 뜨지 않지만) 문제가 있었다.
  • 페이지 접근에 있어서 로그인 한 경우에만 로그인 페이지 그 외의 페이지들을 접근할 수 있도록 하는 기능이 필요하다.
  • 이를 해결하기 위해 '스프링부트 Security'를 적용해보자.

 

아직 spring security에 대한 기본 지식이 많이 부족하므로, 기본적인 것부터 하나씩 추가해보려고 한다.

 

 

Spring Security 기본 설정 시작
  • Gradle에 security 의존성을 추가해준다.
    (gradle 수정 후에는 항상 build(코끼리 누르기)해 줄 것을 잊지말 것!)
implementation 'org.springframework.boot:spring-boot-starter-security'//spring security 추가
  • security 클래스 추가하기.
    config 디렉토리 하에 security 디렉토리를 생성해준 후, LoginIdPwValidator.javaSpringSecurityConfig.java 클래스를 생성해준다.

config 클래스 추가

 

  • 일단 LoginIdPwValidator는 건너 뛴다.

  • SpringSecurityConfig 클래스를 아래와 같이 설정해준다.
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/main", true)//"/"로 가도 됨.
                .permitAll()
                .and()
                .logout();
    }
}
  • SpringSecurityConfig 클래스는 WebSecurityConfigurerAdapter를 상속받아 사용하고, WebSecurityConfigurerAdapter에 정의된 다양한 security함수들을 오버라이딩하여 목적에 맞게 사용할 수 있다.
  • 이 때 @Configuration과 @EnableWebSecurity 어노테이션을 추가해 주면 된다.
  • configure함수를 오버라이딩 한 것이고, 일단은 HttpSecurity 만을 인자로 가지도록 하였다.

 

  • 아래 함수의 역할은 '어떤 URI로 접근하든지 인증이 필요하다'는 것이다.
anyRequest().authenticated()
  • 아래 함수의 역할은 '폼 방식 로그인을 사용할 것'임을 나타내고, 아래 로그아웃도 추가해주었다.
formLogin()
  • 아래 함수의 역할은 '로그인이 완료되면 해당 URI로 이동할 것'임을 나타낸다.
defaultSuccessUrl("/main", true)

 

스프링 시큐리티 예외 처리

현재 상태는 모든 경로에 대해서 사용자 접근이 제한되어 있다. 

로그인 페이지나 소개 페이지 같은 페이지들은 로그인 없이도 접근 가능해야 한다.

따라서 특정 페이지에 대한 경로 제한을 풀어주는 예외처리가 필요하다.

로그인페이지의 URI는 "/"이므로 이 경로에 대해서만 접근을 허용하기 위해 다음과 같은 코드를 SpringSecurityConfig에 추가해 주어야 한다.

'.antMatchers("/").permitAll()'

여기서 .antMatchers("/")는 Spring Security에서 URL 경로에 대한 권한 설정을 하는 부분 중 하나이다. 이 설정은 "/" 경로에 대한 URL 접근을 나타낸다.

여기에 .permitAll()을 붙여주면 모든 사용자가 이 경로에 접근하도록 허용한다.

따라서 이 코드는 로그인페이지에 대해서는 예외적으로 모든 사용자가 로그인을 하지않아도 접근할 수 있게 해준다.

 

허용하고 싶은 페이지가 여러 개라면, .antMatchers("/","/a",..) 이런식으로 경로를 나열해주면 된다.

 

백엔드와 프론트엔드가 분리되지 않은 프로젝트(스프링부트에서 jsp나 타임리프를 붙여서 하나의 프로젝트로 백엔드+프론트엔드를 전부 처리하는 프로젝트)는 css나 이미지 파일 등의 경우 인증이 되지 않은 상태에서도 보여져야 할 수 있다. 이 경우에는 별도로 WebSecurity 하나를 인자로 갖는 configure를 오버라이딩 해서 예외 처리를 할 수 있다.

 

위에서 설명한 두가지 예외처리에 대해 적용하면 아래와 같다.

 

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/").permitAll()//로그인 페이지를 제외하고 나머지 페이지는 접근 금지
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/main", true)//"/"로 가도 됨.
                .permitAll()
                .and()
                .logout();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**");
    }
}

 

내 프로젝트의 경우 /static 폴더 하에 바로 img들이 있고, css 파일이 있으므로 static폴더 하위에 대해 보안 설정에 있어 모두 예외처리를 해주도록 하였다.

 

 

 

 

 

 

오늘은 여기까지.

 

📒 아래의 블로그를 참고함.
https://nahwasa.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-Spring-Security-%EA%B8%B0%EB%B3%B8-%EC%84%B8%ED%8C%85-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0
 

스프링부트 Spring Security 기본 세팅 (스프링 시큐리티)

[ 2023-02-10 추가 ] 스프링부트 3.0 이상에 적용하실 경우 '스프링부트 3.0이상 Spring Security 기본 세팅 (스프링 시큐리티)' 글을 참고해주세요. 버전 상관없이 시큐리티 기본 세팅을 익히실 경우에도

nahwasa.com

 

반응형

+ Recent posts