본문 바로가기

카테고리 없음

24.02.13 마무리

현재 설계하신 도메인에 member와 user가 혼재되어있는것같습니다. 
혼재되어있는 네이밍들을 보편 언어를 지정해보는것은 어떨까요? 도메인 관점에서 사고하고 개발하는데 있어 가장 중요한 첫걸음은 보편언어를 정하는것이라 생각들어요. ""ddd ubiquitous language""를 한번 검색해서 스터디 해보시는것을 추천드려요.
- getPost의 구현 내용을 보았을 때, comment도 함께 전달 객체에 주입하는걸로 확인되는데요. 
보통 두 개 이상의 도메인을 전달 객체에 실어야 한다면, API 재설계를 고려해보는것도 좋은 절차 중 하나에요. 
물론 도메인의 특성마다 다를 수 있겠지만, 코멘트 도메인 같은 경우 매우 많은 데이터가 하위에 존재할 수 있어요. 
따라서, Post를 조회하는 영역에선 Post만 전달객체에 실어주고, postId로 comment를 조회할 수 있는 API를 설계하는것이 좋아보여요.
- 따라서, 서버가 한번에 여러 도메인 데이터를 한번에 내려준다고 좋은 모습은 아니니 적당히 클라이언트와 요청, 응답 계약 모델을 분리하는 사고를 해보시는것을 추천드려요

 

라는 피드백이 왔다. 그래서,

 

package com.example.demo.dto

import com.example.demo.model.Post
import java.time.LocalDateTime

data class PostDto(
    val id: Long,
    val title: String,
    val nickname: String,
    val content: String,
    val createdAt: LocalDateTime,
    val userId: Long,
    val comments: List<CommentDto>  // 이 부분을 추가
) {
    companion object {
        fun from(post: Post, comments: List<CommentDto> = listOf()): PostDto {
            return PostDto(
                id = post.id!!,
                title = post.title,
                nickname = post.nickname,
                content = post.content,
                createdAt = post.createdAt,
                userId = post.userId,
                comments = comments //여기도 추가
            )
        }
    }
}

여기서 user를 쓰고있는걸

data class PostDto(
    val id: Long,
    val title: String,
    val nickname: String,
    val content: String,
    val createdAt: LocalDateTime,
    val memberId: Long, // "userId"를 "memberId"로 수정
) {
    companion object {
        fun from(post: Post): PostDto{
        return PostDto(
                id = post.id!!,
                title = post.title,
                nickname = post.nickname,
                content = post.content,
                createdAt = post.createdAt,
                memberId = post.memberId,
            )
        }
    }
}

이렇게 바꿔준다

 

그리고 

@Entity
@Table(name = "post")
class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,

@Column(name = "title", nullable = false)
var title: String,

@Column(name = "nickname", nullable = false)
var nickname: String,

@Column(name = "content", nullable = false)
var content: String,

@Column(name = "created_at", nullable = false)
var createdAt: LocalDateTime = LocalDateTime.now(),

@Column(name = "user_id", nullable = false)
var userId: Long
)

 

@Entity
@Table(name = "post")
class Post(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,

    @Column(name = "title", nullable = false)
    var title: String,

    @Column(name = "nickname", nullable = false)
    var nickname: String,

    @Column(name = "content", nullable = false)
    var content: String,

    @Column(name = "created_at", nullable = false)
    var createdAt: LocalDateTime = LocalDateTime.now(),

    @Column(name = "member_id", nullable = false) // "user_id"를 "member_id"로 수정
    var memberId: Long // "userId"를 "memberId"로 수정
)

이렇게 멤버로 바꾸고

 

 

 

 

package com.example.demo.service

import com.example.demo.dto.CommentDto
import com.example.demo.dto.PostCreateDto
import com.example.demo.dto.PostDto
import com.example.demo.dto.PostUpdateDto
import com.example.demo.model.Post
import com.example.demo.repository.CommentRepository
import com.example.demo.repository.MemberRepository
import com.example.demo.repository.PostRepository
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service

