IT 잡동사니

클린코드-시스템

케키키케 2022. 2. 24. 03:27

의존성 주입 

DI(Dependency Injection)와 IoC(Inversion of Control)

시스템의 제작과 사용을 분리하여 코드를 깨끗하게 할 수 있다.

의존성 주입은 제어의 역전 기법을 적용한 것이다.

IoC? 

  • 기존 프로그램은 스스로 필요한 객체를 생성하고, 연결하고, 실행했다. 
  • 반면에 AppConfig가 등장한 이후에는 구현 객체는 자신이 담당한 역할만을 수행한다. 프로그램의 제어 흐름을 AppConfig 가 가져간다.
    • AppConfig : 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스
    • 스프링 컨테이너는 @Configuration 이 붙은 설정클래스를 설정정보로 사용한다. @Bean을 모두 호출해서 스프링 컨테이너에 등록한다.
  • 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 '제어의 역전'이라고 한다.
  • 객체의 의존 관계가 객체 자신이 아닌 외부에 의해 결정된다. 
  • 따라서 객체 자체는 자신이 목표하는 기능에 대한 책임만 맡으므로, 단일 책임 원칭을(SRP)를 지키게 된다.
 

 

그럼 어디서 객체를 생성해서 넘겨받을 것인가? 우리는 앞서서, main에서 시스템 제작에 필요한 준비과정을 수행하자고 하였다. 그리고, 특수 컨테이너가 있다. 

책에서는 JNDI(Java Naming and Directory Interface)를 예로 들고 있다. 

JNDI는 의존성 주입을 부분적으로 구현한 기능이다. 이름을 가지고 찾는다?!

 

예제) 과거 JNDI없이 MySQL 데이터베이스에 접근하기 위해 사용한 방법과 JNDI를 이용한 방법

 

 

진정한 의존성 주입

  • 클래스는 완전히 수동적이다.
  • 대신, 의존성을 주입하는 방법으로 setter나 생성자 인수를 제공한다.
  • DI 컨테이너는 필료한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.
  • 대표적인 예로 '스프링 컨테이너'가 있다.
  • 정적인 의존관계를 손대지 않고, 동적인 의존관계를 변경할 수 있다.

 

예시)  주문 서비스는 할인정책에 의존한다. 하지만, 어떤 할인 정책을 따를지 전혀 모른다. (동적인 의존관계를 전혀 모른다)

*동적인 의존관계란, 실행 시점에 결정되는 의존 관계를 의미한다. (정적인 의존관계는 import문을 보고서 파악할 수 있는 관계)

 

 

 

 

DI 초기화 지연

lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부("true", "false", "default")

  • default : spring 의 기본 동작에 맞게 bean 을 생성하며 기본 동작은 false 이다.
  • true 로 설정할 경우 나중에 Bean 을 생성하게 된다.
  • lazy-init="true" 로 설정해도 해당 bean 이 lazy-init="false" 인 bean 에서 참조된다면 의존성 관계로 인해 초기화 프로세스에서 생성되게 된다.

 

확장

  • TDD, 리팩토링을 통해 얻어지는 꺠끗한 코드는 시스템을 조정하고, 확장하기 쉽게 만든다.
  • 관심사를 적절히 분리해야 점진적으로 발전할 수 있다.
  • 비즈니스 로직이 덩치가 큰 컨테이너와 밀접하게 결합되는 것은 좋지 않다. 
    • 의존성이 커진다.
    • 테스트를 복잡하게 만든다.
  • 인터페이스를 잘 활용해야 한다. 

 

 

횡단(cross-cutting) 관심사

  • '흩어진 관심사' 라고도 한다.
  • AOP(Aspect-Oriented Programming) : Aspect 모듈 구성 - 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다.

 

  • 자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘 3가지
    • 자바 프록시, 순수 자바 AOP 프레임워크, AspectJ관점

 

테스트 주도 시스템 아키텍처 구축

의사 결정 최적화의존성 주입

DI(Dependency Injection)와 IoC(Inversion of Control)

시스템의 제작과 사용을 분리하여 코드를 깨끗하게 할 수 있다.

의존성 주입은 제어의 역전 기법을 적용한 것이다.

