Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2dec599
init commit
Curry4182 Jul 31, 2023
ecd61ec
init commit
Curry4182 Jul 31, 2023
a821939
feat: BaseEntity 및 JpaAuditingConfiguration 설정 파일 추가
Curry4182 Jul 31, 2023
75773a1
feat: ApiResponse 공통 응답 모델 추가
Curry4182 Jul 31, 2023
eff50c3
feat: user 관련 api 기능 추가
Curry4182 Jul 31, 2023
6d92014
feat: post 관련 api 기능 추가
Curry4182 Jul 31, 2023
a157d6f
test: user 테스트 기능 추가
Curry4182 Jul 31, 2023
767a062
test: post 테스트 기능 추가
Curry4182 Jul 31, 2023
e0ffc36
docs: rest docs api 결과 docs 추가
Curry4182 Jul 31, 2023
e684a26
docs: post.adoc 내용 수정
Curry4182 Jul 31, 2023
0db39ee
refactor: cursor방식으로 post를 반환할 때 client에 필요한 정보만 넘길 수 있도록 수정
Curry4182 Aug 5, 2023
f74b8d9
refactor: AllargsConstructor를 사용하지 않도록 수정, 클래스 이름 접미사DTO를 Dto로 모두 수정
Curry4182 Aug 5, 2023
c7107c4
refactor: 사용하지 않는 setter는 삭제, setter의 이름을 set 대신 의도를 파악할 수 있는 이름으로 수정
Curry4182 Aug 5, 2023
a9824bc
refactor: 공통적으로 사용하는 부분은 @BeforeAll이 붙어있는 setUP메서드에서 실행될 수 있도록 수정
Curry4182 Aug 5, 2023
aac52b4
refactor: 클래스의 객체에 직접 접근하지 않고 메서드를 통해 속성을 변경할 수 있도록 수정
Curry4182 Aug 7, 2023
3f95c3c
feat: dto와 entity에 유효성 검사 기능 추가, global exception 처리 기능 추가
Curry4182 Aug 7, 2023
54c57af
refactor: post에 update 메서드 추가, setter 메서드 private로 접근 외부에서 안되게 수정
Curry4182 Aug 7, 2023
abdc8c8
refactor: 단순 조회일 경우 Transactional을 readonly로 변경
Curry4182 Aug 7, 2023
e279941
refactor: Lob 어노테이션 대신 Text를 사용하도록 수정
Curry4182 Aug 7, 2023
5171145
refactor: 전체적으로 ApiResponse를 응답하도록 수정
Curry4182 Aug 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.programmers.heheboard.domain.post;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 최근부터 AllargsConstructor를 지양하는 방향으로 생각해서 사용하고 있는데요, 그 이유에 대해서도 한번 생각해보면 좋을 것 같아요!

Copy link
Author

@Curry4182 Curry4182 Aug 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 AllargsConstructor가 일으키는 버그에 대해 검색해 보고 AllargsConstructor를 사용하지 않도록 전체적으로 수정 하였습니다! f74b8d9

