'분류 전체보기'에 해당되는 글 140건

  1. 2009.04.23 AL : PropertyDefinition
  2. 2009.04.23 AL : NodeType
  3. 2009.04.23 AL : Lock
  4. 2009.04.23 AL : sequence
  5. 2009.04.17 자동화 테스트 - 자바스크립트
  6. 2009.04.14 여섯번째 계 - Encapsulation
  7. 2009.04.12 AL의 첫 테스트 버전
  8. 2009.04.09 와~~~
  9. 2009.04.04 AL - Extreme
  10. 2009.04.02 다시 성능
Framework/Another Lore2009. 4. 23. 11:08

Property Definitions(속성정의) :
A node type contains a set of definitions specifying the properties that nodes of this node type are allowed (or required) to have and the characteristics of those properties.  A property definition has all the attributes of a generic item definition as well as the following property-specific attributes:


PropertyType

A property definition must specify a property type. Type[String, Binary, Long, Double, Boolean, Date, Name, Text, Reference, Path, ViertualText, Undefined] : defines integer constants for the available property types as well as for their standardized type names (used in serialization) and two methods for converting back and forth between name and integer value:

[Example Code]
 GlobalStore globalStore = session.getGlobalStore() ;
 PropertyDefinition explain = globalStore.createPropertyDefinition(IDString.propId("explain"), Property.Type.STRING) ;
 assertEquals(explain.requiredType(), Property.Type.STRING) ;

 NodeType newType = globalStore.createNodeType(objectType, "temporaryType", new PropertyDefinition[]{explain}) ;
  MappedDefinition md = newType.getMappedDefinition(IDString.propId("explain")) ;
  assertEquals(md.requiredType(), Property.Type.STRING) ;
  assertEquals(1, newType.getMappedDefinitions().size()) ;
  assertEquals("explain", md.getId().getString()) ;




<Inteface PropertyDefinition extends Serializable>
public IDString getId();
public boolean isSameProperty(IDString propId);
public Property.Type requiredType();



PropertyDefinition은 Id와 PropertyType으로 구성되어 있다.


DB의 컬럼과 비슷하기는 하지만 컬럼과 달리 constraint와 defaultValue를 가지지 않는다. 이러한 것들은 NodeType과 Mapping되는 과정에서 추가되고 PropertyDefinition은 단지 해당 아이디를 가진 PropertyDefinition은 특정 Property.Type을 가진다고 정의할 뿐이다.


'Framework > Another Lore' 카테고리의 다른 글

AL : Node  (0) 2009.04.23
AL : Property  (0) 2009.04.23
AL : NodeType  (0) 2009.04.23
AL : Lock  (0) 2009.04.23
AL : sequence  (0) 2009.04.23
Posted by bleujin
Framework/Another Lore2009. 4. 23. 04:46


An important feature of many repositories is the ability to distinguish the entities stored in the repository by type.
Node types are used to enforce structural restrictions on the nodes and properties in a workspace by defining for each node, its required and permitted child nodes and properties.

Every node has one declared node type. node types are typically used to defined the core characteristics of
a node. In a writable repository a node's primary type is first assigned upon node creation.

Repository implementations may vary as to how flexible they are in allowing changes to the node types assigned to a node. Each repository has a single, system-wide registry of node types. Typically, a repository will come with some implementation-determined set of built-in node types.

Some of these types may be vendor-specific while others may be standard predefined node types defined. Some repositories may further allow users to register new node types programmatically


A node type definition consists of the following attributes:


NodeTypeId


Every registered node type has a name, unique within the repository. NodeTypeId consists of prefix of AnotherLore URI and short local name in a workspace




Supertypes


A node type has zero or one supertype. Supertypes are specified by name. The supertype relation is transitive: If T1 is a supertype of T2 and T2 is a supertype of T3 then T1 is a supertype of T3.



Property Definitions

A node type may contain a list of property definitions, which specify the properties that nodes of that type are permitted or required to have and the characteristics of those properties. The list of property definitions may be empty.


Orderable Member NodeType

A node type may declare its member type orderable, meaning that for all nodes of that type, the order that the member nodes are iterated over can be programmatically controlled by the user. A node type may contain a list of member node definitions, which specify the permitted or required child nodes and their characteristics. member type definitions may be empty. if not setted, in principle have objectType(objectType have no propertyDefinition and is supertype of all nodetype)


Mapping Property Definition

The default values attribute of a property definition defines the values assigned to property if it is auto-created. If the property is single-valued this attribute will hold a single value.

A property definition may impose constraints on the value that the property may hold. These value constraints are defined by an array of strings, whose format differs depending on the type of the property.


[example Code]

NodeType commentType = globalStore.createNodeType(objectType, "comment", new PropertyDefinition[] { comment, regUserId, regDate });
  commentType.setConstraint(COMMENT, new RangeByteLengthConstraint(10, 4000));
  commentType.setConstraint(REGUSERID, new RequireConstraint());
  commentType.setConstraint(REGDATE, new RequireConstraint());      // set constraint
  commentType.setDefaultValue(REGDATE, new CurrentDateValue()) ;  // set default



<Inteface NodeType extends Serializable, Publishable>

 public NodeType setConstraint(IDString propId, IValueConstraint constraint)  throws RepositoryException ;
 public NodeType setDefaultValue(IDString propId, IDefaultValue defaultValue)  throws RepositoryException ;
 public NodeTypeId getId() ;
 public NodeType getSuperType()  throws RepositoryException ;
 public void setMemberType(NodeType memberType)  throws RepositoryException ; 
 public NodeType getMemberType()  throws RepositoryException ;
 public boolean isAllowedMemberType(NodeType memberType)  throws RepositoryException ;
 public GenericIterator<MappedDefinition> getMappedDefinitions() throws RepositoryException;
 public MappedDefinition getMappedDefinition(IDString propId) throws RepositoryException ;
 public boolean containsPropId(IDString propId)  throws RepositoryException ;
 public boolean isNodeType(NodeType thatType)  throws RepositoryException ;
 public boolean isNodeType(String thatTypeId)  throws RepositoryException ;
 public GenericIterator<ValidMessage> getViolateConstraintMessage(Node node) throws RepositoryException;




'Framework > Another Lore' 카테고리의 다른 글

