본문 바로가기

개발 공부

커뮤니티 피드(2) - Post 도메인 개발

* 밑줄: 여기서 처리할 부분

1. 게시글 작성:

  • 사용자가 텍스트를 입력하고
  • 사용자가 공개 대상 (예: 모두 공개, 팔로워 전용)을 선택합니다.
  • 사용자가 게시물을 제출합니다.
    • 단, 게시물의 글자수는 5글자 이상 500자 이하여야 합니다.
// org.communityfeed.post.domain
// Post.java
public class Post {
    // 게시글 아이디
    private final Long id;
    // 작성자
    private final User author;
    // 게시글 내용
    private final PostContent content;
    // 공개 범위
    private PostStatus status;
    
    public Post(Long id, User author, String contentText) {
    	if (author == null) {
        	throw new IllegalArgumentException();
        }
        this.id = id;
        this.author = author;
        this.content = new PostContent(contentText);
        this.status = PostStatus.PUBLIC;
    }
}

// org.communityfeed.post.domain.content
// PostContent.java
public class PostContent {
    private final static int MIN_CONTENT_LENGTH = 5;
    private final static int MAX_CONTENT_LENGTH = 500;
    // 게시글 내용
    private String contentText;
    
    public PostContent(String contentText) {
    	checkText(contentText);
        this.contentText = contentText;
    }
    // 게시글 내용 길이 제한 체크
    private void checkText(String contentText) {
    	if (contentText == null || contentText.isEmpty()) {
        	throw new IllegalArgumentException();
        }
        if (contentText.length() > MAX_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }
        if (contentText.length() < MIN_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }        
    }
}

// PostStatus.java
public enum PostStatus {
    PUBLIC,          // 전체
    ONLY_FOLLOWER,   // 팔로워 한정
    PRIVATE          // 비공개
}
  • Post
    • 역할:
      • 도메인 객체로, 게시글과 관련된 정보를 나타냄
    • 책임:
      • 고유 식별자인 id로 게시글을 식별
      • 작성자 유효성 체크
      • 게시글 내용 캡슐화
      • 게시글의 공개범위 관리
  • PostContent
    • 역할:
      • 게시글 내용을 관리
    • 책임:
      • 게시글 내용에 대한 유효성 체크
      • 게시글 내용을 외부에서 변경 못하도록 하여 불변성 유지

 

2. 댓글 작성:

  • 시용자가 텍스트를 입력하고
  • 사용자가 댓글을 제출합니다.
    • 단, 댓글의 글자수는 100자 이하여야 합니다.
// org.communityfeed.post.domain.comment
// Comment.java
public class Comment {
    // id
    private final Long id;
    // 게시글
    private final Post post;
    // 작성자
    private final User author;
    // 댓글 내용
    private final CommentContent content;
    
    public Comment(Long id, Post post, User author, String contentText) {
    	if (author == null) {
        	throw new IllegalArgumentException();
        }
        if (post == null) {
        	throw new IllegalArgumentException();
        }
        this.id = id;
        this.post = post;
        this.author = author;
        this.content = new CommentContent(contentText);
    }
}

// org.communityfeed.post.domain.content
// CommentContent.java
public class CommentContent {
	private static final int MAX_CONTENT_LENGTH = 100;
    
    private String contentText;
    
    public CommentContent(String contentText) {
    	checkText(contentText);
        this.contentText = contentText;
    }
    // 댓글 내용 길이 유효성 체크
    private void checkText(String contentText) {
    	if (contentText == null || contentText.isEmpty()) {
        	throw new IllegalArgumentException();
        }
        if (contentText.length() > MAX_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }
    }
}
  • Comment
    • 역할:
      • 도메인 객체로, 댓글과 관련된 정보를 포함
    • 책임:
      • 고유 식별자인 id로 댓글을 식별
      • 작성자, 게시글 유효성 체크
      • 댓글 내용 캡슐화
  • CommentContent
    • 역할:
      • 댓글 내용을 관리
    • 책임:
      • 댓글 내용에 대한 유효성 체크
      • 댓글 내용을 외부에서 변경 못하도록 하여 불변성 유지

 

3. PostContent, CommentContent 추상화:

// org.communityfeed.post.domain.content
// Content.java
public abstract class Content {
	String contentText;
    
    protected Content(String contentText) {
    	checkText(contentText);
        this.contentText = contentText;
    }
    // 자식 객체에서 선언할 추상 메서드
    protected abstract void checkText(String contentText);
    
    public String getContentText() { return contentText; }
}

// PostContent.java 수정
public class PostContent extends Content {
	private static final int MIN_CONTENT_LENGTH = 5;
    private static final int MAX_CONTENT_LENGTH = 500;
    // 부모 객체 생성자 호출
    public PostContent(String contentText) {
    	super(contentText);
    }
    // 추상 메서드 구현
    @Override
    protected void checkText(String contentText) {
    	if (contentText == null || contentText.isEmpty()) {
        	throw new IllegalArgumentException();
        }
        if (contentText.length() < MIN_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }
        if (contentText.length() > MAX_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }
    }
}

