아주 오랜만에 공부 글 작성...
스터디하면서 검색한 내용을 메모
- 스터디 issue :
Spring Data
활용한 CRUD 구성
------------------------------------------------------
Spring Data
데이터 저장소라는 특징은 유지하면서, 데이터 접근을 위한 친숙하고 일관된 Spring 기반의 프로그래밍 모델
- 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌!
데이터 엑세스 기술, 관계형/비관계형 데이터베이스, map-reduce 프레임워크(대용량 데이터 처리를 분산 병렬 컴퓨팅에서 처리하기 위한 목적), 클라우드 기반의 데이터 서비스 등을 쉽게 사용할 수 있게 해줌
- 기존 관계형 DB 뿐만 아니라 폭 넓은 지원
데이터베이스에 대한 하위 프로젝트들을 포함하는 상위프로젝트 개념
특징
- repository 메서드 이름에서 동적쿼리 파생
- 기본 속성을 제공하는 기본 도메인 구현
- auditing 지원 (created, last changed)
- javaConfig or xml 을 통합 쉬운 Spring 설정
- 향상된 Spring MVC 컨트롤러와의 통합
주요 모듈
Spring Data Commons
Spring Data JDBC
- JDBC에 대한 Spring Data repository 지원
Spring Data JPA
- JPA 에 대한 Spring Data repository 지원
Spring Data KeyValue, Spring Data MongoDB, Spring Data Redis, Spring Data REST 등
Spring Data 장단점
장점
- 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌
- repository 메서드 이름에서 동적쿼리 파생
단점
- 어렵다 (각 하위 프로젝트들 사용에 있어서 학습시간이 필요함. 학습범위가 넓음)
Spring Data / JPA 구분 및 정리
Spring Data
- 위 내용과 같이 상위 프로젝트 개념
ORM
- 객체 관계 매핑 (Object Relational Mapping)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- 객체와 관계형 데이터베이스 를 ORM 프레임워크가 중간에서 매핑해준다
JPA
- 자바 ORM 기술 표준. 인터페이스 모음
- 구현체 : Hibernate, EclipseLink 등
- (entityManager로 직접 persist, find 등 실행)
- SQL 중심 개발 -> 객체 중심으로 개발하기 위함
- 역사
자바 과거 진영은 EJB 기슬 표준을 만들었는데 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었음 하지만 너무 복잡하고 기술 성숙도도 떨어졌으며 자바 엔터프라이즈 애플리케이션 서버에서만 동작했음 이때 하이버네이트라는 오픈소스 ORM 프레임워크가 등장했는데 EJB의 ORM 기술과 비교해서 가볍고 실용적인데 다 기술 성숙도도 높았음 또한 자바 엔터프라이즈 애플리케이션 서버 없이도 동작해서 많이 사용하기 시작함 결국 EJB 3.0 에서 하이버네이트를 기반으로 새로운 자바 ORM 기술 표준이 만들어짐 : JPA
- JPA 사용 이유
- 생산성
- 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 됨
- INSERT SQL을 작성하고 JDBC API를 사용하는 지루하고 반복적인 일은 JPA가 대신 처리해 줌
- 지루하고 반복적인 CRUD용 SQL을 개발자가 직접 작성하지 않아도 됨
- 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있음
- 유지보수
- SQL에 의존적인 개발은 SQL을 직접 다루기 때문에 엔티티에 필드 하나만 추가해도 관련된 등록, 수정, 조회 SQL 과 결과를 매핑하기 위한 JDBC API 코드 모두 변경해야한다.
- 반면 JPA를 사용하면 이런 과정을 JPA가 대신 처리해주므로 수정해야할 코드가 줄어든다.
- 또한 JPA가 패러다임의 불일치 문제를 해결해주므로 객체지향 언어가 가진 장점들을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있음
- 패러다임의 불일치 해결
- JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해줌
- 성능
- JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공함
- 애플리케이션과 데이터베이스 사이에 계층이 하나 더 있으므로 최적화 관점에서 시도해볼 수 있는 것들이 많음
- 데이터 접근 추상화와 벤더 독립성
- 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많음
- JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 함
- 표준
- 자바 진영의 ORM 기술 표준임
- 표준을 사용하면 다른 구현 기술로 손쉽게 변경할 수 있음
- 생산성
Hibernate
- ORM 프레임워크
- JPA의 구현체
- 내부적으로 JDBC API를 사용
Spring Data JDBC vs Spring Data JPA 비교
Spring Data JDBC
Spring Data JDBC는 JDBC 기반 repositories 구현을 쉽게 해줌
JPA는 다양하고 편리한 기능을 제공하는데, 해당 기능이 단점이 될 수도 있음. 특정 작업이 수행되는 이유를 파악하기 어려울 수 있다. 단순한 개념도 JPA에선 어려울 수도 있다.
- ex) 예상하지 못한 Entity 정보까지 가져올 수 있음
- ex) lazy loading 발생이 언제하는 지 바로 파악하기 어려울 수 있음. 연관이 깊게 되어있는 경우 JPA가 제공하는 기능이 오히려 어려워질 수 있음
그래서 Spring Data JDBC는 아래의 설계로 simple한 개념에 목표로 함
- entity가 로드되는 시점에 SQL문이 실행되고 entity 로딩이 완료됨, 엔티티를 save 해야 저장된다
- vs JPA
- No EntityManager (PersistenceContext)
- No Caching (1차 캐시)
- No Lazy Loading
- Aggregate를 정리하고 복합조회용 객체를 분리하면 Lazy loading이 필수일지 한번 더 생각해볼 수 있다.
- 반대로 Lazy loading이 있어서 깊은 객체 그래프의 Aggregate를 설계하고자하는 유혹에 빠질 수도 있다.
- Lazy loading이 복잡한 엔티티, 슈퍼 엔티티가 나올 수 있는 함정이 있음
- Lazy loading이 필요하다는 것은 모델링을 다시 생각해봐야한다는 신호일 수도 있다
- No Dirty Tracking
- No Proxy
- JPA에서는 dirty tracking, lazy loading, cache 이런 것들을 하기 위해 proxy 를 만들고 있음
- 위의 것들은 Spring Data JDBC의 방향성과 맞지 않는다
Domain Driven Design
- 모든 Spring Data 모듈은 Domain Driven Design 의 'repository', 'aggregate', 'aggregate root' 개념에서 영감을 얻었음
- aggregate 는 원자적인 변경간에 일관성을 보장하는 엔티티의 그룹
- Transaction, Lock의 필수 범위
- 불변식(Invariants, 데이터가 변경될 때마다 유지돼야 하는 규칙)이 적용되는 단위
- ex) OrderItems가 있는 Order
- Order 속성은 변경 내용에 따라 일관성을 유지함
- ex) Order의 numberOfItems 속성은 OrderItems 의 수와 일치함
- 각 aggregate 에는 aggregate 의 엔티티 중 하나인 aggregate root 가 1개씩 있음
- aggregate는 오직 aggregate root의 메서드를 통해서만 조작됨 -> 이것들이 앞서 언급된 원자적인 변경들임
- root를 통해서만 조작이 되므로 일관성을 유지시킬 수 있음
- 일반적으로 Spring Data의 경우 aggegate root당 1개의 Repository 를 가짐 (AGGREGATE 1개당 REPOSITORY 1개)
- Spring Data JDBC 의 경우 aggregate root에서 접근할 수 있는 모든 엔티티가 해당 aggregate root의 일부로 간주됨을 의미함
- aggregate root 를 통해서 aggregate 밖에서 aggregate 안의 객체로 접근함
- aggregate 경계가 있는 시스템의 경우
- 분리할 계획이 없더라도 코드를 고칠 때 영향성을 파악하기가 유리하다
- 별도의 저장소나 API 서버를 분리할 때 상대적으로 유리
- aggregate 별로 Cache 를 적용하기에도 좋다
- aggregate 간의 참조
- 다른 aggregate 의 root를 직접 참조하지 않고 ID로만 참조하기
- Spring Data JDBC 에는 같은 역할을 하는 AggregateReference 라는 기능을 제공하고 있음 (id로 참조)
프레임워크는 설계를 거드는 역할이 되어야 한다
- 편의성이 주는 기능이 설계를 해치는 지 경계해야 한다
- 때로는 다른 가치를 위해 더 긴 코드를 만들 수도 있다
- 고치기 쉬운, 협업하기 쉬운, 확장하기 쉬운 코드를 위해서!
구현체 : SimpleJdbcRepository
구현 예시
Spring Data JPA
- 스프링 프레임워크와 JPA 기반 위에 JPA를 편리하게 사용하도록 도와주는 기술
- JPA를 추상화시킨 Repository 라는 인터페이스를 제공함
- JpaRepository 구현체를 쭉 타고 들어가보면 SimpleJpaRepository 로 가는데 잠깐 소스 보면 entityManager를 쓰고 있음 (em.find(~~~))
- (vs JDBC : SimpleJdbcRepository)
- entity의 변경사항 추적 (lazy loading)
- lazy loading: 필요 시점까지 리소스 로딩을 연기하다가 필요할 때 로딩하는 것
- 구현체 : SimpleJdbcRepository
참고자료
- Reference
- https://www.geeksforgeeks.org/introduction-to-the-spring-data-framework/
- 우아한 CRUD : https://www.youtube.com/watch?v=cflK7FTGPlg
- Domain-Driven Design with Relational Databases Using Spring Data JDBC : https://www.youtube.com/watch?v=GOSW911Ox6s
------------------------------------------------------
DDD
DDD ?
- Domain-Driven Design
- 해당 도메인과 일치하도록 소프트웨어를 모델링하는 데 중점을 둔 소프트웨어 설계 접근 방식이다
- 데이터 중심의 접근이 아니라 Domain 에 집중함
DDD 개념의 구성 요소 (DDD START 책)
ENTITY (엔티티)
- 고유 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
- 주문, 회원, 상품 과 같이 도메인의 고유한 개념을 표현함
- 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
- 관계형 모델의 엔티티와 같은 것이 아님!
- 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공함
public class Order { // 주문 도메인 모델의 데이터 private OrderNo number; private Orderer orderer; ... // 도메인 기능 제공 public void changeShippingInfo(ShippingInfo shippingInfo) { ... } }
- 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체
- 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경하는 것을 막는다
- 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공함
- 고유 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
VALUE (밸류)
- 고유의 식별자를 갖지 않는 개체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
- 배송지 주소를 표현하기 위한 주소나 구매 금액을 위한 금액과 같은 타입이 밸류 타입
- 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다.
- 고유의 식별자를 갖지 않는 개체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
AGGREGATE (애그리거트)
- 도메인이 커질 수록 많은 엔티티와 밸류가 출현하는데, 상위 수준에서 모델을 관리하지 않고 개별 요소에만 초점을 맞추다보면, 큰 수준에서 모델을 이해하지 못해 큰 틀에서 모델을 관리할 수 없는 상황에 빠질 수 있음
- 도메인 모델에서 전체 구조를 이해하는 데 도움이 되는 것이 aggregate
- aggregate는 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
- 주문과 관련된 '주문', '배송지 정보', '주문자', '주문 목록', '총 결제 금액'의 하위 모델을 '주문' 이라는 상위 모델로 묶을 수 있다
- aggregate를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라 볼 수 있게 됨
- aggregate root
- aggregate는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다
- 루트 엔티티는 aggregate에 속해 있는 엔티티와 밸류 객체를 이용해서 aggregate가 구현해야할 기능을 제공함
- aggregate 를 사용하는 코드는 aggregate root가 제공하는 기능을 실행하고 aggregate root를 통해서 간접적으로 aggregate 내의 엔티티나 밸류 객체에 접근한다
- ex) 주문 aggregate는 Order 를 통하지 않고는 ShippingInfo 를 변경할 수 있는 방법을 제공하지 않는다
- aggregate root가 제공하는 메서드는 도메인 규칙에 따라 aggregate에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다.
REPOSITORY (리포지터리)
- 도메인 모델의 영속성을 처리한다
- DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공한다.
- repository는 aggregate 단위로 도메인 객체를 저장하고 조회하는 기능을 정의함
- Order 는 aggregate 에 속한 모든 객체를 포함하고 있으므로 결과적으로 aggregate 단위로 저장하고 조회함
- aggregate는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 repository는 aggregate 단위로 존재한다.
- ex) Order 와 OrderLine 을 물리적으로 각각 별도의 DB 테이블에 저장한다고해서 Order와 OrderLine을 위한 repository를 각각 만들지 않는다.
- Order 가 aggregate root고 OrderLine은 aggregate에 속하는 구성요소이므로 Order를 위한 repository만 존재한다
- 도메인 모델의 영속성을 처리한다
DOMAIN SERVICE (도메인 서비스)
- 특정 엔티티에 속하지 않은 도메인 로직을 제공한다
- '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 등급 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.
- 특정 엔티티에 속하지 않은 도메인 로직을 제공한다
책에 나온 JPA 관련 내용
- JPA는 @manytoone, @OnetoOne 과 같은 애너테이션을 이용해 연관된 객체를 로딩하는 기능을 제공하고 있으므로 필드를 이용해 다른 aggregate를 쉽게 참조할 수 있다.
- ORM 기술 덕에 aggregate root 에 대한 참조를 쉽게 구현할 수 있고 필드를 이용한 aggregate 참조를 사용하면 다른 aggregate 데이터를 쉽게 조회할 수 있다.
- 하지만 필드를 이용한 aggregate 참조는 다음 문제를 야기할 수 있다
- 편한 탐색 오용
- 한 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면 다른 애그리거트의 상태를 쉽게 변경할 수 있게 된다
- 한 애그리거트에서 다른 애그리거트의 상태를 변경하는 것은 애그리거트 간의 의존 결합도를 높여서 결과적으로 애그리거트의 변경을 어렵게 만든다
- 성능에 대한 고민
- 지연로딩, 즉시로딩 고민
- 확장 어려움
- 부하를 분산하기 위해 하위 도메인별로 시스템을 분리할 때 다른 애그리거트 참조하기 위한 기술을 사용할 수 없음
- 편한 탐색 오용
- 이런 세 가지 문제를 완화할 때 사용할 수 있는 것이 ID를 이용해서 다른 aggregate 를 참조하는 것이다.
- ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체만 참조로 연결된다.
- 이는 aggregate 의 경계를 명확히하고 aggregate 간 물리적인 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다.
- 참조하는 aggregate가 필요하면 응용 서비스에서 ID를 이용해서 로딩하면 된다.
- ID를 이용한 참조 방식을 사용하면 복잡도를 낮추는 것과 함께 한 aggregate에서 다른 aggregate를 수정하는 문제를 근원적으로 방지할 수 있다.
- aggregate 별로 다른 구현 기술을 사용하는 것도 가능해진다.
- ex) 주문 aggregate는 RDBMS, 상품 aggregate 는 NoSQL 등
------------------------------------------------------
왜 Spring Data JDBC 는 Lazy Loading, Dirty Tracking 을 안할까?
- Spring Data JDBC 는 ddd 철학을 가지고 나왔기 때문임
- ddd는 Aggregate root 을 통해서만 변경을 추구함
- JPA가 제공하는 편리한 기능은 예상하지 못한 곳에서 변경이 발생함. 일관성이 깨질 수도 있다?
- Spring Data JDBC는 객체와 관계형 데이터베이스 간의 매핑을 최소화하기 위한 기술임. 따라서, 객체지향 프로그래밍에서 일반적으로 사용되는 Lazy Loading, Dirty Tracking과 같은 기능을 제공하지 않는다.
- 그럼 Spring Data JPA 에는 왜 있을까?
- 해당 기능은 영속성 컨텍스트(Persistence Context) 에 의해 가능함
- 생긴 배경은, 객체-관계 매핑(ORM)이라는 기술과 연관 있는데 객체지향 프로그래밍 언어와 관계형 데이터베이스의 데이터 모델 사이의 불일치를 해결하기 위함임
Aggregate 랑 Aggregate root 차이점
- Aggregate는 객체 모델링에서 도메인 객체들을 논리적으로 묶어서 관리하는 단위이며, Aggregate root는 Aggregate를 구성하는 루트 개체
- Aggregate
- 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
- ex) 주문, 배송지 정보, 주문자, 주문목록, 총 결제 금액 등의 모델들을 '주문'이라는 상위 모델(aggregate)로 묶을 수 있다.
- Aggregate root
- 군집에 속한 객체를 관리하는 root
- Aggregate root는 다른 객체에서 참조할 수 있는 진입점(entry point) 역할을 하며, Aggregate root를 통해서만 Aggregate 내부의 다른 객체에 접근할 수 있습니다. 이렇게 Aggregate root가 중심이 되어 Aggregate 내부의 객체들은 일종의 클로저(closure)처럼 동작함
- Aggregate root는 또한 Aggregate 내부의 일관성(invariant)을 보장하는 책임도 가지고 있음. 즉, Aggregate root를 통해서만 Aggregate 내부의 객체들의 상태를 변경할 수 있으며, Aggregate root의 변경으로 인해 Aggregate 내부의 객체들의 상태가 불일치하는 경우를 방지함
- root entity 는 Aggregate에 속해 있는 엔티티와 벨류 객체를 이용해서 Aggregate가 구현해야할 기능을 제공한다.
- ex) 주문 Aggregate 는 Order(root) 을 통해서만 ShippingInfo를 변경할 수 있다. 이를 통해 ShippingInfo가 일관성 없게 바뀌는 것을 막아줌
Spring Data JDBC는 왜 ManyToOne, ManyToMany 를 미지원할까?
- Spring Data JDBC 는 Entity 설계 시 Aggregate 개념 적용을 강하게 주장한다.
- Aggregate 개념에 위배된다 (필요하지 않은 개념이다)
- Aggregate는 오직 Aggregate root의 메서드를 통해서만 조작된다. 다른 Aggregate 의 데이터 변경이 필요하다면 Aggregate root 를 통해서 접근되어서 수정이 발생해야 한다.
- = 하나의 Aggregate는 하나의 Repository 를 통해서 영속성을 관리한다.
- 위 Aggregate 개념에서 다른 Aggregate 객체 접근할 수 있으면 다른 Aggregate 의 상태를 쉽게 변경할 수 있다
- 한 Aggregate에서 다른 Aggregate 상태를 변경하는 것은 Aggregate 간의 의존 결합도를 높여서 결과적으로는 Aggregate 의 변경을 어렵게 만든다