중 아래 댓글들 참고

********************************************************************

이부분은 책의 내용을 그대로 발췌


ejb2.0로컬 참조와 cmr(container managed ralation)이 나타나기 전에, 엔티티 빈들은
큰단위의 도메인 객체들을 모델화 하기 위해 주로 사용되었다.

이것은 주로 원격 통신과 관련된 오버헤드 때문에, 클라이언트 티어의 객체가 엔터프라이
즈 티어에 작은 단위로 접근하는 것을 막기 위해서였다. 큰단위(coarse grained)로 된 설계의 
성능은 클라이언트 티어와 엔터프라이즈 티어 사이에 전송되는 모든 데이터를 캡슐화한
data transer object의 구현으로 한층 더 개선되었다. 비록 이것이 높은 퍼포먼스의 설계를
제공해주기는 했지만, 많은 도메인 객체를 가진 복잡한 시스템에서는 시스템 내에
dto의 과잉을 초래하였다.

또한, 이전의 ejb에서는 빈 공급자가 도메인 객체들 사이의 연관을 유지하기 위한 로직을 명시
적으로 제공해야 했다. 도메인 객체들 사이에 복잡한 관계가 있는 상황 안에서는 dto의 설계가
매우 복잡하게 되었다.

로컬 참조와 cmr의 출현은 ejb를 이용한 엔터프라지으 app 개발에 흥미진진한 새로운 길을 열었다.
여기에서 우리는 프리젠테이션 티어와 엔터프라이즈 티어 사이에서 전송될 수 있는 XML 기반의
동적인 데이터 구조를 생성하기 위한.. JAXP와 빈 인트로스펙션(refelct활용한거)을 연계하여
ejb2.0을 사용하는 강력한 길을 얻을 수 있을 것이다.

엔터프라이즈 티어에서 클라이언트 티어로 데이터를 전송하기 위한 XML의 사용은 app내에 들어
있는 다양한 티어들 사이의 느슨한 결합 구조를 가진 구현을 도와준다. 

하지만, 새로운 도메인 객체들을 엔티티 모델에 추가하려고 할때는 , 이것들을 위한 새로운 DOM
구조(예전에 한번 살펴봄..dom,sax등)를 생성할 책임이 있는 클래스를 추가할 필요가 있다.

우리는 주어진 로컬 ejb의 cmp와 cmr 필드들을 동적으로 네비게이션 할 수 있는 프레임워크를 개발
하고 ,app 내의 다양한 티어들 사이에서 전송될 수 있는 xml문서를 생성할 것이다. 이러한 방법에는
다음과 같은 이점들이 있다.

1.엔터프라이즈와 클라이언트 티어 사이에 결합을 느슨하게 한다.(스크립팅 형식이니..)
2.도메인 객체들 사이의 관계들을 관리하기가 훨씬 더 쉽다.(굿!)
3.시스템에서 복잡한 데이터 전송 객체들을 제거한다. (굿!)
4.xml이 cmp 그리고 cmr 필드들을 동적으로 네비게이션 할 수 있게 만들어졌기 때문에, 빈공급자는
새로운 객체들을 도메인 모델에 추가하려고 할대, 이들의 새로운 dom 구조를 생성할 책임이 있는
클래스를 만들 필요가 없다.

ejb2.0로컬 참조는 빈 컴포넌트들의 작은 단위 접근을 권장한다. 로컬참조를 가진 빈들은 컨테이너에
의해 관리되는 다른 빈들과의 관계에 포함될 수 있다. 예를 들면, 헬프 데스크 시스템에서, UserEJB는
ServiceRequestEJB와 일대 다의 양방향 관계를 가질수 있고, ServiceRequestEJB는 ProductEJB와
일대일의 단방향 관계를 가지고, ServiceRequestHistoryEJB와 일대 다의 양방향 관계를 가질수 있다.

UserEJB는 또한 PhoneEJB와 일대 다의 양방향 관계를 가질수 있다. 그래서 EJB 2.0 로컬 참조와
cMR을 사용하여, 연관된 엔티티들의 복잡한 집합을 설계할수 있다. cmp와 cmr 필드들은 빈클래스
안에서 추상 접근자 메소드(getter/setter)를 사용하여 정의 된다.

cmr필드들을 위한 접근자 메소드들은 관계의 기수성에 의존하여 일의 관계이면.. 빈의 로컬 인터페이스를
, 다의 관계이면 빈의 로컬 인터페이스의 컬렉션을 돌려보낸다. 이들 접근자 메소소들은 빈 컴포넌트들의 로컬 
인터페이스를 통해서 외부에 드러날 수 있다. ejb2.0의 더 깊은 범위는 이 책의 영역에서 벗어난다. 보다 상세한 
ejb2.0규약에서 제공한다. -_-;;;

