Spring발행일 2024. 10. 10.원본 https://blog.naver.com/jword_/223612891029 ↗

[Spring Kotlin API] 게시판 API 만들기

[Spring Kotlin API] 게시판 API 만들기 — #개발자의도구들 #springkotlin #r게시판API 🚨📝 📖 📒✏️💡🔍 AI스쿨 msa기반 java 백...

#Spring#Naver Blog

#개발자의도구들 #springkotlin #r게시판API

🚨📝 📖 📒✏️💡🔍

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.

이미지

게시판 API 만들기

모든 서비스의 기본중의 기본인 게시판 구현을 진행하였습니다. Spring JPA를 사용하였으며, 프론트 테스트는 웹 postman으로 진행하였습니다.

Index

  • UI
  • DB Design
  • Code
  • Entity
  • Controller
  • Service
  • Repostiory
  • Question
  • Answer
  • Thinking

UI

여기는 Server project 게시글 이므로 자세한 UI 로직 보다는 wireframe을 통해 필요한 기능들을 정리하였습니다.

이미지

x는 post에 단순 텍스트뿐 아니라, 이미지나 동영상 역시 전송이 가능합니다. Client - Server 모무 ktolin을 사용하기 때문에 Class 형태의 데이터를 Json으로 보내어 통신합니다.

​

이미지

댓글의 경우 Post와 동일합니다. 하지만, parentId를 추가하여 어떤 게시글의 댓글인지 확인합니다.

​

DB Design

위에서 Request에 대부분의 정보가 포함되어 있지 않습니다. 이는 서버에서 필요한 정보를 대부분 붙여서 DB에 저장해주기 때문입니다.

​

Post에 필요한 정보는 아래와 같습니다.

javascript 코드 예제
                                    id, 작성자, 작성일, 수정일, 조회수, 좋아요 수, 부모 id, tpye -> parentId로 대체

​

SQL로 작성하면 아래와 같습니다.

javascript 코드 예제
                                    CREATE TABLE boards(
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    writer BIGINT NOT NULL,  -- 외래 키로 사용할 것
    text_content TEXT NOT NULL,
    file_url TEXT,
    first_writing_time DATETIME NOT NULL,
    last_modified_time DATETIME NOT NULL,
    like_count BIGINT NOT NULL,
    reading_count BIGINT NOT NULL,
    parent_id BIGINT
);

빠른 조회를 위해 인덱스를 지정해줍니다.

javascript 코드 예제
                                    CREATE INDEX writer
ON x.boards
(writer);

CREATE INDEX parent_id
ON x.boards
(parent_id);

Code

💭코드의 경우 전체 코드를 넣는 것은 크게 의미가 없을 것 같아. 이를 github 링크로 대체하고, 큰 뼈대만 기록하였습니다.

🧑‍💻 Entiy

위 DB 설계를 보며 Entity를 설계 하였습니다.

javascript 코드 예제
                                    package com.example.frontServer.entity

import com.example.frontServer.enum.BoardType
import jakarta.persistence.*
import org.springframework.data.annotation.CreatedBy
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime

@Entity
@Table(name = "boards")
@EntityListeners(AuditingEntityListener::class)
data class Board (
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    val writer : Long,

    @Column(name = "text_content")
    var textContent: String,

    @Column(name="file_url")
    var fileUri: String? = null, //

    @CreatedDate
    @Column(name = "first_writing_time")
    var firstWritingDate: LocalDateTime? = null,

    @LastModifiedDate
    @Column(name = "last_modified_time")
    var lastModifiedDate: LocalDateTime? = null,

    @Column(name = "like_count")
    var likeCount: Long = 0,

    @Column(name = "reading_count")
    var readingCount : Long,

    @Column(name = "parent_id")
    var parentId: Long? = null,

)
🧑‍💻 Controller

DTO

UI에서 이야기한 DTO를 먼저 구현하였습니다. @Validation기능을 적극 활용해 줍시다.

javascript 코드 예제
                                    package com.example.frontServer.dto