public class CreatePostRequestDto {
private String title;
private String content;
private Long userId;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 생각했을때, 필드를 final로 선언하지 않으면 데이터의 조작가능성이 생길 수도 있어서, Dto객체는 final로 선언하는 것이 좋다고 생각합니다 ! 어떻게 생각하실까요 ?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 저도 다시 생각 해보니 그렇게 생각해서 Dto 객체를 record 클래스로 변경 하였습니다! f74b8d9


public Post toEntity() {
return Post.builder()
.title(title)
.content(content)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.programmers.heheboard.domain.post;

import java.util.Objects;

import com.programmers.heheboard.domain.user.User;
import com.programmers.heheboard.global.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name = "posts")
@Entity
@Getter
@NoArgsConstructor
public class Post extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(strategy = GenerationType.AUTO) default라 생략해도 될 것 같아요 의도하신거라면 패쓰

private Long id;

@Column(nullable = false)
private String title;

@Lob
private String content;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lob 어노테이션은 처음 알아가네요 ! 배워갑니다 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2;
@Lob는 내부에서 추가로 별도의 조회를 하여 느릴 수도 있다고 합니다. 컬럼 타입을 text 로 직접 명시해서 사용하는 것은 어떤가요~?

참고


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

@Builder
public Post(String title, String content) {
this.title = title;
this.content = content;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1
Builder로 Post객체를 생성할 시에, nullable하지 않은 필드에 대한 검증(Notnull)이 이루어지지 않은 것 같습니다.
이는 DB에 영속화 시키기 이전까지 잡기 힘든 에러인것 같은데, 유효성 검증로직을 추가하는 것은 어떨까요 ?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자에서 유효성 검사를 하도록 수정하였습니다! 3f95c3c


public void setUser(User user) {
Copy link

@BeommoKoo-dev BeommoKoo-dev Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3
요즘 setter의 사용에 관심이 많은데요, 한 번에 메소드의 사용의도를 파악하기 힘든 setUser보다는,
협업에서도 사용 가능한 메소드이니 allocate / attach User같은 의미있는 메소드명은 어떠실까요 ?!

Copy link
Author

@Curry4182 Curry4182 Aug 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set 대신 의도를 쉽게 파악할 수 있도록 수정하였고 사용하지 않는 setter도 전체적으로 삭제하였습니다~! c7107c4

if (Objects.nonNull(this.user)) {
this.user.getPosts().remove(this);
}

this.user = user;
user.getPosts().add(this);
Copy link

@BeommoKoo-dev BeommoKoo-dev Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1
만약 user가 갖고있는 post의 자료형이 list가 아닌 hashMap 등으로 변경되면 setUser 메소드도 수정이 추가되어야 할 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부 자료형을 모르더라도 추가, 삭제할 수 있도록 수정하였습니다! aac52b4

}

public void changeTitle(String newTitle) {
this.title = newTitle;
}

public void changeContents(String newContent) {
this.content = newContent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.programmers.heheboard.domain.post;

import org.springframework.data.domain.Slice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.programmers.heheboard.global.ApiResponse;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/posts")
public class PostController {

private final PostService postService;

@ExceptionHandler(Exception.class)
public ApiResponse<String> internalServerErrorHandler(Exception e) {
return ApiResponse.fail(500, e.getMessage());
}

@PostMapping
public ApiResponse<PostResponseDTO> save(@RequestBody CreatePostRequestDto createPostRequestDto) {
PostResponseDTO postDto = postService.createPost(createPostRequestDto);
return ApiResponse.ok(postDto);
}

@PutMapping("/{post-id}")
public ApiResponse<PostResponseDTO> updatePost(@RequestBody UpdatePostRequestDto updatePostRequestDto,
@PathVariable("post-id") Long postId) {
PostResponseDTO postResponseDTO = postService.updatePost(postId, updatePostRequestDto);
return ApiResponse.ok(postResponseDTO);
}

@GetMapping("/{post-id}")
public ApiResponse<PostResponseDTO> getOnePost(@PathVariable("post-id") Long postId) {
PostResponseDTO postDto = postService.findPost(postId);
return ApiResponse.ok(postDto);
}

@GetMapping
public ApiResponse<Slice<PostResponseDTO>> getAllPost(@RequestParam int page, @RequestParam int size) {
Slice<PostResponseDTO> posts = postService.getPosts(page, size);
return ApiResponse.ok(posts);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3
slice를 그냥 반환하면 프론트에서 식별하기 힘든 정보들 (ex : Pageable type : "INSTANCE")같은 정보들도 반환되는 것으로 알고 있는데, 프론트단에서 좀 더 알아보기 쉬운 필드만 갖고 있는 Wrapper 클래스를 하나 만들어 주는 건 어떨까요 ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slice를 그대로 반환하지 않고 클라이언트에 필요한 정보만 반환될 수 있도록 수정하고 restdocs도 수정하였습니다~! 0db39ee

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.programmers.heheboard.domain.post;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> {

Slice<Post> findSliceBy(Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.programmers.heheboard.domain.post;

import java.time.LocalDateTime;

import lombok.Builder;
import lombok.Getter;

@Getter
public class PostResponseDTO {
private String title;
private String content;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;

@Builder
public PostResponseDTO(String title, String content, LocalDateTime createdAt, LocalDateTime modifiedAt) {
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.modifiedAt = modifiedAt;
}

public static PostResponseDTO toResponse(Post post) {
return PostResponseDTO.builder()
.title(post.getTitle())
.content(post.getContent())
.createdAt(post.getCreatedAt())
.modifiedAt(post.getModifiedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.programmers.heheboard.domain.post;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.programmers.heheboard.domain.user.User;
import com.programmers.heheboard.domain.user.UserRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PostService {
private final UserRepository userRepository;
private final PostRepository postRepository;

@Transactional
public PostResponseDTO createPost(CreatePostRequestDto createPostRequestDto) {
Post post = createPostRequestDto.toEntity();
User user = userRepository.findById(createPostRequestDto.getUserId())
.orElseThrow(() -> new RuntimeException("User Not Found!"));

post.setUser(user);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3
저는 Post객체에 User는 필수적인 존재라고 생각하여 Post생성자에 user를 넣어주었습니다.
만약 post.setUser(user)메소드를, post를 생성하는 다른 메소드에서 호출하는 것을 깜빡하는 것을 방지하기 위해 생성자 혹은 user세팅을 강제하는 다른 방법을 고안해 보는건 어떨까요 ?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 준혁님과 의논하고 반영하도록 하겠습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post의 생성자에 User를 넣어도 매핑 메서드를 실행하지 않으면 동일한 문제가 발생한다고 생각합니다.


return PostResponseDTO.toResponse(postRepository.save(post));
}

@Transactional
public PostResponseDTO findPost(Long postId) {
Post retrievedPost = postRepository.findById(postId)
.orElseThrow(() -> new RuntimeException("Post Not Found!"));

return PostResponseDTO.toResponse(retrievedPost);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1;
조회 같은 경우에는 성능을 위해 트랜잭션을 읽기 전용 모드로 사용하시는 것이 어떤가요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 꿀팁 감사합니다~ 수정하였습니다~! abdc8c8


@Transactional
public Slice<PostResponseDTO> getPosts(int page, int size) {
PageRequest pageRequest = PageRequest.of(page, size);

return postRepository.findSliceBy(pageRequest)
.map(PostResponseDTO::toResponse);
}

@Transactional
public PostResponseDTO updatePost(Long postId, UpdatePostRequestDto updatePostRequestDto) {
Post retrievedPost = postRepository.findById(postId)
.orElseThrow(() -> new RuntimeException("Post Not Found!"));

retrievedPost.changeTitle(updatePostRequestDto.getTitle());
retrievedPost.changeContents(updatePostRequestDto.getContent());

return PostResponseDTO.toResponse(retrievedPost);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.programmers.heheboard.domain.post;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class UpdatePostRequestDto {
private String title;
private String content;
}