효과적인 디자인 패턴은 facade 컴포넌트를 통해서 app의 큰 단위 유스케이스를 드러내며, 클라이언트 
티어로부터 엔티티 컴포넌트로의 직접적인 접근을 막는다.

이것은 비지니스 서비스를 제공하기 위해 로컬 엔티티 빈들이 협력하는 복잡한 계층 구조와
상호작용하는 리모트 세션빈 컴포넌트와 같은 facade 유스케이스의 구현에 의해 성취될 수 있다.

앞에서 언급된 헬프데스크 예제로 돌아가서 , 주어진 user의 자세한 정보를 얻기위한 하나의 유스케이스가
있다. facade 컴포넌트는 요청된 user엔티티 컴포넌트를 찾고, cmp와 cmr 필드들을 통하여 검색을 하여
필요한 데이터를 얻고, 이를 프리젠테이션 티어로 리턴한다.

데이터 전송 객체를 위한 확실한 서택은 일반 자바 빈이다. UserBean은 cmp와 cmr필드를 나타내는
특성들을 가질 수 있다. cmr 필드의 타입은 관계의 기수성에 따라 java.util.Collection 이거나, 다른 빈 컴포
넌트일수 있다. UserBean은 ServiceRequestBean과 PhoneBean 컬렉션을 가질 수 있다. 

ServiceRequestBean은 한개의 ProdctBean과 ServiceRequestHistoryBean의 컬렉션을 가질수 있다.
추가적으로 이들 빈들은 도한 cmp필드들을 표현하는 원시 타입의 어트리뷰트나 간단한 string을 가질수
있다.

이러한 선택의 중요한 단점은 엔티티 모델이 복잡해질수록 데이터 전송객체의 계층구조가 더 복잡해지는
것으로, 이것은 서비스(엔터프라이즈) 티어와 소비자(프리젠테이션)티어 사이의 결합을 단단하게 할 수 
있다는 점이다. 

관계들의 복잡한 계층 구조를 보면, 더 나은 선택은 데이터 전송 객체로서 XML,DOM 객체를 사용하는 것이다.
(세션빈에서 직접 jdbc에 연동해서 데이터를 가져올때 java.sql.resultset -> java.sql.rowset 사용하기도 함)

facade컴포넌트는 org.w3c.dom.Document 타입의 객체를 생산하고 프리젠테이션 컴포넌트는 xml을 인지하는
jsp커스텀 태크나(오옷!!) xslt(이게 xml변환하는 라이브러리였던가? 예전에 정리했었는데;;) 파일을 사용하여
그것들을 소비할 것이다.

다음과 같은 질문이 있다. cmp 로컬빈의 참조로부터 xml문서를 어떻게 생성할 수 있는가? 

우리는 dom컴포넌트의 서로 다른 타입을 생성하기 위해 팩토리(factory) 기반의 접근을 사용할 수 있다.
하지만, 이것은 팩토리 컴포넌트의 과잉을 생성하여.. 엔티티 모델을 확장할 때 새로운 팩토리 컴포넌트의
추가가 필요할 것이다. (아..하긴..) 

우리가 필요한 것은 cmr을 찾아서 동적으로 dom구조를 생성하기 위해 cmr엔티티 로컬 객체들을 인트로스펙트
(리플렉션)할수 있는 기능을 가진 유틸리티이다.

이 유틸리티는 무한 순환을 피하기 위해 양방향 관계에서 원형 참조를 조심해야하고, 또한 관계 엘리먼트
(realation ship element)들의 검색에서 환경설정이 가능한 drill-down depth (????)를 가져야 한다.

다음 섹션에서 이러한 기능을 제공하는 유틸리티 클래스를 보여준다.

package ws.business.service.util;
//jaxp
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
//dom
import org.w3c.com.Document; //오오.. 기억이 새록새록 내경운 jdom2를 썼었지..
import org.w3c.dom.Element;
//ejb
import javax.ejb.EJBLocalObject; //이거..자바ee6 받아서 설치후.. 외부 라이브러리에서 찾아야함;;
//collection
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
//빈 introspection
import java.beans.Introspetor; //오.. 이런 api들이 제공되었었군.. 나는 직접 reflect 쓰는줄..
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor; 
import java.beans.IntrospectionException;
//reflection 에잉.. 추가해주네..
import java.lang.reflect.Method; //예전 생각한대로.. 조회해가며 게터,세터 찾으려는건가? 찾아서 invoke하고
import java.lang.reflect.InvocationTargetException;

