시작하기에 앞서,
나는 스프링을 공부하기에 앞서 스프링의 핵심이 되는 3가지에 대해 먼저 알아보았다.
IoC (Inversion of Control)
IoC란 제어의 역전이다
프로그램의 제어권은 보통 개발되는 애플리케이션에 존재하였는데 스프링에서는 DI 컨테이너가 객체의 생성, 초기화 서비스 소멸에 관한 모든 권한을 가지면서 객체의 생명주기를 관리한다.
이처럼 제어권한을 프레임워크가 소유한다는 것이 제어의 역전이다.
DI (Dependency Injection)
DI란 말을 해석해보면 의존성 주입이다.
이는 IoC의 연장선이라고 볼 수 있는데, 원래 프로그램의 제어권을 애플리케이션이 들고있어 의존관계를 직접 관리했지만 스프링에서 제어권한이 프레임워크에게 넘어감으로써 필요한 의존관계를 DI 컨테이너로부터 주입받는 것을 의미한다.
이처럼 의존관계를 직접 설정하지않고 프레임워크가 관리해주는 것이 의존성 주입이다.
AOP (Aspect Oriented Programming)
AOP란 관점지향프로그래밍이다.
우리는 여러 비지니스 로직을 개발하다보면 반복되는 로직들을 목격하기도 한다. 공통되는 로직들을 공통적으로 처리될 수 있도록 이 로직을 분리해놓았다가 필요할때에 사용할 수 있도록 하는 것이다.
이처럼 공통된 로직을 모아 필요한 시기에 적용할 수 있도록 프로그래밍하는 것이 관점지향 프로그래밍이다.
이와 같이 말로 표현하면 IoC와 DI 부분은 잘 와닿지 않으니 직접 살펴보자.
살펴보기,
나는 어떤 회사의 직원에 대한 아주 간단한 예시로 이를 살펴보고자한다.
먼저, 간단하게 어떻게 구성했는지 살펴보자.
다음과 같이 이루어져 있다고 가정을 하고 Repository에서는 실제로 DB에 저장하는 것이 아니라 간단히 메모리상에서 저장과 조회할 수 있도록 구현했다.
그리고 Service에서는 Repository에서의 메소드를 토대로 실제로 비지니스 로직을 실행하는 부분을 구현해보았다.
이제 코드를 살펴보자.
@Getter
@Setter
class Employee {
private Long id;
private String name;
private String department;
private String position;
public Employee(String name, String department, String position) {
this.name = name;
this.department = department;
this.position = position;
}
}
- 사원은 id, 이름, 부서, 직급으로 구성되어있고 간단하게 Getter, Setter, 생성자를 추가해주었다.
public interface EmployeeRepository {
void save(Employee employee);
Employee findById(Long employeeId);
List<Employee> findAll();
}
public class EmployeeRepositoryImplement implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
- EmployeeRepository라는 인터페이스를 구성한 뒤, 구현체인 EmployeeRepository를 구성하였다.
public interface EmployeeService {
void join(Employee employee);
Employee findEmployee(Long employeeId);
List<Employee> findAllEmployee();
}
public class EmployeeServiceImplement implements EmployeeService {
EmployeeRepository employeeRepository = new EmployeeRepositoryImplement();
@Override
public void join(Employee employee) {
employeeRepository.save(employee);
}
@Override
public Employee findEmployee(Long employeeId) {
return employeeRepository.findById(employeeId);
}
@Override
public List<Employee> findAllEmployee() {
return employeeRepository.findAll();
}
}
- EmployeeService라는 인터페이스를 구성한 뒤, 구현체인 EmployeeService를 구성하였다.
이 코드에 대해서 살펴보도록 하자. 과연 이 코드는 좋은 코드일까?
그렇다고 보기는 힘들다. 이 이유는 EmployeeService를 보면 알 수 있다.
EmployeeRepository employeeRepository = new EmployeeRepositoryImplement();
다음 코드를 보면 현재 EmployeeService가 비지니스 로직만 실행하는 것이 아니라, 직접 객체를 생성하고 의존 관계를 설정해주는 등 다양한 책임을 가지고 있는 모습을 확인할 수 있다.
그렇다면, SOLID 원칙 중 SRP를 잘 지키고 있다고 보기는 어려울 것이다.
앞서 말한 것처럼 현재 애플리케이션이 제어권을 들고 직접 객체의 생성을 담당하고 있는 모습을 볼 수 있다.
추가적으로 의존관계 또한 우리가 애플리케이션에서 직접 관리하고 있는 모습을 확인할 수 있다.
그러면 어떤 문제가 발생할지 생각해보자.
우리가 추후 메모리를 사용하는게 아니라 DB를 사용하게되어 EmployeeRepositoryImplement가 아닌 다른 구현체로 바꾸게 된다면 수정이 불가피해지게 될 것이다.
그렇다면, SOLID 원칙 중 OCP를 위반하게 되는 것이다.
또한 현재 EmployeeService는 EmployeeRepository를 참조하고 있을 뿐만 아니라, new EmployeeRepoistoryImplement();를 해주면서 구현 객체에 또한 의존하고 있다.
그렇다면, SOLID 원칙 중 DIP를 위반하게 되는 것이다.
자 다음과 같이 이러한 문제가 발생하기 때문에 스프링에서는 IoC, DI를 지원하는 것이다.
이를 간단하게 해결해보자.
@Component
public class EmployeeRepositoryImplement implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
@Component
public class EmployeeServiceImplement implements EmployeeService {
@Autowired
EmployeeRepository employeeRepository;
@Override
public void join(Employee employee) {
employeeRepository.save(employee);
}
@Override
public Employee findEmployee(Long employeeId) {
return employeeRepository.findById(employeeId);
}
@Override
public List<Employee> findAllEmployee() {
return employeeRepository.findAll();
}
}
자 다음을 보면 알 수 있듯이 Component Scan을 통해 스프링이 직접 해당 클래스를 빈에 등록해주어 이를 관리해준다.
그리고 시기적절하게 EmployeeServiceImplement에서 EmployeeRepository를 Autowired를 통해 주입해주는 모습을 확인할 수 있다.
이처럼, 제어권이 프레임워크에 넘어가고 DI 컨테이너에서 관리하여 의존성을 프레임워크가 주입해주는 것을 통해 EmployeeServiceImplement에서는 비지니스 로직만 수행할 수 있으며(SRP), 구현 객체가 바뀌더라도 Component Scan의 대상만 바꾸면 되니 수정이 필요하지 않으며(OCP) 단순히 추상화의 대상만을 의존(DI)하게 된다.
이처럼 위에서 말한 문제들을 깔끔히 해결할 수 있다.
이렇게 스프링 프레임워크는 이러한 핵심을 토대로 우리의 개발을 용이하게 만들어주는 모습을 확인할 수 있다.
'스프링' 카테고리의 다른 글
MVC, Spring MVC (0) | 2021.09.18 |
---|---|
빈 초기화 콜백, 소멸전 콜백 (0) | 2021.09.16 |
같은 타입의 빈 등록(중복) (0) | 2021.09.16 |
수동 빈 등록, 자동 빈 등록 (2) | 2021.09.15 |
스프링 컨테이너, 빈 (0) | 2021.09.15 |