IoC? 

  • 기존 프로그램은 스스로 필요한 객체를 생성하고, 연결하고, 실행했다. 
  • 반면에 AppConfig가 등장한 이후에는 구현 객체는 자신이 담당한 역할만을 수행한다. 프로그램의 제어 흐름을 AppConfig 가 가져간다.
    • AppConfig : 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스
    • 스프링 컨테이너는 @Configuration 이 붙은 설정클래스를 설정정보로 사용한다. @Bean을 모두 호출해서 스프링 컨테이너에 등록한다.
  • 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 '제어의 역전'이라고 한다.
  • 객체의 의존 관계가 객체 자신이 아닌 외부에 의해 결정된다. 
  • 따라서 객체 자체는 자신이 목표하는 기능에 대한 책임만 맡으므로, 단일 책임 원칭을(SRP)를 지키게 된다.

 

@Configuration
public class AppConfig {
 
	@Bean
 	public MemberService memberService() {
 		return new MemberServiceImpl(memberRepository());
 	}
 
	@Bean
 	public OrderService orderService() {
 		return new OrderServiceImpl(memberRepository(), discountPolicy());
 	}
 
	@Bean
 	public MemberRepository memberRepository() {
  		return new MemoryMemberRepository();
 	}
 
	@Bean
	public DiscountPolicy discountPolicy() {
 		return new RateDiscountPolicy();
	 }
}

 

 

그럼 어디서 객체를 생성해서 넘겨받을 것인가? 우리는 앞서서, main에서 시스템 제작에 필요한 준비과정을 수행하자고 하였다. 그리고, 특수 컨테이너가 있다. 

책에서는 JNDI(Java Naming and Directory Interface)를 예로 들고 있다. 

JNDI는 의존성 주입을 부분적으로 구현한 기능이다. 이름을 가지고 찾는다?!

 

예제) 과거 JNDI없이 MySQL 데이터베이스에 접근하기 위해 사용한 방법과 JNDI를 이용한 방법

//JNDI없이 MySQL 데이터베이스에 접근하기 위한 방법
Connection conn = null; 
try { 
    Class.forName ("com.mysql.jdbc.Driver" , true, Thread.currentThread (). getContextClassLoader ()); 
    conn = DriverManager.getConnection ("jdbc: mysql :/ / MyDBServer? user = user1 & password = password1"); 

    ...... 
    conn.close (); 
} 
.....


//JNDI를 사용한 방법
// DB서버명, 사용자명, 비밀번호 또는 DB가 바뀔 수 잇는 경우 유용하다. 코드를 수정하지 않고, xml만 수정한다.

<? Xml version = "1.0" encoding = "UTF-8"?> 
<datasources> 
<local-tx-datasource> 
<jndi-name> MySqlDS </ jndi-name>   //여기 이름이 있다.
<connection-url> jdbc: mysql :/ / localhost: 3306/lw </ connection-url> 
<driver-class> com.mysql.jdbc.Driver </ driver-class> 
<user-name> root </ user-na me> 
<password> rootpassword </ password> 
<exception-sorter-class-name> org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter </ exception-sorter-class-name> 
<metadata> 
<type-mapping> mySQL </ type-mapping> 
</ Metadata> 
</ Local-tx-datasource> 
</ Datasources>



//Java code 

Connection conn = null; 
try { 
    Context ctx = new InitialContext (); 
    Object datasourceRef = ctx.lookup ("java: MySqlDS");  //이름에 일치하는 서비스를 요청한다. 이름을 직접 넣어주는 점. 구현 객체가 Context에 의존하게 된다는 점이 단점이다.
    DataSource ds = (Datasource) datasourceRef; 
    conn = ds.getConnection (); 

...... 
c.close (); 
} 
.....

진정한 의존성 주입

  • 클래스는 완전히 수동적이다.
  • 대신, 의존성을 주입하는 방법으로 setter나 생성자 인수를 제공한다.
  • DI 컨테이너는 필료한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.
  • 대표적인 예로 '스프링 컨테이너'가 있다.
  • 정적인 의존관계를 손대지 않고, 동적인 의존관계를 변경할 수 있다.

 

