Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 1부 Domain-Driven Design

 기록은 기억을 남긴다.
- Baltasar Gracian


여기 
사나이가 있다. 불안한 시선으로 방을 둘러 보던 사나이는 자신의 몸에 새겨진 문신과 폴라로이드 사진에 적힌 문장 줄에 의지해 이전의 상황을 재구성하려고 노력한다. 그는 전직 보험 수사관으로, 아내가 죽던 범인에 의해 뒤통수를 강타당하고 쓰러진다. 사나이는 사건 이전의 일에 대해서는 완벽하게 기억하지만 사건 이후의 일에 대해서는 아주 짧은 순간만을 기억하는 정신적 혼란을 겪게 된다. 10분이 않되는 짧은 시간 밖에 기억하지 못하는 그는 잊지 말아야 기억의 조각들을 자신의 몸과 폴라로이드 사진 위에 기록하며 과거와 현실 사이의 기억을 재편한다.

 

크리스토퍼 놀란 감독의 2000 작인 <메멘토> 단기 기억 상실증(Short-Term Memory Loss) 걸린 주인공이 현실을 기록하고, 기록을 통해 기억을 재생하고, 재생된 기억 위에 현실을 다시 재구성하는 반복적인 과정을 통해 아내를 죽인 범인을 쫓아 가는 과정을 그리고 있다. 주인공은 소멸되는 기억의 끈을 놓치지 않기 위해 끊임없이 자신의 몸과 폴라로이드 사진에 기록을 남기고 기록을 바탕으로 과거를 복구한다.

 

어플리케이션은 <메멘토> 주인공과 같다. 종료 되는 순간 단기 기억 상실증(Short-Term Memory Loss) 걸린 어플리케이션의 모든 기억들은 깨끗하게 증발돼 버리고 만다. 사용자들이 공을 들여 입력한 데이터는 공중으로 사라져 버리고, 분노한 사용자들의 욕설과 비방이 들이닥치던 어느 , 실직자가 당신은 차가운 거리로 내몰리게 지도 모른다. 따라서 어플리케이션은 상태를 끊임없이 기록하고, 기억을 재생하고, 현실을 재구성해야 한다. 유효 시간이 지나고 단기 기억 상실증으로 인해 모든 기억이 소멸되더라도 기록을 통해 어플리케이션의 기억을 되돌릴 있어야 한다.

 

어플리케이션이기억을 재생할 있다고 해서 모든 기억을 동시에 복구하는 것은 소모적인 일이다. 살아 오면서 겪은 모든 일들을 일일이 기억하고 있을 수는 없다. 필요할 사진첩을 들춰 보고 일기장을 펼쳐 보고 다이어리를 열어 보고 순간을 회상할 있으면 족하다. 앞에 닥친 일들을 기억하기에도 벅찬 것이 현실이다. 어플리케이션 역시 마찬가지다. 지금 처리하기 위해 필요한 최소한의 정보만 기억하고 있으면 된다. 시스템의 메모리는 한정된 자원이다. 지금 당장 필요하지도 않은 정보들을 유지하기 위해 값비싼 자원을 낭비할 필요는 없다. 잠시 잊어 버렸다가 필요할 기록을 들춰 보면 된다.

 

신이 인간에게 최고의 선물은 망각이지만 인간이 컴퓨터에게 최고의 선물은 영구적인 기억력이다. 그러나 영구적인 기억력이 하늘에서 뚝하고 떨어지는 것은 아니다. 어플리케이션을 망각의 늪에서 구원하기 위해서는 지금까지 우리가 망각하고 있던 가지 요소가 필요하다. 바로 영속성(persistence) 그것이다.

 

도메인객체의 생명주기