// CommentContent.java 수정
public class CommentContent {
	private static final int MAX_CONTENT_LENGTH = 100;
    // 부모 객체 생성자 호출
    public CommentContent(String contentText) {
    	super(contentText);
    }
    // 추상 메서드 구현
    @Override
    protected void checkText(String contentText) {
    	if (contentText == null || contentText.isEmpty()) {
        	throw new IllegalArgumentException();
        }
        if (contentText > MAX_CONTENT_LENGTH) {
        	throw new IllegalArgumentException();
        }
    }
}

// org.communityfeed.post.domain
// Post.java
public class Post {
	...
    private final PostContent content;
    ...
    public Post(..., PostContent content) {
    	...
        this.content = content;
        ...
    }
}

// org.communityfeed.post.domain.comment
// Comment.java
public class Comment {
	...
    private final CommentContent content;
    ...
    pulic Comment(..., CommentContent content) {
    	...
        this.content = content;
        ...
    }
}
  • Content
    • 역할:
      • 게시글, 댓글의 내용과 관련된 공통 로직을 제공하는 추상 클래스
    • 책임:
      • 유효성을 검증하는 추상 메서드 정의
      • contentText 제공
      • 자식 객체에서 유효성 검증 로직을 구현하도록 강제
  • PostContent
    • 역할:
      • 게시글의 내용을 관리, 유효성 검증
    • 책임:
      • 추상 메서드를 구현하여 게시글 내용의 길이 유효성 체크
      • 부모 객체(추상화 클래스)를 호출하여 contentText 초기화
  • CommentContent
    • 역할:
      • 댓글의 내용을 관리, 유효성 검증
    • 책임:
      • 추상 메서드를 구현하여 댓글 내용의 길이 유효성 체크
      • 부모 객체(추상화 클래스)를 호출하여 contentText 초기화
  • Post
    • 역할:
      • 게시글과 관련된 정보를 관리
    • 책임:
      • 게시글의 내용, 상태, 작성자 등을 관리
      • PostContent 객체를 이용하여 내용을 위임받아 처리
  • Comment
    • 역할:
      • 댓글과 관련된 정보를 관리
    • 책임:
      • 댓글의 내용, 상태, 작성자 등을 관리
      • CommentContent 객체를 이용하여 내용을 위임받아 처리

 

4. 게시물 상호작용:

  • 사용자는 '좋아요' 버튼을 눌러 게시물을 좋아할 수 있습니다.
    • 본인 게시글은 본인이 좋아요를 누를 수 없습니다.
  • 사용자는 댓글 섹션에 메시지를 입력하여 게시물에 댓글을 달 수 있습니다.
  • 좋아요 개수를 누르면 좋아요를 누른 인원들을 볼 수 있음
  • 댓글 개수를 누르면 댓글 리스트가 보일 수 있음
  • 글 수정 버튼을 누르면 글을 수정할 수 있음
    • 본인이 작성한 글이 아니면 수정 할 수 없음
    • 수정이 된 여부와 수정된 시간을 같이 저장해야 함
  • 댓글 수정 버튼을 누르면 댓글을 수정 할 수 있음
    • 본인이 작성한 댓글이 아니면 수정 할 수 없음
    • 수정이 된 여부와 수정된 시간을 같이 저장해야 함

4-1) 좋아요 기능

좋아요를 누르는 기능과 팔로우하는 기능이 동일하므로 공통클래스를 생성하여 처리

// org.communityfeed.user.domain
// UserFollow.java 삭제

// org.communityfeed.common
// PositiveLikeCount.java
public class PositiveLikeCount {
	private int count;
    
    public PositiveLikeCount() {
    	this.count = 0;
    }
    
    public void increase() {
    	count++;
    }
    
    public void decrease() {
    	if (count <= 0) {
        	return;
        }
        count--;
    }
}

// org.communityfeed.user.domain
// User.java 수정
public class User {
	...
    private final PositiveLikeCount following;
    private final PositiveLikeCount follower;
    
    public User(...) {
    	...
        this.following = new PositiveLikeCount();
        this.follower = new PositiveLikeCount();
    }
    ...
    
    public void follow(User targetUser) {
    	...
        targetUser.increaseFollowerCounter();
    }
    
    public void unfollow(User targetUser) {
    	...
        targetUser.decreaseFollowerCounter();
    }
    // 다른 상태의 User 객체에 스스로 처리하게 하기위해 캡슐화
    private void increaseFollowerCountter() {
    	follower.increase();
    } 
    
