Spring/Sample Project

[Spring Boot] 기본 CRUD 구현 - 3 (비지니스 로직 구현, Service와 Repository, Dto와 Domain)

shininghyunho 2023. 10. 5. 20:30

요구사항은 유저가 주문을 할수만 있으면 되서 간단하였다.

 

그러나 구현하면서 몇가지 고민사항이 생겼고 어떻게 해결했는지 서술하겠다.


Service와 Repository의 관계

내가 생각하는 MVC 패턴에서의 역할은 다음과 같다.

Controller - 엔드포인트의 역할로써 외부의 요청과 응답을 담당한다.

Service - 비지니스 로직이 들어가야한다.

Entity - 도메인 데이터를 표현하는 층으로, 데이터의 정합성을 보장해줘야한다.

Repository - Entity의 물리적인 변화를 담당한다.

 

또한 Controller -> Service -> Repository 순으로 접근하기에

Service는 Controller를 Repository는 Service의 존재를 몰라야한다.


저번 포스팅에서도 기술했듯이 현재 여러 Domain이 연관관계를 맺고 있다.

하나의 Order를 생성하기 위해서는 User, OrderItem, Item을 모두 접근해야한다.

그래서 OrderService 에서는 UserRepository, OrderItemRepository, ItemRepository가 모두 필요한다.


그럼 선택지는 2가지이다.

1. 1개의 Service에 N개의 Repository 갖고있기.
2. 1개의 Service는 반드시 1개의 Repository 만 갖고있기.

 

1번 방법으로 구현한다고 해보자.

그럼 OrderService 에서 모든 비지니스 로직을 구현하면 된다.

Service -> Repository를 접근하는 방식으로 간단하게 구현이 된다.

 

단점은 SRP(Single Responsibility Principle) 단일 책임 원칙이 깨지게 된다.

예를 들어 Item Domain에 수정사항이 생기면 Item을 참조하는 모든 Service를 수정해야한다.

코드 또한 중복이 많아져 규모가 커질수록 에러를 유발한다.

  

2번 방법을 사용한다면.

이제는 하나의 Service에서는 하나의 Repository 만 담당한다.

1번의 단점이었던 중복이 사라져 유지보수가 간편해졌다.

 

단점은 Service -> Service를 참조하는 관계가 생성되었다.

그래서 순환참조가 일어나지 않게 설정해줘야하고 이로인해 아키텍처가 복잡해진다.

 

예를들어 Order를 생성하기 위해서는 userId를 통해 User를 확인해야한다.

OrderService -> UserService를 참조하게 된다.

근데 UserService에서 OrderService를 참조해야한다면 어떻게될까?

이미 OrderService가 참조하고있기에 참조할수없다.

결국 이러한 상황이 발생하지 않게하거나 또 다른 클래스를 통해 순환참조를 방지해야한다.

 


결국 프로젝트가 간단하고 직관적이라면 1번 방법을 생각해볼수 있을거같고,

아키텍처를 더 신경쓰더라도 규모가 작지않고 유지보수를 해야한다면 2번 방법을 사용할것같다.

아니면 또 다른 제 3의 Layer를 추가하는것도 방법일것이다.


해당 프로젝트는 여러기능을 계속 추가하는 방식으로 진행할것이므로 2번 방법을 택했다.


DTO와 Domain의 불일치

다시 Order에 관한 이야기다.

사용자가 주문을 한다고 해보자.

사과 2개, 포도 3개, 바나나 5개를 주문한다.

 

그러면 실제로 백엔드에서는 다음과 같이 요청이 오면 좋을거같다.

1. POST /orders // 주문 목록 생성

2. POST /orderItems // 사과 2개 저장
3. POST /orderItems // 포도 3개 저장
4. POST /orderItems // 바나나 5개 저장

 

그렇다면 프론트엔드 입장에서도 다음과 같이 4번의 요청을 해야할까?

 

문제점들이 몇가지 있다.

1. 하나의 로직인데 왜 요청을 여러번 해야할까?

2. 프론트가 비지니스 로직에 대한 책임을 져야할까?

3. 트랜잭션이 깨지는건 어떻게 막을수 있을까?

 

1번은 번거로움의 문제이지만 진짜 문제는 2번, 3번이다.

백엔드의 업무인 비니지스 로직이 프론트까지 전파가 된것이다.

orderItem을 만드는 과정에서 2번 문제가 발생하고,

orderItem을 만들다 중간에 멈추면 트랜잭션이 깨져 3번 문제가 발생한다.


결론은 백엔드의 Domain과 프론트가 생각하는 Domain(Dto)이 같을 필요가없다.

프론트에서는 백엔드가 어떻게 데이터를 처리하는지는 관심사가 아니고,

실제 비지니스 로직에서 처리하는 데이터만 신경쓰면 된다.

 

그래서 프론트에서는 Order를 여러개의 OrderItem이 포함된다는것 모른채

그냥 1개의 Order(주문목록) 으로 생각하면 된다.

 

그래서 Controller도 Order만 있지 OrderItem은 있을 필요가 없다.

 


그렇다면 백엔드의 Domain과 프론트의 Domain(Dto)가 달라도 Restful 하다고 볼 수 있을까?

내 대답은 Yes다.

 

Restful API 에서는 프론트가 URL과 4가지(혹은 더) 명령어를 통해 자원을 접근하다.

그러나 자원이 꼭 백엔드의 물리적 데이터와 같을 필요는 없다.

백엔드에서는 프론트한테 마치 그러한 자원이 있는것처럼 끝까지 속이면 되는것이다.

 

 


지금까지 구현 코드의 주소다.

spring-sample/CRUD/crud at master · shininghyunho/spring-sample (github.com)