이 게시글은 김영한의 스프링 핵심 원리 - 기본편 강의를 바탕으로 작성한 글입니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
파이썬, 노드JS를 통해 프로젝트를 진행하면서 느낀 점은 코드를 분리하고 역할을 나누는 것이 얼마나 중요하고 필요한 지를 알았다. 특히, 점점 기능들을 추가하고 싶을 때, 혹은 코드를 간소화하기 위해서 특정 메서드를 추가해야 할 때 등등이다. 내가 처음으로 접한 프로그래밍 언어는 자바였고(정확히는 대학교 1학년 필수 수업으로 C언어를 먼저 공부하였다.), 가장 이해 안되고 필요성을 몰랐던 것이 인터페이스였다. 그리고 이를 기반으로 한 다형성의 원리 등 기본적인 객체 지향의 개념이 왜 중요한지를 몰랐다. 허나 이번 스프링 인프런 강의를 들으며 다양한 실무 사례를 접하고 나의 작은 프로젝트 경음을 토대로 이해할 수 있었다. 내가 사이드 프로젝트를 진행하면서 필요했던 것이 바로 관심사의 분리였다.
1. 관심사의 분리
각각의 객체들은 역할들이 존재한다. 각 객체들은 책임이 존재하고 서로에게 영향을 끼쳐서는 안된다.(SRP) 하나의 객체 안에 여러 가지의 메서드들, 여러 객체들이 존재한다면 어떻게 될까? 이는 서로의 의존관계가 매우 높다는 뜻이고, 유지 보수가 굉장히 어렵다는 뜻이다.이를 방지하기 위해 객체 안에 구현체 코드를 작성하지 않는다. 즉, 추상적인 개념인 인터페이스를 갖고 있어야 한다.(DIP) 만약 하나의 객체에서 다른 객체의 구현체를 갖고 있다면? 이는 DIP를 위반하는 것이다. 즉, 인터페이스의 구현체를 결정하는 것은 구현체가 아닌 특정 외부에서 결정하고 관리해야 한다. 이를 지키지 않는다면 DIP 뿐만 아니라 OCP까지 위반하게 된다.
- 만약 특정한 정책을 사용해야 하는 경우, 이 정책에 대해서는 인터페이스로 선언해야 한다.
- 실제 서비스에서 사용되는 정책은 한 가지가 아니라 여러 개일 수 있기 때문이다.
- 따라서 서비스 로직에서 특정 정책(구현체)에 의존하게 되면, 이후 변경할 때 서비스 로직도 같이 변경해줘야 한다.(OCP 위반)
- 즉, 서비스 로직은 단순히 인터페이스 참조변수 만을 갖고 있고(구현체가 없는 상태), 이 참조변수에 어떤 구현체가 들어갈 지를 결정해 주는 객체가 존재하면 서비스 로직은 굳이 변경할 필요가 없게 된다.
AppConfig의 등장은 이러한 문제를 해결해준다. 애플리케이션의 전체적인 동작을 구성하고 설정(config)하는 객체로, 구현 객체를 생성하고 연결시키는 역할을 한다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
위처럼 AppConfig는 특정 구현 객체(MemberServiceImpl, MemoryMemberRepository)를 반환하는 메서드들을 갖고 있다. 이 때 반환하는 형식이 생성자 형태로 되어 있다. `new MemberServiceImpl() / new MemoryMemberRepository()` 따라서 이 구현 클래스 안에는 생성자가 별도로 있어야 한다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
위처럼 MemberServiceImpl이라는 구현 클래스는 생성자를 별도로 포함하고 있다. 이 생성자는 매개변수로 인터페이스로 받으며, 실제로 들어오는 구현 객체와 인터페이스 참조변수를 연결한다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
다음은 관심사가 분리되기 전 코드로, 구현체가 인터페이스와 구현체를 같이 의존하고 있다. 이는 OCP와 DIP를 위반하고 있다. AppConfig를 사용하게 되면 MemberServiceImpl은 자신이 구현해야 하는 기능에만 집중하면 된다. MemberRepository 타입으로 어떤 구현체가 들어오던지 신경쓰지 않아도 된다. 그저 외부에서 주입받는 타입으로 실행하면 되기 때문이다.
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
실제로 사용할 때는 AppConfig 객체를 통해서 불러오기만 하면 된다.
2. 의존관계 주입 (생성자 주입)
memeberServiceImpl 입장에서 보면 AppConfig라는 외부 객체가 자신에게 memoeryMemberRepository라는 특정 구현체를 생성해서 주입해준 것이다. 이때 주입하는 방식이 생성자를 통해서 접근한 것이다. 따라서 이를 의존관계 주입을 하는, DI 방식 중 생성자 주입 방식이라고 할 수 있다. 생성자를 통해 주입받을 경우 final로 선언함으로써 구현체의 변동을 막을 수 있다. 또한 구현체가 생성되지 않을 경우 컴파일 에러를 발생시켜 의존관계 누락을 방지할 수 있다.
3. 기타
사실 스프링 인프런 강의를 들으면서 중요한 부분들을 정리하기 위해 작성한 글이다. 따라서 스프링이라는 카테고리에 저장을 하였지만, 위 개념에서 스프링이 사용되지는 않았다. 오히려 객체지향 프로그래밍과 연관이 있다. 스프링을 사용하는 것도 결국 자바의 객체지향 설계를 돕는 역할을 하기 때문에 스프링 기반 어플리케이션에서 OOP와 SOLID 설계 원칙을 우선적으로 잘 지키는 것이 중요한 것 같다.
'Spring Framework > Spring Boot' 카테고리의 다른 글
[JPA] 스프링 Data JPA 기술을 공부하며 배운 내용 정리 (0) | 2024.08.14 |
---|---|
[JPA] 스프링 JPA 기술을 공부하며 배운 내용 정리 (2) (0) | 2024.08.03 |
[JPA] 스프링 JPA 기술을 공부하며 배운 내용 정리 (1) (0) | 2024.08.03 |
멋사 미션을 하며 스프링 JdbcTemplate에 대해 알게 된 내용 (0) | 2024.05.20 |
멋사 미션을 하며 스프링 부트 예외처리에 대해 알게 된 내용들 (0) | 2024.05.14 |