2024.08.28 - [Server/Spring] - 상품 관리 애플리케이션 만들기(1)- 2 데이터 구조 정의
상품 관리 애플리케이션 만들기(1)- 2 데이터 구조 정의
2024.08.28 - [Server/Spring] - 상품 관리 애플리케이션 만들기 (1) - 요구사항 확인 상품 관리 애플리케이션 만들기 (1) - 요구사항 확인스프링 부트를 사용해 상품 관리 애플리케이션을 만들고자 한다.
say-hi-world.tistory.com
이전 장에서는 상품 클래스에 필요한 필드를 정의한 뒤, 코드를 추가하였다.
이번 장에서는 아래 가지를 수행한다.
1. 상품 추가를 위한 컨트롤러 코드 추가하기.
1-1. 우선, 클라이언트로부터 해당 요청을 받는 컨트롤러가 필요하다.
1-2. 다음, 실제로 상품을 리스트에 추가하는 로직이 필요하다.
2. 프로젝트 전체 구조를 잡기 위해 레이어드 아키텍쳐 이해하기.
3. id 필드 다룰 방법 생각하기.
1. 상품 추가
컨트롤러는 클라이언트로부터 온 요청을 처리하기 위한 백엔드 애플리케이션의 시작점이다.
상품에 대한 요청을 받을 컨트롤러의 이름을 ProductController로 지정한다.
아래와 같이 createProduct()를 만들어
클라이언트로 받은 /product에 대한 POST 요청을 다루도록 한다.
지금까지 작성한 코드를 기반으로 Postman을 돌려보자.
406 Not Acceptable 에러가 발생했다.
검색해보면 콘텐츠 네고시에이션 과정이 실패했을 때 반환되는 상태 코드인 걸 알 수 있는 데,
요청의 Accpet 헤더에서 특정 타입만 받도록 걸러져 있다면 발생할 수도 있다.
그러나 확인해보니 '*/*', 즉 모든 타입을 받겠다는 뜻이다.
그렇다면 무엇이 문제일까?
다시 컨트롤러 코드를 확인해보자.
product 를 반환하도록 되어있는데, product 내부에 있는 필드는 모두 private로 지정되어있다.
즉, 애초에 서버가 클라이언트에게 줄 수 있는게 아무 것도 없다.
해결 방안은 두가지이다.
1. 응답으로 제공하려는 필드의 접근 제어자를 public으로 바꾸기
2. 응답으로 제공하려는 필드에 대한 getter 메서드를 만들기 << 권장!
필드 자체를 public으로 만들어버리면 setter와 getter을 동시에 노출한 것과 마찬가지이므로
2번 해결책보다 캡슐화가 '더' 깨진다고 볼 수 있다. (2번 또한 캡슐화가 깨지는 것은 마찬가지긴 하다.)
관련 내용은 다음 포스팅에서 DTO와 함께 더 자세히 다루었다.
Product 클래스 내부에 private 필드를 받아올 수 있는 getter 함수들을 추가하였다.
postman으로 다시 확인해보니 이제 응답이 잘 돌아온다.
보다시피, 클래스 Product의 인스턴스로 생성된 product의 정보가 json형태로 변환되어 돌아왔다.
1. 인스턴스를 JSON으로 변환해주는 일은 누가 담당하는가?
스프링 프레임워크에는 HTTP 요청에 포함된 메시지를 변환(Convert)하는
HTTP 메세지 컨버터가 있다.
이 HTTP 메세지 컨버터 중 JSON을 변환해주는 것이
'MappingJackson2HttpMessageConverter'이다.
2. 응답에서 id는 왜 null 인가?
요청 바디를 다시 보자. 바디에 id에 대한 정보는 포함되지 않았다.
id는 상품이 추가될 때마다 1씩 증가하는 값이므로, 클라이언트가 지정할게 아니라
서버가 요청을 받아서 상품을 등록하는 시점에 정해져야 할 것이다.
클라이언트로 요청을 받는 것 까진 완료되었다.
이제는 받은 요청 바디에 있는 product에 있는 정보를 자바 리스트에 등록하는 로직을 짤 것이다.
앞서 북마크를 구현했던 코드를 살펴보자.
컨트롤러 내부에 리스트를 생성하였다.
그런데 이렇게 구현하는게 적절할까?
만약 또 다른 컨트롤러에서 즐겨찾에 대한 리스트에 접근하고 싶다면 어떻게 해야할까?
new AjaxRestController()를 사용해 AjaxRestController에 대한 인스턴스를 생성하는 경우에는
서로 다른 각자의 리스트를 가지게 되어 데이터를 공유할 수 없게 된다.
레이어드 아키텍쳐
소프트웨어를 설계할 때 소프트웨어를 여러 개의 계층으로 나누는 것을 레이어드 아키텍쳐라고 한다.
레이어드 아키텍쳐 중 도메인을 주도로 설계하는 DDD에 대해 알아보자.
DDD에서는 소프트웨어를 4가지 계층으로 나누다.
1. 표현 계층 : Product Controller - 컨트롤러가 위치하는 계층. 클라이언트로부터 들어오는 요청을 받고 응답해주는 역할을 한다.
2. 응용 계층 : Simple Product Service- 컨트롤러는 받은 요청을 서비스할 수 있는 서비스 계층으로 전달한다.
3. 도메인 계층 : Product - 상품 클래스 자체가 존재한다.
4. 인프라스트럭처 계층 : ListProductRepository - 상품 저장, 삭제 등의 로직이 존재한다.
인프라스트럭처 계층과 응용 계층의 차이가 애매모호하다.
둘 다 실제 상품에 대한 로직을 수행할 수 있는 코드가 위치한 것 같은 느낌이다.
서비스 클래스에서 상품에 대한 요청이 실행되는 건 맞다.
그런데 이때 레포지토리에 있는 함수를 가져와 실행한다.
예를 들면, 상품을 저장하는 요청이 들어왔을 경우
상품 List Array가 위치하는 곳은 서비스가 아닌 '레포지토리'이다.
그러므로, List에 Product를 실제로 저장하는 코드도 레포지토리 내부에 있다.
아래에서 실제로 구현된 코드를 보면 더 명확히 이해 될 것이다.
계층 간 의존성 방향은 위와 같다.
빈 등록과 의존성 주입하기
1. 컨트롤러 -> 서비스 의존성 해결하기
위 의존 방향을 살펴보면, ProductController는 SimpleProductService에 의존한다.
이를 실현하기 위해 ProductController에 SimpleProductService에 대한 의존성을 주입해주어야 한다.
의존성 주입에는 다음의 두 가지 과정이 필요하다.
1. 주입될 의존성(클래스)을 빈(Bean)으로 등록 = SimpleProductService를 Bean으로 등록
2. 빈으로 등록된 의존성을 사용할 곳에 주입 = ProductControlloer에 빈으로 등록된 클래스를 주입
의존성 주입이란 단어가 거창하게 쓰이곤 한다.
실제로는 컨트롤러가 서비스 코드를 사용할 수 있도록, 서비스 클래스를 빈으로 등록하고,
컨트롤러 코드에 빈으로 등록된 서비스 클래스를 주입해주는 것 밖에 되지 않는다.
(물론 이렇게 단순하게 해결될 수 있는 이유는 스프링 프레임워크의 애너테이션 기능 덕분이다.)
표현 계층인 ProductController 코드가 SimpleProductService를 사용할 수 있도록 해야한다.
서비스 클래스 위에 @Service라는 애너테이션을 달면, 앞으로 해당 클래스는 스프링 프레임워크에 의해 생성되어 관리될 것이다.
Bean 등록을 마쳤으니,
Product Controller에 빈으로 등록된 클래스를 주입하자.
SimpleProductService 인스턴스를 생성한 후,
인스턴스에 대한 생성자를 작성할 때 위에 Autowired 애너테이션을 달아주면, 의존성을 주입할 수 있다.
2. 서비스 -> 레포지토리 의존성 해결하기
마찬가지로 레포지토리 클래스 위에 @Repository 애너테이션을 달아주고,
서비스 클래스 안에 레포지토리 클래스 필드를 만든 후 그에 대한 생성자에 @Autowired를 달아주면 된다.
잠시 레포지토리 코드를 보고가자면, 내부에 새로 보는 타입이 있다.
CopyOnWriteArrayList 라는 리스트이다.
일반 ArrayList는 Thread Safety 가 없어서 웹 애플리케이션같이 여러 개의 스레드가 동시에 동작하는 멀티 스레드라는 특수한 환경에는 적합하지 않다.
그러므로, 멀티스레드 환경에서는 CopyOnWriteArrayList를 사용하길 권장한다.
CopyOnWrite 의 의미를 알면 이해하기 쉬울 듯 하다.
ArrayList에 대해 Write작업(add,set 등등)이 수행될 때, underlying array를 'copy'해서 작업하겠다는 것이다.
따라서 멀티 스레드 환경에서 안전할 수 있다.
마지막으로 1씩 증가하는 id를 구현하고 끝낸다.
ListProductRepository 에서 proudct를 list에 넣을 때 1씩 증가하여 설정하도록 한다.
AtomicLong을 사용하여 구현할 수 있다. (CopyOnWrite과 마찬가지로 스레드 안정성을 가지는 클래스이다. Long 값을 안전하게 다룰 수 있다.)
- AtomicLong() : 초기값이 0인 AtomicLong을 생성
- AtomicLong(longVal) : 인자의 값으로 초기화된 AtomicLong을 생성
1L로 초기화한후, getAndAdd(1L) 을 통해 1씩 증가시켜 product의 id로 set하도록 하자.
product의 id가 외부 클래스인 레포지토리에서 다루어질 예정이므로,
product클래스 내에 id필드에 대해서만 setter을 추가적으로 작성할 필요가 있다.
'스터디 > Server' 카테고리의 다른 글
상품 관리 애플리케이션 만들기 4 - DTO와 getter, setter (0) | 2024.08.30 |
---|---|
JSON을 주고 받는 AJAX 코드 구조 (0) | 2024.08.27 |
비동기 상호작용이란? (2) | 2024.08.27 |
[백엔드 개발에 필요한 최소한의 js 지식] 자바스크립트에서의 함수 (0) | 2024.08.27 |
nodejs (0) | 2024.08.23 |