public class DOMGenerator
{
  //문서 만든다.
  private Document doc;
  //사용자가 drill-down depth를 정의한다.. .. 이거 element의 계층 구조 의미하는 거였나? xml이 데이터를 트리처럼 관리하니
  private int drillDownDepth;
  //특정 참조를 저장하기 위한 사적 인스턴스 변수
  private ArrayList _circularRef = new ArrayList();
  //현재의 depth of the drilldown으로 저장하기 위한 사적 인스턴스 변수
  private int _currentDepth;
  //getEJBLocalHome, getLocalHandle, getClass and getPrimaryKey 메소드들은 
  //처리하지 말아야 한다. why???
  private static String RESERVED = "EJBLocalHome^localHandle^class^primaryKey";
  //대박이다.. 예약어를 xor로 묶었어.. 헐.. 대박.. 천재다..

  public DOMGenerator(String docElementName, int drillDownDepth)
  {
     try
     {
        DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = fact.newDocumentBuilder(); //익숙해서 좋고~
        doc = builder.newDocument(); //이제 나도 팩토리 패턴에 익숙해진건가?
     }
     catch(ParserConfigurationException ex)
     {
        throw new RuntimeChainedException(ex); //오.. 체크드를 언체크드로 변환후 그래도 콜러에게 알리네? 워커가 착함;;
     }
     //drilldown depth를 저장
     this.drillDownDepth = drillDownDepth;
     //사용ㅈ가 정의한 이름을 사용하여 문서 요소를 만든다.
     doc.appendChild(doc.createElement(docElementName)); //아.. 어렴풋이 기억이 난다.
   }


   //이 메소드는 dom을 만들기 위해 로컬 참조를 넘긴다.
   public Document getDOM(EJBLocalObject local)
   {
      try
      {
         //cmp와 넘겨진 로컬 참조를 위한 관계 필드를 사용하여 요소를 만들기 위해 이메소드를 호출한다.
         populateElement(doc.getDocumentElement(), local); //먼가 심오한데?.. local 인스턴스를 통해 reflection으로 
         //클래스 정보에서 각종 메서드,관계들 파헤쳐서 doc에 넣어주는듯..
         //dom을 리턴
         return doc;
       }
      catch(IntrospectionException ex)
      {
         throw new RuntimeException(ex.getMessage()); //오 이런게 일반적 기법이구나~
      }
      catch(IllegalAccessException ex) //흠..리플렉션 패키지로 접근제한자 다 뚤을수 있는데?
      {
         throw new RuntimeException(ex.getMessage()); 
      } 
      catch(InvocationTargetException ex)
      {
         throw new RuntimeException(ex.getMessage()); 
      } 
   }