@Service
class PostServiceImpl(
private val postRepository: PostRepository,
private val memberRepository: MemberRepository,
private val commentRepository: CommentRepository
) : PostService {

@Transactional
override fun createPost(createPostRequest: PostCreateDto, userId: Long): PostDto {
    // 사용자 조회
    val member = memberRepository.findById(userId)
        .orElseThrow { IllegalArgumentException("유효하지 않은 사용자입니다.") }

    // 게시글 생성
    val post = Post(
        title = createPostRequest.title,
        nickname = member.nickName,
        content = createPostRequest.content,
        userId = userId
    )

    // 게시글 저장
    val savedPost = postRepository.save(post)

    // 저장된 Post를 PostDto로 변환 후 반환
    return PostDto.from(savedPost)
}

override fun getPost(postId: Long): PostDto {
    val post = postRepository.findById(postId)
        .orElseThrow { IllegalArgumentException("해당 id의 게시글이 존재하지 않습니다.") }
    val comments = commentRepository.findByPostId(postId)  // postId에 해당하는 Comment 객체들을 조회
    val commentDtos = comments.map { CommentDto.from(it) }  // Comment 객체들을 CommentDto로 변환
    return PostDto.from(post, commentDtos)  // 변환된 CommentDto 리스트를 PostDto의 생성자에 전달
}

override fun getAllPosts(): List<PostDto> {
    val posts = postRepository.findAllByOrderByCreatedAtDesc()
    return posts.map { PostDto.from(it) }
}

override fun updatePost(postId: Long, updatePostRequest: PostUpdateDto, userId: Long): PostDto {
    // 게시글 조회
    val post = postRepository.findById(postId)
        .orElseThrow { IllegalArgumentException("해당 id의 게시글이 존재하지 않습니다.") }

    // 사용자 검사
    if (post.userId != userId) {
        throw IllegalArgumentException("본인이 작성한 게시글만 수정할 수 있습니다.")
    }

    // 게시글 수정
    post.title = updatePostRequest.title
    post.content = updatePostRequest.content

    // 수정된 게시글 저장
    val updatedPost = postRepository.save(post)

    // 저장된 Post를 PostDto로 변환 후 반환
    return PostDto.from(updatedPost)
}


override fun deletePost(postId: Long, userId: Long) {
    // 게시글 조회
    val post = postRepository.findById(postId)
        .orElseThrow { IllegalArgumentException("해당 게시글이 존재하지 않습니다.") }

    // 사용자 검사
    if (post.userId != userId) {
        throw IllegalArgumentException("본인이 작성한 게시글만 삭제할 수 있습니다.")
    }

    // 게시글 삭제
    postRepository.deleteById(postId)
}
}

여기도 유저를

package com.example.demo.service

import com.example.demo.dto.CommentDto
import com.example.demo.dto.PostCreateDto
import com.example.demo.dto.PostDto
import com.example.demo.dto.PostUpdateDto
import com.example.demo.model.Post
import com.example.demo.repository.CommentRepository
import com.example.demo.repository.MemberRepository
import com.example.demo.repository.PostRepository
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service

