Spring

Spring의 핵심 Concept는 무엇인가

Tommy__Kim 2023. 5. 23. 08:52

들어가기에 앞서

Spring은 자바 언어 기반의 프레임워크입니다. 
자바언어는 객체지향언어라는 주된 특징을 지니고 있습니다. 
스프링은 객체지향언어가 가진 특징을 극대화시켜 주는 프레임워크입니다.
다시 말해 스프링은 좋은 객체지향 애플리케이션을 개발할 수 있도록 도와주는 프레임워크입니다.

좋은 객체지향이란 무엇인가? 

해당 부분에 대해 간략하게 정리해 놓은 내용을 참고 부탁드립니다.

좋은 객체 지향 설계의 5가지 원칙

 

좋은 객체 지향 설계의 5가지 원칙

Robert Martin의 클린코드에서 좋은 객체 지향 설계의 원칙을 SOLID로 정의 하였다. S : SRP(Single Responsibility Principle) 단일 책임 원칙 O : OCP(Open/Closed Principle) 개방-폐쇄 원칙 L : LSP(Liskov Substitution Principle)

tommykim.tistory.com

좋은 객체지향 설계와 스프링의 연관성에 대해서

스프링의 도움 없이 개발

우선적으로 스프링의 도움 없이 개발을 해보려고 합니다. 

다음과 같이 구체 클래스 MemberserviceImpl가 인터페이스 MemberRepository를 의존하도록 개발을 해 유연한 설계를 하고자 합니다.

public class MemberServiceImpl implements MemberService{
    MemberRepository memberRepository = new MemberRepositoryImplV1();
    @Override
    public void join(String name) {
        memberRepository.save(name);
    }

    @Override
    public Member getMember(String name) {
        return memberRepository.getMember(name);
    }
}

이 부분에서 좋은 객체지향 설계에 대한 문제점이 존재합니다. 

MemberServiceImpl이 MemberRepository를 의존하는 것 같지만, 실제로는 MemberRepository와 MemberRepositoryImplV1을 의존하고 있습니다. 

좋은 객체지향 설계의 5가지 원칙 중 의존관계 역전 원칙 (Dependency Inversion Principle)을 위반하고 있습니다.

 

기존에 사용하던 DB의 변경, ORM의 변경 등 Repository가 수정되어야 하는 경우에는 MemberRepositoryImplV2를 사용해야 합니다. 그러한 경우에 앞서 봤던 코드에서 변경이 발생합니다.

public class MemberServiceImpl implements MemberService{
//    MemberRepository memberRepository = new MemberRepositoryImplV1();
    MemberRepository memberRepository = new MemberRepositoryImplV2();
    @Override
    public void join(String name) {
        memberRepository.save(name);
    }

    @Override
    public Member getMember(String name) {
        return memberRepository.getMember(name);
    }
}

Repository의 구현체가 변경됨에 따라 Code의 변경이 발생합니다. 

좋은 객체지향 설계의 5가지 원칙 중 OCP(Open/Closed Principle)을 위반하고 있습니다.

 

스프링의 도움없이 DIP / OCP를 만족하게 할 수 있습니다. 

구성을 담당하는 코드를 별도로 사용하면 됩니다. 

기존의 의존성 관계와 달라진 점은 AppConfig에서 MemberService의 구현체와 MemberRepository의 구현체를 알고 있다는 점입니다.

이와 같이 설계를 할 경우 OCP / DIP를 지킬 수 있습니다. 코드로 알아보겠습니다. 

public class AppConfig {

    public MemberRepository memberRepository() {
        return new MemberRepositoryImplV1();
    }

    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
}
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(String name) {
        memberRepository.save(name);
    }

    @Override
    public Member getMember(String name) {
        return memberRepository.getMember(name);
    }
}

AppConfig에서는 각 인터페이스에 대한 구현체 정보를 담고 있습니다.

MemberService의 구현체에서는 이제 MemberRepository 인터페이스만 의존하고 있습니다. (DIP 준수)

public class App {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        memberService.join("tommy");
        System.out.println(memberService.getMember("tommy").getName());
    }
}

클라이언트에서는 AppConfig에서 memberService를 꺼내 사용하면 됩니다. 

만약 MemberRepositoryV2 버전을 사용해야 한다면 어떻게 변경하면 될까요?

다음과 같이 AppConfig에서만 의존 정보를 변경해 주면 됩니다. 

public class AppConfig {

    public MemberRepository memberRepository() {
//        return new MemberRepositoryImplV1();
        return new MemberRepositoryImplV2();
    }

    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
}

앞서 AppConfig를 사용하기 이전과 달리 MemberRepository의 구현체가 변경되어도  MemberServiceImpl의 코드는 변경되지 않습니다. (OCP 준수)

 

스프링을 사용하여 개발

스프링을 사용하여 개발하는 경우에는 다음과 같이 사용할 수 있습니다. 

스프링에서는 Application Context라는 스프링 컨테이너에 key - value 형식으로 인터페이스에 대한 구현체를 등록해 놓습니다.

사용할 때에는 스프링 컨테이너에 등록된 구체 클래스 정보를 가지고 사용하게 됩니다.

 

@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
//        return new MemberRepositoryImplV1();
        return new MemberRepositoryImplV2();
    }

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
}

스프링 컨테이너에 등록을 할 경우에는 Configuration 파일에 @Configuration 어노테이션을 붙여주고 각 인터페이스에 대한 구현체를 @Bean을 사용해 등록해 주면 됩니다.

public class App {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        memberService.join("tommy");
        System.out.println(memberService.getMember("tommy").getName());
    }
}

사용을 할때에는 앞서 만든 AppConfig 클래스파일을 AnnotationConfigApplicationContext에 넣어주면 됩니다. 

memberService 구현체를 꺼내서 사용하고 싶은 경우에는 getBean 메서드를 사용해 꺼내쓸 수 있습니다. 

 

마무리

오늘은 Spring 프레임워크가 객체지향 언어인 Java를 사용해 개발을 할 때 어떠한 부분에서 도움을 주는지 알아보았습니다. 

좋은 객체지향 설계원칙을 지키면서 Java 언어를 사용하기에는 AppConfig.class와 같은 구체 클래스를 등록해 주고, 의존관계를 주입해 주는 구성담당 클래스가 필요합니다. 해당 글에서는 해당 부분만 알아보았지만, 이 외에도 싱글톤 객체 생성 방식 등 좋은 점이 많습니다. 

Spring은 좋은 객체지향 설계원칙을 보다 편하게 지킬 수 있도록 도와주는 프레임워크입니다. 

 

해당 예제는 다음 링크에서 확인해 보실 수 있습니다.

https://github.com/BeomSeogKim/java-defrag/tree/main/dicontainer

 

GitHub - BeomSeogKim/java-defrag: java 기능 관련 모음

java 기능 관련 모음. Contribute to BeomSeogKim/java-defrag development by creating an account on GitHub.

github.com

 

'Spring' 카테고리의 다른 글

프론트 컨트롤러 패턴은 무엇인가요?  (0) 2023.06.01
[스프링] 빈 생명주기 콜백에 관하여  (0) 2023.05.25
Enum Validation  (0) 2023.04.24
Spring Bean의 생명주기  (0) 2023.04.21
싱글톤에 대해서  (2) 2023.04.10