2024.08.29 - [분류 전체보기] - 상품 관리 애플리케이션 만들기 (1) - 3 프로젝트 전체 구조 잡기
상품 관리 애플리케이션 만들기 (1) - 3 프로젝트 전체 구조 잡기
2024.08.28 - [Server/Spring] - 상품 관리 애플리케이션 만들기(1)- 2 데이터 구조 정의 상품 관리 애플리케이션 만들기(1)- 2 데이터 구조 정의2024.08.28 - [Server/Spring] - 상품 관리 애플리케이션 만들기 (1) -
say-hi-world.tistory.com
이전 글에서 상품 추가 api가 기능적으로 잘 작동한다는 것을 확인했다.
하지만 한 가지 더 알아야 할 것이 있다. 바로 dto이다.
dto의 의미와 역할을 먼저 살펴보고, getter와 setter를 어떻게 사용하는 것이 적절한지 알아보자.
dto와 modelmapper
dto(data transfer object)는 말 그대로 '데이터를 전송하는 역할을 가진 객체'를 의미한다.
클라이언트에게 노출되는 데이터 구조와 백엔드 애플리케이션 내부 데이터 구조를 분리하기 위해 사용된다.
컨트롤러 코드와 레포지토리 코드를 살펴보자.
도메인 객체인 product는 표현 계층인 컨트롤러에서도 사용되고,응용 계층인 애플리케이션 서비스와 인프라스트럭처 계층인 레포지토리에서도 사용된다.
현재 상태에서는 문제가 없어 보이지만, product의 필드가 변경되면 표현 계층에서 인프라스트럭처 계층까지 전 계층이 변경의 영향을 받게 될 것이다. 실무에서는 데이터 구조가 바뀌는 일이 자주 일어나고, 때로는 내부적으로 동일한 도메인 객체라 하더라도 상황에 따라 조금씩 다른 데이터를 클라이언트에게 전달해야 할 때도 있다.
이런 상황에 적합한 것이 바로 dto이다.
컨트롤러가 위치하는 표현 계층 패키지에 dto 클래스를 추가한다.
id 필드에 대한 setter과 없다는 점만 빼고는 당연하게도 proudct 클래스와 거의 같다.
(dto를 서비스가 위치하는 응용 계층에 위치시키는 것이 적절하다는 의견도 있다. 중요한 것은 어디든 간에 '일관성'있게 위치시키는 것이다.)
다시 product 클래스로 돌아가보자.
getter는 http 응답을 주기 위해 추가되었던 메서드였다. product는 더 이상 http응답을 주는 데 사용하지 않으므로(대신 dto를 사용할 것이다) , product에 있던 getter는 제거해도 된다. setId 만 남겨두고 나머지 getter들은 제거하였다.
이제 http 응답을 주고 받는데 dto를 쓸 것이므로, 컨트롤러 코드에서 product 도 productdto로 수정하자.
컨트롤러는 서비스에 있는 로직을 사용하므로, 서비스에 있는 product도 product dto로 수정해야 한다.
dto는 표현 계층부터 응용 계층가지 역할을 하고, 그 안쪽까지 전달되지는 않는다. 아직 작성되지 않은 부분인 Product와 ProductDto를 서로 변환하는 코드는 작성하기에 그렇게 어려운 일이 아니다.
생성자를 사용해도 되고, 정적 패토리 메서드라는 이름의 디자인 메서드를 사용해도 되지만, 그 과정에서 product에서 제거했던 getter를 다시 추가해줘야 한다는 문제가 있다.
도메인 객체에 대한 getter는 어쩔 수 없이 추가해야 하는 경우가 생기기 때문에 추가해도 된다.
하지만 여기서는 getter없이 두 클래스를 변환하는 modelmapper 매핑 라이브러리에 대해 알아보자.
getter없이 두 클래스를 변환?
modelmapper?
<Dto와 엔티티>
ProductDto를 Dto라고 하면 Product도 지칭하는 용어가 있지 않을까?
본문에서는 도메인 객체라고 표현했지만, Product는 구체적으로 '엔티티'라고 부를 수 있다.
도메인 객체이면서, id를 가지는 객체를 '엔티티'라고 하고
도메인 객체이면서 id를 가지지 않는 존재를 '값 객체'(value object)라고 부른다.
( dto와 엔티티 변한을 어디서 수행해야하는가도 이슈가 있다. )
modelmapper 외에도 여러 매핑 라이브러리가 있다. 매핑 라이브러리마다 내부적인 구현 방식은 조금씩 다르지만, 사용 방법은 대체로 비슷하다.
modelmapper는 자바에서 제공하는 리플렉션 api를 사용하여 두 클래스 사이의 변환 기능을 제공하는 라이브러리이다. modelmapper와 비슷한 기능을 제공하는 mapstruct 라이브러리도 있지만, 여기서는 modelmapper에 대해서만 살펴본다.
modelmapper는 자바에서 기본으로 제공하는 라이브러리가 아니기 때문에, 메이븐 의존성을 추가해줘야한다.
메이븐 의존성은 pom.xml파일에서 추가한다.
modelmapper를 사용하기 위해 매번 new 키워드로 modelmapper를 생성하는 방법도 고려할 수 있지만,
미리 빈으로 등록한 후 의존성을 주입받아서 사용하는 것이 성능상 더 유리하다.
main 함수가 있는 application 클래스에 다음과 같은 코드를 추가한다. modelmapper 클래스의 인스턴스를 생성한 후 빈으로 등록하는 코드이다.
이를 생성할 때는 바꿔줘야 할 것이 하나 있다. 기본 설정은 매개변수가 없는 생성자로 인스턴스를 생성한 후 , setter 로 필드값들(id 등)을 초기화하여 변환하는 것이다.
setter 없이도 product와 proudctdto를 변환 가능하도록 하려면 getConfiguration()을 통해 설정을 세팅하여 빈으로 등록해야한다.
modelmapper가 private인 필드 (지금은 id를 포함한 모든 필드가 private이다) 에 리플렉션 api로 접근하여 변환할 수 있도록 만들어준다.
컨트롤러에서는 productdto를 사용해 통신하기로 했다만, 서비스에서는 dto를 실제 엔티티로 변환하여 다룰 것이다.
아래 두가지 과정이 필요하다.
1. simpleproductservice로 가서 빈으로 등록한 modelmapper에 대한 의존성을 주입. (새로운 필드 선언과 그에 맞게 생성자 수정)
2. product add 함수 내부에서 인자로 받은 product dto를 product 엔티티로 변환하는 코드 추가. 레포지토리를 호출한 후, 레포지토리루터 반환 받은 savedproduct (id에 대한 값이 설정되어 돌아왔을 것이다)를 다시 dto로 변환하여 dto형태로 컨트롤러에 반환할 수 있도록.
보다시피 dto가 실제 entity로 변해서 다루어져야 할 곳은 레포지토리 뿐이고, 서비스에는 과정에 맞게 적절한 변환이 필요할 뿐이다.
modelmapper 을 사용할 땐 map을 호출해서 사용하면 된다. map 메서드는 두 개의 인자를 필요로 하는데
(변화시킬 대상, 어떤 타입으로 변환할지) 형태로 사용하면 된다. (변화시킬 대상 -> 어떤 타입으로 변환) 이렇게 기억하면 된다.
ex) Product product = modelmapper.map(productDto, Product.class)
ex) ProductDto savedProductDto = modelmapper.map(savedProduct, ProductDto.class)
getter, setter
지금까지 우리는 도메인 객체인 Product에서 getter을 제거했다.
도메인 객체에 대한 getter와 setter가 반드시 필요한 경우가 아니라면 getter와 setter를 사용하지 않고 최대한 표현력 있는 메서드를 사용하는 편을 추천한다.
이런 습관이 도메인 객체를 캡슐화하여 객체지향적인 코드를 만들기에 도움되기 때문이다.
실무에서 사실 getter는 종종 필요할 때가 있다.
데이터베이스에 접근하거나 dto를 엔티티로 변환하는 과정(여기서는 modelmapper 라이브러리를 사용하여 getter의 필요성을 없앴긴 하지만) 에서 말이다.
그러나 setter는 생성자를 사용하거나, 표현력 있는 메서드로 대체가 가능하다.
/
도메인 주도 설계 아키텍쳐 그림을 보다보면 dto로의 의존성 때문에 표현 계층(컨트롤러, dto)과 응용 계층(서비스)이 함께 그려져 있는 경우가 많다. 도메인 주도 설계에서 이야기하는 레이어드 아키텍쳐에는 반드시 지켜야하는 두 가지 의존성의 방향이 있다.
- 도메인 계층은 다른 계층에 의존하지 않아야한다.
- 다른 모든 계층은 인프라스트럭쳐 계층(레포지토리)에 의존하지 않아야한다.
지금까지 우리가 짠 코드의 의존성 방향은 다음과 같다.
서비스 코드를 살펴보면, 표현계층(dto), 도메인계층(엔티티), 인프라스트럭쳐계층(레포지토리)에 모두 의존하고 있다.
레포지토리 코드를 활용해야 실제 저장 로직을 수행할 수 있는데, 레포지토리 코드에 어떻게 의존하지 않게 구성할 수 있을지 고려가 필요하다.
'스터디 > Server' 카테고리의 다른 글
상품 관리 애플리케이션 만들기 (1) - 3 프로젝트 전체 구조 잡기 (0) | 2024.08.29 |
---|---|
JSON을 주고 받는 AJAX 코드 구조 (0) | 2024.08.27 |
비동기 상호작용이란? (2) | 2024.08.27 |
[백엔드 개발에 필요한 최소한의 js 지식] 자바스크립트에서의 함수 (0) | 2024.08.27 |
nodejs (0) | 2024.08.23 |