
디렉토리를 주제별로 나누어 주자. 토비님의 강의에서는 애그리거트 단위로 나눠주는 부분에 해당한다.
일단 기본적으로 글을 올리고, 수정하고, 삭제할 수 있어야 한다(Create, Read, Update, Delete).
포스트 CRUD 기능은 post 디렉토리를 만들어서 넣어주도록 하겠다.
@Entity
@Getter
@NoArgsConstructor(access= AccessLevel.PROTECTED)
@AllArgsConstructor(access= AccessLevel.PROTECTED)
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
private String title;
@Setter
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id")
@JsonIgnore
private Member member;
public static Post of(PublishBlogpostFormRequest request, Member member){
Long id = requireNonNull(request.getPostId());
String title = requireNonNull(request.getTitle());
String content = request.getContent();
return new Post(id, title, content, requireNonNull(member));
}
}
일단 포스트(블로그 글) 엔티티를 만들어 줬다.
- @Entity를 사용해서 JPA(Java Persistence API)의 관리를 받는다. 이로써 데이터베이스에서 포스트 데이터를 가져와서 자바의 포스트 객체를 생성하는 것이 가능하다.
- 롬복의 @Getter를 써서 모든 필드에 get 함수를 만들어줬다. 블로그 글의 id, 제목, 내용을 읽어야 하기 때문이다.
- 롬복의 @NoArgsConstructor를 써서 기본 생성자를 만들어줬다. JPA가 블로그 포스트 객체를 만들 때 기본 생성자가 쓰이며, 필수이다. 대부분의 JPA 구현 라이브러리는 자동으로 생성해주긴 하지만 써주는것이 권장된다. 여기에 protected 옵션을 넣어서 다른곳에서 사용하는 것을 막았다.
- 글 작성자 Member는 레이지로딩을 걸었고 혹시 글 정보를 json으로 보낼 때 순환참조되는 것을 막기 위해서 @JsonIgnore를 넣었다.
만약 데이터베이스가 자동생성이 아니라면 테이블을 만들어주자.
public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findByMember(Member member, Pageable pageable);
}
포스트 리파지토리를 만들었다. 리파지토리는 데이터베이스에서 블로그 포스트 데이터를 가져오거나 포스트를 데이터베이스에 저장할 때 사용되는 클래스이다.
- @Repository 어노테이션은 상속받는 인터페이스에 포함되어 있다. 스프링이 서버가 구동될 때 하나의 리파지토리 객체를 만들고 관리한다.
- 스프링 데이터 JPA를 사용하면 인터페이스만 만들어도 기본적인 리파지토리 클래스를 만들어준다.
- 스프링에서 제공하는 Page 클래스를 사용하면 블로그 홈에서 간편하게 페이지네이션 기능을 쓸 수 있다.
@Service
@RequiredArgsConstructor
public class PostService implements PostFinder, PostModify, PostPermission {
private final MemberRepository memberRepository;
private final PostRepository postRepository;
public Page<Post> getUsersPosts(String userId, Pageable pageable) {
Member member = memberRepository.findByEmail(new Email(userId))
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + userId));
return postRepository.findByMember(member, pageable);
}
public Post getPost(Long postId) {
return postRepository.findById(postId)
.orElseThrow(() -> new PostNotFoundException("Post not found"));
}
public void publishPost(String userId, PublishBlogpostFormRequest request){
Member member = memberRepository.findByEmail(new Email(userId))
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + userId));
Post post = Post.of(request, member);
postRepository.save(post);
}
public void editPost(String userId, PublishBlogpostFormRequest request){
Post post = getPost(request.getPostId());
post.setTitle(request.getTitle());
post.setContent(request.getContent());
postRepository.save(post);
}
public void deletePost(Long postId, Authentication auth){
checkAuthorPermission(postId, auth);
postRepository.deleteById(postId);
}
public void checkAuthorPermission(Long postId, Authentication auth) {
Post post = getPost(postId);
if (!post.getMember().getEmail().address().equals(auth.getName())) {
throw new SecurityException("You are not the author of this post");
}
}
}
서비스 단은 처음엔 안만들려고 했는데 포스팅 기능을 만들면서 DTO도 만들고 변환과정도 넣고 유저 검증로직도 넣고 하다보니까 결국 만들게 되더라.
- 영속성 관리가 되는 객체에 save함수를 쓰는 것만으로 JPA가 데이터 삽입이 아닌 수정을 알아서 해준다.
@ResponseStatus(HttpStatus.NOT_FOUND)
public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(String message) {
super(message);
}
}
그리고 그냥 예외도 하나 추가해줬다.
- 런타임에러(언체크에러)는 함수에 throws를 쓰지 않아도 된다.
- 컨트롤러에서 직접 404를 반환하게 하는 것 보다는 여기서 @ResponseStatus 어노테이션을 쓰거나 @RestControllerAdvice와 @ExceptionHandler를 써서 전역 처리 클래스를 만드는 쪽을 추천함. (서비스가 조회 결과로 null을 반환하면 컨트롤러에서 404로 응답하도록 하는 건 결국 서비스를 컨트롤러에 맞춰서 짜는 것이므로 의존성의 역전임)
@Data
public class PublishBlogpostFormRequest {
private Long postId;
@NotBlank
@Size(min=1, max=50)
private String title;
private String content;
}
글 작성 DTO
- 유저 ID를 포함하지 않는 이유는 어차피 컨트롤러에서 유저 Authentication 체크를 통해서 현재 유저가 누구인지를 알아내는데 프론트엔드에서 해당 정보를 넘겨줄 이유가 없기 때문이다.

세가지 포트를 서비스 하나로 구현한게 조금 그렇긴 한데.
'스프링 부트로 블로그 서비스 개발하기' 카테고리의 다른 글
| 포스트 CRUD 기능 - 컨트롤러단 + API 테스트 (0) | 2025.12.07 |
|---|---|
| 포스트 CRUD 기능 - 테스트 코드 (1) | 2025.12.06 |
| 회원가입 기능 - 테스트 코드 작성 (0) | 2025.12.02 |
| 회원가입 기능 - 컨트롤러단 인풋 검증 (0) | 2025.11.27 |
| 회원가입 기능 - 스프링 시큐리티 커스텀 UserDetailsService + 도커 MySQL 적용하기 (0) | 2025.11.26 |