본문 바로가기
2023 활동 - 4학년/[1월 ~ 4월] sw 아카데미 백엔드 과정

[2023.02.23 / CNU SW 아카데미] SpringBasic Part1 - D17

by 은행장 노씨 2023. 2. 23.

1. IoC

오늘은 '제어의 역전(inversion of control)'에 대하여 한번 알아보겠다. 

  • Order Entity 에서 제어를 했다. 
  • 객체가 자신이 사용할 객체를 스스로 선택하지 않고, 스스로 생성하지도 않는다. 
    -> 생성할 때 전달(주입)을 받게 된다. 
  • 서블릿, 스프링 같은 프레임워크에서는 제어의 권한이 프레임워크에 있다. 
    - 프레임워크는 전체 흐름의 제어 권한을 가지고 있다. 
    - 애플리케이션 코드가 프레임워크에 짜놓은 틀에서 수동적으로 동작하게 된다. => Hollywood Principle

 

학습 목표

- OrdeContext : 애플리케이션의 주요 객체에 대해서 생성과 관계설정
- OrderService : Order에 대한 비즈니스 로직

 

OrderService는 Voucher 서비스와 Order에 대한 정보를 기록하고, 조회할 수 있는 repository에 대해서 의존성을 가진다. 

코드를 보려면 아래의 [더보기]를 누른다. 

더보기
public class OrderService {
	private final VoucherService voucherService;
    private final OrderRepository orderRepository;
    
    public OrderService(VoucherService voucherService,OrderRepository orderRepository) {
    	this.voucherService = voucherService;
        this.orderRepository = orderRepository;
    } 
    
    // Order 생성에 대한 책임을 갖게 된다.
    // voucher 없는 경우
    public Order createOrder(UUID customerId, List<OrderItem> orderItems) {
    	var order = new Order(UUID.ramdomUUID(), customerId, orderItems);
        orderRepository.insert(order);       
        return order;
    }
    
    // Order 생성에 대한 책임을 갖게 된다.
    // voucher 있는 경우
    public Order createOrder(UUID customerId, List<OrderItem> orderItems, UUID voucherID) {
    	var voucher = voucherService.getVoucher(voucherID);
    	var order = new Order(UUID.ramdomUUID(), customerId, orderItems, voucher);
    	// order 기록을 다시 꺼낼 수 있기 때문에 저장한다. order의 연속성을 보장한다. 
        orderRepository.insert(order);
        voucherService.useVoucher(voucher); 	// 주문을 한다면 바우처를 통해서 하게 된다. 
        
        return order;
    }
    
}


public interface OrderRepository {
	public void insert(Order order) {
    	
    }
}

// Service와 Repository에 대한 책임을 담당한다. 의존 관계
// 각각 컴포넌트를 생성하는 메소드를 만들어보자. 
public class OrderContext {
	VoucherRepository voucherRepository() {
    	return new VoucherRepository() {
        	@Override
            public Optinal<Voucher> findById(UUID voucher) {
            	return Optinal.empty();
            }
        };
    }
    
    OrderRepository orderRepository() {
    	return new orderRepository() {
        	@Override
            public void insert(Order order){
            	return Optinal.empty();
            }
        };
    }
    
   	public VoucherService() {
    	return new VoucherService(voucherRepository());
    }
    
	public OrderService orderService() {
    	return new OrderService(VoucherService());
    }
}

// 바우처에 대한 정보를 어디에서 불러와서 클래스를 생성해양 한다. 
public class VoucherService {
	// 바우처도 repository가 필요하다. 
    private final VoucherRepository voucherRepository;
    
    public VoucherService(VoucherRepository voucherRepository) {
    	this.voucherRepository = voucherRepository;
    }
    
	public Voucher getVoucher(UUID voucherID) {		// OrderService에서 이용
    	return voucherRepository
            .findById(voucherID)
        	.orElseThrow(() -> new RuntimeException("Can not find a voucher for " + voucherID));
    }
    
    public void useVoucher(Voucher voucher) {
    
    }
}

// 어떻게 보관하는지에 관해서 계속 바뀜 -> interface
public interface VoucherRepository {

	// Optional<Voucher> : Entity에 대한 정보가 없을 수도 있다. 
    // 관계에 대한 설정을 context에서 가져간다. 
	Optional<Voucher> Voucher findById(UUID voucherID);
}
// OrderTester.java

public class OrderTester {
	public static void main(String[] args) {
    	var customerId = UUID.randomUUID();
        var orderItems = new ArrayList<OrderItem>() {{
        	add(new OrderItem(UUID.random(), 100L, 1));
        }};
        
        var orderContext = new OrderContext();
        var orderService = orderContext.orderService();
        // order를 만들 때, voucher가 있을 수도 있고 없을 수도 있다.
        var order = orderService.createOrder(customerId, orderItems);
        
    }
}

지금까지 짠 코드를 시각화 해보면...

출처 : 프로그래머스 캠퍼스

  • OrderContext는 주문에 대한 전반적인 도메인 객체에 대한 생성을 책임지고 있다. 
  • OrderService는 자신이 직접 OrderRepository를 선택하지 않는다.
    - VoucherService 또한 직접 생성하지 않는다. 
  • 객체를 만드는 제어권을 OrderContext에게 넘겼다.  
  • IoC Container : IoC가 일어나는 공간
    - IoC Container에서 개별 객체들의 의존 관계 설정이 이뤄지게 된다. 
    - 객체의 생성과 파괴를 담당한다. 