   //
   private void populateElement(Element parent, EJBLocalObject local) throws IntrospectionException,
   IllegalAccessException, InvocationTargetException
   {
      //현재 로컬 참조를 링크된 참조 목록에 추가한다.
      _circullarRef.add(local); //참조를 기억해둠
      //현재의 drilldown depth를 증가시킨다.
      _currentDepth++;
      //현재 로컬 참조에 대한 빈 정보를 얻는다.
      BeanInfo info = Introspector.getBeanInfo(local.getClass()); //오`~ 이해되고 있음..리플렉션 사용해 각종정보추출
      //속성 descrptors의 목록을 얻고 배열에서 반복시킨다.
      PropertyDescriptor[] properties = info.getPropertyDescriptors(); //굿굿!
      
      for(int i=0; i< properties.length; i++)
     {
         //속성 읽기 메소드를 얻는다. 프로퍼티 디스크립터라는 자료형이 편하긴 한듯.. 
         Method propReadMethod = properties[i].getReadMethod(); //메소드 순회하면서 어트리뷰트.. properties[i] 이름
         //과 일치하는 게터 메서드를 클래스에 선언된 메서드들을 탐색해서 찾고..실행(invoke)해서 값을 얻는듯.
         //속성 이름을 얻는다.
         String propName = properties[i].getName();
         //reflection을 사용하여 속성 값을 얻는다.
         Object prop = propReadMethod.invoke(local, null); 
         //예전되어 있던 속성을 건너뛴다.
         if(RESERVED.indexOf(propName) > = 0) continue; // 천재다..

         try
         {
            //ejb 로컬 참조로 속성을 캐스트 한다. 구현 방법이 아주 다양하기에 instanceof를 사용하지 말자!
            EJBLocalObject locProp = (EJBLocalObject)prop;
           //만일 링크 참조목록에서 로컬 참조를 이미 이용하고 있거나,
           //현재의 drilldown depth가 설정된 driildown depth보다 더 크다면.. 건너뛴다. = 트리 순회 로직
           if(isCircularRef(locProp) || _currentDepth >= drillDownDepth) continue;
           
           //속성으로 요소를 만든다.
           Element child = doc.createElement(propName);
           //자식 로컬 참조의 관계 필드와 연속 필드로 새로 만들어진 요소를 반복해서 만든다.(핵심= 재귀)
           populateElement(child, locProp); //내가 파일 시스템 트리 탐색할때 재귀 시도한거처럼..
         }
         catch(ClassCastException ex1)
         {
             //ClassCastException이 throw되었으면.. 핸들링! 내가 전에 깨달은 익셉션 핸들링
             //속성을 로컬 참조의 컬렉션으로 캐스팅하도록 한다.
             //이때 구현방법이 컨테ㅣ너마다 다른 instanceof는 사용하지 않도록 한다.
             try
             {
                 Collection colProp = (Collection)prop;
                 //현재의 drilldown depth가 설정된 driildown depth보다 크다면 처리과정을 건너띈다. =트리순회에서 제한조건
                 if( _currentDepth >= drillDownDepth) continue;

                 //컬렉션을 캡슐화할 자식 요소를 만든다.
                Element child = doc.createElement(propName);
                Iterator it = colProp.iterator(); 
                //당근 현재 catch된 블럭은 컨테이너 타입(트리에서 마지막 노드x)이니까.. 내부 요소를 접근해야지..
                while(it.hasNext())
                {
                    //컬렉션에서 각각의 로컬 참조를 얻는다.
                    EJBLocalObject locProp = (EJBLocalObject)it.next();
                    //만일 링크 참조 목록에서 로컬 참조를 이미 이용하고 있거나, 현재의 drilldown depth가
                    //설정된 drilldown depth보다 크다면 처리 과정을 건너뛴다. = 위에도 나왓지만 중복처리 방지이자 무한루프
                    //방지
                    if( isCircullarRef(locProp)) continue;
                     
                    //컬렉션에 현재 로컬 참조를 위한 지속 정보와 관계 정보를 포함할 요소를 만들고, 
                    //그것을 컬렉션을 나타내는 요소에 추가하ㅕ 컬렉션을 만든다.
                    Element grandChild = doc.createElement(propName + "-child");
                    child.appendChild(grandChild); //예전에 사용자 정의 익셉션을 스택및 재귀호출을 이용해 처리한 어떤
                    //분의 코드에선.. 트리의 깊이 * '\t' 함으로서 층을 표현햇지만.. dom구조의 xml은 계층구조가 알아서
                    //잘 표시되니.. 그런 구현까진 신경안써도 될듯
                    populateElement(grandChild, locProp); //손주 노드 탐색 ㄱㄱ (진짜 내가 예전에 파일 시스템에서 트리
                    //순회하려던 것과 유사하다...내 삽질도 의미가 있었다 ㅠㅠ)
                 }
                 //위의 while을 통과했다면.. 현재 노드의 하위 계층을 전부 추가했을테니..(재귀를 통해)
                 //자식들을 만든다.
                 parent.appendChild(child);
              }
              catch(ClassCastException ex2)
              {
                 //ClassCastException이 발생했다면.. 속성은 연속필드이고.. 
                 //그값이 애트리뷰트로 연재 노드에 추가된다. 헐.. 진짜 내가 구상했던 트리식 파일 시스템 처리와 거의 똑같네..
                 //isDirectory따라 재귀 순회하고.. isFile이라면.. 현재 노드의 데이터 필드에 추가하고;; 내가 대박친건가.. 아님
                 //사람들 생각이 다 비슷한건가;;
                 parent.setAttribute(propName, prop.toString() );
              }
          }
      }

      //순환 참조를 추적하기 위해 목록에서 현재의 로컬 참조를 삭제한다. <- 이해불가..내가 전에 이걸 몰라서 실패했나?
      _circularRef.remove(local); //하여간 심플한..순회 종료조건들이다.
      //현재의 drilldown depth를 감소시킨다. 
      _currentDrpth--;
   }

