[Spring Kotlin API] 게시판 API 만들기
[Spring Kotlin API] 게시판 API 만들기 — #개발자의도구들 #springkotlin #r게시판API 🚨📝 📖 📒✏️💡🔍 AI스쿨 msa기반 java 백...
#개발자의도구들 #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에 필요한 정보는 아래와 같습니다.
id, 작성자, 작성일, 수정일, 조회수, 좋아요 수, 부모 id, tpye -> parentId로 대체
SQL로 작성하면 아래와 같습니다.
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
);
빠른 조회를 위해 인덱스를 지정해줍니다.
CREATE INDEX writer
ON x.boards
(writer);
CREATE INDEX parent_id
ON x.boards
(parent_id);
Code
💭코드의 경우 전체 코드를 넣는 것은 크게 의미가 없을 것 같아. 이를 github 링크로 대체하고, 큰 뼈대만 기록하였습니다.
| 🧑💻 Entiy |
|---|
위 DB 설계를 보며 Entity를 설계 하였습니다.
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기능을 적극 활용해 줍시다.
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가 처리해야할 부분은 아래와 같습니다.
POST: 게시글 작성 기능
GET: 특정 유저의 게시글 받아오기, 특정 게시글 id의 reply목록을 받아오기
DELETE : 특정 게시글 삭제하기
\* 추가로 고려할 부분이 있다면 이후 수정을 진행합니다.
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 |
|---|
@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로 선언만 해주면됩니다.
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 방법에 대해 따로 기술을 하겠습니다. |
|---|