다시 주문 도메인으로 돌아가 보자. 이전 아티클에서 ENTRY POINT 대한 저장, 조회 등의 컬렉션 연산을 수행하기 위해 REPOSITORY 사용했다. 기억이 나지 않는가? 머리를 쥐어 뜯을 필요는 없다. 정도의 차이는 있지만 단기 기억 상실증은 모든 사람들에게 나타나는 일반적인 증상이다. 다행히 이전 아티클에서 REPOSITORY 관해 기록을 두었으니 아티클을 다시 읽어 보며 기억을 복구하기 바란다. 우리는 주문 도메인을 분석한 Customer, Product, Order ENTRY POINT 식별하고 ENTRY POINT 생명 주기를 관리하기 위해 CustomerRepository, ProductRepository, OrderRepository 도메인 모델에 추가했다.

그림1 주문 도메인 모델의 REPOSITORY

 

이제 Order OrderLineItem 생명 주기를 자세히 살펴 보자. Order OrderLineItem Customer newOrder() 메소드를 통해 생성된다. newOrder() 메소드는 Order CREATION METHOD order() 호출하며, 메소드는 내부적으로 생성자를 호출하여 Order 클래스를 생성한 반환한다. OrderLineItem Order with() 메소드를 사용하여 생성되며, with() 메소드 역시 OrderLineItem 생성자를 호출하여 인스턴스를 생성한다.


 

Order.java
public static Order order(String orderId, Customer customer) {          

return new Order(orderId, customer);

}

 

Order(String orderId, Customer customer) {

super(orderId);

this.customer = customer;           

}

      

public Order with(String productName, int quantity)

throws OrderLimitExceededException {

return with(new OrderLineItem(productName, quantity));

}


 

Order OrderLineItem 생성자가 호출되는 순간 사용자가 입력한 주문 정보를 저장하고 있는 주문 AGGREGATE 생성된다. 그러나 객체를 생성하는 것으로 끝난다면 지금까지 이야기해 왔던 REFERENCE OBJECT 추적성과 유일성을 만족시킬 없다. 생성된 주문 객체가 어플리케이션의 생명주기 동안 동일한 객체로 참조되기 위해서는 REPOSITORY 필요하다. 생성된 객체는 REPOSITORY 의해 관리되며 객체가 필요한 경우 REPOSITORY 통해 해당 REFERENCE OBJECT 얻을 있게 된다. 물론 REPOSITORY 통해 등록된 객체와 조회된 객체의 식별자(identity) 동일해야 한다. 테스트를 추가하자.


 

OrderTest.java
public void testOrderIdentical() throws Exception {

Order order = customer.newOrder("CUST-01-ORDER-01")

                      .with("상품1", 10)

                      .with("상품2", 20);

orderRepository.save(order);

            

Order anotherOrder = orderRepository.find("CUST-01-ORDER-01");

assertEquals(order, anotherOrder);

}   


녹색 막대다. REPOSITORY 통해 등록된 주문 객체들은 추적성과 유일성이라는 REFERENCE OBJECT 본연의 특성을 만족시키고 있다. 녹색 막대를 보고 났더니 기분이 상쾌해 졌다. 이제 색다른 것을 시도해 볼까?

 

이번에는 생성된 주문 내역을 삭제하는 시나리오를 구현해 보자. 주문의 생명 주기를 관리하는 클래스는 OrderRepository이므로 여기에 삭제 관련 메소드를 추가하자. , 잠깐. 이게 아니지. 가끔 깜빡 때가 있다. 메소드가 아니라 테스트를 먼저 추가해야 한다. OrderRepositoryTest 클래스에 주문을 삭제하는 테스트를 추가하자.



