회원 가입을 했을 때 쿠폰을 제공하는 기능을 위와 같이 만들었다. 코드 설명을 하자면 먼저 멤버 서비스를 보면 회원의 정보를 받아서 회원 객체를 만들고 이를 저장한다. 그리고 이후에 쿠폰 서비스의 give 메서드를 호출해서 회원에게 쿠폰을 제공하는 기능을 작성하였다. 쿠폰 서비스의 give 메서드를 보면 회원을 받아와서 쿠폰을 만들고 이를 저장하도록 기능 구형을 하였다. 이 코드를 패키지 관점에서 본다면 멤버 서비스에서는 쿠폰 서비스를 사용하기 때문에 멤버에서 쿠폰으로의 의존성이 발생하게 되고, 쿠폰 서비스는 멤버를 가지고 있어서 쿠폰에서 멤버로의 의존성이 발생하게 된다.
정리하면 의존성 사이클이 발생하게 되는데 의존성 사이클을 해결하는 방법은 여러가지가 있는데 인터페이스를 통한 의존성 역전을 통해서 의존성 사이클을 해결할 수 있고 이벤트를 사용하는 방법도 있을 것이다. 이외에도 여러 가지 방법이 있는데 이벤트에 대해서 알아보려고 한다.
위에서 의존성 사이클이 발생했던 코드를 조금 간단하게 표현해 보았는데, 이제 이벤트를 도입함으로써 어떤 식으로 의존성 사이클이 개선되는지 한번 알아보자. 먼저 멤버 서비스에서 쿠폰서비스를 직접 호출하고 있는데 이벤트를 사용하게 되면 더 이상 직접 호출을 하지 않고
이렇게 회원 가입을 했다는 이벤트를 발송하게 된다. 누가 이것을 처리할지는 모르겠지만 일단 멤버 패키지에서는 그냥 회원 가입을 했다는 이벤트만 발행한다.
그리고 나서 해당 이벤트를 받아서 쿠폰을 회원에게 제공하도록 쿠폰 패키지 내부에서 이를 처리하는 코드를 작성해 주면 되는데
이때 화살표의 방향을 보면 알겠지만 의존성 사이클이 해결되게 된다.
이번에는 직접 코드로 적용해 보려고 한다.
위에서 보았던 의존성 사이클이 발생하는 코드이다. 이제 이벤트를 한번 도입해 보자.
먼저 멤버 패키지에서 위와 같이 이벤트를 생성한다. 이 코드는 그냥 클래스라고 보면 된다. SignUp 이벤트를 생성 한 이후에 멤버 서비스는 쿠폰 서비스를 직접 호출하는 것이 아니라
위와 같이 이벤트를 발행하게 된다. 이때 이벤트를 발행할 때는 스프링에서 제공해주는 ApplicationEventPublisher라는 것을 통해서 이벤트를 발행할 수 있다.
이벤트를 처리하는 방법은 @EventListener라는 어노테이션을 통해서 SignUp 이벤트를 처리 할거야 라고 알려주면 된다. 이렇게 함으로써 멤버 서비스에서는 회원 가입 이후에 회원과 이벤트를 발행하고 쿠폰 서비스는 해당 이벤트를 받아서 쿠폰을 제공하도록 기능을 작성할 수 있다.
그리고 화살표를 보면 알 수 있지만 의존성 사이클이 해결된 것을 알 수 있다.
인터페이스 VS 이벤트
의존성 사이클 해결을 위해 인터페이스를 사용하는 두 가지 방법
첫 번째 방법
멤버 회원 가입 이후에 쿠폰을 발급해야 되기 때문에 멤버 패키지 내부에 CouponGiveService라는 인터페이스를 작성한다. 그리고 쿠폰 패키지 내부에서 해당 인터페이스의 구현체인 CouponGiveServiceImpl를 작성한다. 그러면은 위와 같이 의존성이 쿠폰에서 멤버로만 향하게 된다.
회원 가입 이후에 메일을 전송해야 한다면?
그러면 CouponGiveService와 비슷하게 MailSendService를 만들어야 되고 또 이제 메일 패키지에서는 해당 인터페이스를 구현해야 되는데 이것이 문제가 될 수 있다. 이런 추가적인 로직이 계속해서 추가된다고 가정을 하면 아래와 같은 코드가 탄생하게 된다.
회원가입 이후에 쿠폰도 제공하고 메일도 발생하고 또 어떤 것도 하고 다른 것도 하는 코드가 작성이 되는데 무엇이 문제인지 살펴보면 먼저 회원 가입이라는 주 기능에 집중하기가 힘들어진다. 왜냐하면 쿠폰도 제공하고 메일도 제공 해주는 다른 부가적인 기능이 코드에 붙기 때문에 주 기능에 집중하기 힘들다. 그리고 멤버 서비스가 의존하는 것들이 되게 많아져서 멤버 서비스의 결합도가 높아지는 단점이 있다. 또 쿠폰과 메일 패키지로의 코드상에서의 사이클은 해결되었지만 논리적으로는 계속 회원 가입 이후에 쿠폰도 제공 해야 되고 메일도 전송해야 되기 때문에 논리적인 의존성은 여전히 존재하는 문제가 있다.
이것을 이벤트를 통해서 처리하면 위와 같이 이벤트 하나만 발송하면 된다. 이렇게 간편하게 처리할 수 있는 것들을 인터페이스를 쓰면 조금 복잡하게 처리가 된다는 문제가 있다.
두 번째 방법
회원 가입 이후에 처리할 로직들을 인터페이스로 정의 해주는 것이다.
쿠폰을 제공하기 위해서는 CouponGiveSignUpCallback 클래스를 작성해서 회원 가입 이후에 쿠폰을 작성하는 기능을 작성해주면 된다.
메일을 전송해야 한다면 MailSendSignUpCallback이라는 클래스를 작성해주면 된다.
위에서 보았던 것과 비교해서 인터페이스는 하나만 만들어도 되고 구현체들이 많아지니까 위에서 발생했던 의존성이 많아진다는 문제를 해결할 수 있다.
그리고 이를 위와 같이 사용할 수 있다. 이 멤버 서비스에서는 SignUpCallBack이라는 인터페이스를 구현한 구현체들을 리스트로 의존성 주입을 받게 된다. 그리고 회원 가입 이후에 이들을 반복문으로 수행을 하면서 하나씩 호출을 해주게 되는데 이는 이벤트와 되게 유사해 보이지만 우선 리스트로 받아야 되고 for문을 돌려야 되고 또 인터페이스를 구현할 때 해당 implements XXX을 계속 작성해 줘야 되기 때문에 이벤트에 비해서 조금 더 복잡해진다. 그리고 회원 가입 외에 다른 기능이 있다면 의존하는 인터페이스가 많아질 수 있다. 예를 들어서 회원의 정보를 업데이트하는 기능이 추가될 수도 있는데 그러면 회원 수정이후에 어떤 로직들을 처리하기 위해서는 MemberUpdateCallback이라는 인터페이스가 또 추가되어야 한다. 마지막으로 이벤트는 트랜잭션과 관련해서 추가로 제공 해주는 기능이 있다. @TransactionalEventListener라는 키워드로 검색해보면 쉽게 찾을 수 있다.
그럼 이벤트는 항상 좋은가?
- 반환 타입이 필요한 경우에 사용이 불가능하다.
예를 들어서 회원 서비스에서 회원 가입 이후에 쿠폰을 제공하고 그 메서드에 반환 타입을 다시 회원 서비스에서 사용하는 경우에는이벤트를 사용할 수 없다. 그래서 이런 경우에는 인터페이스를 사용해야 된다.
- 로직의 흐름이 명확하지 않다.(상황에 따라 장점일 수도, 단점일 수도 있다.)
위에서 보았던 코드를 다시 보면 이 코드는 회원 가입 이후에 쿠폰을 제공하고 메일을 전송하고 무엇을 하고 무엇을 하는 로직의 흐름이 명확한 반면에 해당 이벤트를 사용할 코드는 그냥 이벤트를 발송하기만 할 뿐 뒤에서 어떤 로직이 처리되는지를 알기가 어렵다. 그래서 이것은 상황에 따라서, 어플리케이션 상황에 따라서 장점이 될 수도 있고 단점이 될 수도 있다.
정리
- 이벤트를 통해서 의존성의 방향을 제어할 수 있다.
방법은 위와 같이 A서비스가 B서비스를 직접 호출하는 대신에 이벤트를 생성하고
해당 이벤트를 발행하고
해당 이벤트를 처리함으로써
의존성의 방향을 반대로 바꿀 수 있었습니다
참고
https://www.youtube.com/watch?v=TJUIkLFpgGo&ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC
'개발 관련 강의 정리 > 10분 테코톡' 카테고리의 다른 글
[10분 테코톡] 알린의 암호 정리 (0) | 2023.07.01 |
---|---|
[10분 테코톡] 🎹 김김의 JVM Specification 정리 (0) | 2023.06.30 |
[10분 테코톡] 지토의 Auto Configuration 정리 (0) | 2023.06.27 |
[10분 테코톡] 😼 피카의 TDD와 단위테스트 정리 (0) | 2023.06.26 |
[10분 테코톡] 이오의 OSI 7계층 정리 (0) | 2023.06.25 |
댓글