스프링 DI란?

시루봉로·2023년 2월 10일
0

스프링 입문

목록 보기
1/4
post-thumbnail

의존이란?

DI는 'Dependency Injection'의 약자로 '의존 주입' 이라고 한다. 이때, 의존은 객체 간의 의존을 의미한다.

public class MemberRegisterService {
    private MemberDao memberDao = new MemberDao();
    
    public void register(RegisterRequest req){
        Member member = memberDao.selectByEmail(req.getEmail());

        if(member != null){
            throw new DuplicateMemberException("dup email " + req.getEmail());
        }

        Member newMember = new Member(
                req.getEmail(), req.getPassword(), req.getName(),
                LocalDateTime.now());
        memberDao.insert(newMember);
    }
}  

만약 서로 다른 회원은 동일한 이메일 주소를 사용할 수 없다는 요구사항이 있다고 가정해보자. 이 제약사항을 처리하기 위해 위와 같은 코드를 구현한다. 여기서 눈여겨볼 점은 MemberRegisterService 클래스가 DB처리를 위해 MemberDao 클래스의 메서드를 사용한다는 점이다. 회원 데이터가 존재하는지 확인하기 위해 MemberDao 객체의 selectByEmail() 메서드를 실행하고, 회원 데이터를 DB에 삽입하기 위해 insert() 메서드를 실행한다.

이렇게 한 클래스가 다른 클래스의 메서드를 실행할 때 이를 '의존'한다고 표현한다. 또한 의존은 변경에 의해 영향을 받는 관계를 의미한다. 예를 들어 MemberDao의 insert() 메서드의 이름이 변경된다면 이 메서드를 사용하는 MemberRegisterService 클래스의 소스 코드도 함께 변경된다. 이렇게 변경에 따른 영향이 전파되는 관계를 '의존'한다고 표현한다.

DI를 통한 의존 처리

DI는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다. 앞서 의존 객체를 직접 생성한 MemberRegisterService 클래스에 DI 방식을 적용하면 아래와 같이 구현할 수 있다.

public class MemberRegisterService {
    private MemberDao memberDao;
    
    public MemberRegisterService(MemberDao memberDao){
    	this.memberDao = memberDao;
    }
    
    public void register(RegisterRequest req){
        Member member = memberDao.selectByEmail(req.getEmail());

        if(member != null){
            throw new DuplicateMemberException("dup email " + req.getEmail());
        }

        Member newMember = new Member(
                req.getEmail(), req.getPassword(), req.getName(),
                LocalDateTime.now());
        memberDao.insert(newMember);
    }
}  

직접 의존 객체를 생성했던 코드와 달리 바뀐 코드는 생성자를 통해 의존 객체를 전달받는다. 즉 생성자를 통해 MemberRegisterService가 의존하고 있는 MemberDao 객체를 주입받은 것이다.

그래서 DI를 왜 사용하는건데?

앞서 의존 객체를 직접 생성하는 방식과 달리 의존 객체를 주입하는 방식은 객체를 생성하는 부분의 코드가 조금 더 길다. 그냥 생성하기만 해도 되는데 왜 굳이 생성자를 통해 의존하는 객체를 주입하는 걸까? 그 이유는 객체 지향 설계와 관련있다.

DI는 의존 객체 변경에 있어 유연함을 가지고 있다. 의존 객체를 직접 생성하는 방식은 필드나 생성자에서 new 연산자를 이용하여 객체를 생성한다.

// 회원 등록 기능 제공 클래스
public class MemberRegisterService {
	private MemberDao memberDao = new MemberDao();
	...
}

// 회원 암호 변경 기능 제공 클래스
public class ChangePasswordService {
	private MemberDao memberDao = new MemberDao();
    ...
}

여기서 MemberDao 클래스는 회원 데이터를 DB에 저장한다고 가정해보자. 이 상태에서 회원 데이터의 빠른 조회를 위해 캐시를 적용해야 하는 상황이 발생했다. 그래서 MemberDao 클래스를 상속받은 CachedMemberDao 클래스를 만들었다.

public class CachedMemberDao extends MemberDao {
	...
}

이 캐시 기능을 적용한 CachedMemberDao를 사용하려면 MemberRegisterService 클래스와 ChangePasswordService 클래스의 코드를 아래와 같이 변경해주어야 한다.

// 회원 등록 기능 제공 클래스
public class MemberRegisterService {
	private MemberDao memberDao = new CachedMemberDao();
	...
}

// 회원 암호 변경 기능 제공 클래스
public class ChangePasswordService {
	private MemberDao memberDao = new CachedMemberDao();
    ...
}

동일한 상황에서 DI를 사용하면 수정할 코드가 줄어든다. 예를 들어 생성자를 통해 의존 객체를 주입 받도록 구현했다.

// 회원 등록 기능 제공 클래스
public class MemberRegisterService {
	private MemberDao memberDao;
    public MemberRegisterService(MemberDao memberDao){
    	this.memberDao = memberDao;
    }
	...
}

// 회원 암호 변경 기능 제공 클래스
public class ChangePasswordService {
	private MemberDao memberDao;
    public ChangePasswordService(MemberDao memberDao){
    	this.memberDao = memberDao;
    }
    ...
}

두 클래스의 객체를 생성하는 코드는 다음과 같다.

MemberDao memberDao = new MemberDao();
MemberRegisterService reqSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);

이제 MemberDao 대신 CachedMemberDao를 사용하도록 수정해보자. 수정해야 할 소스 코드는 단 한 곳뿐이다.

MemberDao memberDao = new CachedMemberDao();
MemberRegisterService reqSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);

DI를 사용하면 의존할 객체를 사용하는 클래스가 얼마나 많든 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한 곳뿐이다. 앞서 의존 객체를 직접 생성했던 방식에 비해 변경할 코드가 한 곳으로 집중되며, 의존성이 줄어든 것을 확연히 알 수 있다.

정리하자면 DI는 아래와 같은 장점이 있다.

  • 의존성이 줄어든다.
  • 재사용성이 높은 코드가 된다.
  • 테스트하기 좋은 코드가 된다.
  • 가독성이 높아진다.

참고 자료

본 글은 아래 링크를 기반으로 정리하였습니다.

profile
안녕하세요.

0개의 댓글