@Service
class PostServiceImpl(
    private val postRepository: PostRepository,
    private val memberRepository: MemberRepository,
) : PostService {

    @Transactional
    override fun createPost(createPostRequest: PostCreateDto, memberId: Long): PostDto {
        // 사용자 조회
        val member = memberRepository.findById(memberId)
            .orElseThrow { IllegalArgumentException("유효하지 않은 사용자입니다.") }

        // 게시글 생성
        val post = Post(
            title = createPostRequest.title,
            nickname = member.nickName,
            content = createPostRequest.content,
            memberId = memberId
        )

        // 게시글 저장
        val savedPost = postRepository.save(post)

        // 저장된 Post를 PostDto로 변환 후 반환
        return PostDto.from(savedPost)
    }

    override fun getPost(postId: Long): PostDto {
        val post = postRepository.findById(postId)
            .orElseThrow { IllegalArgumentException("해당 id의 게시글이 존재하지 않습니다.") }
        return PostDto.from(post) // 댓글 정보를 반환하는 부분을 제거
    }

    override fun getAllPosts(): List<PostDto> {
        val posts = postRepository.findAllByOrderByCreatedAtDesc()
        return posts.map { PostDto.from(it) }
    }

    override fun updatePost(postId: Long, updatePostRequest: PostUpdateDto, memberId: Long): PostDto {
        // 게시글 조회
        val post = postRepository.findById(postId)
            .orElseThrow { IllegalArgumentException("해당 id의 게시글이 존재하지 않습니다.") }

        // 사용자 검사
        if (post.memberId != memberId) {
            throw IllegalArgumentException("본인이 작성한 게시글만 수정할 수 있습니다.")
        }

        // 게시글 수정
        post.title = updatePostRequest.title
        post.content = updatePostRequest.content

        // 수정된 게시글 저장
        val updatedPost = postRepository.save(post)

        // 저장된 Post를 PostDto로 변환 후 반환
        return PostDto.from(updatedPost)
    }


    override fun deletePost(postId: Long, memberId: Long) {
        // 게시글 조회
        val post = postRepository.findById(postId)
            .orElseThrow { IllegalArgumentException("해당 게시글이 존재하지 않습니다.") }

        // 사용자 검사
        if (post.memberId != memberId) {
            throw IllegalArgumentException("본인이 작성한 게시글만 삭제할 수 있습니다.")
        }

        // 게시글 삭제
        postRepository.deleteById(postId)
    }
}

이렇게 바꾸고

interface PostService {
    fun createPost(createPostRequest: PostCreateDto, userId: Long): PostDto

    fun getPost(postId: Long): PostDto
    fun updatePost(postId: Long, updatePostRequest: PostUpdateDto, userId: Long): PostDto
    fun getAllPosts(): List<PostDto>
    fun deletePost(postId: Long, userId: Long)
}

얘도 유저 아이디를

interface PostService {
    fun createPost(createPostRequest: PostCreateDto, memberId: Long): PostDto
    fun getPost(postId: Long): PostDto
    fun updatePost(postId: Long, updatePostRequest: PostUpdateDto, memberId: Long): PostDto
    fun getAllPosts(): List<PostDto>
    fun deletePost(postId: Long, memberId: Long)
}

 

이렇게

@RestController
@RequestMapping("/posts")
class PostController(private val postService: PostService) {

@PostMapping
fun createPost(@AuthenticationPrincipal user: MemberPrincipal,
               @RequestBody createPostRequest: PostCreateDto): ResponseEntity<PostDto> {
    val userId = user.id
    val postDto = postService.createPost(createPostRequest, userId)
    return ResponseEntity.ok().body(postDto)
}

@GetMapping("/{postId}")
fun getPost(@PathVariable postId: Long): ResponseEntity<PostDto> {
    val postDto = postService.getPost(postId)
    return ResponseEntity.ok().body(postDto)
}

@PutMapping("/{postId}")
fun updatePost(@AuthenticationPrincipal user: MemberPrincipal, @PathVariable postId: Long,
               @RequestBody updatePostRequest: PostUpdateDto
): ResponseEntity<PostDto> {
    val userId = user.id
    val postDto = postService.updatePost(postId, updatePostRequest, userId)
    return ResponseEntity.ok().body(postDto)
}

@GetMapping
fun getAllPosts(): ResponseEntity<List<PostDto>> {
    val postDtos = postService.getAllPosts()
    return ResponseEntity.ok().body(postDtos)
}

@DeleteMapping("/{postId}")
fun deletePost(@AuthenticationPrincipal user: MemberPrincipal, @PathVariable postId: Long): ResponseEntity<Void> {
    val userId = user.id
    postService.deletePost(postId, userId)
    return ResponseEntity.noContent().build()
}
}이녀석도

@RestController
@RequestMapping("/posts")
class PostController(private val postService: PostService) {