AL : Property  (0) 2009.04.23
AL : PropertyDefinition  (0) 2009.04.23
AL : Lock  (0) 2009.04.23
AL : sequence  (0) 2009.04.23
자동화 테스트 - 자바스크립트  (0) 2009.04.17
Posted by bleujin
Framework/Another Lore2009. 4. 23. 03:14


A lock is placed on a node by calling node.lock(boolean isDeep). The node on which a lock is placed is called the holding node
of that lock.


Shallow and Deep Locks

A lock can be specified as either shallow or deep. A shallow lock applies only holding node and its properties. A deep lock applies to its holding node and descendants. Consequently, there is a distinction between a lock being held
node and a lock applying to a node. A lock always applies to its holding node. However, if it is a deep lock, it also applies to all nodes in the holding node's subgraph. When a lock applies to a node, that node is said to be locked.

Lock Owner

Initially, the session through which a lock is placed is the owner of that lock. This means the session has the power to alter the locked node and to remove the lock.

Repositories may support client-specified lock owner information.


Placing and Removing a Lock

When a lock is placed on a node, it can be specified to be either a session-scoped lock. A session-scoped lock automatically expires when the session through which the lock owner placed the lock expires. or can is released througt explicitly unlocked


Getting a Lock

node.getLock() returns the Lock object that applies to the node. if session is not same, lock cant called unlock()

void lock.unlock()
Removes the lock


Testing for Lock Holding

boolean node.isLocked()

returns true if the node holds a lock; otherwise returns false. To hold a lock means that the node has actually had a lock placed on it specifically, as opposed to having a lock apply to it due to a deep lock held by a node above.


Lock Object

The Lock object represents a lock on a particular node. It is acquired either on lock creation through node.getLock() 


[Example Code]
  Node parent = session.createNode(root, objectType, "parent");
  Node child = session.createNode(parent, objectType, "child");
  boolean isDeep = true;
  parent.lock(isDeep);
  assertTrue(child.isLocked());
  assertTrue(parent.isLocked());

  Lock lock = parent.getLock() ;
  lock.unlock();
  assertFalse(child.isLocked());
  assertFalse(parent.isLocked());



<Inteface Lock>
 public Node getNode() throws RepositoryException; // Getting the Lock Holding Node
 public boolean isDeep(); // Testing Lock Depth
 public void unlock() throws RepositoryException, InterruptedException;
 public boolean isOwner(UserBean user); // Testing Lock Owning Session


LockException

When a method fails due to the presence or absence of a lock on a particular node a LockException is thrown.
LockException extends RepositoryException,


종종 내가 특정 노드를 수정하고 있을때 (즉 edit Mode일때) 다른 누군가가 수정을 할 수 없게 하고 싶을 경우가 있다.
그러나 잘 알다시피 웹은 디스커넥트 환경이기 때문에 이러한 Lock의 구현이 조금 다르다.

첫번째로 했던 방법은 editMode로 갈때 userId와 node.UUID를 특정 테이블에 표시를 해두는 방식이다. 그리고 editMode로 들어가기 전에 이런 표시가 있는지 살펴보는 뻔한 방식을 생각해 볼 수 있다. 이 방법의 단점은 첫번째 사용자가 editMode에서 다른 사이트로 가버리거나 브라우저를 끄고 놀러가버렸을 때이다.

적절한 process를 거쳐 취소나 저장을 누르지 않은 상태에서 다른 곳으로 가버렸기 때문에 먼저 마크했던 정보가 여전히 남아있어서 다른 사용자는 여전히 접근할 수 었다는 단점이 있다. 관리자가 수동으로 풀어주는 것도 한두번이다.

두번째의 문제는 editMode로 갈때와 viewMode일때의 구분이 어렵다는 것이다. 모두 select이며 이걸 viewMode로 쓸지 editMode로 쓸지는 전적으로 프로그램에게 달려 있다.

또 다른 문제는 만약 setProperty시 Lock을 건다고 해도 commit까지의 시간이 아주 짧아(ns 레벨) 효과가 아주 미비하고 추가적으로 관리해야 할  Lock정보가 너무 많아지는 문제가 있다.



그래서 AL에서는 DB에서 사용하는 Lock 개념과 좀 달리 접근할 필요가 있었다.

첫번째 DB등의 persitence에 마킹하는 방법은 사용자가 다른 곳으로 가버렸을때나 AL의 restart등에도 남아있기 때문에 Lock 정보는 메모리에서 관리한다.

두번째 최소한의 정보를 관리하기 위해 Lock은 별도로 호출하기 전까지는 자동으로 걸리지 않는다.

세번째 Lock은 얼마의 시간이 지나면 자동으로 release 된다.



첫번째와 세번째 조건을 만족하기 위한 가장 좋은 장소는 Session이다. http Session이 아니라 AL은 웹과 상관없이 자체적으로 Session을 관리한다. session은 일정 시간이 지나도록 행동이 없으면 자동으로 invalidate 되기 때문에 위 조건을 만족할 수 있다.

두번째로는 node.lock() 을 호출하는 순간에만 lock이 걸린다. lock이 걸린다고 setProperty를 호출 할 수 없는 것은 아니다. 다만 node.isLocked에서 반환되는 값이 달라질 뿐이다.

명시적으로 해제하고 싶으면 해당 Owner가 node.getLock().unlock()를 호출해야 한다. 추가적으로 lock escalation으로 관리해야 할 락을 줄인다.


'Framework > Another Lore' 카테고리의 다른 글

AL : PropertyDefinition  (0) 2009.04.23
AL : NodeType  (0) 2009.04.23
AL : sequence  (0) 2009.04.23
자동화 테스트 - 자바스크립트  (0) 2009.04.17
와~~~  (0) 2009.04.09
Posted by bleujin
Framework/Another Lore2009. 4. 23. 02:05


[Example Code]
  DatabaseSequence dseq = new DatabaseSequence(session.getGlobalStore().getNamespace(), DBController.getTestInstance()) ;
  Sequence seq = new CacheSequence(dseq) ;
  
  IDString seqId = IDString.seqId("seq_name") ;
  seq.reset(seqId) ;
  
  long i = seq.nextVal(seqId) ;
  assertEquals(1, i) ;

  assertEquals(1L, seq.currVal(seqId)) ;
  assertEquals(2L, seq.nextVal(seqId)) ;
  assertEquals(3L, seq.nextVal(seqId)) ;
  
  seq.reset(seqId) ;
  assertEquals(0, seq.currVal(seqId)) ;
  for (int j = 1; j < 111; j++) {
   assertEquals(1L *j, seq.nextVal(seqId)) ;
   assertEquals(1L *j, seq.currVal(seqId)) ;
  }

