▶디렉토리 분리
이런식으로 도메인을 기준으로 디렉토리를 나눠줬다.
공부도 할 겸 헥사고날 아키텍처를 적용해볼까?? 생각도 해봤지만
그건 너무 오버 엔지니어링이라는 생각이 들어서 그냥 도메인을 기준으로 계층형 아키텍처를 선택했다.
위 ERD를 바탕으로 Entity코드를 짰다.
▶BaseEntity
우선 created_at, updated_at, deleted_at은 자주 사용되기 때문에 BaseEntity에 넣어줬다.
@Getter
@MappedSuperclass //다른 엔티티 클래스가 이 클래스를 상속받을 수 있게 함
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "deleted_at")
private LocalDateTime deletedAt;
}
이제 위 필드를 가지고있는 Entity는 위 BaseEntity을 상속받아서 사용하면 된다.
▷ BaseEntity를 사용하는 이유
어떤 코드를 사용한다면,
그 코드를 사용하는 이유를 항상 생각하면서 개발을 하면 좋을 것 같다.
BaseEntity를 사용하면 좋은 이유 -> 중복된 코드 제거 가능 , 유지보수성 향상, 공통 필드 자동 관리
▶ User 도메인
user 도메인의 entity에는 enums에 Provider로 소셜로그인 시 kakao인지 naver인지 설정할 enum을 만들어줬고,
User, LocalUser, SocialUser Entity를 만들어줬다.
public enum Provider {
KAKAO, NAVER
}
우선 enum은 이렇게 만들어줬다.
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
@Table(name = "users")
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String email;
@Column(name = "nickname", nullable = false)
private String nickName;
/*@Column(name = "profile_img")
private String profileImg;*/
}
그 다음으로 LocalUser와 SocialUser에 공통적으로 존재하는 필드들을 모아둔 User Entity 코드이다.
프로필사진 기능은 나중에 추가할예정이기 때문에 주석처리 해주었다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "local_users")
public class LocalUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
@Column(nullable = false)
private String password;
@OneToOne
@MapsId //식별관계 설정
@JoinColumn(name = "id") //user의 id를 참조
private User user;
//비밀번호 변경 메서드
public void changePassword(String newPassword) {
this.password = newPassword;
}
}
다음은 LocalUser 이다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "social_users")
public class SocialUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Provider provider;
@Column(name = "provider_key", nullable = false)
private String providerKey;
@OneToOne
@MapsId //식별관계 설정
@JoinColumn(name = "id") //user의 id를 참조
private User user;
}
이건 socialUser!
User Entity 코드들을 짜면서 했던 고민들을 아래에서 풀어보려고 한다.
▷ @NoArgsConstructor(access = AccessLevel.PROTECTED) ,@AllArgsConstructor(access = AccessLevel.PRIVATE) 를 사용하는 이유
@NoArgsConstructor(access = AccessLevel.PROTECTED)를 사용하는 이유
JPA는 엔티티 객체를 리플렉션을 통해 생성하는데, 이때 기본 생성자가 있어야지 생성이 가능하다.
알다시피 NoArgsConstructor은 기본생성자를 생성해주는 lombok이다. 이때 access레벨을 PROTECTED로 해줌으로써
외부에서 이 생성자를 직접 호출하지 못하게 한다.
JPA에 의해 호출될 수는 있지만, 외부에서 new를 통해 객체를 생성할 수는 없게된다.
-> 객체의 생성 방법을 제한하고, 의도치 않은 사용 방지
@AllArgsConstructor(access = AccessLevel.PRIVATE)를 사용하는 이유
AllArgsConstructor는 모든 필드를 초기화하는 생성자를 만들어주는 lombok이다.
access = AccessLevel.PRIVATE 옵션을 사용해서 이 생성자의 접근 수준을 private으로 설정해줬다.
그 이유는 모든 필드를 초기화하는 생성자가 필요할 때, 외부에서 이 생성자를 직접 호출하지 못하게 하여서,
객체 생성 방식을 제한하고 싶을 때 사용한다.
따라서, 객체를 생성할 때 @Builder 패턴만을 사용하도록 하기 위해서
접근 수준을 private로 설정해준것이다.
그러면 왜 LocalUser와 SocialUser에는 @AllArgsConstructor(access = AccessLevel.PRIVATE)와 @Builder를 사용하지 않았나?
아직 코드 개발을 들어가지 않아서 LocalUser와 SocialUser의 객체를 생성할 일이 있을까??라는 생각이 들었다.
따라서 개발을 하면서 객체를 생성할 일이 있다면, 그때 추가해 줄 생각이다.
▷ @MapsId
@MapsId는 외래키(FK)를 기본키(PK)로 사용하는 경우에 사용되는 어노테이션이다.
즉, 식별관계에서 사용되는 어노테이션이다.
나는 식별관계보다는 비식별관계를 사용해서 FK는 그냥 FK로 사용하는것을 선호하긴 한다.
하지만 이번에 식별관계를 사용한 이유는
1. 데이터 일관성
부모와 자식 엔티티 간의 일대일 관계에서 동일한 식별자를 사용하여 데이터의 일관성을 유지할 수 있음.
2. 생명주기 관리
자식엔티티의 생명주기가 부모 엔티티에 종속되므로, 부모 엔티티가 삭제되면 자식 엔티티도 자연스럽게 삭제되거나 무효화 됨.
이렇게 두가지 정도의 이유가 있다.
▶ Letter 도메인
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
@Table(name = "letters")
public class Letter extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@Column(name = "send_at",nullable = false)
private LocalDateTime sendAt;
@Column(name = "is_opened", nullable = false)
@ColumnDefault("false")
private boolean isOpened;
@Column(name = "d_day", nullable = false)
private Integer dDay;
}
다음으로는 Letter 엔티티이다.
사실 뭐 특별한건 없다. 위에서 설명을 다해서...
여기서 말할거는 @ManyToOne 정도?? 가 있을 것 같다.
아 ManyToOne 들어가기 전에
각 필드의 위에 @Column을 붙여줘야 하나요?? 라는 질문이 있을 수도 있을 것 같아서 말하자면
이건 각 팀마다 정해서 붙여도되고 안붙여도 될 것 같다. 나는 코드의 통일성을 위해 다 붙이는 편이다.
또한 자바에서는 카멜케이스, DB에서는 스네이크케이스를 쓰기 때문에
자바에서 카멜케이스로 네이밍한 필드들 위에는 name을 통해 스네이크 케이스로 명시해주는 편이다.
카멜케이스로 네이밍하지 않는 필드들은 nullable만 체크해준다.
▷ @ManyToOne(fetch = FetchType.LAZY)
ManyToOne이다!
프로젝트를 시작할 때 연관관계 매핑을 정확하게 이해하고 설계하는것이 정말 중요하다고 생각한다.
노션에 혼자 정리를 해놨지만... 좀 더 다듬어서 블로그에 정리해서 올릴 예정이다.
우선 다들 알겠지만 관계를 설정할 때 양방향 관계를 거고 시작하지는 않는다.
왜냐하면 양방향 연관관계는 복잡성을 증가시키고, 유지보수 및 성능 문제를 일으킬 수 있기 때문이다.
이것도 나중에 블로그 글을 하나 올려야겠다...지금은 프로젝트 내용만 전달하겠다!
따라서 나는 우선 단방향으로 설정한 뒤 양방향 매핑이 필요하면 그때 양방향을 설정한다.
다대일 연관관계에서 N쪽이 주인관계, 1쪽이 mappedBy를 통해 주인이 아닌 관계가 되게 양방향을 건다.
하지만 지금은 아직 본격적인 개발에 들어가지 않았으므로 FK를 가지고 있는 테이블에 JoinColumn을 걸어서 단방향 관계를 만들어줬다.
▶ DraftLetter 도메인
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Getter
@Table(name = "draft_letters")
public class DraftLetter extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private String title;
@Column
private String content;
@Column(name = "send_at")
private LocalDateTime sendAt;
}
여기서는 뭐 설명할게 없을 것 같다.
그냥 임시저장이기 때문에 contene와 sendAt은 null이어도 괜찮게 설정한 부분? 정도가 있다.
(@Column의 nullable의 디폴트 값은 true이다. 따라서 @Column만 붙여주면 null이 허용된다.)
이정도로 Entity 코드에 대한 설명과 했던 고민들에 대한 포스팅을 끝내겠다.
혼자 공부한거니... 틀린 부분이 있을수도 있다!
혹시 틀린 부분이 있다면 댓글로 알려주시면 정말 감사하겠습니다!
'프로젝트 > Letter To Me💌' 카테고리의 다른 글
[Letter To Me] API 명세서 작성 완료 - 백엔드 (0) | 2024.08.12 |
---|---|
[Letter To Me] 프로젝트 ERD 설계 - 백엔드 (0) | 2024.08.12 |
[Letter To Me] 미래의 나에게 편지를 보내는 웹 서비스 - 디자인 수정 (0) | 2024.08.11 |
[Letter To Me] 미래의 나에게 편지를 보내는 웹 서비스 Letter To Me - 기획/디자인 (0) | 2024.08.10 |