    @PostMapping
    fun createPost(@AuthenticationPrincipal member: MemberPrincipal,
                   @RequestBody createPostRequest: PostCreateDto): ResponseEntity<PostDto> {
        val memberId = member.id
        val postDto = postService.createPost(createPostRequest, memberId)
        return ResponseEntity.ok().body(postDto)
    }

    @GetMapping("/{postId}")
    fun getPost(@PathVariable postId: Long): ResponseEntity<PostDto> {
        val postDto = postService.getPost(postId)
        return ResponseEntity.ok().body(postDto)
    }

    @PutMapping("/{postId}")
    fun updatePost(@AuthenticationPrincipal member: MemberPrincipal, @PathVariable postId: Long,
                   @RequestBody updatePostRequest: PostUpdateDto
    ): ResponseEntity<PostDto> {
        val memberId = member.id
        val postDto = postService.updatePost(postId, updatePostRequest, memberId)
        return ResponseEntity.ok().body(postDto)
    }

    @GetMapping
    fun getAllPosts(): ResponseEntity<List<PostDto>> {
        val postDtos = postService.getAllPosts()
        return ResponseEntity.ok().body(postDtos)
    }

    @DeleteMapping("/{postId}")
    fun deletePost(@AuthenticationPrincipal member: MemberPrincipal, @PathVariable postId: Long): ResponseEntity<Void> {
        val memberId = member.id
        postService.deletePost(postId, memberId)
        return ResponseEntity.noContent().build()
    }
}

 

그리고 코멘트도 동일하게 전부 member로 바꿔버린다.

 

그리고 Post를 조회하는 영역에선 Post만 전달객체에 실어주고, postId로 comment를 조회할 수 있는 API를 설계하는것이 좋아보여요. 를 하기 위해서 

@GetMapping("/{postId}/comments")
fun getCommentsByPost(@PathVariable postId: Long): ResponseEntity<List<CommentDto>> {
    val commentDtos = commentService.getCommentsByPostId(postId)
    return ResponseEntity.ok().body(commentDtos)
}

이걸 postcontroller에 넣고

interface CommentService {
    fun createComment(memberId: Long, dto: CommentCreateDto): CommentDto
    fun updateComment(memberId: Long, id: Long, dto: CommentUpdateDto): CommentDto
    fun getComment(id: Long): CommentDto
    fun deleteComment(memberId: Long, id: Long)
    fun getCommentsByPostId(postId: Long): List<CommentDto>// 추가함

}

그리고

@Service
class CommentServiceImpl(
    private val commentRepository: CommentRepository,
    private val postRepository: PostRepository,
    private val likeRepository: LikeRepository,
    private val memberRepository: MemberRepository
) : CommentService {

    // 기존의 CommentServiceImpl 메소드들...

    @Transactional(readOnly = true)
    override fun getCommentsByPostId(postId: Long): List<CommentDto> {
        val comments = commentRepository.findAllByPostId(postId)
        return comments.map { CommentDto.from(it) }
    }
}
코멘트 서비스임플을 이렇게 추가하고
interface CommentRepository : JpaRepository<Comment, Long> {
    // 기존의 CommentRepository 메소드들...
    
    fun findAllByPostId(postId: Long): List<Comment>
}리포지터리에도 이걸 추가한다.

 

그러면 이제

GET/posts는 post만 조회하고 GET/posts/{postId}/comments로 조회를 해야 post에 딸린 댓글을 볼수있다.

 