<interface Sequence>
 public void reset(IDString seqName);
 public long currVal(IDString seqName);
 public long nextVal(IDString seqName);


적합한 PK의 역할을 할 수 있는 컬럼(들)이 없거나 그 컬럼(들)의 length가 길때 Sequecne는 꽤 유용하다.

다만 DB의 sequence는 몇가지 불편한 점이 있다.
이를테면 parent - child 구조의 data를 하나의 트랜잭션으로 입력하려고 할때 parent의 PK가 sequence 라면

parent가 입력되기 전까지 sequence를 모른다.
child는 parent의 PK를 알아야 입력이 가능하다. 라는 명제가 충돌한다.

그나마 오라클 같은 경우 sequence는 별도의 세그먼트 이기 때문에

현재 사용하는 sequence의 next value를 알아와서 그 값으로 parent와 child의 값을 설정한후 입력한다.
가 가능하지만..

다른 DB의 경우 이를테면 MSSQL의 경우 seq는 table에 IDENTITY형으로 종속되어 있기 때문에
하나의 트랜잭션으로 유지하기 위해 꽤 번거로운 짓을 많이 해야 한다.


AL에서는 그래서 별도의 Sequence 객체를 제공하는데.
원리는 간단하다. key, value 의 하나의 테이블을 만들고 key에는 sequnceName을 value에는 currValue를 저장하고 nextValue를 요청했을 경우 value+1로 업데이트 하고 해당 value를 던져주면 된다. 이렇게 구현할 경우 필요할 경우 sequece를 아주 간단하게 만들수 있다. 

예컨데 이런 SQL 이다. 

    function reset(v_seqName varchar2)
    return number
    is
    begin
        update sequence_tblc set seqValue = 0 where seqName = v_seqName ;
   
        return SQL%ROWCOUNT ;
    end ;

    function currVal(v_seqName varchar2)
    return number
    is
        v_result number ;
    begin
        select NVL(max(seqValue), -1) into v_result from sequence_tblc where seqName = v_seqName ;
       
        If v_result = -1 then
            insert into sequence_tblc(seqName, seqValue) values(v_seqName, 0) ;
            return 0 ;
       
        end if ;
        return v_result ;
    end ;
   
    function nextVal(v_seqName varchar2)
    return number
    is
        v_result number ;
    begin
        update sequence_tblc set seqValue = seqValue+1 where seqName = v_seqName returning seqValue into v_result;
       
        IF SQL%ROWCOUNT = 0 Then
            insert into sequence_tblc(seqName, seqValue) values(v_seqName, 1) ;
            return 1 ;
        End if ;
       
        return v_result ;
    end ;

다만 이렇게 사용하면 Sequence를 사용하기는 매우 편하지만 nextVal을 호출할때마다 DB Call이 일어난다. 그리고 Table update 방식은 DB sequence 방식보다 무겁다.

AL에서는 그래서 DB의 sequnce의 아이디어를 그대로 사용한다. AL.Sequnece의 nextVal을 호출하면 실제로는 +20으로 업데이트하고 20개의 값을 queue에 저장해둔다. queue가 비어 있지 않으면 queue값을 poll 해주고 20개를 모두 소모해 비어 있으면 다시 +20을 해주고 반복한다.

'Framework > Another Lore' 카테고리의 다른 글

AL : NodeType  (0) 2009.04.23
AL : Lock  (0) 2009.04.23
자동화 테스트 - 자바스크립트  (0) 2009.04.17
와~~~  (0) 2009.04.09
AL - Extreme  (0) 2009.04.04
Posted by bleujin
Framework/Another Lore2009. 4. 17. 00:23

AL을 혼자서 꾸역꾸역 만들다보니 단점은

온갖 여러가지 일을 해야 한다는 점이다. 이전에 회사에 소속되어 무슨 일을 할때는 다른 누군가가 해주던 백업및 배포용 build script도 만들어야 하고 호출하는 테스트 프로그램도 직접 만들고 나중에 고생하기 싫으면 유닛 테스트도 훨씬 더 정교하게 만들어야 한다.

이렇게 혼자서 이것저것 하는 방식은 최근 5년간에는 거의 하지 않았기 때문에 이것 저것 번거럽고 귀챃지만
덕분에 훨썬 더 많은 자동화 기술을 강제적으로 사용해야 되는 동기가 되고 있다.

웬만한건 다 자동화로 처리하는데 예외중의 하나가 테스트 프로그램으로 만든 WebApplication의 테스트이다. 가능한 Jsp에 code를 최소화하긴 했지만 그럼에도 수동적인 테스트를 할때마다 10분씩을 잡아먹고 있다.

그래서 이전에 만든적이 있는 스파이더에 몇시간 동안 사이트의 링크를 따라가면서 테스트 하는 툴을 만들다가
자바 스크립트 링크를 사용하는 것에서 막혔는데..구글한테 물어보니 링크로 스크립트를 사용하는 것이 크롤링 솔류션에서도 꽤 난감한 문제이긴 한가보다.

DOM을 스크립트 엔진에 로드해서 하는 방식은 배꼽이 더 커지기 때문에 어렵고 단순히 귀찮음을 떠나서 성능 테스트를 할때도 가상 브라우저로 써야 하기때문에 더 고민하다가 정 방법이 없으면
시나리오를 저장해서 동작하는 방식으로라도 써야. -ㅅ-;


AL은 세가지 모드가 있는데.
첫번째는 프레임워크 라이브러리 형태로 호출해서 쓰는 방식이고
두번째는 StandAlone 형태로 별도의 프로세스 서비스로 동작하면 WebDav나 WS로 네트워크 콜해서 쓰는 방식이 있고
세번째는 지들끼리 서로 통신하는 분산 방식으로 구상하고 있다.

첫번째는 머 원래 만들기가 어렵지 않으니 곧 대충 대충 될것 같고.. 문제는 2번째와 3번째..

'Framework > Another Lore' 카테고리의 다른 글

AL : Lock  (0) 2009.04.23
AL : sequence  (0) 2009.04.23
와~~~  (0) 2009.04.09
AL - Extreme  (0) 2009.04.04
AL - Code Example  (0) 2009.03.20
Posted by bleujin



