Spring Boot

스프링 핵심 원리

snowkit 2022. 10. 4. 00:40

객체 지향 프로그래밍

  • 객체 지향 프로그래밍은 개발과 유지보수를 쉽게 만들기 위해 생겨난 개념이다.
  • 다형성, DIP, OCP등을 통해 장점을 알아보자.

Reference Code

public class OrderServiceImpl {
    // Bad
    private final OrderRepository orderRepository = new MemoryOrderRepository();
}

public interface OrderRepository {
    void save(Order order);
}

public class MemoryOrderRepository implements OrderRepository {}

public class JpaOrderRepository implements OrderRepository {}

다형성

개념

  • 하나의 타입(인터페이스, 클래스, 메소드)이 다른 역할을 하는 것

인터페이스, 클래스

  • 구현 또는 상속 필요
  • OrderRepository는 하나의 인터페이스지만, 자신을 구현하는 MemoryOrderRepository를 대입할 수도 있고 JpaOrderRepository를 대입할 수도 있다.

메소드

Overriding

  • 구현 또는 상속 필요
  • save(Order order)는 하나의 메소드지만 MemoryOrderRepositoryJpaOrderRepository에서 다르게 구현할 수 있다.

Overloading

  • println(String x), println(int x)는 같은 이름의 메소드지만 다른 타입의 파라미터를 대입할 수 있다.

장점

  • 역할(Interface)과 구현(Class)을 분리할 수 있다.
  • 클라이언트를 변경하지 않고 서버의 구현 기능을 유연하게 변경할 수 있다.
  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.

DIP(Dependency Inverse Principle)

설명

  1. 상위 모듈(OrderServiceImpl)은 하위 모듈(MemoryOrderRepository, JpaOrderRepository)을 import해선 안된다.
  2. 상위 모듈과 하위 모듈은 같은 추상화(OrderRepository)를 의존해야 한다. (추상화: Interface 또는 abstract class)
    • 상위 모듈에선 import
    • 하위 모듈에선 implements 또는 extends
  3. 추상화(OrderRepository)는 세부 사항(MemoryOrderRepository, JpaOrderRepository)에 의존하지 않고 세부 사항이 추상화에 의존한다.
    • 추상화는 세부 사항을 import하지 않기 때문에 의존 관계를 알 수 없다.
    • 세부 사항은 추상화를 implements 또는 extends 하기 때문에 의존 관계를 알 수 있다.

OCP(Open-Close Principle)

설명

  • 확장에는 열려있고 변경에는 닫혀있어야 한다.

DIP, OCP 위배

Reference Code의 문제점

  1. OrderServiceImpl 내부에서는 OrderRepository를 의존하고 있는 것처럼 보이지만, 동시에 MemoryOrderRepository도 의존하고 있다.
    • 상위 모듈(OrderServiceImpl)에서 하위 모듈(MemoryOrderRepository)을 import하고 있으므로 DIP에 위배된다.
  2. 만약 요구사항이 바뀌어서 MemoryOrderRepositoryJpaOrderRepository로 바꾸어야 한다면 OrderServiceImpl을 다음과 같이 수정해야 한다.
    • private final OrderRepository = new MemoryOrderRepository(); -> private final OrderRepository = new JpaOrderRepository();
    • 수정이 일어났으므로 OCP에 위배된다.
  3. 다형성을 사용했지만 DIP와 OCP가 전부 위배되었다.
    • 다형성 만으로는 DIP, OCP를 준수할 수 없다.

해결방법

  1. 상위 모듈에서 모든 하위 모듈과의 의존을 끊고, 인터페이스만 선언하며, 상위 모듈의 생성자에서 하위 모듈을 주입 받는다.
public class OrderServiceImpl {
    // private final OrderRepository orderRepository = new MemoryOrderRepository();
    private final OrderRepository orderRepository;

    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}
  1. 객체를 생성하고 연관관계를 맺어주는 역할을 할 설정 클래스를 생성한다.
public class AppConfig {
    public OrderService orderService() {
        return new OrderServiceImpl(orderRepository());
    }

    public OrderRepository orderRepository() {
        return new MemoryOrderRepository();
    }
}
  1. 객체가 필요한 곳에서는 이제 new OrderServiceImpl()이 아닌 설정 클래스(AppConfig)를 통해 가져온다.
public class OrderServiceTest {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        OrderService orderService = appConfig.orderService();
    }
}

DIP, OCP 준수

  • 이제부턴 MemoryOrderRepositoryJpaOrderRepository로 바꾸어야 한다면, 논리 코드를 변경할 필요 없이 설정 코드만 변경하면 된다.
  • 다형성과 설정 클래스를 통해서 DIP, OCP를 준수할 수 있게 되었다.

IoC, DI

IoC(Inversion of Control, 제어의 역전)

  • 기존에는 상위 모듈(OrderServiceImpl)이 스스로 필요한 하위 모듈 객체를 생성하고, 연결하고, 실행했다. 즉, 상위 모듈이 프로그램의 제어 흐름까지 담당했다.
  • 이제는 설정 클래스(AppConfig)가 제어 흐름(OrderServiceImpl -> MemoryOrderRepository)을 담당한다. 즉, 설정 클래스가 하위 모듈 객체를 생성하고, 상위 모듈 연결까지 담당하게 되었다.
  • 상위 모듈(OrderServiceImpl)은 추상화(OrderRepository)에 어떤 객체가 연결되는지와 관계없이 자신의 로직 실행만 담당하면 된다.
  • 이렇듯 프로그램의 제어 흐름을 작성한 코드 외부에서 담당하는 것을 제어의 역전(IoC)이라고 한다.

프레임워크, 라이브러리

  • 프레임워크는 내가 작성한 코드를 대신 제어하고, 대신 실행한다.(Spring, Spring Security, JUnit, JPA)
  • 라이브러리는 내가 작성한 코드가 직접 제어 흐름을 담당한다.(Gson, SnakeYAML, Math, String)

DI(Dependency Injection, 의존성 주입)

  • 객체(OrderServiceImpl)의 외부(AppConfig)에서 의존 객체(MemoryOrderRepository)를 주입한다.
  • 클래스(OrderServiceImpl)에 인터페이스만 선언되어 있을 때, 자신은 어떤 객체가 주입될 지 알 수 없다.

스프링 컨테이너(Spring Container)

  • 기존에는 개발자가 직접 Java 코드로 AppConfig를 만들어서 직접 객체를 생성하고 DI를 했지만, 이제는 스프링 컨테이너를 통해서 객체를 생성하고 등록하고 찾아서 DI를 한다.
  • ApplicationContext를 스프링 컨테이너라고 한다.
  • 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.
    • @Component, @Controller, @Service, @Repository, @Configuration, @Bean
  • 스프링 컨테이너는 @Configuration이 붙은 클래스(AppConfig)를 설정 정보로 사용한다.
    • @Configuration클래스 안에 있는 @Bean이 붙은 메소드를 모두 호출하고 반환된 객체를 스프링 컨테이너에 등록한다.
    • @Bean이 붙은 메소드명을 그대로 스프링 빈의 이름(key)으로 사용한다.
    • applicationContext.getBean(key) 메소드를 사용해서 스프링 빈을 찾을 수 있다.