간단한 API를 직접 만들어보자(2)

2024. 5. 16. 20:55TIL

 

🎯 오늘 진행한 내용

card의 controller작성 및 dto작성에 이어서 user의 controller와 dto를 작성

package org.example.spartatodolist.domain.user.controller

import org.example.spartatodolist.domain.user.dto.SignUpRequest
import org.example.spartatodolist.domain.user.dto.UpdateUserProfileRequest
import org.example.spartatodolist.domain.user.dto.UserResponse
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class UserController {

    @PostMapping("/signup")
    fun signup(@RequestBody signupRequest: SignUpRequest): ResponseEntity<UserResponse> {
        TODO()
    }

    @PutMapping("/users/{userId}/profile")
    fun updateUserProfile(@PathVariable userId:Long, @RequestBody updateUserProfileRequest: UpdateUserProfileRequest):ResponseEntity<UserResponse> {
        TODO()
    }
}

 

 

이제 card와 user의 dto작성이 끝났으니 service layer를 구현할 차례이다

package org.example.spartatodolist.domain.card.service

import org.example.spartatodolist.domain.card.dto.CardResponse
import org.example.spartatodolist.domain.card.dto.CreateCardRequest
import org.example.spartatodolist.domain.card.dto.UpdateCardRequest

interface CardService {

    fun getAllCardList():List<CardResponse>

    fun getCardById(cardId:Long):CardResponse

    fun createCard(request: CreateCardRequest) : CardResponse

    fun updateCard(cardId:Long, request: UpdateCardRequest)

    fun deleteCard(cardId:Long)
    
}

구현해야할 함수의 목록을 interface형태로 작성해놓은 다음 

 

package org.example.spartatodolist.domain.card.service

import org.example.spartatodolist.domain.card.dto.CardResponse
import org.example.spartatodolist.domain.card.dto.CreateCardRequest
import org.example.spartatodolist.domain.card.dto.UpdateCardRequest
import org.springframework.stereotype.Service

@Service
class CardServiceImpl:CardService {
    override fun getAllCardList(): List<CardResponse> {
        TODO("Not yet implemented")
    }

    override fun getCardById(cardId: Long): CardResponse {
        TODO("Not yet implemented")
    }

    override fun createCard(request: CreateCardRequest): CardResponse {
        TODO("Not yet implemented")
    }

    override fun updateCard(cardId: Long, request: UpdateCardRequest) {
        TODO("Not yet implemented")
    }

    override fun deleteCard(cardId: Long) {
        TODO("Not yet implemented")
    }

}

그 interface를 구현하는 class를 생성한다

 

controller와 servie를 연결해 보자

class CardController(
    private val cardService: CardService
)

card controller의 생성자로 cardService를 연결하면 cardService를 구현하고 있는 CardServiceImpl 로 자동으로 연결이 된다.

 

controller와 service를 연결한 후에 각 기능에 맞춰서 연결을 하는 작업을 진행한다

@RequestMapping("/cards")
@RestController
class CardController(
    private val cardService: CardService
) {

    @GetMapping()
    fun getCardList(): ResponseEntity<List<CardResponse>>{
        return ResponseEntity.status(HttpStatus.OK).body(cardService.getAllCardList())
    }

    @GetMapping("/{cardId}")
    fun getCard(@PathVariable cardId: Long): ResponseEntity<CardResponse>{
        return ResponseEntity.status(HttpStatus.OK).body(cardService.getCardById(cardId))
    }

    @PostMapping()
    fun createCard(@RequestBody createCardRequest: CreateCardRequest): ResponseEntity<CardResponse>{
        return ResponseEntity.status(HttpStatus.CREATED).body(cardService.createCard(createCardRequest))
    }

    @PutMapping("/{cardId}")
    fun updateCard(@PathVariable cardId: Long, @RequestBody updateCardRequest: UpdateCardRequest):ResponseEntity<CardResponse>{
        return ResponseEntity.status(HttpStatus.OK).body(cardService.updateCard(cardId, updateCardRequest))
    }

    @DeleteMapping("/{cardId}")
    fun deleteCard(@PathVariable cardId: Long): ResponseEntity<Unit>{
        cardService.deleteCard(cardId)
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
    }
}

 

 

각 함수의 return 타입을 ResponseEntity<card정보의 dto>로 지정했기때문에 

return또한 ResponseEntiy타입을 해준 형태이다. 

return타입을 CardResponse dto가 아닌 ResponseEntity로 해준 이유는

ResponseEntity.status(HttpStatus.OK)

 status 상태 코드를 함께 반환하기 위해서이다

 

@DeleteMapping("/{cardId}")
fun deleteCard(@PathVariable cardId: Long): ResponseEntity<Unit>{
    cardService.deleteCard(cardId)
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

CRUD중 삭제를 담당하는 deleteCard 함수같은경우는 

삭제에는 return이 필요가 없기 때문에 ResponseEntity<Unit>을 작성하고

상태코드로는 NO_COTENT 표시와

body()안의 내용도 필요가 없기때문에 build()를 작성하면 된다.

 

 

여기까지 controller의 기본적인 작성이 끝났으니 다음에는 예외처리에 대해서 간단하게 작성해보자

프로그램이 실행되다가 잘못된 Id값을 받는다거나 다양한 오류사항을 접하는 경우가 많기 때문에 예외처리를 하는 과정이 꼭 필요하다. 

package org.example.spartatodolist.domain.exception

import org.example.spartatodolist.domain.exception.dto.ErrorResponse
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice

@RestControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(ModelNotFoundException::class)
    fun handleModelNotFoundException(e:ModelNotFoundException): ResponseEntity<ErrorResponse> {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse(message=e.message))
    }
}

 

