[45일차] 도메인 엔티티 클래스 정의, 서비스,레포지토리 구현

유태형·2022년 7월 1일
0

코드스테이츠

목록 보기
45/77

오늘의 목표

  1. 도메인 엔티티 클래스 정의
  2. 서비스, 레포지토리 구현



내용

도메인 엔티티 클래스 정의

이전의 DDD설계에서 도메인, 에그리게이트, 에그리게이트 루트에 대해서 알아보았습니다. DDD설계를 객체지향의 클래스와 데이터베이스의 테이블로 각각 동일하게 변환시킬 수 있어야 합니다. Spring Data JDBC는 ORM을 사용하고 데이터베이스는 실제로 테이블형태로 데이터를 주고 받기 때문에 일치시켜야 합니다.



엔티티 설계

엔티티는 데이터 접근 계층에서 사용되는 객체입니다. 엔티티간의 관계는 다음과 같습니다.

  • 1:1 : 둘 중 한곳에서 객체 참조
  • 1:N : 1에서 N을 List<>Set<>, Map<>등의 컬렉션으로 참조
  • N:N : 새로운 객체를 만들어 1:N, N:1관계로 나누어 컬렉션으로 참조합니다.


테이블 설계

데이터 베이스에서의 설계는 관계형 데이터베이스에서 배웠던 외래키로 관계를 쉽게 정의할 수 있습니다.

  • 1:1 : 둘 중 한곳에 외래키
  • 1:N : N에서 1의 주키를 외래키로써 참조
  • N:N : 새로운 테이블을 만들어 1:N, N:1관계로 나누어 외래키로써 참조

Spring 데이터 접근계층 엔티티 - 데이터베이스 테이블 간의 연결을 위해서는 이름을 지정하는 데에 있어 몇가지 규칙이나 속성등이 존재합니다.



애그리거트 객체 매핑 규칙

  1. 모든 엔티티 객체의 상태는 애그리거트 루트를 통해서만 변경할 수 있다.
  • 도메인 규칙의 일관성을 유지하도록 하기 위해서입니다.
  1. 동일한 애그리거트 내에서의 앤티티 객체 참조
  • 동일한 애그리거트 내에서는 엔티티 간에 객체로 참조
  1. 애그리거트 루트 대 애그리 거트 루트 간의 엔티티 객체 참조
  • 애그리거트 루트 간의 참조는 @Id로 참조합니다.
  • 1:1, 1:N 관계일 때 외래키 방식과 동일합니다.
  • N대N 관계일때 객체 참조 방식이 함게 사용됩니다.
@Getter
@Setter
@Table("테이블")
public class 엔티티{
	@Id
    private long id;
    
    private AggregateReference<엔티티,@Id의타입> 다른_애그리거트_루트의_id;
    
    @MappedCollection(idColumn = "엔티티_ID")
    private Set<동일애그리게이트_엔티티> 앤티티s = new LinkedHashSet<>();
}
  • @Table("테이블") : Spring Data JDBC가 자동으로 엔티티명으로 테이블명을 만들어 주지만, 다른 이름으로 테이블명을 정의하고 싶은경우 @Table에너테이션으로 이름을 지정할 수 있습니다.
  • AggregateReference<엔티티,@Id의타입> : 다른 애그리거트의 루트 엔티티와 관계를 정의하기 위해 사용되는 클래스입니다. 제네릭으로 루트 엔티티의 클래스타입과, 루트엔티티의 @Id변수의 타입을 지정합니다.

애그리거트 루트와 애그리거트 루트 간에는 객체로 직접 참조하는 것이 아니라 @Id로 참조합니다.

  • MappedCollection(idColumn = "엔티티ID") : 동일 애그리거트 내의 다른 엔티티 객체가 애그리거트 객체를 참조하기 위한 엔티티ID로 외래키를 지정해 줍니다.(해당 엔티티에서 별도 변수 지정X)
  • Set<동일애그리거트_엔티티> 엔티티s = new LinkedHashSet<>() : 애그리거트 루트에서 동일 애그리거트내 엔티티 객체를 참조하기 위한 참조 변수입니다. 1:N관계이므로 Set<>을 통해 여러 개의 엔티티를 참조할 수 있습니다.
private LocalDateTime createdAt = LocalDateTime.now();

LocalDateTime은 시간을 저장하는 클래스입니다. .now() 메서드로 현재 시간을 저장한 객체를 반환합니다.




서비스, 레포지토리 구현

레포지토리 인터페이스

public interface 레포지토리 extends CrudRepository<엔티티,@Id타입> {
	Optional<엔티티> 쿼리메서드(멤버변수);
    