객체 지향의 여섯번째 계는 숨기는 것이다. 흔히 Encapsulation이라고 불리며 인터페이스등을 이용하여 메소드의 상세 구현여부를 숨기는 것으로 알려져 있다.

인터페이스를 통한 메소드의 구현 말고도 숨겨할 예는 몇가지 더 있다.
예컨데 Type이다.

User 객체를 만든다고 할때 new User(String userId) 와 같이 생성하는 건 좋지 못한 습관이다. 아마도 User.create(String userId) 같은 Factory Pattern 얘기를 떠올렸을지 모르지만 그 보다 좀더 근본적인 문제이다. 위 코드의 문제는 String userId 그 자체에 있다. DB repository를 생각한다면 아마도 userId는 varchar 타입에 저장될테고 varchar와 String은 별 문제가 없어보인다.


코드를 좀더 구현해서 getUser(String userId)를 구현해야 할때가 되면 슬슬 걱정이 되기 시작한다. 과연 인자로 넘어온 userId는 소문자일까?

getUser(String userId) {
     String _userId = userId.toLowerCase() ;l
      ......
}
와 같이 코드를 수정한다. 다시 두번째 걱정이 든다. 과연 인자로 넘어온 userId는 null이 아닐까?

getUser(String userId) {
     if (userId == null) throw new IllegalArgumentException("userId must be nullable") ;
     String _userId = userId.toLowerCase() ;l
      ......
}
와 같이 코드를 수정한다. 다시 세번째 걱정이 든다. 앞의 User를 생성하는 new User(String userId)의 인자는 소문자가 넘어갔을까?

// in User Class
public boolean equals(Object obj){
     if (obj instanceof User) return false ;
     User that = (User)obj ;
     this.getId().equalsIgnoreCase(that.getId()) ;
}
와 hashCode 메소드를 추가한다. 다시 네번째 걱정이 든다. 과연 DB에는 소문자가 저장이 될까?

// in User Class
public String getId(){
     return id.toLowerCase()  ;
}
와 같이 코드를 수정한다. 다시 다섯번째 걱정이 든다. 어라? 다른 메소드들은 모두 괜찮은건가?? .. .이하 반복



이러한 걱정이 드는 이유는 간단하다. 조금 더 생각해 보면 userId는 단순한 String 이상의 의미가 있다. 이를테면 length는 4byte - 20byte, 허용되는 문자는 AlpahNumUnderbar이며 '_'로 시작해서는 안된다 등의 세부 규칙이 있으며 User 객체를 구분하는 Key의 role을 가지고 있다. 그리고 이러한 정보들은 String에 담기지 않기 때문에 걱정이 들고 이것저것 잔뜩 체크하는 코드들을 중복해서 심어놓게 된다.


이 문제를 해결하기 위해서는 두가지 방법이 있다.

첫번째 방법은 지금보다 훨씬 더 단순하게 만들어서 길을 단 하나만 만들어놓고 입구에서만 체크하는 방법이다. 구조를 단순한게 만드는 작업이 쉽지 않지만 프로그램이 CRUD만을 하는 것이라면 그렇게까지 어렵지는 않다. 프리젠트 레이어에서 모델 레이어까지 거의 원방향 직통 터널을 뚫는 것과 같다. 이 방법을 사용한다면 Entity객체를 만들지 않는게 좋다. 단순한 게시판에 이방법을 사용할때 Board 객체니 Article 객체니 하는것은 단순하게 만드는데 방해가 되기 때문에 그래서 좋지 않다.

음 그러나 만들기는 어렵지 않으나 지속적인 유지보수하기에는 어려운 방법이다. 왜냐하면 엔트로피는 증가하기 마련이고 프로그램은 복잡해지기 마련이다. 일명 세컨드 시스템 이펙트로 프로젝트가 성장 할수록 누군가가 터널에 구멍을 뚫어 저것과 인터페이싱하면 그닥기능과 저닥기능도 될수 있다고 누군가가 주장하기 시작할테고 이것저것 통합하다보면 얼마 지나지 않아 뚫린 곳으로 검증되지 못한 데이타가 들어오고 그것을 체크하기 위한 코드로 프로그램은 누더기가 될것이다. (성장하지 않는 프로그램이라면 - 이를테면 파일럿 예제라든가 - 첫번째 방법은 좋은 방법이 된다.)



두번째 방법은 구조를 단순하게 만들기가 힘들다면 더 복잡하게 만들어서 String을 사용하지 않고 userId가 가지는 규칙과 role을 가지는 새로운 타입을 만드는 방법이 있다. 예컨대 

 private IDString(String id) {
     this.id = id ;
 }

 private static IDString create(String id, int minLength, int maxLength){
        if (id == null) throw new IllegalArgumentException("....") ;

       if (StringUtil.isAlphaNumUnderbar(lowerId, minLength, maxLength)) {
            return new IDString(lowerId) ;
       }
        throw new IllegalArgumentException("......") ;
 }

 public static IDString userId(String userId){
    return create(userId, 2, 12) ;
 }

IDString class를 만들어서 

public User(IDString userId){ // 생성자
   ...
}

public User getUser(IDString userId) {
   .....
}

와 같이 인터페이스를 하는 방법을 사용할 수 있다. 만약 이런 식으로 사용하는 ID 형식의 Type이 많다면 공통점을 묶을 수도 있고 UserId Class 로 별도의 클래스로 만들수도 있다. (균형을 찾는 것은 언제나 어렵다.)

위와 같이 별도의 클래스를 사용하면 인자로 넘어오는게 소문자인지 허용할 수 없는 문자가 들어오는지 일일이 체크하지 않아도 된다.(이를 확실히 보장하려면 해당 클래스를 final로 만들수도 있다.) IDString이 null 로 넘어오는 것을 막을 수는 없지만 불필요한 toLowerCase() 같은 함수를 쓰지 않음으로서 가능성을 대폭 줄여준다. (null의 문제는 설계로 줄여줄수는 있어서 이론적으로 완벽히 막을 수는 없다. )


그래서 인캡슐은 단순히 숨기기만 하는게 아니다. 코드는 숨기는 대신 의도는 드러내야 한다. int, Sring 자체는 아무 의미없는 스칼라 값이기 때문에 의미를 부여하기 위해 첫번째 변수명을 잘 지어야 하고 규칙과 롤이 있어서 충분하지 않다면 그 의도를 반영해주는 포장을 해야 한다. 