package org.example.spartatodolist.domain.exception

data class ModelNotFoundException(val modelName:String,val id:Long):RuntimeException(
    "Model $modelName not found with given id: $id"
)

예외처리는 기본적으로 Exception()을 상속받으면 되는데 지금 경우에는 발생할수도 있고 , 발생하지 않을 수도 있는 프로그램 실행 상황에서의 예외처리를 하기 위해서 RuntimeException을 상속받은 상태이다.

즉 위의 코드는 예외처리가 발생했을경우에 어떤id에서 어떤 model 발견되지 않았는지를 알게 해주는 코드이며

@RestControllerAdvice 어노테이션을 통해 GlobalExceptionHandler class가 전역적으로 예외처리를 한다고 지정해준다.

예를들어  1번카드와,2번카드,3번카드만 있는 상황에서 4번카드를 조회하거나 삭제를 할려고 하면 ModelNotFoundException의 예외처리를 발동한다

 

@Transactional 추가하기

만약 삭제를 하거나,생성을 하거나,수정을 하는 도중에 오류가 발생한다면 모두 롤백이 되어야만 할 것이다.

예를들어 user가 card를 생성할때 (제목:NULL, 내용: "즐거운 하루였다 ") 식으로 어긋난 정보를 입력했을때 생성이 되어서는 안된다. 제목이 반드시 존재해야하는데 제목은 없고 내용만 있는 부분적인 정보가 담긴 card가 생성되서는 안되기 때문에  CRUD중 C(생성),U(수정),D(삭제)는 @TRANSACTIONAL 어노테이션으로 지정해줘야한다 

@Transactional 어노테이션을 사용하기 위해 db관련 패키지를 build.gradle.kts안에 작성하고

implementation("org.springframework.boot:spring-boot-starter-data-jpa")

 

각 C U D 의 기능을 수행하는 함수에 @Transactional 어노테이션을 추가해준다

@Transactional
override fun createCard(request: CreateCardRequest): CardResponse {
    TODO("Not yet implemented")
}

@Transactional
override fun updateCard(cardId: Long, request: UpdateCardRequest): CardResponse {
    // modelNotfound예외처리 
    TODO("Not yet implemented")
}

@Transactional
override fun deleteCard(cardId: Long) {
    // modelNotfound예외처리
    TODO("Not yet implemented")
}

 

 

@Transactional은 db와 관련된 어노테이션이기 때문에 데이터베이스를 연결시켜야한다

intellij에서 DB와 연결을 하는 방법은 우측의 Database 아이콘을 클릭

 

 

해당하는 SQL타입인 PostgreSQL을 선택

 

연결할 DB생성

 

 

intellij와 DB 연결 완료

 

postgreSQL을 사용하기때문에 build.gradle.kts에 설치

runtimeOnly("org.postgresql:postgresql")

 

db의 url주소,이름,비밀번호등을 알려줘야하기때문에 application.yml파일안에 정보를 입력한다

application.properties->application.yml 로 refactor

 

DB와의 연결이 끝났으니 이제 데이터들을 DB로 저장을 해야한다

각 DATABASE와의 매핑을 해야하기 때문에 Entity clss를 작성한다

이 코드는 데이터베이스의 card 테이블과 매핑하는 코드이다

package org.example.spartatodolist.domain.card.model

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table

@Entity
@Table(name="card")
class Card (
    @Column(name="title")
    var title:String,

    @Column(name= "description" )
    var description: String? = null,


){

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id:Long?=null
}

 

@Entity
@Table(name="card")

@Entity 어노테이션으로 이 클래스가 데이터베이스의 테이블과 매핑될 class라는 것을 지정하고

@Table 어노테이션으로 이 클래스가 card라는 이름의 Table가 매핑된다고 지정

 

@Column(name="title")
var title:String

title이라는 컬럼과 title을 매핑

@Column(name= "description" )
var description: String? = null

description이라는 컬럼과 description을 매핑

 

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long?=null

@Id 는 해당 속성이 primary key역할을 한다고 선언

@GeneratedValue(strategy = GenerationType.IDENTITY)는 데이터베이스에 위임하여 id값을 자동으로 생성한다

 

이로서 전체적인 controller작성과 비즈니스 로직을 구현하는 service 작성, 그리고 데이터를 저장하는 db와 연결하는repository의 entity 작성까지 전체적인 뼈대를 완성하였다.

 

지금 남은 과정은 구체적인 비즈니스 로직을 구현하는 함수를 작성하는것과 repository를 작성하는것만이 남은 상태이다

 

 

📓 오늘의 한줄

"Comfort makes more prisoners than all the jails combined."

- Grant Cardone -

 

 

'TIL' 카테고리의 다른 글

(알고리즘) 과일장수  (0) 2024.05.18
간단한 API를 직접 만들어보자 (3)  (0) 2024.05.17
간단한 API를 직접 만들어보자(1)  (0) 2024.05.15
알고리즘(푸드 파이트 대회)  (0) 2024.05.14
예외처리를 전역적으로  (0) 2024.05.13