    @Query("SELECT * FROM ... WHERE 엔티티_ID = "맴버변수")
    Optoinal<엔티티> 메서드(멤버변수);
}

CrudRepository의 기능으 사용하기 위해선 레포지토리가 상속을 받고 있습니다. 또 상속을 받음으로써 인터페이스 구현체를 만들지 않고도 매직 컨테이너가 자동으로 구현을 해줍니다. CrudRepository는 입력,수정,삭제,조회등의 간단한 메서드를 제공합니다.

Optinal<엔티티> 쿼리메서드(멤버변수) 는 SQL문을 직접 입력하여 데이터베이스를 조작하는 방식과 다르게 메서드를 이용하여 데이터베이스를 조작하는 방식입니다.
findBy + WHERE절 컬럼명 + (WHERE절 조건 데이터)형식으로 쿼리메서드를 정의하면 조건에 맞는 데이터들을 테이블에서 조회합니다.

예를들어 Optional<엔티티> findByEmail(String email);로 쿼리 메서드를 정의하였다면 해당 메서드는 SQL로

SELECT *
FROM 엔티티
WHERE EMAIL = email;

이 될것입니다.

주의할점은 자바는 대소문자를 구분하여 카멜표기법을 따르지만 데이터베이스의 칼럼은 대소문자를 구분하지 않아서 언더스코어(_)표기법을 따릅니다. 카멜 표기법의 대문자는 앞에_를 추가하여 언더스코어 표기법으로 치환 가능합니다.

반대로 SQL문을 직접적으로 이용하여 데이터 베이스를 조작할 수도 있습니다. @Query("쿼리문") 에너테이션 사용시 개발자가 정의한 SQL문 대로 데이터베이스를 조작합니다. 또 SQL문에 :멤버변수를 삽입하여 메서드의 메게변수의 값과 동적으로 SQL문을 구성할 수도 있는 동적 쿼리 파라미터(named parameter)를 삽입할 수 도 있습니다.



서비스 클래스

@Service
public class 서비스{
	private final 레포지토리 레포지토리;
    
    //(1)
    public 서비스(레포지토리 레포지토리){
    	this.레포지토리 = 레포지토리;
    }
    //(2)
    public 엔티티 create엔티티(엔티티 엔티티){
    	verfiedExistsEmail(엔티티.getEmail());
        
        return 레포지토리.save(엔티티);
    }
    //(3)
    public 엔티티 update엔티티(엔티티 엔티티){
    	엔티티 find엔티티 = findVerfied엔티티(엔티티.get엔티티Id());
        
        Optional.ofNullable(엔티티.get멤버1())
        	.ifPresent(멤버1 -> find엔티티.set멤버1(멤버1));
        Optional.ofNullable(엔티티.get멤버2())
        	.ifPresent(멤버2 -> find엔티티.set멤버2(멤버2));
            
        return 레포지토리.save(find멤버);
    }
    //(4)
    public 엔티티 find엔티티(long 엔티티Id){
    	return findVerified엔티티(엔티티Id);
    }
    //(5)
    public List<엔티티> find엔티티s(){
    	return (List<엔티티>) 레포지토리.findAll();
    }
    //(6)
    public void delete엔티티(long 엔티티Id) {
        엔티티 find엔티티 = findVerified엔티티(엔티티Id);

        레포지토리.delete(find엔티티);
    }
    //(7)
    public 엔티티 findVerified엔티티(long 엔티티Id){
    	Optional<엔티티> optional엔티티 =
        	레포지토리.findById(엔티티Id);
            
        엔티티 find엔티티 =
        	optional엔티티.orElseThrow(() ->
            	new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
        
        return find엔티티;
    }
    //(8)
    private void verifiedExistsEmail(String email){
    	Optional<엔티티> 엔티티 = 레포지토리.findByEmail(email);
        if(엔티티.isPresnet())
        	throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS));
    }
}
  • (1) : 서비스에 레포지토리를 DI로 주입합니다.(스프링 빈)
  • (2) : 생성시 이미 존재하는지 확인후 엔티티 객체를 데이터베이스에 추가합니다.
  • (3) : 해당 데이터가 데이터베이스에 존재하는지 확인후 존재한다면 매개변수로 주어진 엔티티를 이용하여 값을 수정합니다.
  • (4) : 엔티티 클래스를 이용하여 데이터베이스에 존재하는 데이터를 반환합니다.
  • (5) : CrudRepository의 findAll()은 데이터베이스의 모든 레코드를 엔티티객체로 만들어 반환합니다.
  • (6) : 데이터 베이스에 해당 값이 존재하는지 확인후 데이터베이스에서 제거합니다.
  • (7) : CrudRepositoryfindById(엔티티Id)를 호출하면 Optional<엔티티>객체를 반환합니다.(DB에 데이터가 있을수도 없을수도 있습니다.), orElseThrow()메서드는 해당 Optional객체가 객체를 가진다면 객체를 반환하고 없으면 예외를 발생시킵니다.
  • (8) : 레포지토리에서 정의한 findByEmail(email)은 쿼리 메서드로 매개변수를 이용하여 데이터를 Optional<엔티티>로 반환합니다. 엔티티.isPresent()는 객체가 존재하면 true를 리턴하고 없으면 false를 리턴합니다. 객체가 없어 false를 리턴할시 이전에 정의하였던 커스텀Exception을 실행합니다.



후기

DDD를 기반으로하는 데이터 접근 계층에서의 접근 구현과, 데이터 베이스의 설계를 학습하였습니다. 확실한건 DDD를 완벽히 보단 이런 방식이다라는 감은 잡은것 같습니다. JPA를 학습하게 된다면 더욱 선명해질 것 같습니다.




GitHub

없음!

profile
오늘도 내일도 화이팅!

0개의 댓글