    private void decreaseFollowerCounter() {
    	follower.decrease();
    }
    ...
}

// org.communityfeed.post.domain
// Post.java 수정
public class Post {
	...
    private final PositiveLikeCount likeCount;
    ...
    public Post(...) {
    	...
        this.likeCount = new PositiveLikeCount();
        ...
    }
    
    public void like(User targetUser) {
    	// 본인 확인
    	if (author.equals(targetUser)) {
        	throw new IllegalArgumentException();
        }
        likeCount.increase();
    }
    
    public void unlike() {
    	likeCount.decrease();
    }
}

// org.communityfeed.post.domain.comment
// Comment.java 수정
public class Comment {
	...
    private final PositiveLikeCount likeCount;
    
    public Comment(...) {
    	...
        this.likeCount = new PositiveLikeCount();
    }
    
    public void like(User targetUser) {
    	// 본인 확인
    	if (author.equals(targetUser)) {
        	throw new IllegalArgumentException();
        }
        likeCount.increase();
    }
   	
    public void unlike() {
    	likeCount.decrease();
    }
}

 

4-2) 게시글, 댓글 내용 수정 기능

수정 기능이 동일하므로 공통 클래스로 처리

// org.communityfeed.post.domain.common
// DatetimeInfo.java
public class DatetimeInfo {
	private LocalDateTime datetime;
    private boolean isEdited;
    
    public DatetimeInfo() {
    	this.datetime = LocalDateTime.now();
        this.isEdited = false;
    }
    
    public updateEditDatetime() {
    	this.datetime = LocalDateTime.now();
        this.isEdited = true;
    }
    
    public LocalDateTime getDatetime() { return datetime; }
    
    public boolean isEdited() { return isEdited; }    
}	

// org.communityfeed.post.domain.content
// Content.java 수정
public abstract class Content {
	...
    final DatetimeInfo datetimeInfo;
    
    protected Content(...) {
    	...
        this.datetimeInfo = new DatetimeInfo();
    }
    
    public void updateContent(String updateContentText) {
    	checkText(updateContentText);
        this.contentText = updateContentText;
        this.datetimeInfo = updateEditDatetime();
    }
    ...
}

// org.communityfeed.post.domain
// Post.java 수정
public class Post {
	...
    public void updatePost(User targetUser, String updateContent, PostStatus status) {
    	if (!author.equals(targetUser)) {
        	throw new IllegalArgumentException();
        }
        this.content.updateContent(updateContent);
        this.status = status;
    }
}

// org.communityfeed.post.domain.comment
// Comment.java 수정
public class Comment {
	...
    public void updateComment(User targetUser, String updateContent) {
    	if (!author.equals(targetUser)) {
        	throw new IllegalArgumentException();
        }
        this.content.updateContent(updateContent);
    }
}

 

  • PositiveLikeCount (공통 클래스)
    • 역할:
      • 좋아요/팔로우 등 숫자 카운트를 담당하는 클래스.
      • 카운트 값의 증가/감소를 추적하고, 음수로 감소하는 것을 방지.
    • 책임:
      • 카운트 값을 관리 (increase(), decrease()).
      • 공통적으로 사용 가능한 유틸리티 클래스로 설계되어, User, Post, Comment 등에서 재사용.
  • User
    • 역할:
      • 사용자 정보를 관리하고, 다른 사용자와의 팔로우 관계를 처리.
    • 책임:
      • 본인의 팔로잉/팔로워 수 관리 (following, follower).
      • 다른 사용자를 팔로우하거나 언팔로우 (follow(), unfollow()).
      • 팔로워 수를 다른 객체에서 직접 변경하지 못하도록 캡슐화 (increaseFollowerCounter(), decreaseFollowerCounter()).
  • Post
    • 역할:
      • 게시글 작성자(author), 내용(content), 상태(status) 등을 관리.
      • 게시글의 좋아요와 수정 작업을 처리.
    • 책임:
      • 게시글 좋아요 수 관리 (likeCount).
      • 게시글 내용을 수정 (updatePost()).
      • 본인이 아닌 사용자가 좋아요/수정하려고 하면 예외를 발생시켜 데이터 무결성 유지.
  • Comment
    • 역할:
      • 게시글에 대한 댓글 정보를 관리.
    • 책임:
      • 댓글 좋아요 수 관리 (likeCount).
      • 댓글 내용을 수정 (updateComment()).
      • 본인이 작성한 댓글만 수정할 수 있도록 제한.
  • DatetimeInfo
    • 역할:
      • 작성 또는 수정된 날짜/시간 정보를 관리.
    • 책임:
      • 생성 시점의 시간 기록.
      • 내용 수정 시 수정된 시간 업데이트 (updateEditDatetime()).

 

 

출처: https://fastcampus-community-feed.notion.site/883fa62553224723a74fa97654bc41e8