[이웃사이] 요구사항 분석 및 Entity 구현

아양시·2022년 9월 6일
0

이웃사이

목록 보기
2/6

🧾 요구사항 분석

  우리가 만드는 서비스 "이웃사이"는 아파트에 거주하는 사람들이 사용하는 모습을 상상하면 이해하기 쉽다. 같은 라인에 거주하는 사람들을 하나의 단위로 묶어 커뮤니티를 형성하고 긴급 요청 서비스를 제공한다. 또한, 세대별로 하나의 계정이 존재하며, 한 세대 내 즉, 한 계정 내에 여러 개의 프로필을 만들 수 있다. (넷플릭스의 계정 방식을 생각하면 된다.)

기능은 크게 [회원 관리 / 얼굴 인식 및 연령 추정 / 비디오폰 모드 인터페이스 / 공지사항 / 커뮤니티 게시글 / 민원 / 댓글 / 긴급 도움 요청 / 계정 관리 / 프로필 관리] 가 있다.
이 중, 내가 맡은 부분은 공지사항, 커뮤니티 게시글, 댓글이다.

  많은 웹 서비스들에서 흔히 존재하는 게시판과 댓글 기능이지만, 스프링부트를 이용한 백엔드 파트 첫 역할이기 때문에 좋은 경험이라고 생각한다.

설계 시 고려 해야할 것은 다음과 같다.

  • 사용자는 계정과 프로필을 가진다.
  • 사용자에는 일반 사용자관리자가 있으며, 각각 가지는 권한이 다르다.
  • 각 게시글에는 공개범위가 존재한다. (전체공개 / 라인공개)
  • 각 게시글은 댓글을 가질 수 있다.
  • 커뮤니티 게시글은 카테고리를 가진다.

이를 바탕으로 클래스 다이어그램을 그려보았다.

✍️ 클래스 다이어그램


본 서비스의 전체 클래스 다이어그램을 그려봤다.
(개발하면서도 필드나 연관관계 등 계속해서 수정사항이 생겨서 사실 개발이 거의 끝나갈 때쯤 완성했다.)

구현하고 나서 intellij에서 만들어주는 클래스 다이어그램도 생성해봤다.
JetBrains의 IDE들은 모두 해당 기능을 제공해주는 것 같다.


💻 Entity 구현하기

🤔 Board 클래스와 상속

  공지사항과 커뮤니티라는 두 종류의 게시판을 만들어야 했고, 두 도메인에서 중복되는 내용이 많을 것 같아 본능적으로 상위 클래스를 만들고 상속받는 그림을 생각했다. 그래서 Board라는 클래스를 만들어서 제목, 내용, 작성자, 공개범위 등 공통적인 필드를 담았다.

@Getter
@MappedSuperclass
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class Board extends BaseEntity {

    @Id @GeneratedValue
    private Long id;

    private String title;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    private Profile writer;

    @Enumerated(EnumType.STRING)
    private Scope scope;

    protected void setTitle(String title) {
        this.title = title;
    }

    protected void setContent(String content) {
        this.content = content;
    }

    protected void setWriter(Profile writer) {this.writer = writer;}

    protected void setScope(Scope scope) {
        this.scope = scope;
    }

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

    public void changeContent(String content) {
        this.content = content;
    }

    public void changeScope(Scope scope) {
        this.scope = scope;
    }
    
}
  • Board는 테이블을 생성하지 않고, 상속받을 용도로만 사용할 것이기 때문에 abstract class로 선언하고 @MappedSuperclass 어노테이션을 달아줬다.
  • JPA의 관리를 받기 위해서는 최소 protected로 선언된 생성자가 필요하다.
  • 연관관계에서 XtoOne을 설정할 때는 반드시 FetchType.Lazy로 연관관계 객체를 불러오는 시점을 해당 필드를 참조할 때로 설정한다.
  • 필드명에 range, like 등 키워드 및 예약어는 피한다.
  • Enum 타입을 사용할 때는 EnumType.String을 설정해서 DB에 문자열로 값을 저장함으로써 수정에 용이하게 한다.
  • setter는 열어두지 않음으로써 무자비한 데이터 수정을 경계하고, 필요 시에 제한된 범위에서만 가능하도록 설정한다. (위의 경우에서는 하위 클래스의 초기화에 사용하기 위해 protected로 setter를 따로 정의했다.)
  • 필드 수정 기능 구현 시에는 change 등의 키워드를 사용해서 의미를 분명하게 한다.
  • Entity 필드 자료형으로 기본 자료형은 null을 표현할 수 없기 때문에 대신 Wrapper Class를 사용한다.

