티스토리 뷰

본 게시글은 <도메인 주도 설계 철저입문> 책을 읽고 공부를 위해 기록해둔 게시글입니다.

+ 자바 ORM 표준 JPA 프로그래밍 강의 참고

---

 

도메인 객체에는 엔티티와 값 객체가 있다

-> 도매인 객체를 중심으로 한 개발이 바로 도메인 주도 설계!

 

도메인 객체를 사용함으로써

1. 자기 서술적인 코드 작성 가능

2. 도메인에 변경사항 발생 시 유연한 대처 가능

의 이점을 얻을 수 있다.

 

# JPA에서의 데이터 타입: 엔티티 타입과 값 타입

엔티티 타입: @Entity로 정의된 객체. 식별자를 이용한 추적

값 타입: int, String 등 자바 기본 타입 및 객체. 식별자가 존재하지 않음.

-> 식별자가 존재하지 않으므로 값 변경 시 완전 다른 값으로 대체된다.

 

# 언제 엔티티를 쓰고 언제 값 객체를 쓰나?

- 상황에 따라 케바케

- 생애 주기를 갖지 않거나, 생애 주기가 무의미한 것은 값 객체로 사용해도 무방하다

- 객체간 식별이 필요하다면 엔티티를 사용한다

- 엔티티로 만들어야 하는 것을 착각해서 값 객체로 만들지 말자.. 그냥 웬만하면 엔티티를 만들자

 

 

# 값의 성질

1. 불변

2. 교체할 수 있다

3. 등가 비교 가능

 

1. 불변

- 자바의 기본 타입(int, double..)은 절대로 공유되지 않는다. (값을 복사해서 사용하는 것.)

- 자바의 Wrapper 클래스, String 클래스는 주소값을 참조할 수는 있으나, 변경할 수는 없다.

 

값 타입은 왜 불변하게 만드는가? = 값 타입은 단순하고 안전해야 한다.

-> 공유 참조로 인해 재앙과 같은 상황이 발생할 수 있다 

(나는 딱 한 놈을 수정하려 하는데 같은 객체를 참조하던 다른 애도 딸려서 변경되는 등 치명적인 버그가 생길 수 있다)

= 서로 다른 객체가 동일한 값 타입의 인스턴스를 공유해서 사용하도록 만들면 안된다

-> 값 타입은 반드시 새로운 값 객체를 생성하고 복사해서 사용해야 한다.

 

2. 교체 가능

- 값을 교체한다는 것은 아예 새로운 객체를 만들고, 값을 통으로 갈아끼운다는 것이다.

 

3. 등가 비교 가능

- 값 끼리는 동일성, 동등성 비교가 가능하다

동일성(Identity) 비교: 참조값 비교, == 비교 (JAVA 기본 primitive 타입)

동등성(Equivalance) 비교: 내부 값 비교, equals() (이외 나머지)

 

(동등싱 버교를 하기 위해서는 equals 메소드를 별도로 오버라이딩 해야한다.)

 

++ 토막 상식 ++

왜 IDE에서는 equals() 메소드를 오버리이딩 할 때 hashCode() 메소드도 같이 만들어줄까?

- hash값을 사용하는 컬렉션(HashMap, HashSet 등)은 객체가 동일한지를 판별할 때 hashCode() 메소드를 먼저 사용하고, 같은 경우에만 equals() 메소드롤 사용하기 때문이다.

- 따라서 hashCode() 메소드가 정의가 안되어있으면 equals() 메소드가 true를 반환한다 해도 그 이전에 hashCode() 메소드에서 false가 나버리기 때문에 두 객체를 서로 다른 객체로 인식하게 된다

(따로 hashCode 메소드를 정의하지 않으면 Object에 정의된 hashCode(), 객체의 고유한 주소 값을 변환 후 리턴함)

 

* equals()로 비교한 두 객체가 동일하다면 hashCode() 값도 같아야 한다

 

# 임베디드 타입(Embedded Type)

- 기본 값 타입을 모아 새로운 값 타입을 정의해 사용하는 값 타입

- C언어에서 구조체와 같은 느낌

- 재사용성 굳. 높은 응집도. (값 타입 고유의 메소드 추가 가능)