어쩌면 이런 질문이 떠오를지도 모른다. 이봐~ 그래봐야 결국에는 String을 사용할거잖아? 언제까지 포장 객체를 쓸수는 없는 노릇이니까 말야. 물론 맨 마지막에는 이런 포장을 뜯어야 한다. 포장은 객체 매개 변수로 전달이 되는 운송과정에서 의미가 있다. String 객체와 달리 IDString으로 포장된 String은 컴파일러와 다른 누군가 혹은 자신에게 그 값이 어떤 값이며 왜 쓰이고 있는 지를 전달해준다. 그리고 마지막 포장을 풀고 String이 필요해질때 getter 메소드가 있는 Bean의 위치를 통해 그 값의 의미를 알게 된다.


Encapsulation에는 단순히 다형성만을 뜻하는 것이 아닌 하나의 패러다임에 가깝다. 아마도 varchar로 저장되겠지만 varchar에 대응되는 String형을 사용하지 말아야 한다는 것은 몰라도 되는 것을 숨겨야 할 뿐 아니라 이미 알고 있는 것도 숨기는게 좋다는 의미이다. (@TODO : 브룩스-맨먼쓰미신-나중에 추가)



'Framework > 아키텍쳐 일반' 카테고리의 다른 글

AOP  (0) 2009.06.08
Self-Signed Java Applets  (0) 2009.06.01
중복을 볼 수 있는 눈  (0) 2009.03.13
Here is Dragon  (0) 2009.03.12
프로그래밍 패러다임  (0) 2009.03.12
Posted by bleujin
pds2009. 4. 12. 23:42



실행방법 :
1. 파일을 다운로드 받는다(http://drop.io/datptht/asset/anotherlore-jar  anotherLore.jar 약 30M)
2. 알집으로 열어서 webapps/ 디렉토리만 현재 디렉토리에 압축을 푼다.
   현재 디렉토리에 다운 받은 jar 파일 하나와 webaspps 디렉토리가 있어야 한다.
   현재 디렉토리 
       -  anotherLore.jar
       -  webapps/
3. command 창에서 java -jar anotherLore.jar 로 실행(require JRE1.5 higher)한다. (jetty server가 기동한다.)
4. http://localhost:8080/board/   로 접속한다.. 끝. -ㅅ- (중단하려면 Ctrl+c를 누른다.)




============== 4월 12일

1. noticeType은 versionable 시키지 않았기에 상관없지만 generalBoard는 versionable code로 되어 있었는데 버전을 3번 수정하는 PK 오류가 나는 걸 수정.

2. 불필요하게 nodeType을 매번 억세스 하는 걸 수정.

3. Property.Type에 virtualText, referenceType 추가.
예컨데 generalBoard의 2번째 Textarea는 VirtualText Type으로 되어 있는데 벨로시티 템플릿을 사용한다.
아래와 같이 입력/수정 할 경우..
$node.getUUIDString() <br/><br/>
#foreach($n in $node.getNodeByPath('/').getChildNode())
   $n<br/>
#end
View 화면에서는 위의 템플릿이 적용된 다른 내용이 보이게 된다. $node는 현재 node를 가르키지만 이를 통해 모든 node를 억세스 가능하다.

4. 파일공유 사이트 바꿈.

=============== 4월 5일
AL은 DataStore를 확인할 수 있는 NodeTypeInfo, NodeInfo Page가 있고

AL로 만든 간단한 Sample Program인 Notice, GeneralBoard, Diagram 3가지가 있다.
AL은 게시판을 만들어 주는 어드민 모드 프로그램이 아니라 프로그램를 만드는 프로그램이다. 3가지의 다른 데이타 모델을 사용하는 샘플 프로그램은 모두 같은 인터페이스 모델로 저장이 된다.

모든 DataModel은 NodeType으로 쉽게 만들수 있다.
anotherLore.jar의 com.bleuji.lore.impl.board에 위의 3개의 샘플 프로그램 소스가 있는데 예를 들어 데이타 모델은 아래와 같이 만들면 된다.


  private void initBoard(AnotherLore lorethrows RepositoryException {

    UserSession session = lore.login(new TestCredential("admin")"localhost");
    globalStore = session.getGlobalStore();

    NodeType objectType = globalStore.createNodeType(NodeType.NO_SUPERTYPE, "_object"new PropertyDefinition[0]);
    Node root = session.createNode(Node.NO_PARENT, objectType, "/");
    Node anonymousRoot = session.createNode(root, objectType, "anonymous");

    // notice DataModel
    NodeType noticeBoardType = globalStore.createNodeType(objectType, "notice_board"new PropertyDefinition[0]);

    PropertyDefinition boardNo = createProperty(BOARDNO, Property.Type.LONG);
    PropertyDefinition subject = createProperty(SUBJECT, Property.Type.STRING);
    PropertyDefinition regUserId = createProperty(REGUSERID, Property.Type.STRING);
    PropertyDefinition regDate = createProperty(REGDATE, Property.Type.DATE);
    PropertyDefinition content = createProperty(CONTENT, Property.Type.STRING);

    PropertyDefinition[] noticeProperties = new PropertyDefinition[] { boardNo, subject, regUserId, regDate, content };
    NodeType noticeType = globalStore.createNodeType(objectType, "notice", noticeProperties);

    noticeType.addPropertyConstraint(boardNo, new NumberConstraint());
    noticeType.addPropertyConstraint(subject, new RangeByteLengthConstraint(1200));
    noticeType.addPropertyConstraint(regUserId, new RequireConstraint());
    noticeType.addPropertyConstraint(regDate, new RequireConstraint());

    noticeBoardType.setMemberType(noticeType);

    Node noticeBoardRootNode = session.createNode(root, noticeBoardType, "noticeboard");

    // extend board Data Model
    NodeType generalBoardType = globalStore.createNodeType(objectType, "general_board"new PropertyDefinition[0]);
    NodeType articleType = globalStore.createNodeType(noticeType, "article"new PropertyDefinition[] { createProperty(ATTACH_FILE, Property.Type.BINARY),
        createProperty(FORMET_TEXT, Property.Type.VIRTUAL_TEXT) });
    articleType.addPropertyConstraint(boardNo, new NumberConstraint());
    articleType.addPropertyConstraint(subject, new RangeByteLengthConstraint(1200));
    articleType.addPropertyConstraint(regUserId, new RequireConstraint());
    articleType.addPropertyConstraint(regDate, new RequireConstraint());

    PropertyDefinition comment = createProperty(COMMENT, Property.Type.STRING);
    NodeType commentType = globalStore.createNodeType(objectType, "comment"new PropertyDefinition[] { comment, regUserId, regDate });
    commentType.addPropertyConstraint(comment, new RangeByteLengthConstraint(104000));
    commentType.addPropertyConstraint(regUserId, new RequireConstraint());
    commentType.addPropertyConstraint(regDate, new RequireConstraint());

    articleType.setMemberType(commentType);

    Node generalBoardRootNode = session.createNode(root, generalBoardType, "generalboard");
    generalBoardType.setMemberType(articleType);

    // web note(diagram) Data Model
    PropertyDefinition name = createProperty("name", Property.Type.STRING);
    PropertyDefinition nextNoteNum = createProperty("nextNoteNum", Property.Type.INTEGER);
    NodeType workspaceType = globalStore.createNodeType(objectType, "workspace_type"new PropertyDefinition[] { name, nextNoteNum });
    workspaceType.addPropertyConstraint(name, new RequireConstraint());
    workspaceType.addPropertyConstraint(nextNoteNum, new RequireConstraint());

    PropertyDefinition note_content = createProperty("note_content", Property.Type.STRING);
    NodeType noteType = globalStore.createNodeType(objectType, "note_type"new PropertyDefinition[] { note_content });
    noteType.addPropertyConstraint(content, new RequireConstraint());

    Node workspace = session.createNode(session.getNodeByPath("/"), workspaceType, "workspace");

    session.save();
    session.logout();

  }

  private PropertyDefinition createProperty(String pid, Property.Type requireTypethrows RepositoryException {
    return globalStore.createPropertyDefinition(pid, requireType);
  }

}

API는 가능한 jcr170과 비슷하게 하려고는 했지만 여러가지 자잘한 이유로 조금씩 변경했다.
예컨데 new RequireConstraint()라는 constraint는 실제로는 데이타베이스에 
<configured-object>
   <class-name>com.bleujin.lore.core.constraint.RequireConstraint</class-name>
</configured-object>
와 같은 XML 형태로 저장이 된다.




AL의 기본 모드는 인메모리에서 동작하게 되어 있다. 따라서
만약 오라클에 저장을 하고 싶으면

 webapps/board/WEB-INF/default-config.xml 파일을 열어서 

1. root.database-controller.database-manager 에 userId, userPassword, jdbcURL을 설정하고
 (해당 아이디는 connect, resource 권한을 가지고 있어야 한다.)
 
2. root.license.startmode를 oracle:recreate로 바꿔주고 재시동하면 된다. 


오라클을 DataStore로 사용했을 경우 root.license.startmode에는
  oracle:recreate, oracle:reset, oracle:none 3가지를 적용할 수 있는데, 

1)  oracle:recreate는 시작할때 관련 테이블과 sequence를 모두 지우고(없으면 무시) 다시 테이블등의 세그먼트를 만든다.
2)  oracle:reset 세그먼트를 다시 만들지는 않지만 테이블의 데이타는 모두 지우고 시작한다. 
3)  oracle:none 이미 오라클에 관련 테이블이 있다면 그냥 아무짓 안하고 시작한다.(이전에 저장했던 데이타가 남는다.)

  처음 오라클 모드로 시작하는 거면 oracle:recreate로 시작한다. (가능한 쉬운 기동을 위해 과정을 단순하게 만들려고 했다.)


