1. 스프링 사용 없이 구현해보기
강의에서 제공하는 회원 서비스와 다이어그램은 다음과 같다.
간략하게 회원 서비스만 다뤄보자
//MemberServiceImpl
package project.member;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
public void join(Member member){
memberRepository.save(member);
}
public Member findMember(Long id){
return memberRepository.findById(id);
}
}
//MemoryMemberRepository
package project.member;
import java.util.Map;
import java.util.HashMap;
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<> ();
@Override
public void save(Member member){
store.put(member.getId(), member);
}
@Override
public Member findById(Long id){
return store.get(id);
}
}
MemberRepository 라는 인터페이스를 만들고 MemoryMemberRepository 라는 구현 클래스를 만들었다.
저번에 설명한 역할과 구현이라는 설계 방식을 잘 따르는 것처럼 보인다.
그러나 만약에 외부 DB에 연결을 한 후 Member 정보를 DB에 저장하기로 결정했다면?
DbMemberRepository를 만든 후 MemoryMemberRepository와 교체하면 된다.
이는 OCP 원칙을 준수하는 것 처럼 보이지만 다른 문제점이 생긴다.
public class MemberServiceImpl implements MemberService{
//private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository = new DbMemberRepository();
.
.
.
바로 MemberServiceImpl 클래스를 변경해야지만 변경사항을 적용할 수 있다는 것이다.
Repository를 변경하기 위해 그걸 의존하는 클라이언트(Service) 클래스까지 변경해야 하고
이는 전체가 부분에 의존함을 의미한다. 즉 DIP, OCP 원칙을 위배한다고 볼 수 있다.
어떻게 해결 할 수 있을까??
관심사의 분리
어플리케이션을 공연, 인터페이스를 배역, 구현 객체를 배우라고 비유해보자.
지금 구현한 코드는 주연 배우가 본인의 원래 책임 뿐 아니라 조연 배우를 섭외하는 책임까지 가지고 있는 셈이다.
책임 즉 관심사의 분리가 필요하다.
공연 기획자의 역할을 하는, 즉 객체를 생성하고 의존관계를 연결하는 별도의 설정 클래스를 만들어보자
//MemberServiceImpl
package project.member;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
//생성자 주입
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
.
.
}
//AppConfig
package project;
import project.member.MemberService;
import project.member.MemberServiceImpl;
import project.member.MemoryMemberRepository;
import project.member.MemberRepository;
public class AppConfig{
// 설정 내용
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
우선 MemberServiceImpl 객체에 생성자를 만든 후 생성자의 매개변수를 통해 MemberRepository를 주입받도록 한다.
이것을 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.
이후 AppConfig라는 설정 클래스를 만든 후 AppConfig에서 객체들을 생성하면 된다.
이렇게 바꾼다면 MemverServiceImpl은 더 이상 MemoryMemberRepository에 의존하지 않게 된다.
단지 MemberRepository 인터페이스에만 의존하면 된다.
즉 구현이 아닌 역할에 의존하게 된다.
//MemberApp
package project;
import project.member.Member;
import project.member.MemberService;
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
.
.
.
나중에 MemberService를 사용할 일이 생긴다면 다음과 같이 설정 파일에서 memberService를 불러오면 된다.
이렇게 구현한다면 DbMemberRepository로 교체할때도 오직 설정파일만
return new MemoryMemberRepository(); -> return new DbMemberRepository();
으로 교체하면 된다.
이렇게 변경한 코드는 스프링의 핵심 원리인 IOC와 DI를 사용하고 있다.
1. IOC(Inversion of Control , 제어의 역전)
기존 프로그램은 클라이언트 구현 객체가 스스로 서버 구현 객체를 생성하고 관리했다.
즉 구현 객체가 제어 프름을 스스로 조종했다.
AppConfig를 만든 후에 구현 객체는 자신의 로직만을 담담한다. 즉 프로그램의 제어 흐름을 AppConfig가 가져간다.
MemberServiceImpl 입장에서 제어 흐름의 권한은 모두 AppConfig가 관리하고 있다.
이처럼 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라
한다.
2. DI(Dependency Injection, 의존관계 주입)
MemberServiceImpl은 MemberRepository 라는 인터페이스에 의존한다.
즉 프로그램이 실행되기 전까지 어떤 구현 객체가 사용될지 알 수 없다.
애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서
클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를
쉽게 변경할 수 있다.
AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을
IoC 컨테이너 또는 DI 컨테이너라 한다.
의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
하지만 몇가지 찜찜한 점이 존재한다.
1. 결국 Repository를 변경한다면 AppConfig의 코드를 바꿔야한다
2. 서비스가 커진다면 설정파일에서 하나하나 등록하고 관리하기 힘들지 않을까?
이런 문제점을 Spring이 제공하는 기능들을 통해 해결해보자.
'Spring > Spring 기본' 카테고리의 다른 글
사지방에서 Spring boot 공부하기 #6 - Autowired와 자동 의존관계 주입 (0) | 2023.05.08 |
---|---|
사지방에서 Spring boot 공부하기 #5 - 스프링 빈 등록과 조회 (0) | 2023.04.22 |
사지방에서 Spring boot 공부하기 #4 - 스프링 빈이란? (0) | 2023.04.18 |
사지방에서 Spring boot 공부하기 #2 - 스프링과 객체지향 (2) | 2023.04.16 |
사지방에서 Spring boot 공부하기 #1 - 개발환경 설정 (0) | 2023.04.06 |