- 모든 값 타입은 값 타입을 소유한 엔티티에 생명주기를 의존한다. (엔티티가 없어지면 값 타입도 없어진다)

- JPA에서는 @Embedded, @Embeddable 애노테이션을 붙여서 사용한다

(값 타입에는 기본 생성자 (NoArgs~) 필수)

예시

** 임베티드 타입을 사용한다고 해서 테이블이 별도로 생성되는 것이 아니다 (매핑되는 테이블은 같다)

-> 잘 설계된 ORM 애플리케이션은 매핑 테이블의 수 보다 클래스의 수가 더 많은 법이다.

 

** 임베디드 타입과 같은 값 타입은 공유해서 사용할 수 없다. (사용하면 안된다)

-> 따라서 임베티드 값 타입 역시 불변하게 만들어야 한다,

-> 어떻게? = 생성자로만 값을 생성하고, Setter를 사용하지 않게 만든다.

 

# 핵심

1. 도메인 객체에는 엔티티와 값 객체가 있다.

2. 값 타입은 정말 값 타입이라 판단될 때에만 사용하자

= 엔티티로 만들어야 할 것을 값 타입을 만들어서 쓰지 말자.

= 식별자가 존재하고, 지속적으로 추적, 변경이 필요한 객체는 값 타입이 아니라 엔티티로 만들어야 한다.

3. 값 객체를 사용할 때에는 공유참조를 항상 조심하자

 

# 예제

그냥 지나가기는 뭐 해서 학교에서 진행중인 캡스톤 프로젝트에 값 타입을 하나 만들어봤다.

로그인 아이디랑 비밀번호 속성을 하나로 묶어서 Account 값 객체를 만들어서 사용해 보기로 했다.

 

 

얘가 Account, 임베디드 타입 값 객체이다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Embeddable
public class Account {

    @NotBlank
    @Column(nullable = false, unique = true)
    private String loginId;

    @NotBlank
    @Column(nullable = false)
    private String loginPw;

    // private int failCount; --> 로그인 5회 이상 실패 시 1분간 로그인 불가
    // private LocalDateTime lastLoginTime; --> 마지막 로그인 이력

    // 추가적인 메소드가 들어갈 수 있다
    public void changePassword(String newEncodedPassword) {
        this.loginPw = newEncodedPassword;
    }
}

@Setter를 만들지 않고, @NoArgs 생성자는 protected로 제한해 둠으로써 객체가 불변하도록 설정하였다.

값 객체에는 @Embeddable 애노테이션을 붙여서 얘가 값 타입이라는 것을 알 수 있게 해줘야한다.

뭐 기타 id, pw는 당연히 NotNull 해야하고 id는 유니크 해야한다.

@NotBlank 애노테이션 같이 입력 폼 데이터 검증할 때 사용하는 애노테이션도 여기 작성해도 잘 작동한다.

 

 

얘는 엔티티

@Getter
@Entity
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "admin")
public class Admin implements UserDetails {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "admin_id")
    private Long id;

    @NotBlank
    @Column(nullable = false)
    private String name;

    @NotBlank
    @Column(nullable = false, unique = true)
    private String tel;

    @Embedded
    Account account;
    
    ... 기타 등등...
    
}

Account 값 타입을 사용하는 클래스. @Embedded 애노테이션을 붙인다 (생략해도 되긴 한다)

 

 

그리고 중요한거!! 리포지토리

@Repository
public interface AdminRepository extends JpaRepository<Admin, Long> {

    Admin findByAccountLoginId(String loginId);

    @Query("select a from Admin a where a.name like %?1%")
    List<Admin> findByNameContaining(String name);

    Admin findByName(String name);

    Admin findByTel(String tel);
}

JPA를 사용하면 당근빠따 JpaRepository를 많이 사용할텐데 여기서 값 객체 속 속성에 접근하려면 findBy + [값 객체] + [값 객체 속성] 으로 접근하면 된다.

값 객체는 Account, 값 객체 속성은 LoginId이기 때문에 합치면 findByAccountLoginId가 된 것이다

 

만들어보니 나름 괜찮은 것 같긴 하다. 객체지향 냄새가 물씬 풍기는 것 같기도 하구.. ㅎ

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함