generalBoard를 통해 업데이트 되는 Binary형태의 Property는 root.file-repository-path를 수정하지 않으면 /another/data/property에 저장이 되고 파일정보만 AL에 저장된다.


실제 많은 사용자를 버티려면 TypeCache를 적용해야 하지만 NoCache JIT 상태로도 그럭저럭 빠른 속도를 보여준다. 나중에 자세한 성능 테스트 자료를 올리겠음.(국내에 30M 정도를 공유할 수 있는 쓸만한 웹하드 사이트가 없다는 걸 이 글 작성하면서 처음 알았다 -ㅅ-)




'pds' 카테고리의 다른 글

AL Client (XUL Sample)  (0) 2009.07.26
AL Ver 0.1  (0) 2009.07.24
Framework - Source  (0) 2009.02.19
Bleujin Framework Jar  (0) 2009.01.15
Database 발표 자료  (0) 2009.01.12
Posted by bleujin
Framework/Another Lore2009. 4. 9. 15:45

계획된 2주 예정보다 하루 일찍 드디어 AL Project - DBPersistence[Oracle]의 테스트 케이스 95개가 모두 성공하였다.


예전에 보던 룸펜스타의 별돌이의 기분과 비슷하다.

'Framework > Another Lore' 카테고리의 다른 글

AL : sequence  (0) 2009.04.23
자동화 테스트 - 자바스크립트  (0) 2009.04.17
AL - Extreme  (0) 2009.04.04
AL - Code Example  (0) 2009.03.20
AL - Abstraction & Model  (0) 2009.03.13
Posted by bleujin
Framework/Another Lore2009. 4. 4. 16:42

숫자 0과 1의 차이는 무한한 차이이다. 존재와 무존재의 차이니까 말이다.

그럼 1과 2의 차이는? 1과 10의 차이는? 그 차이는 앞의 0과 1의 차이보다 크지 않다. 0과 1의 차이가 질적인 차이라면 1과 10의 차이는 그냥 10의 차이일 뿐이다. 그렇다면 1과 10000의 차이는 그냥 10000의 차이일까? 그럴수도 있고 아닐수도 있다. 흥미로운건 어느 양을 초과하게 되면 단지 양의 차이가 다시 질적인 차이를 일으킨다.

갈매기의 꿈의 조나단은 얼마의 속도를 돌파하게 되자 단지 하나의 갈매기가 아니라 자유로운 갈매기가 되었다. 물은 100도에 이르는 순간 더이상 액체가 아니라 기체로 형질 변환을 일으킨다. 자동차도 시속 1000km로 달리는 순간 날수 있다. 이런 양적인 변화가 질적인 변화를 일으키는 얼마든지 있다.

