해결된 질문
작성
·
17
답변 2
0
박명규님 안녕하세요.
좋은 질문 남겨주셔서 감사합니다.
간접참조를 사용하는 방식은 도메인 주도 설계(DDD)에서 애그리게이트(Aggregate) 사이의 직접적인 객체 참조 대신 서로의 식별자만 참조해서 결합도를 낮추는 방법입니다.
질문의 요지가 “간접참조를 사용할 경우의 객체지향적인 설계 방법”을 문의하셨는데 사실 간접 참조를 사용하는 방식은 객체지향 설의 일부 제약을 완화하게 됩니다.
따라서 객체지향의 설계에 객체 참조를 사용할 경우에는 객체지향 원칙과는 조금 다르게 DDD의 규칙에 따라 설계를 진행하게 됩니다.
질문으로 돌아와서 Screenig에 movieId를 참조할 경우 몇가지 해결 방법이 있습니다.
1. Movie 인스턴스 전달
Screening에 Reservation을 생성하는 책임을 그대로 둘 경우 아래 처럼 movieId에 해당하는 Movie를 전달할 수 있습니다.
이 Movie는 질문에서 언급하신 것처럼 서비스 레이어에서 조회한 후에 전달하면 됩니다.
public class Screening {
private Long id;
private Long movieId;
private Integer sequence;
private LocalDateTime screeningTime;
public class Screening {
public Reservation reserve(Long customerId, Movie movie, int audienceCount) {
if (!movieId.equals(movie.getId()) {
throw new IllegalArgumentException();
}
Money fee = movie.calculateFee(toScreeningCondition()).times(audienceCount);
return new Reservation(customerId, this.id, audienceCount, fee);
}
public ScreeningCondition toScreeningCondition() {
return new ScreeningCondition(sequence, screeningTime);
}
...
}
이 방식에는 몇 가지 전제 조건을 따라야 하는데 DDD에서는 하나의 애그리게이트만 수정해야 하기 때문에 Movie의 상태를 변경해서는 안됩니다.
따라서 Movie의 메서드 중에서 부수 효과가 없는 메서드만 사용해야 하는데 메서드를 주의해서 사용하거나 CQS(명령-쿼리 분리) 원칙에 따라 구성할 필요가 있습니다.
여기에서는 Screening이 Reservation에도 결합되게 되는데 이 의존성이 문제가 된다면 이어지는 2번처럼 생성 책임을 외부로 뺄 수 있습니다.
2. Screening과 Movie의 의존성은 낮추고 Movie에게 값 객체 전달
1번의 이슈가 문제가 되는 경우에는 Screening과 Movie를 직접 의존하게 만들지 않고 둘 사이의 협력을 애플리케이션 서비스인 ReservationService에서 조율하는 방법을 사용합니다.
이 경우 결합도를 낮추기 위해 Movie에서 사용하는 Screenin의 데이터는 값 객체로 변환해서 Movie의 calculateFee 메서드에 전달합니다.
@Service
public class ReservationService {
@Transactional
public Reservation reserveScreening(Long customerId, Long screeningId, Integer audienceCount) {
Customer customer = customerRepository.findById(customerId).get();
Screening screening = screeningRepository.findById(screeningId).get();
Movie movie = movieRepository.findAggregateById(screening.getId()).get();
Money fee = movie.calculateFee(screening.toScreeningCondition());
Reservation reservation = new Reservation(customer.getId(), screening.getId(), audienceCount, fee);
reservationRepository.save(reservation);
return reservation;
}
}
지금까지 설명드린 것처럼 DDD의 애그리게이트는 일관성 경계를 위해 객체 사이의 협력을 제한하는 경향이 있기 때문에 간접참조를 활용하는 방법은 객체지향의 설계 원칙을 트레이드오프하게 됩니다.
개인적으로는 부수효과를 잘 통제한다면 1번처럼 파라미터로 전달하는 방식도 가능하다고 보고 있습니다.
하지만 2번 방식처럼 서로 참조하지 않도록 만들 수 있다면 이 방식을 따르는게 더 좋은 방법이겠죠.
여기에서 설명드린 부분에 대한 자세한 내용이 궁금하시다면 도메인 주도 설계에 대해 깊이 있게 학습하시는 것을 추천드립니다.
답변이 되었는지 모르겠네요. 😊
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다.
박명규님이 제기하신 간접 참조에 관한 질문은 객체지향 설계에서 중요한 주제입니다. 객체 간의 관계를 직접 참조가 아닌 간접 참조로 설정하는 이유는 결합도를 낮추고 유연성을 높이기 위해서입니다. 이렇게 하면 객체 간의 변경이 독립적으로 이루어질 수 있습니다. 관련해서 적절한 설계 방법을 안내 드리겠습니다.
간접 참조는 객체가 다른 객체를 직접 참조하지 않고, 주로 ID와 같은 식별자를 통해 참조하는 방식입니다. 이런 방식은 객체 간의 결합도를 낮추며, 특히 데이터베이스와의 통신이 필요한 경우나, 객체의 실제 내용이 여러 종류일 수 있는 경우에 유용합니다.
식별자 사용: 객체가 다른 객체의 ID만을 보유하고, 해당 객체의 실제 데이터가 필요할 때 ID를 사용해 조회합니다.
서비스 레이어: 비즈니스 로직(AccountService, MovieService 등) 레이어에서 식별자를 이용해 DAO나 저장소(Repository)를 통해 실제 객체를 조회하고, 필요한 메시지와 함께 전달합니다.
DAO/Repository 패턴: 데이터 접근 계층에서 MovieId
를 사용하여 데이터베이스에서 Movie
객체를 조회합니다.
public class Screening {
private Long movieId; // Movie 객체의 직접 참조 대신 ID를 보관
public Reservation reserve(Customer customer, int audienceCount, MovieService movieService) {
Movie movie = movieService.findMovieById(this.movieId); // 서비스 레이어를 통한 Movie 객체 조회
Money fee = movie.calculateFee(this).times(audienceCount);
return new Reservation(customer, this, audienceCount, fee);
}
}
위의 예시처럼 Screening
은 Movie
객체를 직접 참조하지 않지만, reserve
메서드에서 MovieId
를 사용하여 MovieService
를 통해 실제 Movie
객체를 가져와 필요한 작업을 수행합니다. 이 구조는 Screening
객체의 결합도를 낮추고, 객체 변경의 유연성을 제공합니다.
이에 대한 자세한 내용은 객체 레퍼런스 처리 방법에 대한 질문에서 유사한 논의가 진행된 바 있습니다. 이 링크를 통해 객체 간의 간접 참조 설정에 대해 더욱 깊이 있는 이해를 얻으실 수 있습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.