예시)  주문 서비스는 할인정책에 의존한다. 하지만, 어떤 할인 정책을 따를지 전혀 모른다. (동적인 의존관계를 전혀 모른다)

*동적인 의존관계란, 실행 시점에 결정되는 의존 관계를 의미한다. (정적인 의존관계는 import문을 보고서 파악할 수 있는 관계)

 

 

import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;

public class OrderServiceImpl implements OrderService{
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;

	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}

	...
}
//JUnit 프레임워크. 내가 작성한 코드를 제어하고, 대신 실행한다.
//나는 기능만 작성하고 JUnit 프레임워크가 알아서 실행한다.

@SpringBootTest
@Transactional
@Rollback
public class AccessGroupServiceTest {

    @Autowired
    private AccessGroupRepository accessGroupRepository;

    @Autowired
    private AccessGroupUserRepository accessGroupUserRepository;

    @Autowired
    private AccessGroupService accessGroupService;

    @Autowired
    private UserRepository userRepository;

    @MockBean
    private UserService userService;

    @PersistenceContext
    private EntityManager em;

    private final User user1 = new User("parksonghee", "박송희");
    private final User user2 = new User("cripsy19945", "백건호");
    private final User user3 = new User("iu", "아이유");

    private final String accessGroupName1 = "LW_일반_그룹";
    private final String accessGroupName2 = "LW_매출_그룹";
    private final String accessGroupName3 = "L1_일반_그룹";

    private final String accessGroupDescription = "LW 일반 그룹입니다.";

    @BeforeEach
    void 접근그룹_생성_필수조건_사용자_설정() {
        userRepository.save(user1);
        userRepository.save(user2);
        userRepository.save(user3);
        Mockito.when(userService.getCurrentUser()).thenReturn(user1);
    }

    @Nested
    class 접근그룹_생성_테스트 {

        AccessGroupDto.AccessGroupCreateRequest request = null;

        @BeforeEach
        void 접근그룹_생성요청_설정() {
            request = new AccessGroupDto.AccessGroupCreateRequest();
            request.setName(accessGroupName1);
            request.setDescription(accessGroupDescription);
        }

        @Test
        void 접근그룹_정상생성() {
            AccessGroupDto.AccessGroupInfo created = accessGroupService.createAccessGroup(request);

            Assertions.assertNotNull(created.getId());
            assertEquals(created.getName(), accessGroupName1);
            assertEquals(created.getDescription(), accessGroupDescription);
        }

        @Test
        void 접근그룹_생성실패_중복이름() {
            accessGroupService.createAccessGroup(request);
            Assertions.assertThrows(DuplicateException.class, () -> accessGroupService.createAccessGroup(request));
        }
    }

 

DI 초기화 지연

lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부("true", "false", "default")

  • default : spring 의 기본 동작에 맞게 bean 을 생성하며 기본 동작은 false 이다.
  • true 로 설정할 경우 나중에 Bean 을 생성하게 된다.
  • lazy-init="true" 로 설정해도 해당 bean 이 lazy-init="false" 인 bean 에서 참조된다면 의존성 관계로 인해 초기화 프로세스에서 생성되게 된다.

확장

  • TDD, 리팩토링을 통해 얻어지는 꺠끗한 코드는 시스템을 조정하고, 확장하기 쉽게 만든다.
  • 관심사를 적절히 분리해야 점진적으로 발전할 수 있다.
  • 비즈니스 로직이 덩치가 큰 컨테이너와 밀접하게 결합되는 것은 좋지 않다. 
    • 의존성이 커진다.
    • 테스트를 복잡하게 만든다.
  • 인터페이스를 잘 활용해야 한다. 

 

 

횡단(cross-cutting) 관심사

  • '흩어진 관심사' 라고도 한다.
  • AOP(Aspect-Oriented Programming) : Aspect 모듈 구성 - 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다.

  • 자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘 3가지
    • 자바 프록시, 순수 자바 AOP 프레임워크, AspectJ관점

 

테스트 주도 시스템 아키텍처 구축

의사 결정 최적화

참고

SoC : https://velog.io/@seanlion/soc1

LazyInit Test : https://www.lesstif.com/spring/spring-bean-lazy-init-20775616.html