AL은 유연성을 극도로 올린 프레임워크이다. 항상 시소의 양끝처럼 하나가 올라가면 하나가 내려가는 관계는 아니지만 일반적으로 하나의 특성을 올리면 다른 하나의 특성은 떨어진다. 대표적인 특성 하나가 성능이다. 유연성이 높아질수록 일반적으로 성능은 떨어진다. 그러나 성능은 유연성과 달리 극단적으로 추구한다고 해서 양적에서 질적인 변화로 바뀌기 어려운 분야이다. 더군다나 성능은 항상 한계 속도가 있다. 그것에 한없이 가까워지도록 노력할 수는 있지만 결코 돌파할 수는 없는 선(이를테면 하드웨어)이 있다. 반면에 유연성은 질적인 변화가 쉽과 한계점의 제한으로부터 더 자유롭다.

유연성을 갖춘 프레임워크는 많다. 일찌기 RAD툴이라는 말이 나오기 시작한 15년 전부터 유연성은 프레임워크의 핵심중 하나였다. 최근의 루비온 레일즈도 그러한 예이다. 그러함에도 왜 AL을 만드는가? 이유는 간단하다. 이전의 유연함을 강조하는 것들은 모두 프로세싱과 관련되어 있다. 함수가 유연해지고 구조가 유연해진다고 해서 그리 큰 성과를 거두기는 어렵다. 왜냐하면 프로세싱의 밑단계에는 모델이 깔려있기 때문이다.

데이타모델이 정적인 상황에서 프로세싱이 유연해서 달성할 수 있는 효과에는 한계가 있다. DB Table과 OR Mapping하는 프로세싱 프레임워크의 단점은 바로 이것이다. 이는 어디까지나 제한적인 유연성이며 선을 벗어나지 못하는 유연성이다. AL은 유연한 프레임워크를 강조하지만 프로세싱보다 모델에 집중하고 있고 가장 유연하지 못하는 모델을 가장 유연하게 만들려고 노력하는 프레임워크이다.

지난 1주일동안 1년반이 넘게 팽개쳐 뒀던 AL을 다시 뜯어보고 있다. 평소에도 나는 프로그래밍을 잘한다고 생각한 적이 없다. 그리고 DB도 마찬가지이다. 다만 DBA중에서는 프로그래밍을 잘하고 프로그래머 중에서는 DB를 잘 안다고 생각한다. 각기 다른 전문가의 대화를 통해서는 충분한 정보의 전달이 어렵지만 나는 다른 컨피던스 전환시에 엔트로피 손실이 거의 없기 때문에 아마도 나에게 가장 잘맞는 프레임워크라고 생각하고 있고 아마 그래서 가장 완성해 보고 싶은 프레임워크기이기도 하다.

'Framework > Another Lore' 카테고리의 다른 글

자동화 테스트 - 자바스크립트  (0) 2009.04.17
와~~~  (0) 2009.04.09
AL - Code Example  (0) 2009.03.20
AL - Abstraction & Model  (0) 2009.03.13
AL - 배경  (0) 2009.03.07
Posted by bleujin
IT 이야기2009. 4. 2. 04:55

다시 퍼포먼스 얘기를 해보자.

문제는 아래와 같다.

권한 설정문제인데...
약 10만개의 Tree 형태의 Category Node 가 있다. 권한 정보도 트리 형태로 관리되고 있다. 권한의 종류는 대략 100여가지 정도이다. 그룹과 사용자는 M:N의 관계이며 하나의 그룹은 사용자 혹은 다른 그룹을 하위로 가지고 있다. 사용자는 최대 1000명이 가능하며 그룹은 아마도 수백내외 정도이다.

이제 특정 사용자(혹은 그룹)은 특정 카테고리에 특정 권한을 설정할 수 있다.
이를테면 Category_Read권한을 가진 사용자(혹은 그룹에 속한자)만이 해당 카테고리를 볼 수 있고 해당 권한(혹은 상위 권한)을 가지지 않은 사용자는 해당 Category가 화면에 보이지 않아야 한다.



즉 3개의 Tree 형태를 가지고 권한 정보를 설정하게 된다. 만약 catB에 GroupA가 category_root 권한을 설정하게 되면 GroupA 하위의 모든 그룹에 속하는 사용자는 catB 이하의 모든 카테고리에 대해서 category_root 권한 이하의 모든 권한을 가지게 된다. 단 이때 권한정보에 카테고리 상속여부를 False로 설정하게 되면 catB의 카테고리에 대해서만 권한을 가진다.

그러다가 bleujin 사용자만 catE 카테고리에 대해서는 category_read 권한을 제외하고 싶다면 다시 catE에 bleujin이 category_read 권한을 revoke여부를 true로 설정하게 되면 기존의 권한트리는 변하지 않은채 bleujin의 권한만 재 설정할 수 있다. (이는 DB 모델과 비슷하게 권한이 없다라는 것도 일종의 권한이다 라고 추상화 모델이다. 이경우 상위 카테고리인 catB에는 권한을 주고 하위 카테고리인 catE에는 revoke권한을 주면 catB,catF는 권한을 가지고 catE는 권한이 없다.)

일반적인 시스템처럼 권한정보가 사용자 그리고 카테고리 별 권한별로 row 복제가 되지 않는 다는 점에 주의 하여야 한다. 잘못하면 하나의 권한 정보를 설정할때 수첫만 row가 설정되야 할지도 모르며 이후 관리가 무척 어렵기 때문이다. 권한 정보를 수정하지 않고 특정 사용자가 특정 그룹에서 제외되면 그룹이 가지는 권한은 더이상 적용되지 않는다.

처음 대하면 약간 머리가 핑 돌지만 이 모델은 권한트리에서 생각할 수 있는 가장 유연한 모델이다. 특정 사용자가 특정 카테고리에 특정 권한을 가지는가를 체크하는 것은 그리 어려운 일이 아니다. 특정 사용자의 모든 상위 그룹과 특정 권한의 모든 상위 권한 그리고 특정 카테고리의 모든 상위 카테고리를 읽어서 카테고리 상속여부와 rovoke권한 여부를 잘 조합하면 하나의 SQL로 0.1초 이내의 응답속도를 보장할 수 있다.

