[MyBoard #2] 도메인 모델 설계, 어떤 고민을 했을까?

콜 파머가 될 남자·약 20시간 전
0

myboard-dev-log

목록 보기
2/2
post-thumbnail

이번 글에서는 MyBoard 프로젝트의 도메인 모델을 어떻게 설계했는지,
그 과정에서 어떤 고민을 했고 어떤 기준으로 구조를 결정했는지 정리해보려 합니다.


🧱 핵심 도메인 구성

MyBoard는 게시판 서비스로서 다음과 같은 주요 도메인으로 구성되어 있습니다.

  • User: 사용자 계정 정보를 관리
  • Article: 게시글 정보 (제목, 내용, 작성자 등)
  • Comment: 댓글 정보 (내용, 작성자, 게시글 참조)
  • ArticleLike, CommentLike: 좋아요 기록을 위한 조인 테이블

🤔 설계하면서 고민한 지점들

1. 양방향 연관관계 사용 이유

현재는 UserArticle, Comment 간에 양방향 연관관계를 사용하고 있습니다.

// User
@OneToMany(mappedBy = "user")
private List<Article> articles;

// Article
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

이 구조를 선택한 이유는 다음과 같습니다:

  • 양방향 연관은 서비스나 테스트 코드에서 더 유연하게 활용할 수 있음
    • 예: 사용자가 작성한 게시글 목록을 조회하는 경우
    • 단방향보다 조회/응답 설계가 명확해질 수 있음

다만, 양방향 연관관계는 아래와 같은 점을 주의해야 합니다:

  • 순환 참조 문제 발생 가능
    → DTO 변환 시 @JsonManagedReference, @JsonBackReference 또는 ModelMapper 설정이 필요
  • 엔티티 생명주기 관리가 어려워질 수 있음

현재는 JPA 기본 사용법을 익히고 연관 탐색을 실습하는 목적에서 양방향을 적용했지만,
필요에 따라 단방향으로 리팩토링할 계획도 가지고 있습니다.


2. 좋아요 기능의 설계 기준

ArticleLike, CommentLike는 별도의 조인 테이블로 분리했습니다.

  • (article_id, user_id) / (comment_id, user_id) 복합 유니크 제약 조건을 걸어 중복 방지
    → (추후, 비즈니스 로직에서 중복을 체크하는 방식과 DB 유니크 제약의 필요성에 대한 고민을 따로 포스팅할 예정입니다)

이렇게 설계하면 다음과 같은 이점이 있습니다:

  • 사용자가 어떤 글/댓글에 좋아요를 눌렀는지 기록 가능
  • 추후 ‘내가 좋아요한 게시글 목록’ 등의 기능으로 확장 가능

추가로, 성능을 고려하여 likeCount 필드를 도메인에 따로 두었습니다.

@Column(nullable = false)
private long likeCount = 0L;

이는 좋아요 수를 매번 조인해서 계산하기보다,
실시간으로 증가/감소시키는 방식을 통해 빠르게 조회할 수 있도록 하기 위함입니다.


3. 생성 메서드 도입으로 의도 명확화

엔티티 생성 시 단순 생성자 대신 정적 팩토리 메서드를 사용했습니다.

public static User create(String email, String password, String username) {
    return User.builder()
        .email(email)
        .password(password)
        .username(username)
        .build();
}
public static Article create(String title, String content, User user) {
    return Article.builder()
        .title(title)
        .content(content)
        .author(user.getUsername())
        .user(user)
        .likeCount(0L)
        .build();
}

정적 팩토리 메서드는 객체 생성의 명확한 의도를 전달할 수 있고,
추가적인 유효성 검사나 로직 삽입이 용이한 장점이 있습니다.


4. BaseTimeEntity로 공통 필드 관리

모든 엔티티에 공통으로 필요한 createdAt, updatedAt 필드를
@MappedSuperclass로 정의된 BaseTimeEntity를 상속하여 관리하고 있습니다.

@Getter
@MappedSuperclass
public class BaseTimeEntity{
	@Column(nullable = false)
	private LocalDateTime createdAt;

	@Column(nullable = false)
	private LocalDateTime updatedAt;

	@PrePersist
	public void onCreate(){
		this.createdAt = LocalDateTime.now();
		this.updatedAt = this.createdAt;
	}

	@PreUpdate
	public void onUpdate(){
		this.updatedAt = LocalDateTime.now();
	}
}

이렇게 하면 각 엔티티에서 중복 없이
시간 정보를 자동으로 관리할 수 있어 유지보수가 편리합니다.


💬 마무리

이번 도메인 설계에서는 연관관계 방향, 테이블 분리 기준, 생성 방식 등을 고민하며
단순히 돌아가는 코드가 아닌, 유지보수성과 확장성을 고려한 구조를 만들고자 했습니다.

아직 실무 수준의 복잡한 도메인은 아니지만,
향후 단방향 전환, 연관 최소화, 이벤트 기반 구조 전환 등도 실험해볼 계획입니다.

다음 글에서는 JWT 기반 인증/인가 구현기를 공유할 예정입니다.
읽어주셔서 감사합니다 🙌

profile
콜 파머가 개발자라면 사회적 인지도는 어느 정도일까

0개의 댓글