OrderTest.java
public void testDeleteOrder() throws Exception {

orderRepository.save(customer.newOrder("CUST-01-ORDER-01")

                        .with("상품1", 5)

                        .with("상품2", 20);

Order order = orderRepository.find("CUST-01-ORDER-01");

 

orderRepository.delete("CUST-01-ORDER-01");

 

assertNull(orderRepository.find("CUST-01-ORDER-01"));

assertNotNull(order);

}



주문 건을 생성한 REPOSITORY 등록한다. 등록이 성공하면 해당 주문을 REPOSITORY로부터 삭제한다. 삭제 역시 조회의 경우와 마찬가지로 삭제할 주문 객체를 명시하기 위해 주문 ID 인자로 전달한다. REPOSITORY에서 삭제되었다는 것을 확인하기 위해 주문 객체를 조회한 결과값이 null인지를확인한다.

 

여기에서 마지막 단정문을 눈여겨보자. 테스트 메소드는 REPOSITORY에서 delete() 메소드를 호출하여 주문 객체를 삭제하기 전에 find() 메소드를 호출하여 REPOSITORY로부터 생성된 객체에 대한 참조를 보관한다. delete() 메소드를 호출하여 REPOSITORY로부터 해당 객체를 삭제한 앞에서 조회한 객체가 null아닌지를 검증한다.

 

REPOSITORY 관점에서의 삭제는 이상 해당 객체를 REFERENCE OBJECT 취급하지 않겠다는 것을 의미한다. , 시스템이 해당 주문 객체의 추적성을 보장하지 않겠다는 의미이다. 따라서 일단 삭제가 완료되면 이상 REPOSITORY 통해 해당 객체를 얻을 없게 된다. 이것을 주문 객체 자체의 소멸과 혼동해서는 된다. 단지 주문 객체가 REPOSITORY 제어에서 벗어나 추적성과 유일성을 잃을 뿐이지 객체 자체가 소멸되는 것은 아니다. 객체의 소멸은 가비지 컬렉터에 의해서만 가능하다.

 

REPOSITORY 삭제 메소드를 추가하기 위해 Registrar 삭제와 관련된 기본 기능을 추가하자. ENTRY POINT 클래스와 연관된 Map으로부터 검색 키의 엔트리를 삭제한 반환하도록 구현한다.

Registrar.java
public static EntryPoint delete(Class<?> entryPointClass,

String objectName) {

return soleInstance.deleteObj(entryPointClass, objectName);

}

 

@SuppressWarnings("unused")

private EntryPoint deleteObj(Class<?> entryPointClass, String objectName) {

Map<String,EntryPoint> theEntryPoint =

   entryPoints.get(entryPointClass);

return theEntryPoint.remove(objectName);  

}



Registrar 사용하여 OrderRepository 주문 삭제 메소드를 추가한다.



OrderRepository.java
public Order delete(String identity) {

return (Order)Registrar.delete(Order.class, identity);

}



테스트를 실행해 보자. 역시 예상대로 통과한다. 테스트 중독은 행복한 일이다.

 

모든 테스트가 성공했고, 시스템은 REFERENCE OBJECT 정상적으로 추적하고 있으며, REPOSITORY 객체의 생명 주기를 관리하기 위해 필요한 모든 기능을 제공하고 있다. 사용자들은 증가하고 주문은 나날이 늘어만 간다. 이런 추세라면 연말 인센티브는 거하게 기대해도 좋을 같다. 참으로 아름다운 것이 인생인 같다.

 

그런데 갑자기 분위기가 이상하다. 사람들이 얼굴이 사색이 돼서 우왕좌왕하는 것을 보니 뭔가 문제가 생긴 같다. 이런, 시스템이 다운됐다니! 그럼 지금까지 처리된 데이터는 어떻게 되는 것인가. 맙소사! 인센티브는 고사하고 고객들한테 몰매를 맞기 전에 어서 짐을 싸서 줄행랑을 치는 좋을 같다. 죽지 못해 사는 것이 인생인 같다.


핑백

  • Domain-Driven Design | Jongmin Kim&#039;s Blog 2014-09-02 01:18:28 #

    ... ion과 Aspect-Oriented Programming 2부 2008/12/05 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 1부 2008/11/30 Domain-Driven Design의 적용-2.AGGR ... more