[Spring] 03-2. 관심사의 분리

지찬우·2022년 12월 25일
0

Spring

목록 보기
11/27
post-thumbnail

이 시리즈는 인프런 강의(김영한 님의 ‘스프링 핵심 원리 - 기본편’)로 공부하며 혼자 기록하고, 사람들과도 공유할 수 있도록 작성하는 글이다. 최대한 추가적인 정보는 공식 홈페이지, 문서를 보며 얻을 예정이다.
(개인적인 생각과 이해가 들어가 있기 때문에 저의 ‘무식함’이 있을 수 있습니다😜 혹시라도 이 글을 보게 되시는 분이 계시다면 잘못된 부분 댓글로 많이 알려주시면 너무 감사하겠습니다!!)

GitHub Repository : https://github.com/jcw1031/spring-core-study


관심사의 분리

처음에 관심사의 분리라는 말만 들었을 때 무슨 의미인지 몰랐다. 강의에서 예시를 들어 이해하기 쉽게 설명해 주셔서 해당 예시로 설명해 보도록 하겠다.

애플리케이션의 하나의 공연이고, 각각의 인터페이스를 배역이라고 생각하자. 구현체들은 배역을 수행하는 배우들이다. B 배역의 배우를 A 배역의 배우가 직접 선택하지 않는다. 하지만 우리의 이전 코드는 배우가 상대 배역의 배우를 선택하고 초빙하는 것과 같다. A 배역의 배우는 상대 배역의 배우를 선택하고, 직접 초빙까지 해야 하는 다양한 책임을 갖고 있다. 이 내용은 굉장히 중요한 내용이다!!

관심사를 분리하자

배우는 본인의 역할인 배역을 잘 수행하는 것에 집중해야 한다. 상대 배역의 배우가 어떤 배우든지 연기를 할 수 있어야 한다. 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 책임을 가진 별도의 공연 기획자가 필요하다. 이 전 시간 마지막에 얘기했던 그 ‘누군가’가 바로 공연 기획자, AppConfig이다.

AppConfig 등장

애플리케이션의 전체 동작 방식을 구성(config)하기 위해 구현 객체를 생성하고 연결하는 책임을 가진 별도의 설정 클래스를 만들어 보자.

core 패키지에 AppConfig 클래스를 생성해 놓는다.


그리고 MemberServiceImpl로 한 번 가보자.

이 전에는 회원 저장소 인터페이스의 객체를 생성하고 어떤 구현체를 할당할지를 회원 서비스 구현체 MemberServiceImpl이 직접 선택하고 생성하였다. 이제는 회원 저장소 인터페이스에만 의존하도록 코드를 변경해야 한다.

인터페이스 객체를 선언하고, 생성자를 통해 구현체를 받아서 인터페이스 객체에 할당하는 방식이다. 생성자에서 받아오는 구현체도 모두 MemberRepository 인터페이스의 구현체이기 때문에 MemberRepository로 받아온다. 이렇게 하면 MemberServiceImplMemberRepository 인터페이스에만 의존하게 된다.

MemberServiceImplMemoryMemberRepository에 대한 코드는 전혀 찾아볼 수 없다!


다시 AppConfig로 돌아가 보자. MemberServiceImpl의 생성자를 통해 MemberRepository의 구현체를 할당하고 있다. MemberService구현체가 어떤 것이 들어올지 알지 못한다. 하지만 MemoryMemberRepositoryDbMemberRepositoryMemberRepository 역할을 구현하는 것이기 때문에 문제가 되지 않는다.


이제 OrderServiceImpl도 똑같이 변경해 보자. 회원 저장소와 할인 정책이 필요하니 MemberRepository 인터페이스 객체와 DiscountPolicy 인터페이스 객체를 선언한다. 그리고 생성자를 통해 MemberRepository 구현체와 DiscountPolicy의 구현체를 받아와 할당시킨다.


AppConfig에도 코드를 추가하도록 하자. OrderServiceImpl의 생성자를 통해 MemberRepository 구현체와 DiscountPolicy 구현체를 할당하고 있다.