   //이것은 참조가 링크된 참조 목록에 있는지 여부를 검사하기 위한 유틸리티 메소드이다.
   private boolean isCircularRef(EJBLocalObject local)
   {
      Iterator it = _circularRef.iterator(); //전체 로직을 완벽히 이해는 못했지만.. 대박..
      while(it.hasNext())
      {
         if(local.isIdentical( (EJBLocalObject)it.next() ) ) return true;
         
         return false;
       }
   }
}

아래의 코드는 이 클래스를 사용하기 위한 방법을 보여준다.
User local = userHome.findByPrimaryKey("1");
return new DOMGenerator("user", 4).getDOM(local); //아름답다...

엔티티 모델에 의존하여 생성된 dom이 아래에 나와있다.


<?xml version="1.0" encoding="UTF-8" ?>
  <user firstName="Meeraj" id="1" lastName="Kunnu">
   <requests>
      <requests-child description="Outlook not working" id="2" status="p">
          <product description="Install Laptop" id="1" name="SVC01"/>
       </requests-child>
       <requests-child description="PC not booting" id="1" status="O".
           <histories>
              <histories-child description="Information requested" id="2" loggedAt="2000-12-12 12:!2:12.0" />
              <histories-child description="Request logged" id="1" loggedAt="2000-12-12 00:00:00.0" />
            </histories>
            <product description="Install Laptop" id="1" name="SVC01" />
         </requests-child>
     </requests>
     <phones>
         <phones-child id="2" number="0771 8210586" type="M" />
         <phones-child id="1" number="0231 2973536" type="W" />   
     </phones>
   </user>
                


우리는 j2ee app의 엔터프라이즈와 프리젠테이션 티어 사이에서 복잡한 데이터를 전송하기
위한 멋지고 유연한 모델을 보았다. 프리젠테이션 티어는 엔터프라이즈 티어에 의해 생성된
xml데이터를 xml을 인지하는 jsp 커스컴 태그와 xslt를 사용하여 클라이언트 디바이스(이를테면
file)상에 표현할 수 있다.

도메인 모델에 새로운 정의를 추가할때, 당신이 해야할 필요가 있는 유일한 일은 .. 작은 단위 로컬
엔티티 빈들을 사용하기 위한 그런 정의들을 정의하는 것과 cmr을 사용하기 위한 연관을 정의하는 것이다.

dom생성기(generator)는 동적인 xml문서들을 생성하기 위하여 그런 관계들을 검색하는 일반적인
방법을 제공한다.

하지만, 임의의 다른 설계 솔루션처럼... 이러한 접근은 또한 약점과 결정들을 가지고 있다. 

첫번째 명백한 측면은 xml기반의 data transfer object에 대해 개발자들이 자주 걱정하는 퍼포먼스
이다. app서버 (wls 6.1)와 데이터 서버(adaprive sql anywhere)가 동시에 돌아가는 1ghz,256mb의
windows mf 컴퓨터 상에서 복잡한 관계들을 가진 천개의 레코드 집합을 처리하는데 0.5초 이하 정도의
시간이 걸렸다.

두번째 명백한 측면은 타입 안정성이다. xml이 가장 높은 레벨의 데이터 추상화를 제공하기 때문에 
어트리뷰트나 텍스트노드들의 값은 항상 string 문자처럼 다루어진다. 

만약 당신이 엔터프라이즈 티어로부터 조회된 dto상에서 비지니스 연산을 수행하고자 한다면,
자바빈  컴포넌트(일반 자바클래스)를 사용하는 dto패턴을 채택하는 것이 더 좋다.

하지만, xml스키마는 xml문서상의 타입 안정성을 위한 강력한 메커니즘을 제공한다. 또한 이문서에서
설명된 접근 방법에서, 엔티티와 그것의 종속적인 엔티티에 연관된 대부분의 비지니스 로직은
엔티티 그 자체 내에 구현될 것이고, 독립적인 엔티티들과 연관된 비지니스 로직은
session facade에서 구현될 것이다. 두 경우 모두 모데인 객체로서 로컬 엔티티의 객체를 사용한다.

이러한 접근방법의 가장 큰 단점중 하나는 xml기반의 데이터 모델링을 위해 잘 정의된 기술이 없다
는 것이다. xml을 사용하여 도메인 객체의 관계를 모델링하기 위한 어떤 uml표기법이나 대중적인
스테레오 타입을 찾아내지 못했다.


'프로그래밍 > ejb' 카테고리의 다른 글

해쉬맵의 활용 전략(map dto, dto factory)  (0) 2014.01.02
ejb관련 - EJB.. 그만 공부하자..  (0) 2014.01.02
by givingsheart 2014. 1. 2. 09:37