@Service
class MemberServiceImpl(
private val memberRepository: MemberRepository,
private val jwtPlugin: JwtPlugin,
private val passwordEncoder: PasswordEncoder
): MemberService {
override fun signUp(signUpRequest: SignUpRequest): Member {
if (signUpRequest.nickName == signUpRequest.password) {
throw IllegalArgumentException("비밀번호는 닉네임과 같을 수 없습니다.")
}

    if (signUpRequest.password != signUpRequest.passwordConfirm) {
        throw IllegalArgumentException("비밀번호와 비밀번호 확인이 일치하지 않습니다.")
    }

    if (memberRepository.existsByNickName(signUpRequest.nickName)) {
        throw IllegalArgumentException("이미 존재하는 닉네임입니다.")
    }
    val encodedPassword = passwordEncoder.encode(signUpRequest.password)


    val newMember = Member(nickName = signUpRequest.nickName, password = signUpRequest.password)
    return memberRepository.save(newMember)
}

override fun login(loginRequest: LoginRequest): LoginResponse {
    val member = memberRepository.findByNickName(loginRequest.nickName)
        ?: throw IllegalArgumentException("닉네임 또는 패스워드를 확인해주세요.")

    if (member.password != loginRequest.password) {
        throw IllegalArgumentException("닉네임 또는 패스워드를 확인해주세요.")
    }

    return LoginResponse(
        accessToken = jwtPlugin.generateAccessToken(
            subject = member.id.toString(),  // 사용자의 ID를 subject로 사용
            nickName = member.nickName  // 사용자의 닉네임을 nickName으로 사용
        )
    )
}
override fun existsByNickName(nickname: String): Boolean {
    return memberRepository.existsByNickName(nickname)
}
}

여기에서 encodedPassword가 회색이니까 그걸 해결하려면

로그인안에 있는걸 
if (!passwordEncoder.matches(loginRequest.password, member.password)) {
    throw IllegalArgumentException("닉네임 또는 패스워드를 확인해주세요.")
}이렇게하고

화원가입은
val newMember = Member(nickName = signUpRequest.nickName, password = encodedPassword) // encodedPassword를 불러오게 수정된 부분
        return memberRepository.save(newMember)

 

이미지를 게시글 생성에 넣으려면

@RequestPart(required = false) image: MultipartFile?,

이걸 먼저 postcontroller에 createPost안에 넣어서

@PostMapping
fun createPost(@AuthenticationPrincipal member: MemberPrincipal,
               @RequestBody createPostRequest: PostCreateDto,
               @RequestPart(required = false) image: MultipartFile?
               ): ResponseEntity<PostDto> {
    val memberId = member.id
    val postDto = postService.createPost(createPostRequest, memberId)
    return ResponseEntity.ok().body(postDto)
}

이렇게 하고

package com.example.demo.dto

import com.example.demo.model.Post
import java.time.LocalDateTime

data class PostDto(
    val id: Long,
    val title: String,
    val nickname: String,
    val content: String,
    val createdAt: LocalDateTime,
    val memberId: Long,
    val imageUrl: String?  // 이미지 URL 필드 추가
) {
    companion object {
        fun from(post: Post): PostDto{
            return PostDto(
                id = post.id!!,
                title = post.title,
                nickname = post.nickname,
                content = post.content,
                createdAt = post.createdAt,
                memberId = post.memberId,
                imageUrl = post.imageUrl  // Post의 imageUrl을 PostDto의 imageUrl에 할당
            )
        }
    }
}

 

여기까지 했는데, 어려운데다 시간도 다되서 다른것들을 복습해보기로 했다. 그리고 일할때 가져야할 자세를 교육받았는데, 요약하면

 

 

 

의문, 질문은 공격이 아니니 긍정적으로 받아들이기

 

모호한 표현 말고 생각 제대로 말하기.

무거운 기술 같아요! 말고 코드에 생각 있으면 그사람에게 뭐라하지 말고 만든 코드에 바라는점 이야기

 

코드리뷰는 새 지식도 얻고 좋은 코드를 공유할수있어서 좋다

 

스낵토크는 일과는 관계없는 잡담같은건데 이것도 좋다고 한다.

 

면접은 대화니 답을 외우기보다 왜? 이걸 썻는지를 잘 알아보기

 

리뷰할때는 개선이 필요하다 할때 왜인지 구체적인 이유 대기

 

리뷰할때 할말 없으면 그냥 칭찬하기. 리뷰를 위한 리뷰는 안됨 

 

팀과제할때 루틴짠것도 왜 이렇게 했는지, 어떤 이점을 얻었는지 생각하기