문제는 바로 이것이다. 특정사용자가 접근했을때 category_read 이상의 권한을 가진 카테고리 리스트를 Tree 형태로 보여 줄수 있는가... 이다. 이때 특정 사용자가 catE에만 category_read 권한(혹은 그 상위 권한)을 가지고 있다면 catA와 catB에는 category_read 권한이 없어서 내용은 볼수 없지만 카테 고리의 이름은 보여야 한다. 상위 카테고리가 보이지 않으면 Tree 형태의 category모델에서 catE에 접근할 수 조차 없기 때문이다.

이 문제를 더욱 어렵게 하는 것은 카테고리 리스트를 보는 것은 매우 자주 일어나므로 반응 시간이 2초 이내여야 한다는 사실이다. 이게 왜 문제가 되냐면 아무런 특정 로직이 없이 10만개의 category를 Tree 형태로 읽는데에만 약 4-5초가 걸린다는 사실이다. 게다가 단순히 트리 형태의 category를 읽는 것은 아무런 의미가 없고 상위 그릅과 상위 권한도 고려해야 하며 카테고리 상속여부와 revoke 권한도 고려를 해야 하는 로직을 생각하면 일찌감치 날 샌 문제와 마찬가지이다.

catB가 보여야 하나 말아야 하는 정보는 단순히 catB와 관련된 권한 정보만 가지고는 알 수 없다. 어쩌면 catB의 11단계 밑의 카테고리에 해당 사용자가 속하는 상위그룹에게 root 권한이 설정되어 있으면 catB는 Tree 리스트에 보여야 하기 때문에 하위의 모든 카테고리 정보도 보아야 한다.


이제 생각을 해보자. 이전 성능의 첫번째 행동강령은 기준과 목표가 있어야 한다. 였다.
여기서의 기준은 10만개의 카테고리와 수천명의 사용자 그리고 수백개의 권한 정보를 아무런 로직없이 단순히 데이타베이스에서 읽는데만 5초 이상이 걸린다는 기준이 있고 목표는 2초 이내이므로 이미 정상적인 방법으로는 불가능하기 때문에 이때 아무리 멋진 프로그래밍 모델을 생각하더라도 그쪽은 답이 없다.

이때 중요한 점이 바로 이점이다. 이미 정상적인 형태의 connect by start with로는 절대 답이 나올수 없기 때문에 다른 방법을 고려해야 한다는 사실 그 자체를 아는게 중요하다. 기준이 없으면 자신이 올바른 길을 가고 있은지 아닌지를 스스로가 알수 없다.

아까 하나의 카테고리에 특정 사용자가 특정 권한이 있는지 여부를 체크하는데 0.1초가 걸린다고 하였다.(SQL에 익숙하지 않으면 이 SQL을 짜는 것도 쉽지 않다.) 단순히 catA를 보여줘야 하는지 여부를 개별적으로 체크한다면 catA의 모든 하위 카테고리를 조사해야 되고 10만 * 0.1초 이므로 약 2시간 30분이 걸린다. 따라서 이 방법도 답이 될 수 없다.

당시에 약 2달간 생각했지만 위 문제를 풀지 못했다. 그러나 수학은 풀지 못했다고 해결이 되는게 아니라 풀 수 없다고 증명할 수 있어야 하는데 그냥 카테고리만 읽는데만 5초가 걸리는데 어떻게 2초 이내의 응답속도가 가능할까? 라는 근거는 충분한 증명이 되지 못했다.

문제는 하위 카테고리를 "모두" 읽지 않고 상위 카테고리가 보여줄지 여부를 결정 할 수 있느냐의 여부였고 2달이 지난후 약 3일 걸려서 복잡 다양한 설정 Case를 분석해서 0.6초 정도의 반응속도를 보여주는 200라인 정도의 SQL을 짰는데 구문이 대단히 복잡하다거나 그런건 아닌데 발상이 어렵다. 대신 SQL에서 모든 리스트를 구해서 넘겨주므로 프로그램에서는 그냥 보여주기만 하면 된다. 위 문제는 생각하기에 따라서 아주 재미있으므로 흥미가 있다면 직접 작성해보는 것도 도움이 된다.



위 얘기를 하는 것은 성능 문제에 있어 기준과 목표는 가장 중요한 행동강령이라는 사실을 다시 한번 강조하기 위해서이다. 앞의 로그분석 프로그램의 예를 다시 얘기하면 로그가 약 100만건이라면 이를 일단 분석하는 비지니스 로직은 제쳐두고 일단 100만건을 읽는데 보통의 서버급에서 2-3초가 걸린다. 좀더 좋은 서버라면 1초 정도면 가능하다. 이때 이 기준은 매우 중요한 판단 기준이 된다.

만약 해당 프로그램이 100만건을 분석하는데 약 3분이 걸린다면 과연 그 비지니스 로직이 2분 57초가 걸릴만큼 복잡한가에 대해서 자문해볼 수 있는 여지가 있기 때문이다. 그렇지 않다면 내가 해당 데이타를 읽는 방법이 잘못됐거나 비지니스 로직이 잘못됐다는 것을 뜻한다.

따라서 일단 100만건을 분석하는데 단순히 읽는 속도인 2초에 한없이 가깝게 가는게 일차 목표가 된다. 그러나 분석 데이타가 1억건이라면 얘기가 달라진다. 단순히 곱셉을 하면(실제로는 단순곱셈이 아니지만) 1억건을 모두 읽는데 약 200초가 걸리며 200초가 수용할 수 있는 수준이 아니라면 이때는 기존의 단순히 읽는 방법으로는 이미 답이 없음을 뜻한다.

따라서 데이타 분산을 했을때의 읽기 속도, 집계 테이블을 유지했을때의 속도와 단점 등등을 미리 측정해서 알고 있다면 이러한 경우 해답을 찾는 문제가 훨씬 더 단순해진다. 만약 이러한 기준을 모른다면 말 그대로 보물이 나올때까지 삽으로 닥치는 대로 이곳저곳 땅을 파는 것과 같다. 아마도 겨우 동전 몇개를 발견하는 것에 그치겠지만 말이다.




'IT 이야기' 카테고리의 다른 글

아키텍트의 역할2  (1) 2012.06.19
아키텍트의 역할  (0) 2012.06.19
아키텍트 vs 트러블슈터 vs 컨설턴트  (0) 2009.03.26
튜닝  (0) 2009.03.26
interface  (0) 2009.03.25
Posted by bleujin