이제 AppConfig애플리케이션의 실제 동작에 필요한 구현 객체를 생성하도록 하였다. 생성한 객체 인스턴스의 참조를 생성자를 통해 주입해준다.

MemberServiceImplMemoryMemberRepository
OrderServiceImplMemoryMemberRepository, FixDiscountPolicy

아래의 구조로 변경된 것이다.


이로써 DIP를 준수할 수 있게 되었다. MemberServiceImplMemberRepository 인터페이스에만 의존하면 되고 구현체 클래스를 몰라도 되기 때문이다. 객체를 생성하고 연결하는 역할(AppConfig)과 실행(xxxServiceImpl)하는 역할명확하게 분리되었다!!


클라이언트인 MemberServiceImpl 입장에서는 마치 의존관계를 외부에서 주입해 주는 것 같다고 하여 이를 DI(Dependency Injection), 우리말로는 의존관계 주입 또는 의존성 주입이라고 한다.

AppConfig 실행

이제 AppConfig를 사용해 의존관계를 주입해주도록 하여 실행을 해보도록 하자. 기존의 main 메서드가 있는 클래스들의 코드를 수정하여 실행해 보자.

MemberApp 수정

기존에 MemberService의 구현체를 직접 생성하여 할당했던 코드는 지운다. 이제 AppConfig 객체를 생성하고, MemberService 객체에 memberService() 메서드(AppConfig에 만들었던 메서드)를 통해 구현체를 할당한다. 나머지 코드는 동일하다.

AppConfigmemberService() 메서드가 기억이 나지 않으면 위로 가서 한 번 보고 오자.


실행을 해보기 전에, OrderApp에서 생성자 오류가 발생하기 때문에(기본 생성자가 없기 때문에) 일단은 null을 할당하자.


다시 MemberApp으로 와서 실행을 해보자. 역시 문제없이 잘 작동한다.


OrderApp 수정

OrderApp도 수정한다. MemberApp과 마찬가지로 기존에 직접 구현체를 생성하여 할당하던 코드는 과감히 지우고, AppConfig 객체를 생성하여 memberService()orderService()를 통해 구현체를 할당한다.


역시 OrderApp도 잘 동작한다.


테스트 코드 수정

기존에 작성했던 테스트 코드도 수정해 주어야 한다.

MemberServiceTest의 기존에 있던 구현체를 생성하여 할당하던 코드를 지우고, 인터페이스 객체를 선언하는 코드를 추가한다. 그리고 인터페이스 객체에 구현체를 할당해 주기 위해 @BeforeEach 어노테이션을 사용한다.

@BeforeEach 어노테이션은 각 테스트가 시작되기 전에 무조건 실행되는 부분을 나타낸다.


@BeforeEach 어노테이션 아래에 메서드를 만들고, AppConfig를 생성하여 memberService()를 사용해 인터페이스 객체에 구현체를 할당한다.


OrderServiceTest도 동일하게 수정하면 된다.


테스트를 실행해 보면, 모두 초록빛이다 ✅


정리

AppConfig를 통해 관심사를 확실하게 분리하였다. 애플리케이션의 실제 동작에 필요한 구현 객체를 생성하고, 이를 주입해 주고 있다. xxxServiceImpl인터페이스에만 의존하고, 자신의 기능을 수행하는 책임만 가지게 되었다.


지금까지 ‘구현체를 변경하였을 때 클라이언트에 변경이 있으면 안 된다.’라는 주제를 꽤 오랜 시간 다룬 것 같다. 그만큼 중요하다는 의미인 것 같다. 이제 다음 시간에는 AppConfig를 리팩터링 하고 정액 할인 정책에서 정률 할인 정책으로 변경했을 때 클라이언트에도 영향이 미치는지, 그리고 지금까지의 전체적인 흐름을 한 번 훑어보도록 하자.

profile
좋은 개발자가 되자.

0개의 댓글