지금 보니 좀 문제가 있어 보인다. DTO 로 convert 하는 식으로 하면 JPA 를 사용하는 이유가 없어진다. 애초에 서로 도메인의 경계를 잘 나누면 된다. 그래도 옛날의 나의 생각이었기에 글은 냅두도록 한다.
개발을 하다 보면 레이어 분리를 어떻게 해야 잘 하는지에 대한 고민을 하게 된다.
모놀리틱에서 개발을 한다고 했을 때 보통 나는 다음과 같은 레이어로 분리를 하여 개발을 하게 된다.
- serivce
- repository
- domain
- controller
일반적인 jpa + spring 사용 시에 나오는 패키지 구조이다.
presentation, business, persistence 레이어로 분리했다고 볼 수도 있을 것 같다.
service 클래스에서는 repository 나 다른 service 클래스를 DI 받아서 사용한다고 했을 때 각각 다음과 같은 repository 와 service 클래스가 존재한다고 해보자.
- A repository
- B repository
- Q service
- W service
- E service
@Service
class QService(
private val aRepository: ARepository
) {
fun ... () { ... }
}
@Service
class WService(
private val bRepository: BRepository,
private val aRepository: ARepository
) {
fun ...() { ... }
}
@Service
class EService(
private val aRepository: ARepository,
private val bRepository: BRepository,
private val wService: WService
) {
fun ...() { ... }
}
간단하게 이야기하자면 이미 W Service 에서 a Repository 를 DI 받아 의존관계가 생긴 상태에서 E Service 에서도 a Repository 와 b Repository, w Service 까지 DI 받아 의존관계가 생겼다는 것이다.
나는 이렇게 의존 관계가 설정되는 것이 좋지 않다고 생각한다.
우선 트랜잭션이 걸린 상태라고 했을 때 어쩌다가 entity 가 우연찮게 변경이 된다면 하이버네이트 dirty checking 으로 인해서 업데이트가 될 수도 있기 때문이다.
여러 사람이 개발하게 될 수록 이런 실수가 생길 수도 있을 가능성이 높을 것 같다고 생각한다.
그리고 entity 를 관리하게 되는 곳이 너무 많아진다.
a Repository, b Repository 에 정의된 메소드를 사용해서 entity 를 조회한다고 했을 때 w Service 에서도 값 변경, 업데이트, 조회를 구현할 수도 있고, e Service 에서도 구현할 수 있다.
그만큼 entity 클래스를 변경했을 때 영향이 가는 부분이 많아진다는 것이다.
나도 요즘 빨리 개발만 한다고 좀 생각하지 않고 개발을 하다 보니 위에서 얘기한 부분을 좀 놓친 것 같다.
그래서 우선 위와 같이 의존관계가 복잡해지고 여러 곳에서 entity 를 관리할 수 있게 하지 않으려고 아래와 같이 개발을 했다.
- A repository
- B repository
- Q service
- W service
- E service
@Service
class QService(
private val aRepository: ARepository
) {
fun getA(id: Int): AResult {
val entity = aRepository.findById(id) ?: throw ...
return AResult.convert(entity)
}
fun updateA(updateARequest: a) {
val entity = getA(a.id)
entity.update(updateARequest)
aRepository.save(entity)
}
}
@Service
class WService(
private val bRepository: BRepository
) {
fun getB(id: Int): BResult {
val entity = bRepository.findById(id) ?: throw ...
return BResult.convert(entity)
}
fun updateB(updateBRequest: a) {
val entity = getB(a.id)
entity.update(updateBRequest)
bRepository.save(entity)
}
}
@Service
class EService(
private val qService: QService,
private val wService: WService
) {
fun calc(request: Request) {
...
}
}
물론 상황마다 다르겠지만 정리하자면 dto 를 활용해서 entity 를 관리하는 곳이 많아지게 하지 않고, 복잡한 의존관계가 설정되지 않도록 해서 추후 변경이나 추가가 됐을 때 영향이 많이 가지 않도록 했다.
물론 DTO 도 그만큼 많이 생기지만 패키지로 잘 분리를 해 놓으면 또 보기 좋은 것 같기도 하다.
물론 저런 식이 아니라 다른 식으로 해도 되지만 나는 보통 저런 방식으로 한다.
다른 책들을 읽고 개발 방식이 변경되면 또 글을 쓰도록 하겠다.