import jakarta.validation.constraints.NotEmpty
import org.springframework.web.multipart.MultipartFile

data class SaveBoardRequest(
    @field:NotEmpty
    val textContent: String,

    val file: MultipartFile?,

    @field:NotEmpty
    val parentId: Long,
)

Controller가 처리해야할 부분은 아래와 같습니다.

javascript 코드 예제
                                    POST: 게시글 작성 기능
javascript 코드 예제
                                    GET: 특정 유저의 게시글 받아오기, 특정 게시글 id의 reply목록을 받아오기
javascript 코드 예제
                                    DELETE : 특정 게시글 삭제하기

\* 추가로 고려할 부분이 있다면 이후 수정을 진행합니다.

javascript 코드 예제
                                    import com.example.frontServer.dto.*
import com.example.frontServer.security.AuthUserDetails
import com.example.frontServer.service.BoardService
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*

@RestController
class BoardController(
    private val boardService: BoardService
) {
    @PostMapping("/board")
    fun save(
        @Valid @RequestBody saveBoardRequest: SaveBoardRequest
        @AuthenticationPricipal user: AutheUserDetails
    ) : ResponseEntity<SomeToClientDto>{
        // save 로직 구현
    }

    @GetMapping("/boards")
    fun findBoards(): ResponseEntity<List<SometoClientDto>> {
        // 전체 게시글 조회 로직 구현
    }

    @GetMapping("/board")
    fun findBoardById(
        @RequestParam id: Long
    ) : ResponseEntity<SomeToClientDto> {
        // id로 Board 조회 로직
    }

    @DeleteMapping("/board")
    fun delete(
        @RequestParam id: Long
    ): ResponseEntity<SomeToClientDto> {
        // delte 로직
    }
}

\* file 전송은 조금 까다롭기 때문에 여기에 정리해두겠습니다.

\* return은 설명이 더 필요하기 때문에 여기에 따로 기록해두었습니다.

(링크는 업데이트 예정)

🧑‍💻 Service
javascript 코드 예제
                                    @Service
class BoardService(
    val boardRepository: BoardRepository
) {
    fun save(request: SaveBoardRequest, userId:Long): Boolean {
        // userId get from Aturhentication
        boardRepository.save(
            Board(
                writer = userId,
                writer = userId,
                textContent = request.textContent,
                imgUri = imgUrl,
                readingCount = 0,
                likeCount = 0,
            )
        )
    }

    fun findAll(): List<SomeToCtrDto> {
        // logic
    }

    fun findById(id: Long): SomeToCtrDto {
        // logic
    }

    fun deleteById(id: Long): Boolean {
        // write logic
    }
}

적당한 Logic을 구현해주면 되고, parmter와 return 값이 중요합니다. save, delte의 경우 현재는 직접 Boolean으로 return을 하고 있습니다.

​

-\* 이 부분에도 여기에서 보실 수 있습니다.

🧑‍💻 Repostory

우에 해당하는 기능들은 SpringJpa에서 일반적으로 지원하는 기능이기 때문에 interface로 선언만 해주면됩니다.

javascript 코드 예제
                                    package com.example.frontServer.repository

import com.example.frontServer.entity.Board
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface BoardRepository: JpaRepository<Board, Long>, BoardQueryDslRepository {
}

Question

🤔

​

Thinking

프로젝트를 진행하면서 고민한 부분에 대해서 정리해 보았습니다.

🤔 return 값에 대한 고민 ​ Client <- Controller <- Service CRUD에 따른 Return값을 어떻게 줘야할지 항상 많은 고민을 했었습니다. 구현한 원칙은 아래와 같습니다.각 레이어는 반드시 DTO로 전달할 것message를 String 형태로 직접 Return하지 말 것DB의 오류를 너무 신경쓰지 말 것 ​여러 고민을 계속하고, 멘토님께 질의를 통해 얻은 결론을 통해 위 3가지 원칙을 고수하게 되었습니다. 이후 추가로 RFC7807이 제시하는 패턴과 현실적인 return 방법에 대해 따로 기술을 하겠습니다.

​