본문 바로가기

Spring

OCP 적용 DI 구조

반응형

OCP (개방 폐쇄 원칙)

확장에는 열려있고 변경에는 닫혀있음

 

Repository 사용을 생각해본다

데이터 저장에 사용되는 값을 읽어오는 경로와 방법이 다양하게 있을 수 있다. 

ex) memory, DB (JDBC, JPA, ...)

save, findByID, findByName ... 등의 메소드를 가진 추상 클래스 (인터페이스)를 생성하고,

다양한 사용에 대한 repository에서 추상 클래스를 상속받아 자신의 방법에 맞게 구현한다면?

인터페이스 확장이 가능할 것이다.

 

public interface MemberRepository {
 Member save(Member member);
 Optional<Member> findById(Long id);
 Optional<Member> findByName(String name);
 List<Member> findAll();
}

메모리가 타겟인 repository라면 이렇게 구현이 될 것이다.

public class MemoryMemberRepository implements MemberRepository {
 private static Map<Long, Member> store = new HashMap<>();
 private static long sequence = 0L;
 @Override
 public Member save(Member member) {
 member.setId(++sequence);
 store.put(member.getId(), member);
 return member;
 }
 @Override
 public Optional<Member> findById(Long id) {
 return Optional.ofNullable(store.get(id));
 }
 @Override
 public List<Member> findAll() {
 return new ArrayList<>(store.values());
 }
 @Override
 public Optional<Member> findByName(String name) {
 return store.values().stream()
 .filter(member -> member.getName().equals(name))
 .findAny();
 }
 public void clearStore() {
 store.clear();
 }
}

 

repository는 service에서 사용하는 객체이고 스프링 컨테이너에서 주입을 해주기 위해 bean으로 등록되어야 한다.

bean으로 등록된 객체는 스프링 컨테이너에서 싱글톤으로 관리하며, 주입이 필요한 경우 컨테이너에서 알아서 주입해 줌

 

bean을 등록하는 방법은 scan방법과 직접 등록 두가지 가 있다

scan 방법은 @Service, @Repository 같은 어노테이션 내부에서 동작하는 @Component를 통해 컨테이너가 bean으로 인식하며, @Autowired를 통해 컨테이너가 알맞은 객체를 찾아서 주입해준다.

@Service
public class MemberService {
 private final MemberRepository memberRepository;
 @Autowired
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
}

※ DI는 필드, 생성자, setter 3가지 방식이 있는데 

setter 같은 경우 MemberRepository 생성자 자동 호출 후에 public으로 연 Setter를 통해 수행되므로 외부에서 해당 메소드가 노출되어 사용될 위험이 있다.

또한 필드 주입 방식은 만약 컨테이너에 의해 Repository가 주입된 경우를 생각하면 필드 주입은 이후 변경할 수가 없다

생성자는 주입을 위해 생성자를 열어 두었으므로 이후에 변경이 가능하다.

@Repository
public class MemoryMemberRepository implements MemberRepository {}

 

하지만 이렇게 scan식으로 구현 시, service에서 사용할 repository를 결정하는 작업은 repository 파일에서 어노테이션을 지우고 붙이고 하는 변경이 필요하다.

 

따라서 직접 구현 방식으로 생각해본다.

직접 구현은 @Configuration 어노테이션이 붙은 객체 안에서 @Bean을 통해 컨테이너에 인식시킬수 있다.

Service 객체에서는 @Service, @Autowired를 제거하고 진행한다.

@Configuration
public class SpringConfig {
 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository());
 }
 @Bean
 public MemberRepository memberRepository() {
return new MemoryMemberRepository();
 }
}

만약 MemoryMemberRepository가 아닌 이를테면 JpaMemberRepository를 MemberService에 주입하고 싶다면 SpringConfig에서 MemberRepository return 대상을 JpaMemberRepository으로 바꾸기만 하면 된다.

따라서 수정은 다른 repository 파일을 변경할 것 없이 config 파일만 수정하는 것으로 제한되며 수정에 폐쇄라는 원칙을 보장하게 된다.

반응형

'Spring' 카테고리의 다른 글

스프링 컨테이너 ApplicationContext  (0) 2023.08.02
DB 접근 기술  (0) 2023.07.29
Spring boot traffic 제어 + redirect  (0) 2021.09.02
Sentry + Spring Boot (monitoring tool)  (0) 2021.08.31
Spring 에러 전역 처리  (0) 2021.08.31