본문 바로가기
개발/Spring

[Spring] 최근에 느낀 Spring 의 장점 (객체지향, DI)

by baau 2025. 1. 6.

1년 전 취업 준비를 했을 때를 돌이켜 보면, 스프링을 사용해 보면서 좋았던 점에 무엇인지에 대한 면접 질문을 많이 받았다.

 

그때마다 면접 족보?를 통해 달달 외웠던 내용을 잘 풀어서 얘기했던 것 같다. 

사실 취준 때는 현재 많은 기업들이 스프링을 사용하기 때문에 취업 시장에서 유리했기 때문에 선택했던 이유가 가장 컸다. 그래서 사실 아래 내용을 100% 와닿으면서 스프링을 사용해 본 적은 없어 면접 대답도 술술 대답하지 못했던 적도 있다.

  • 스프링 부트의 장점 (톰캣 내장, 손쉬운 설정..)
  • 스프링 DI, AOP
  • @Transactional 
  • ...

 

하지만 1년 정도 실무를 경험하고, 최근 스프링 핵심 원리 - 기본 편 강의를 다시 들으면서 왜 많은 기업에서 스프링을 선택하고, 스프링의 장점을 체감할 수 있었다.

 

 

스프링은 우선 자바 언어 기반의 프레임워크이기 때문에, 자바 혹은 코틀린의 객체 지향 언어의 강력한 특징을 살려내주는 프레임워크이다.

객체 지향 언어 특징 중 다형성을 통해 좋은 객체지향 설계를 할 수 있다. (SOLID)

public class GiftCouponService implements CouponService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
}

 

위 코드에서 MemberRepository의 구현체 중 MemoryMemberRepository에서 RdbMemberRepsitory을 사용하도록 변경하기 위해서는 어떻게 해야 할까?

 

public class GiftCouponService implements CouponService {

    // private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final MemberRepository memberRepository = new RdbMemberRepository();
}

 

위와 같이 new MemoryMemberRepository에서 new RdbMemberRepository()로 변경해주어야 한다.

MemberRepository라는 인터페이스와 각 구현 객체를 분리를 했음에도 코드 변경이 일어난다. 이때 여러 SOLID 원칙을 위반하게 된다.

 

SPR 위반

  • GiftCouponService는 기프트 쿠폰의 기능 외에 어떤 MemberRepository를 사용할지에 대한 구성의 역할도 가지고 있다.
  • 실행과 구성, 둘의 책임을 가지고 있다.

OCP 위반

  • 인터페이스를 구현한 새로운 클래스 (RdbMemberRepository)를 만들어서 새로운 기능을 구현했지만, GiftCouponService 코드의 변경이 일어났다.
  • 새로운 기능을 확장할 때, 기존 코드의 변경이 일어났다.

DIP 위반

  • GiftCouponService는 MemberRepository 에만 의존하는 것뿐만 아니라, 사실 RdbMemberRepository 도 의존하고 있다.
  • RdbMemberRepository라는 구현체에 의존하고 있어, DIP를 위반했다.

 

그럼 아래 코드는 어떠한가?

public class GiftCouponService implements CouponService {

    private final MemberRepository memberRepository;
}

 

 

인터페이스만 의존하고 있고, 구현체는 의존하고 있지 않기 때문에 SOLID 원칙을 만족한다.

하지만 memberRepository를 사용 시, NullPointException이 발생할 것이다. 외부에서 memberRepository 주입이 필요하다. (DI)

 

public class GiftCouponService implements CouponService {

    private final MemberRepository memberRepository;
    
    public GiftCouponService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

public class BeanFactory {
	
    public GiftCouponService GiftCouponService() {
    	return new GiftCouponService(new MemoryMemberRepository());
    }
}

 

다음과 같이 외부(BeanFactory)에서 MemberRepository의 구현체를 주입해주어야 한다. 그래야 GiftCouponService 가 MemberRepository의 구현 클래스의 의존하지 않게 된다.

 

따라서 실제 GiftCouponService가 MemoryMemberRepository를 사용하지 않고, RdbMemberRepository로 변경해야 할 때, GiftCouponService 코드는 수정할 필요가 없다.

 

위 코드는 Spring을 사용하지 않고, 단순 자바를 통해서 DI의 과정을 보여준 예시이다.

Spring을 사용하면 위 코드보다 더 쉽게 자동 의존 주입을 할 수 있고, 더 안전하게 싱글톤으로 객체를 관리하는 장점이 있다. 스프링은 객체를 생성하고, 관리하면서 객체의 의존 관계를 연결해 주는 데, 이를 DI 컨테이너, IoC 컨테이너라고 한다.

 

Spring DI 관련 내용은 자세하게 다음 포스팅에 정리할 예정이다.

 


 

강의에서 김영한 님이 아래와 같은 말을 하셨다.

"실무에서는 지금 결정하기 어려운 요구사항이 있다. 이런 정책이 결정될 때까지 개발을 무기한 기다릴 수는 없다"

 

취준 할 때는 이 말을 들었을 때, 나에게 많이 와닿지 않았다. 왜냐하면 취준 때는 팀원끼리 요구사항을 결정하고, 개발을 하면 되는 환경이었기 때문이다. 하지만 실무를 경험해 보니, 위와 같은 순간이 매 달 한 번씩은 마주하는 것 같다. 그때는 항상 요구사항이 제대로 결정되기 전까지 기다렸다가 개발을 했던 날들이 많았다. 그래서 배포 일정을 맞추지 못한 날들도 있었다. 하지만 강의를 듣고, 마인드? 개발하는 방식? 이 조금씩 달라지고 있고, 발전하고 있는 것 같다. 

 

강의에서 배운 것 처럼 최대한 역할과 구현을 분리하여 설계하고 개발함으로 써 언제든지 새로운 요구사항이 생기더라도 빠르게 대처할 수도록 노력하고 있다. 기존 코드를 변경하지 않고, 혹은 최소화하고 기간 내에 해야하는 개발 부담도 많이 줄어들 수 있었다. 

 

스프링의 객체지향적 설계와 DI 가 최근에 가장 많이 느낀 스프링의 장점이라는 생각이 들었고, 기록하고 싶어 포스팅을 하게 되었다.