- (OrderSerive - OrderRepository), (OrderService - VoucherService) 관계 정의   

- 객체지향 프로그램을 할 때, 느슨한 결합도를 만드는 것이 중요하다!

객체가 자신이 사용하는 객체를 직접 만들지 않으면 된다. 
객체 생성에 대한 권한을 위임하자. 

 


2. DDD

Repository와 Service가 무엇일까?

출처 :&nbsp;https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design

 

Aggregate

: 일종의 Entity.

  • Entity들의 집합
  • 각각의 Aggregate는 root가 존재한다. => root는 하나의 Entity다. 
  • Aggregate => ACID(Atomic, Constant, Isolate, Durable) 트랜잭션이 만들어진다.
    - 상태가 변환이 되는 것이 Commit 단위가 된다. 
  • 서비스에서 이런 형태의 트랜잭션을 한다. 
    - 서비스는 상태가 없다. 메소드만 있다.
     

 

* Repository에서 하는 일...

- Order를 실제로 저장하고, (상태가 복구가 된다.)

- Order의 상태를 불러오고, 

 

- OrderService도 Domain model Layer에 존재한다. 

  • OrderService -> Repository를 이용해서 실제 Order 트랜잭션 
Repository는 Entity를 저장하는 저장소다. 

 


3. ApplicationContext

Application Context는 일종의 ioc Container이다. 

출처 : 프로그래머스

order Context에서 객체에 대한 생성과 조합이 이루어졌다. 

  • 개별 객체들의 의존관계 설정이 자동으로 이루어진다. 
  • 객체들의 생성과 파괴 조합 등을 관장한다. 
  • register : 필요한 의존관계를 맺어줌
    - 생명주기를 Managing하면서 객체에 대한 인스턴스를 만들어준다. 
  • IoC Container를 스프링에서 Application Context라는 Interface를 통해서 제공한다. 
  • Bean Factory를 상속한다.

Bean

: IoC Container에서 관리되어지는 객체

스프링에서 제공하는 Application Context, BeanFactory, IoC Container에 의해 관리되어지는 객체를 말한다. 

- 객체가 ioc에서 관리되는 객체와 아닌 객체를 분리하기 위해서 Bean을 만들었다. 

- 어노테이션 기반으로 Bean을 정의한다. 

 

Configuration Metadata

- 스프링의 ApplicaitonContext는 실제 만들어야할 Bean 정보를 Configuration Metadata로 받아옴

- 어플리케이션에서 객체들을 도식화 한 것이다. 도면

- xml 기반으로 작성하거나 java file 기반으로 작성할 수 있다. 

  • xml : GenericXmlApplicationContext 구현체 사용
  • java : AnnotationConfigApplicationContext 구현체 사용

- 공식문서에 따르면 최근에는 자바 기반

 

OrderContext => AppConfiguration

// AppConfiguration.java
// OrderContext to AppConfiguration
// Bean을 정의한 도면이라는 것을 알려줘야 한다.

@Configuration
public class AppConfiguration {

	@Bean
	VoucherRepository voucherRepository() {
    	return new VoucherRepository() {
        	@Override
            public Optinal<Voucher> findById(UUID voucher) {
            	return Optinal.empty();
            }
        };
    }
    
    @Bean
    OrderRepository orderRepository() {
    	return new orderRepository() {
        	@Override
            public void insert(Order order){
            	return Optinal.empty();
            }
        };
    }
    
    @Bean
   	public VoucherService() {
    	return new VoucherService(voucherRepository());
    }
    
    @Bean
	public OrderService orderService() {
    	return new OrderService(VoucherService());
    }
}

@Bean을 정의한 Configuration 메타 데이터가 된다. 

 

// OrderTester.java

public class OrderTester {
	public static void main(String []args) {
    	var applicationContext = new AnnotationConfigapplicationContext(AppConfiguration.class);
        
        var customerId = UUID.randomUUID();
        var orderService = applicationContext.getBean(orderService.class);
        var orderItems = new ArrayList<orderItem>() {{
        	add(new OrderItem(UUID.randomUUID(), 100L, 1));
        }};
        var order = orderService.createOrder(customerId, );
        Assert.isTrue(order.totalAmount() == 100L, MessageFormat.format("{0} is not 100L", order.totalAmount()));
    }
}

실행해보면 여러 로그가 찍힌다.

싱글톤 Bean이 된다. 


4. Dependency Injection

IoC를 이야기할 때 꼭 Dependency Injection이라는 용어가 나온다.

- IoC를 구현하는 패턴이다. 

  • 전략 패턴
  • 서비스 로케이터 패턴
  • 팩토리 패턴
  • 의존관계 주입패턴

생성자를 통해서 주입을 받는 패턴을 생성자 주입 패턴이라고 한다. 

스프링은 Constructor-based 외에 setter-based 도 제공한다. 

 

Dependency Resolution

자세한 설명은 아래의 공식문서 사이트에서 읽어보자. 

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io