댓글 기능도 커뮤니티 댓글과 민원 댓글이 있으므로 동일하게 Comment 상위 클래스를 만들고 상속하는 구조로 구현했다.

🤔 Board에서 작성자(writer)의 타입

  본 서비스에서는 사용자의 로그인 종류가 계정과 프로필로 두 가지이기 때문에 작성자라는 필드에는 어떤 타입을 사용해야 할 지가 고민이었다. 처음에는 커뮤니티 게시글의 경우, 한 세대 내에서도 각 구성원들이 각자 독립적으로 작성할 수 있기 때문에 당연히 Profile로 하는 게 맞다고 생각해서 구현했었다.

  하지만 작성자의 라인 정보를 가져올 일이 많았고, 해당 정보는 Account 객체에서 얻을 수 있었다. 따라서 작성자를 Profile로 설정한 경우, 라인 이름을 가져오기 위해서는
Profile -> Account -> Line -> name
의 참조 과정을 거쳐야 했다. 이 과정이 빈번하게 일어나서 성능이 저하되는 것을 고려해 작성자의 타입을 Account로 수정했었다.

  결과적으로 1. 한 세대 내에서도 각 구성원들이 독립적으로 글을 작성할 수 있어야 하고, 2. Account로 타입을 바꿔도 극적인 성능 개선이라고 판단되지 않았기 때문에
작성자의 타입은 Profile로 하되, Line과 House 참조가 잦은 Community 클래스에 writerLineName과 writerHouseName을 추가적으로 저장하기로 했다.
이렇게 하는 경우, 비교적 적게 발생하는 객체 생성 시에 Profile에서 참조를 해서 각 필드에 저장하고, 많이 발생하는 조회 시에는 참조 없이 해당 필드를 읽어주면 되기 때문에 너무 극심한 성능 저하는 피할 수 있을 것이다.

  여기서 배운 것은 조금 중복되는 필드라도 따로 저장하는 것이 더 효율적일 때가 있다는 것이다. 본능적으로 중복이라는 것을 피하는 방향으로 코드를 작성하는데, 약간의 중복은 오히려 낫기도 하나보다.

🤔 댓글 도메인 나누기

  흔히 댓글은 게시글에 종속된 것으로 생각이 되어서, 게시글 도메인 내에서 Entity를 만들고 게시글 필드에 댓글 리스트를 가지는 것으로 구현을 할 수 있는데,
우리는 댓글도 따로 관리할 만한 하나의 도메인으로 판단해서, 댓글을 특정 게시글에 종속시키지 않고 따로 도메인을 나누었다.

Comment 추상 클래스에 댓글 내용, 작성자 등의 필수 정보를 담고, 이를 상속 받아서 연관관계만 설정하는 구조이다. 추후에 댓글 기능이 필요한 다른 게시물을 구현하게 될 경우, 동일하게 Comment를 상속받아 연관관계만 설정해서 사용할 수 있다.




확실히 직접 설계하고 구현해보면서 빠르게 많이 배우는 것 같다.
그리고 함께 느껴지는 설계의 중요성 ,,
가능한 한 초기에 화면 구성과 필요한 데이터를 최대한 정리해야 중간 수정을 많이 덜 수 있을 것이다.

profile
BE Developer

0개의 댓글