Framework/Database2009. 3. 12. 17:21




파란색을 실험실로 간주했을때 dc는 실험실의 연결창구 역할을 하게 된다. Client는 기본적으로는 DBController(이하 dc)를 통해서 메시지 교환을 한다. 

DBController는 Facade 패턴의 예중 하나로 Facade(건물의정면) 패턴은 복잡한 서브 시스템에 통일된 인터페이스를 제공함으로써 복잡한 API를 단순화 시켜준다. 시스템을 서브 시스템 단위로 나누어 구성하는 것은 시스템의 복잡도를 낮춰주지만, 동시에 서브 시스템 사이에서의 통신 부하와 결합도가 증가하게 된다. 이러한 서브 시스템 사이의 의존도를 낮추고, 서브 시스템의 사용자 입장에서 사용하기 편리한 인터페이스를 제공하고자 하는 것이 facade 객체이다.

Facade 객체는 실생활에서의 고객 서비스 센터와 유사하다. 가령, 어떤 상품을 구매하는 과정에서 문제가 생겼다고 가정할 때, 고객이 문제의 성격에 따라 해당 부서에 직접 연락하는 것이 아니라 고객 서비스 센터를 통하는 것은 Facade 패턴에 대한 좋은 유추 사례가 될 수 있다.

여기서 DBController Facade 역할이 하는 일은 무엇일까 ?

첫째 복잡한 것을 단순하게 보여준다. 실험실 내부에서 작동하고 있는 내부에서 작동하고 있는 많은 클래스들의 관계나 사용법을 의식하지 않도록 해 준다. Java 접근자는 구조적인 이유로 public을 쓸수밖에 없는 경우가 종종 있는데 그중에 일부만 - 즉 개념적인 published 메소드 - 만을 노출시킴으로써 보이는 API를 적게 해 주고 단순하게 해 준다.

둘째 실험실의 컴포넌트로부터 클라이언트를 격리하여 실험실과 클라이언트 사이의 의존성을 낮춘다. 이런 캡슐화는 클라이언트와 서브시스템의 생존 기간이 다르기 때문에 필수적이다. Facade 패턴을 사용한다고 해도, 필요한 경우 실험실의 클래스에 직접 접근할 수도 있다.(이러한 것을 터널링이라고 부른다.) 즉, 일반화 정도(generality)와 개발의 편의성 사이에서의 적당한 합의점을 찾아야 한다.

Facade 패턴의 일반적인 특징에 추가로 위 DBController은 2가지를 더하고 있다.(SRP에는 위반되지만 ..) 첫번째는 실험실에서의 checked exception을 runtime exception으로 바꾼다. exception framework에서 말한대로 첫째로 대부분의 SQLException을 client에서는 잡아서 현명한 처리를 하지 않으며 둘째로 기껏해야 로그 기록하는 정도라면 본래 코드를 작성하는데 오히려 방해가 되기 때문이다. 이런 예외 처리가 현재 상황에서 적합하지 않다면 DC를 상속받아 해당 메소드를 재정의 하여야 한다.(실제 필요한적이 한번도 없었기 때문에 미리 만들어 두지 않았다.)

다른 한가지 역할은 Servant Filter 등록을 관리한다. 비실험실인 client는 현실의 요구사항이 있다. 이를테면 특정 쿼리의 실행을 모니터 하고 싶을 수도 있다.(느린 쿼리, 배치형 쿼리같은 특정 타입, 특정 테이블을 사용하는 쿼리 등등) 그리고 이 부분은 모두 필터로 관리되어 런타임중에 추가 삭제가 가능해야 한다. (나중에 다시 언급)


보통의 경우 Facade 객체는 Singleton 패턴으로 사용되는 경우가 많지만 DC의 경우 실제 커넥션 풀링등의 리소스 관리는 DBManager에게 맡기고 있으므로 배치 처리용으로 Servant가 없는 별도의 BatchDBController를 사용해도 되고 시간이 오래 걸리는 쿼리의 경우 비동기적으로 작동하는 AsyncDBController를 사용해도 된다.

'Framework > Database' 카테고리의 다른 글

Framework - 커서(Keyset, Dynamic)  (0) 2009.03.13
Framework (Servant)  (0) 2009.03.12
Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Framework (Handler)  (0) 2009.03.08
Framework (Rows)  (0) 2009.03.07
Posted by bleujin


주석(코멘트)에 대한 대부분의 책은 단 한마디로 요약할 수 있다.
 "주식을 충실하게 달아라"

이는 다른말로 대부분의 프로젝트에는 주석이 충실하지 않다는 반증이고 주석 다는게 쉽지 않다는 말이기도 하다. 사실 좋은 코드와 나쁜 코드를 구분할 수 있는 것처럼 주석에도 좋은 주석과 나쁜 주석이 있다.

주석 안에는 무엇이 들어갈까 ? 주석을 달때 주의점은 아래와 같다 .

 - 어떻게가 아니라 왜를 기술하라 이를테면
    틀린예) GlbWLRegistry에 있는 WidgetList 구조를 업데이트 한다.
    라고 주석을 달아서는 안된다. 왜냐하면 그 내용은 코드안에 박혀 있기 때문이다. 따라서 "왜" 이렇게 코드를 작성했는지에 대해 주석을 달아야 한다. 
    바른예) 나중을 위해서 widget 정보를 캐시함 O


 - 코드를 묘사하지 마라
    틀린예) i++ ; // increment i
    DRY의 원칙은 단순히 코드끼리의 중복만을 의미하는 것은 아니다. 위 틀린예는 옆의 코드와 주석이 중복되기 때문에 DRY 원칙을 위배한 것이다. 코드와 중복되는 내용을 다시 코멘트로 쓰지 말아라. 


 - 코드를 대신하지 마라
    나쁜예) // 이 변수는 foo 클래스에 의해서만 접근해야 함. 
    코드의 주의사항을 달기보다는 코드를 다시 작성해라. 변수의 사용법을 설명하는 주석을 달기보다는 변수의 이름을 다시 지어라. 코드로 충분히 할 수있는 것을 주석으로 대체하지 말아라. 

    
 - 유용성을 유지하라
    주석은 당연한 것을 말하는 것이 아니라 당연하지 않다고 생각되는 의외의 것을 기록해야 한다. 주석으로 코드를 쉽게 설명할 생각은 하지 말고 그냥 코드를 쉽게 만들어라. 만약 더 이상 코드를 쉽고 명료하게 만들수가 없을 때에만 주석이 필요하다. 주석을 쓰레기 코드를 덮는 포장지로 사용해서는 안된다. 더이상 코드에 손댈게 없을때에야 진실되고 명료하고 알기 쉬운 주석을 달도록 노력해라. 


 - 주의를 흩트리지 마라
   코드가 주인이라는 사실을 잊지 말자. 따라서 주석을 달음으로서 불필요하게 코드 리딩과정에 주의를 흐트려서는 안된다. 쓸데 없는 아스키 기교로 주석의 네모 박스를 이쁘게 유지하는 등의 바보같은 노력을 하지 마라.
블록의 끝에  // end if ( a < 1 ) 같은 주석이 그럴듯 하게 보일지 모르지만 이 주석을 달 시간에 차라리 코드를 더 이쁘게 작성하고 지나친 중첩 block를 사용하지 말아라. 이런 주석은 코드가 수정될때 같이 수정이 되지 않아서 부패가 되고 썩어 버린다. 불필요한 코드만큼이나 불필요한 주석 그리고 공허한 주석을 달지 말아라.


주석에 대한 이런 관점은 주석은 필요악이라는 관점이다. - 비록 많은 사람들이 동의 하지 않을지라도 - More Comment, Better Code가 절대 아니다. 방법론에서 문서화들이 그러한 것처럼 Better Code를 추구하기 위한 과정일뿐 주석자체는 목적이 아니다. 그래서 좋은 코멘트를 작성하는 것보다는 좋은 코드를 작성하는것에 집중하는 것이 좋다.

Posted by bleujin
Framework/Database2009. 3. 12. 16:31

이전 글을 작년 7월에 써서 올해 1월 12일날 포스팅을 해서 기억도 잘 안난다 -ㅅ-; (심지어 거기에 나온 내용들은 이미 7년전쯤에 해본 내용이다.)

  public void testStatic() throws Exception {
    DBManager dbm = new MSSQLPoolDBManager("com.microsoft.jdbc.sqlserver.SQLServerDriver", 
         
"jdbc:microsoft:sqlserver://novision:1433;DatabaseName=test", "bleu", "bleu") ;
    Connection conn = null;
    ResultSet rs = null;
    PreparedStatement pstmt = null;

    try {
      conn = dbm.getConnection()
      pstmt = conn.prepareStatement(getProcSQL());
      rs = pstmt.executeQuery()
      populate(rs)
    catch (SQLException ex) {
      throw ex;
    finally {
      closeWithoutException(rs);
      closeWithoutException(pstmt);
      dbm.freeConnection(conn)
    }
  }

  private String getProcSQL() {
    return "select custNo, fixLength from customer_tblc";
  }

  private void populate(ResultSet rowsthrows SQLException {
    int i = 0;

    while (rows.next() && i++ < 10) {
      System.out.print(rows.getString(1));
    }
    System.out.println();

  }

"위 평범한 JDBC 코드는 customer_tblc에서 10건을 가져와서 10건을 화면에 찍는 프로그램이다"
는 사실이 아니라는 걸 얘기 했었다.

위 코드를 default cursor인 Forward Only cursor로 실행하면(빨간색 화살표는 여러번 반복된다는 뜻)


   1. WAS는 쿼리 실행을 요청한다. 
   2-1. DBServer에서는 cusotmer_tblc의 row를 읽는대로 Working Memory에 올린다.
   2-2  동시에 WorkingMemory에 어느정도의 데이타가 쌓이면(대략 2-8k) 바로 WAS에 전송한다.
   2-3  WAS는 패킷을 받아서 받았다는 confirm 메시지를 전송한다. 

   위 2-1 ~ 2-3 과정은 DB가 쿼리의 모든 row를 읽을때까지 반복된다. 
   WAS에서는 사용하는 10건의 row를 받았고 이후의 데이타는 쓸모 없지만 주구장창 모두 받아야 한다. 
   3. WAS는 나머지 데이타도 모두 받아서 버린후에 DB close를 요청한다.
   DB서버는 WorkingMemory를 정리하고 close 된다.

만약 테이블에 단지 20건 정도라면 괜찮지만 1000000건만 되도 99.9999%의 비용을 더 소모하였다.

단점은
  - WAS에서 필요한 양과 상관없이 해당 쿼리의 모든 데이타를 읽어야 한다.(컴퓨터에게 읽는다는 의미는 하드에 있는 바이트 배열을 메모리에 복제하였다는 얘기이다.)
  - WAS에서 필요한 양과 상관없이 모든 데이타를 네트워크에 전송함으로서 막대한 네트워크 부하가 발생한다. 
  - 모든 데이타를 받아야 종료(DB Close) 할수 있다.

장점
   - 읽는대로 바로 바로 전송해버리기 때문에 DB 메모리가 많이 소모되지 않는다.
   - 네트워크에 전송할때 여러 row를 묶어서 패킷으로 전송한다.
   - 커서는 비동기적으로 작동하기 때문에 최소한의 정보 - 현재 커서의 위치 - 만을 유지할 수 있다.

  public void testStatic() throws Exception {
    Connection conn = null;
    ResultSet rs = null;
    PreparedStatement pstmt = null;

    try {
      conn = dbm.getConnection()
      pstmt = conn.prepareStatement(getProcSQL(), ResultSet.TYPE_SCROLL_INSENSITIVE
                                                , ResultSet.CONCUR_READ_ONLY)
;
      rs = pstmt.executeQuery()
      populate(rs)//
    catch (SQLException ex) {
      throw ex;
    finally {
      closeWithoutException(rs);
      closeWithoutException(pstmt);
      dbm.freeConnection(conn);
    }
  }

위와같이 insensitive cursor로 실행하면


  1. WAS는 쿼리 실행을 요청한다.  
  2. DB는 쿼리의 "모든" 결과를 메모리에 snapshot 형태로 만든다. 모든 데이타의 load를 완료하면 Client의 신호를 기다린다
  3. Client(WAS)는 Fetch(rs.next())를 요청한다.
  4. DB는 cursor 정보를 유지하면서 currentRow의 정보를 Client에게 보낸다. 
  5. 패킷(1개의 row만 들어있음)을 받았다는 confirm 메시지를 보낸다.
  3,4,5의 동작을 9번 더 반복한다
  
  6. WAS는 DB close를 요청한다.
  DB는 결과셋과 cursor정보를 메모리에서 삭제하고 연결정보를 close한다.  


ForwadOnly와 달리 3번, 4번, 5번은 비동기적으로 작동한다. 즉 모든 결과셋을 Snapshot형태로 메모리에 모두 올리고 나서야 Client의 Fetch(res.next())를 실행할 수 있다.(이동안 Client은 Hang 상태이다. 역시 이전 글의 네트웨크 모니터링 캡쳐화면과 같이 보도록 하자.)


단점은
  - WAS에서 필요한 양과 상관없이 해당 쿼리의 모든 데이타를 읽어야 한다.(컴퓨터에게 읽는다는 의미는 하드에 있는 바이트 배열을 메모리에 복제하였다는 얘기이다.)
  - DB에서 읽은 모든 Row를 결과셋에 Snapshot 형태로 저장해야 하기 때문에 공유 자원인 DB 메모리가 많이 소모된다. (이 메모리는 재사용되지 않는다. 즉 같은 프로그램을 2번 실행한다는 것은 SnapShot을 2번 유지한다는 것을 뜻한다. )
   - 동기적으로 작동하기 때문에 Client의 fetch 요구시마다 row단위의 데이타 전송이 이루어진다.(패킷의 크기가 작다.)
   - cursor를 해당 프로그램이 종료될때까지(DB close 할때까지) 유지해야 하고 더 많은 정보를 관리해야 한다.
   - 첫번째 Row가 Client에 전송되는 시간이 느리다.


장점 
   - Client가 필요한 양만 네트워크에 전송한다. 


단점이 너무 심각해 보이는 위 두가지 경우는 황당해 보이겠지만 90% 이상의 사이트에서 발생하고 있는 현실이다. 말 그대로 불편한 진실인 것이다. (이 블로그에서 소개하는 DB Framework는 위 방식으로 작동하지 않는다.)


커서의 종류는 보통 ForwardOnly, Static, Keyset, Dynamic 네가지 종류가 있으며 JDBC 코드로는 작성하기는 어렵지만 다음에는 나머지 두개의 커서의 실행과정도 분석해 보자. (앞서 언급했듯이 JDBC에는 커서를 지정한다기 보다 다른 선택에 의해 자동 종속되고 Keyset과 Dynamic가 종속되는 경우는 극히 드물다. 반면에 C#등의 MS 계열은 커서 모드를 설정할 수 있도록 하고 있다. 다만 일부 조건을 만족시켜야 된다.)






'Framework > Database' 카테고리의 다른 글

Framework (Servant)  (0) 2009.03.12
Framework (DBController)  (0) 2009.03.12
Framework (Handler)  (0) 2009.03.08
Framework (Rows)  (0) 2009.03.07
Framework (IQueryable)  (0) 2009.03.06
Posted by bleujin


프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고, 결정하는 역할을 한다. 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해준다.

프로그래머가 어떤 언어를 사용하는 것과는 상관없이 프로그래밍 패러다임에 대해 알아두는 것은 새로운 관점의 세계관을 가진다는 것을 뜻한다. XP 켄트벡의 프로그래머들은 일년에 하나의 새로운 언어를 배워두는걸 권장하는 것은 아마도 이 때문이 아닌가 싶다. 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해준다.

프로그래밍 패러다임과 프로그래밍 언어와의 관계는 프로그래밍 언어가 여러 프로그래밍 패러다임을 지원하기도 하기 때문에 복잡할 수도 있다. 어떤 언어들은 하나의 특정한 패러다임을 지원하기도 하는데, 스몰토크와 자바가 객체지향 프로그래밍을 지원하는 반면에, 헤스켈과 스킴은 함수형 프로그래밍을 지원한다.

여러가지 패러다임을 지원하는 언어들도 있는데, C++, 자바 스크립트, 커먼 리스프, 파이썬, 오즈가 이런 언어들이다. 예를 들어서 C++는 절차적 프로그래밍, 객체기반 프로그래밍, 객체지향 프로그래밍, 제네릭 프로그래밍의 요소들을 지원하도록 설계되었다. C++에서는 순수하게 절차적 프로그램을 작성할 수 있고, 순수하게 객체지향 프로그램을 작성할 수 있으며, 두 가지 패러다임 모두의 요소를 포함한 프로그램을 작성할 수도 있다. 자바 스크립트는 구조적 프로그래밍이고, 함수형 프로그래밍이고, 프로토타입 기빈 프로그래밍이며, 객체기반 프로그램밍 언어이지만 절차적 프로그래밍 방식도 사용한다.


구조적 프로그래밍과 비구조적 프로그래밍

구조적 프로그래밍(영어: structured programming)은 구조화 프로그래밍으로도 불리며 프로그래밍 패러다임의 일종인 절차적 프로그래밍의 하위 개념으로 볼 수 있다. GOTO문을 없애거나 GOTO문에 대한 의존성을 줄여주는 것으로 가장 유명하다.역사적으로 구조적 프로그램을 작성하기 위하여 몇가지 다른 구조화 기법과 방법론이 개발되어 왔다. 

데이크스트라의 구조적 프로그래밍은 프로그램의 논리 구조는 제한된 몇 가지 방법만을 이용하여 비슷한 서브 프로그램들로 구성된다. 프로그램에 있는 각각의 구조와 그 사이의 관계를 이해하면 프로그램 전체를 이해해야 하는 수고를 덜 수 있어, SoC에 유리하다. (데이크스트라의 관점에서 파생된 관점 : 하위 프로그램의 시작점은 한 군데이지만 끝점은 여러 개일 수 있다. )

저수준의 관점에서 구조적 프로그램은 간단하고, 계층적인 프로그램 제어 구조로 구성된다. 이 제어 구조들은 하나의 구문으로 간주되며, 동시에 더 간단한 구문들을 결합시키는 방법이다. 더 간단한 구문들은 또 다른 제어 구조일 수도 있고, 할당문이나 프로시저 호출과 같은 기본 구문일 수도 있다.(에츠허르 데이크스트라가 확인한 3가지 형태의 구조는 순차, 선택, 반복이다.)

설계에 있어서 구조적 프로그래밍이 항상 그런 것은 아니지만 하향식 설계와 관련이 있다. 하향식 설계를 할 때, 설계자는 큰 규모의 프로그램을 더 작은 공정으로 나누어 구현하고, 각각 검사한 다음에 전체 프로그램으로 합친다.

모든 절차적 프로그래밍 언어에서 구조적 프로그래밍을 할 수 있다. 1970년쯤부터 구조적 프로그래밍이 인기있는 기법이 되었기 때문에, 대부분의 새로 나온 절차적 프로그래밍 언어들이 구조적 프로그래밍을 고취시키기 위한 특징을 추가하였고 구조화되지 않은 프로그래밍을 쉽게 하기 위한 특징들은 남겨둔 것들도 있었다. 잘 알려진 구조적 프로그래밍 언어에는 파스칼(Pascal)과 에이다(Ada)가 있다.


비구조적 프로그래밍은 하나의 연속된 덩어리에 모든 코드를 넣는 프로그래밍 패러다임이다. 대비되는 개념으로는 구조적 프로그래밍이 있는데, 이는 프로그램의 작업이 (함수나 서브루틴으로 알려진) 더 작은 부분으로 나누어 필요할 때마다 호출하는 것이다. 비구조적 프로그래밍 언어는 코드의 특정부분으로 건너뛰는 GOTO문과 같은 흐름 제어문에 의존할 수 밖에 없다.

구조화되지 않은 원시 코드는 읽고 디버그하기가 매우 어렵고, 구조적인 작성을 지원하는 프로그래밍 언어에서는 추천하지 않는다. 그러나 프로그램 구조는 항상 조건문과 GOTO문을 조합하여 구현할 수 있기 때문에 구조가 모든 언어에서 필요한 것은 아니다. MS-DOS의 배치 파일과 같은 많은 스크립트 언어나 베이직이나 포트란 같이 오래된 언어에서는 여전히 사용되기도 한다. GOTO문을 쓰는 것에 수행 속도상의 이점은 없다. (실제로, 컴파일러가 최적화 할 수 있는 것들을 혼란시켜 오히려 불이익이 될 수도 있다.)

어셈블리어는 대체로 비구조적 언어인데, 기본이 되는 기계어 코드가 구조적이지 않기 때문이다. 어셈블리 언어에 있는 유일한 구조는 함수의 시작과 끝 같이 컴파일 도구에서 쓰는 것들이다.



명령행 프로그래밍과 선언형 프로그래밍

전산학에서 명령형 프로그래밍(Imperative programming)은 선언형 프로그래밍과 반대되는 개념으로, 프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다. 자연 언어에서의 명령법이 어떤 동작을 할 것인지를 명령으로 표현하듯이, 명령형 프로그램은 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이다.

명령형 프로그래밍 언어는 함수형 프로그래밍이나 논리형 프로그래밍언어와 같은 다른 형태의 언어와 다르다. 헤스켈 같은 함수형 프로그래밍 언어는 구문들을 순서대로 써 놓은 것이 아니며, 명령형 프로그래밍 언어와는 다르게 전역적인 상태가 없다. 프롤로그와 같은 논리 프로그래밍 언어는 "어떻게" 계산을 할지 보다는 "무엇"이 계산될 것인지를 정의한다는 생각으로 작성된다.

거의 대부분의 컴퓨터 하드웨어는 명령형으로 구현된다. 거의 모든 컴퓨터 하드웨어들이 컴퓨터의 고유 언어인 기계어를 실행하도록 설계되어 있는데, 이것이 명령형으로 씌어 있다. 낮은 수준의 관점에서 프로그램의 상태는 메모리의 내용으로 정의되고, 구문들은 기계어의 명령어로 정의된다. 높은 수준의 언어 구현은 변수와 더 복잡한 구문을 사용하지만, 여전히 같은 패러다임을 따른다. 요리법이나, 공정 점검표같은 것들은 컴퓨터 프로그램은 아니지만, 명령형 프로그래밍과 비슷한 형태의 이해하기 쉬운 개념이다. 각각의 단계의 지시 사항들이 있고, 상태라는 것은 현실 세계에 반영된다. 명령형 프로그래밍의 기본 생각이 개념적으로 친밀하고, 직접적으로 구체화되어 있어서, 대부분의 프로그래밍 언어들은 명령형이다.

보통 할당문은 메모리에 있는 정보에 연산을 수행하고, 결과값을 나중에 사용하기 위해 메모리에 저장한다. 추가로, 고급 명령형 언어는 산술 연산, 함수연산, 결과 값을 메모리에 할당하는 연산을 결합한 복잡한 수식을 계산한다. 반복문은 이런 연속된 구문을 여러번 실행하게 한다. 반복문은 미리 정의된 횟수만큼 반복하기도 하고, 어떤 조건이 바뀔때까지 반복하기도 한다. 조건 분기문은 구문의 덩어리를 어떤 조건이 만족하는 경우에만 실행하게 할 수 있다. 그렇지 않으면, 그 구문의 덩어리를 실행하지 않고 그 다음부터 실행한다. 비조건 분기문은 실행 순서를 프로그램의 다른 부분으로 옮기는 것이다. 여러 언어에서 제공하는 GOTO문, 서브프로그램, 프로시저, 호출문들이 비조건 분기문이다.

명령형 프로그래밍의 전형적인 예는 포트란과 알골이다. 파스칼, C, 에이다는 또 다른 예이다.


선언형 프로그래밍은 두 가지 구분되는 뜻이 있는데 두 가지 뜻 모두 통용되고 있다.

한 정의에 따르면, 프로그램이 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명하는 경우에 "선언형"이라고 한다. 예를 들어, 웹 페이지는 선언형인데 웹페이지는 제목, 글꼴, 본문, 그림과 같이 "무엇"이 나타나야하는지를 묘사하는 것이지 "어떤 방법으로" 컴퓨터 화면에 페이지를 나타내야 하는지를 묘사하는 것이 아니기 때문이다. 이것은 전통적인 포트란과 C, 자바와 같은 명령형 프로그래밍 언어와는 다른 접근방식인데, 명령형 프로그래밍 언어는 프로그래머가 실행될 알고리즘을 명시해주어야 하는 것이다. 간단히 말하여, 명령형 프로그램은 알고리즘을 명시하고 목표는 명시하지 않는데 반해 선언형 프로그램은 목표를 명시하고 알고리즘을 명시하지 않는 것이다.

또 다른 정의에 따르면, 프로그램이 함수형 프로그래밍 언어, 논리형 프로그래밍 언어, 혹은 제한형 프로그래밍 언어로 쓰여진 경우에 "선언형"이라고 한다. 여기서 "선언형 언어"라는 것은 명령형 언어와 대비되는 이런 프로그래밍 언어들을 통칭하는 것이다.

이 두가지 정의는 서로 겹치는 부분도 있다. 특히, 제한형 프로그래밍과 논리형 프로그래밍은 필요한 해의 특성을 설명하고(무엇) 그 해를 찾는데 사용하는 실제 알고리즘은 설명하지 않는다(어떤 방법). 그러나 대부분의 논리형과 제한형 언어들은 알고리즘을 설명할 수 있고, 상세한 부분을 구현할 수 있어서 첫 번째 정의를 따르는 엄밀한 의미의 선언형 프로그래밍 언어는 아니다.

마찬가지로, 명령형 프로그래밍 언어로 선언형으로 프로그램을 작성할 수도 있다. 라이브러리나 프레임워크 내부의 비선언형 부분을 캡슐화하여 이렇게 할 수 있다. 이런 형태의 예가 제이유닛 유닛 테스트 프레임워크에 반영되어 쓰이고 있는데, 이것은 정의만 되어 있으면 프레임워크로 등록하여 유닛을 테스트하는 것을 가능하게 한다.

선언형 프로그래밍은 특수 분야 언어(영어: Domain-specific language, DSL)의 형태로 자주 사용된다. 특수 분야 언어의 한 가지 결점은 튜링 완전성이 없다는 것이다. 그 말은 할 수 없는 일이 있다는 것이다. 스프레드시트에서는 전자메일을 보낼 수 없고 전자메일을 이용하여 은행 계좌를 계산할 수 없다는 것이다. 이러한 이유로 특수 분야 언어들은 때로 범용 언어에 내장된다. 이렇게 하면 프로그래머가 특수 분야 언어가 힘을 발휘하는 분야에서 이것을 이용할 수 있고, 특수 분야 언어로 하기 어렵거나 불가능한 문제는 범용 언어를 이용할 수 있다.

여기서 소개한 Validation Framework는 바로 이 선언형 프로그래밍 패러다임을 사용한것이다. 이 밖에도 잘 알려진 선언형 프로그래밍을 포함한 프레임워크 루비 온 레일와 jUnit등의 Unit 시리즈가 있다.



메시지 전달 프로그래밍과 명령행 프로그래밍

명령행 프로그래밍은 위에...

메시지 전달 프로그래밍은 주로 분산환경에서 독자적인 메모리 공간을 가지는 각각의 프로세스가 하나의 문제를 해결하기 위하여 프로세서들은 정보(메시지)들을 교환하는 방식을 말한다. 메시지 전달의 간단한 형태는 Point to Point 통신이다. 이는 한 프로세서가 다른 프로세서로 메시지를 보내는 것인데, 보내는 방식에 따라 동기와 비동기로 나눌 수 있다. 동기 방식은 메시지의 완료를 다른 프로세서에 보내주는 것이며, 비동기 방식은 단지 언제 메시지를 보냈는지만 알 수 있다. 

개념적으로 본다면 DB 프로세스는 프로그래밍 프로세스와 독립된 메모리 공간을 가지는 분산환경이며 프로그래밍 언어와 데이타베이스는 각각이 독립적인 서비스 이기 때문에 둘 중 어느 누구도 상대방에 의존하지 않는 중립적인 메시지를 교환하는게 맞다고 생각했기 때문에 이 블로그에서 소개하는 DB Framework는 이 메시지 전달 프로그래밍 패러다임을 사용한 것이다. 



절차적 프로그래밍과 함수형 프로그래밍

절차적 프로그래밍(procedural programming)은 절차지향 프로그래밍 혹은 절차지향적 프로그래밍이라고도 불리는 프로그래밍 패러다임의 일종으로서, 때때로 명령형 프로그래밍과 동의어로 쓰이기도 하지만, 프로시저 호출의 개념을 바탕으로 하고 있는 프로그래밍 패러다임을 의미하기도 한다. 프로시저는 루틴, 하위프로그램, 서브루틴, 메서드, 함수(수학적 함수와는 다르고 함수형 프로그래밍에 있는 함수와는 비슷한 의미이다.)라고도 하는데, 간단히 말하여 수행되어야 할 연속적인 계산 과정을 포함하고 있다. 프로그램의 아무 위치에서나 프로시저를 호출될 수 있는데, 다른 프로시저에서도 호출 가능하고 심지어는 자기 자신에서도 호출 가능하다.

절차적 프로그래밍 언어들은 절차적 프로그래밍 접근 방식을 따름으로써 프로그래머의 작업을 수월하게 한다. 알골과 같은 언어가 절차적 프로그래밍 언어의 표준적인 예이다. 그 밖에 포트란, PL/I, 모듈라-2, 에이다 등이 있다.

함수형 프로그래밍은 프로그래밍 패러다임의 하나로, 계산을 수학적 함수의 조합으로 생각하는 방식을 말한다. 이것은 일반적인 프로그래밍 언어에서 함수가 특정 동작을 수행하는 역할을 담당하는 것과는 반대되는 개념으로, 함수를 수행해도 함수 외부의 값이 변경될 수 없다.

알론조 처치가 1930년대에 개발한 람다 대수는 함수에 대한 이론적 기반을 세웠다. 이것은 프로그래밍 언어가 아니라 수학적 추상화였지만, 이것은 함수형 프로그래밍의 근간을 이루었다. 처음으로 만들어진 함수형 프로그래밍 언어는 IPL이었다. 존 매카티가 만든 리스프는 훨씬 향상된 함수형 프로그래밍 언어였고, 이것은 현대적 함수형 프로그래밍의 여러 특징을 가지고 있었다. 리스프를 발전시키고 간단하게 만든 언어로 스킴이라는 것도 나왔고1980년대에는 그동안의 함수형 프로그래밍에 대한 연구를 바탕으로 헤스켈이 만들어졌다.



 

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

중복을 볼 수 있는 눈  (0) 2009.03.13
Here is Dragon  (0) 2009.03.12
아키텍쳐 패턴 - Broker 패턴  (0) 2009.03.12
아키텍쳐 패턴 - Pipes and Filter 패턴  (0) 2009.03.11
아키텍쳐 패턴 - Layer 패턴  (0) 2009.03.11
Posted by bleujin



만약 현재 개발중인 환경이 독립적인 협력 컴포넌트들로 구성된 이질적인 분산시스템이라면 Broker 패턴을 생각해 봐야 한다.

분산은 아래와 같은 장점이 있다.
  - 경제성
  - 성능과 범위성
  - 고유 분산성
  - 신뢰성

그해 비해 단점은 중앙집중적 시스템이 가지고 있는 것과는 근본적으로 다른 소프트웨어가 필요하다는 것이다. 보통의 경우 분산 시스템은 느리고(비효율적) 예외의 종류가 비약적으로 증가하기 때문에 만들기가 훨씬 더 어렵다.

간단히 말하자면 브로커 패턴은 중개인을 통해 원격 컴포넌트의 위치 투명성을 제공하는 것이다. 컴포넌트는 위치 투명한 서비스 호출을 통해서 다른 컴포넌트가 제공하는 서비스에 억세스 할수있고 런타임에 컴포넌트의 추가나 제거가 가능해야 한다. 그리고 아키텍쳐는 특정 시스템에 국한된 세부사항(system-specific details)과 특정 구현에 국한된 세부사항(implementation-specific details)을 숨겨야 한다. 

브로커패턴의 예에는 CORBA(common object request broker architecture)와 DCOM(OLE 2.x)이 있는데 현재는 두 사양 모두 많이 사용되지는 않고 EJB2도 위 패턴을 도입했지만 느린 속도등의 이유로 역시 활성화 되지는 못하였다. 

개인적인 의견이지만 분산환경에서 위치투명성이란 코드적 관점이지 프로그래머적 관점은 아니라고 생각한다. 무슨 말이냐면 브로커 패턴을 이용한 분산 시스템에서 코드는 위치투명한 컴포넌트의 서비스 코드를 사용하지만 그걸 작성하는 사람은 이게 원격호출인지 아닌지를 알고 있어야 한다는 뜻이다. 그렇지 않으면 최소한 지금의 네트워크 환경에서는 Hello World 정도의 프로그램이 아닌 보통의 실제 어플리케이션의 복잡함을 감당할 실행속도가 나오기가 힘들다고 생각한다. (비록 coarse-grained 패턴을 쓴다고 해도 말이다. ) 이 블로그의 AL Framework는 이런 관점의 브로커 패턴을 사용한 프레임워크이다.


Broker 패턴은 분산 어플리케이션을 개발하면서 어쩔 수 없이 야기되는 복잡성을 줄인다. Broker 패턴을 사용할 경우 개발자는 분산 구조를 투명하게, 다시 말해서 개발을 위해 필요한 부분과 필요하지 않은 부분을 명확하게 구별해 파악함으로서 분산의 전체 구조를 바라볼 수 있다. 확장된 이 분산 어플리케이션은 이기종 머신에서 실행될 수 있고 각기 다른 프로그래밍 언어로 작성될 수 있다.



브로커 패턴은 아래와 같은 장단점이 있다.
장점
1. 위치 투명성이 제공된다.
2. 컴포넌트의 가변성과 확장성이 보장된다.
3. 플랫폼간의 이식 가능성이 제공된다.
4. 서로 다른 Broker 시스템들간의 상호 운용성이 지원된다.
5. 재사용성이 확보된다.

 

단점
1. 효율이 낮다.
2. 장애 허용성이 낮다.
3. 테스트와 디버깅이 한편으론 용이하고 다른 한편으론 복잡하다.

Broker 패턴은 거대 규모의 인프라 패러다임으로 단일 어플리케이션을 구축할 때에는 잘 사용되지 않으며 여러 어플리케이션 전체를 위한 플랫폼 역할을 한다.
 

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

Here is Dragon  (0) 2009.03.12
프로그래밍 패러다임  (0) 2009.03.12
아키텍쳐 패턴 - Pipes and Filter 패턴  (0) 2009.03.11
아키텍쳐 패턴 - Layer 패턴  (0) 2009.03.11
Class Design  (0) 2009.03.07
Posted by bleujin
Framework/스팸 필터링2009. 3. 12. 02:21

잘 알려져 있는 문제인
"n개의 도시가 있는데, 집에서 (1번 노드) 출발하여 모든 도시를 단 한번씩 거치며 집으로 돌아오는 최소 거리 경로를 찾아라." 길찾기 문제의 문제가 주어졌을 경우

아마도 깊이 우선이니, 너비우선이나 혹은 이기적 알고리즘(greedy algorithm) 같은 걸 떠올릴지 모른다. 알고리즘에 좀더 익숙하다면 분할 정복(devide and conquer), 동적 프로그래밍(dynamic programming), 한정 분기(branch and bound), 다익스트라 알고리즘 등을 사용할 수 도 있다. 이런 결정론 알고리즘은 정확한 해(전역 최적해)를 보장하지만 문제크기가 커지면 현실적인 시간 내에 풀이가 불가능 하거나 greedy algorithm처럼 지역 최적해로 수렴하게 된다.

개인적이지만 이 문제에 대해 가장 좋아하는 풀이방법은 메타 휴리스틱한 방법중 하나인 개미 군락 최적화(ant colony optimization)이다. 이 알고리즘은 자연에서 얻은 영감이 바탕이 된 개미의 군락생활에서 먹으를 집으로 이동하는 자업을 효율적으로 수행하기 위해
 - 이동 경로에 페로몬을 뿌림
 - 가한 페로몬의 경로를 선호
 - 시간에 따라 페로몬 증발 이라는 조건을 활용한다.

간단한 알고리즘을 구성하면
페로몬 값을 초기화 한다
repeat {
   해를 생성한다.  // 높은 페로몬 경로가 선택될 확률이 높다.
   페로몬을 갱신한다.
} until(멈춤 조건) ;
그동한 발생한 해중에 가장 좋은 것을 출력한다.

이 알고리즘을 좋아하는 이유는 첫번째는 처음에는 좋은 해를 찾지 못한다고 해도 시간이 지날수록 점점 더 좋은 해를 구할 가능성이 높고 두번째로 환경이 변해도(최적의 길이 변해도) 그에 대해 적응력을 발휘한다는 점이다. 여기서 프로그래밍 십계중 제 4계를 말할 수 있다. 만약 한번에 답을 찾는 것이 어려운 문제라면 연속성을 고려해라. 처음부터 옳은 답을 맞춰야 할 필요는 없다.

만약 소프트웨어가 단지 수학에 불과하다면 정확한 답과 순간적인- 측정 당시의 효율이 우선이 되겠지만 소프트웨어에서 일어나는 문제는 대부분 "잘~"에 관여한 문제이고 스팸판단도 이와 같은 문제라고 생각한다. 스팸판단에 있어 단어 사용등에 있어 수학모델을 사용하는 결정론적인 알고리즘보다 스팸일 가능성이 높은 순으로 정렬할 수 있고 변하는 환경에 대응할 수 있는 알고리즘 시스템이 나아보인다.


두번째로 생각할 것은 "여러개를 결합하면 가장 좋은 단일 알고리즘 보다 좋다"라는 Hybrid한 접근방식이다.

생체 인식에 있어 가장 많이 알려진 방법은 지문 인식이지만 역사가 오래되어서 정교한 만큼이나 그만큼 해킹 기술도 발달하였다. 지문인식이 대단한 정밀 기술로 보일지도 모르지만 아파트 현관문에 사용되는 정도의 지문 기술은 영화에 나오는 최신 기술따위를 사용하지 않아도 지문을 얻을 수 있다면 열쇠보다도 더 쉽게 뚫을 수 있다.(비록 열쇠를 가지고 다닐 필요가 없어서 편리는 하겠지만) 최근에는 지문인식은 -종이나 젤라틴 등에 - 복제된 지문데이타를 방지하기 위해 땀샘 인식을 하는 등의 좀더 복잡해졌지만 복잡해진 만큼이나 비싸지며 동일인임에도 불구하고 거부할 가능성도 높아진다.

생체인식의 분야에는 사실 영화등에서 소개된것만 해도 홍채, 열적외선, 목소리 등이 있고 발걸음이나 손등의 정맥등을 이용한 생체인식 방법도 사용되고 있다. 먼 미래에 언젠가 기술이 충분하게 성숙이 되면 가격도 싸고 인식의 오류 가능성도 극히 낮은 어떤 방법이 나올지도 모르겠지만 현재로서 프로그래밍 관점에서 생각해보면 가장 합리적인 방법은 이 여러가지 방법을 모두 적용한 하이브리드 모델이 나아보인다. 간단한 지문인식 시스템 정도는 뚫을 수 있겠지만 목소리도 비슷하고 홍채도 비슷하고 열적외선의 분포도 비슷하고 손등의 정맥위치도 비슷하고 발걸음도 비슷하게 할 수 있는 확률은 어느 한 분야의 최고의 기슬을 사용한 것보다 낮을 것이다.

단순히 계산을 빨리 해야 하는등의 문제가 아니라 좀더 "잘" 판단해야 하는 문제에 있어서는 베이지언 필터링 같은 하나의 알고리즘에 의존하기 보다는 이처럼 하이브리드 알고리즘이 더 많은 장점을 가질 수 있다.


'Framework > 스팸 필터링' 카테고리의 다른 글

판단에 있어서..  (0) 2009.03.16
구현  (0) 2009.01.12
판단의 기본 아이디어  (0) 2009.01.12
개요  (0) 2009.01.09
Posted by bleujin


앞글의 예외처리의 예외에서 소개한 코드는 아키텍쳐 레벨에서 보면 Pipes and Filter 패턴의 한 예이며 가장 많이 알려진 PAF 패턴의 예는 유닉스의 명령어이다. 유닉스의 모든 명령어는 char*가 입출력의 표준 메시지 포맷이기 때문에 ls -al | grep 'abc' | vi 식의 사용이 가능하다. 그리고 티스토리의 플러그인 방식도 PAF의 일례라고 볼 수 있다. 앞의 두 예 모두 순서에 상관없지만 PAF가 항상 순서에 관계없이 동작하는 것은 아니다.


PAF 패턴은 아래와 같은 특징이 있다.

1. 미래에 시스템의 기능을 향상시키기 위해서는 (심지어 사용자도) 프로세싱 단계를 변경하거나 혹은 재조합할 수 있어야 한다.
2. 작은 프로세싱 단계들은 서로 다른 상황에서 거대한 컴포넌트보다 쉽게 재사용된다.
3. 인접하지 않은 프로세싱 단계들 간에는 정보를 공유하지 않는다.
4. 입력데이타는 각기 다른 소스에서 얻는다.
5. 여러가지 방법으로 결과를 표시하거나 저장 할수 있어야 한다.
6. 단계를 멀티프로세싱 방식으로 처리하는 것도 수용할 수 있다. 예를 들어 병렬 혹은 준 병렬 방식을 체택할 수 있다.


절차는 아래의 단계를 밟는다.
1. 시스템의 테스크를 일련의 프로세싱 단계에 따라 구분한다.
2. 각 파이프 사이로 전달할 수 있도록 데이터 포맷을 정의한다.
3. 각 파이프를 어떻게 연결할지 결정한다. (Push? Pull?)
4. 필터를 설계하고 구현한다.
5. 오류 핸들링을 설계한다.
필요할 경우 단계의 중간에 파일에 추가적인 정보를 남기는 것도 고려할 수 있다.


PAF는 아래와 같은 장단점이 있다.

장점

1. 필터 교환에 유연하다.
2. 재조합에 유연하다.
3. 필터 컴포넌트를 재 사용할 수 있다.
4. 파이프라인의 프로토 타입을 빠르게 만들 수 있다.
5. 병렬 프로세싱의 효율성

단점

1. 상태정보를 공유하게 되면 비용이 많이 들며 유연성을 저하시키는 요인이 된다.
2. 병렬 프로세싱을 사용해 효율성을 얻을 것이라는 기대는 그저 망상일 뿐인 경우가 종종있다.
3. 데이타 변환에 과부하가 발생한다.
4. 오류 핸들링을 구현하기 힘들다.


 

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

프로그래밍 패러다임  (0) 2009.03.12
아키텍쳐 패턴 - Broker 패턴  (0) 2009.03.12
아키텍쳐 패턴 - Layer 패턴  (0) 2009.03.11
Class Design  (0) 2009.03.07
나쁜 디자인의 징후  (0) 2009.02.22
Posted by bleujin


GOF의 객체 패턴을 확대하면 아키텍쳐도 몇가지의 패턴을 가진다는 걸 알수 있다.

가장 대표적으로 Layer 패턴을 들 수 있는데 시스템의 규모가 커서 분해(decomposition) 할 필요가 있고 하위 레벨과 상위 레벨의 이슈가 서로 혼재해 있을때 나온다. 앞글의 실험실과 비실험실은 결국 레이어 패턴과 비슷하지만 굳이 실험실과 비실험실이라고 부른 것은 기계적인 효율성이 필요한 곳과 아닌곳을 구분하려고 실험실이라는 용어를 사용하였다. 레이어 패턴의 대표적인 예는 많이 알려진 OSI 7 Layer가 있고 Java의 VM자체가 하나의 Layer이다.

레이어 패턴은 아래와 같은 특징이 있다.

1. 나중에 소스 코드가 변경하다고 하더라도 그것이 시스템 전체에 파문을 일으켜서는 안된다. 그런 변경이 있더라도 오직 하나의 컴포넌트 내에만 국한되어야 하며 다른 컴포넌트에는 영향을 미쳐선 안된다.

2. 인터페이스는 안정성(stability)을 갖추어야 하며, 표준 구현부에 미리 지정되어 있는 것이 좋다.

3. 시스템의 각 부분들은 교환가능해야 한다. 일반적으로 변경에 대비해 설계하면, 시스템을 발전시키도록 촉진할 수 있다.

4. 현재 설계하고 있는 시스템과 동일한 하위 레별의 이슈 때문에 추후에 다른 시스템을 구축해야 할 필요가 있을지 모른다.

5. 각 컴포넌트는 이슈 하나만을 담당하도록 구현되어야 한다. 만약 한 컴포넌트가 다른 이슈를 구현하면 일관성을 잃게 된다.

6. 컴포넌트 경계를 넘나드는 처리가 발생할수록 성능을 하락시킬 가능성이 커진다.


"전산학의 모든 문제는 또 다른 수준의 간접층으로 해결할 수 있다."
라는 유명한 말을 통해 레이어의 개념을 알린 이는 램슨이라고 알려져 있는데 램슨은 사실 서브루틴의 개념을 만든 휠러에게서 따왔다고 밝힌바 있다. 휠러는 그 말에 이어서
 "그러나 그러면 또 다른 문제가 생기는 것이 일반적이다."
라며 간접과 계층화는 공간과 시간의 부담을 추가하고 코드의 가독성을 해칠수 있는 문제점도 같이 언급하였다.

보통의 경우에는 공간과 공간상의 추가 부담은 그리 크지 않기 때문에 일반적으로 큰 관심사가 되지 못한다. 대부분의 경우 추가적인 포인터 참조나 서브루틴 호출에 의한 시간 지연은 전반적인 구조 개선에 비할때 사소한 수준에 그친다. 사실 요즘의 현대적인 프로그래밍 언어들은 추가적인 유연성을 얻기 위한 목적으로 일부 연산들의 경우 항상 하나의 간접층을 거치도록 하는 경향을 보이고 있다. 예를 들어 java나 C#의 경우 객체에 대한 모든 접근이 하나의 포인터 간접을 거치게 하는데, 이는 쓰레기 수거를 위한 것이다. 또한 Java에서는 인스턴스 메서드에 대한 거의 모든 호출이 하나의 조회 테이블을 통해서 분배되는데, 이는 다른 클래스를 상속하는 클래스들이 실행시점에서 메서드를 재 정의 할 수 있도록 하기 위한 것이다.

모든 객체 접근과 메서드 호출에 부가되는 이러한 추가부담에도 불구하고 두 플랫폼은 시장에 선전을 펼치고 있다. 어떤 경우에서는 개발자가 코드에 집어넣은 간접을 컴파일러가 최적화를 통해서 제거하기도 한다. 대부분의 컴파일러들은 함수를 호출하는 것이 함수의 코드를 호출지점에 직접 삽입하는 것(소위 인라인 함수_보다 비싼 경우 자동적으로 그러한 인라인 처리를 수행한다.

반면 코드의 가독성에 대한 간접의 영향은 아주 중요한 문제이다. 지난 50년간 CPU의 속도는 엄청나게 빨라진 반면 코드를 이해하는 사람의 능력은 별로 발전하지 않았다는 점을 감안한다면 충분히 이해할 수 잇을 것이다. 그래서 애자일 프로세스 옹호자들은 오늘이 구체적인 요구가 아니라 미래에 생길 수도 있는 애매하고 명시되지 않는 요구사항들을 처리하기 위해 계층들을 도입할 때에는 아주 신중해야 한다고 조언한다. 스몰더스는 이에 대해 성능 안티패턴을 논의하면서 계층은 케이크를 위한 것이지 소프트웨어를 위한것이 아니다 라고 비꼰 바 있다.


레이어 패턴은 다은과 같은 단계를 거친다.

# 테스크를 레이러로 묶는 추상기준을 정의해야 한다. 이 추상기준을 플랫폼으로부터 개념적 거리(conceptual distance)로 삼는 경우가 많은데 실제 소프트웨어 개발에서는 하드웨어로부터의 거리와 개념적 복잡성의 추상적 기준을 혼합하여 사용한다.

1. 사용자를 위한 시각적 요소
2. 특수하게 지정된 애플리케이션 모듈
3. 공통 서비스 레벨
4. 운영체제 인터페이스 레별
5. 운영체제
6. 하드웨어

# 추상 기준에 따라 얼마나 많은 추상 레별로 나눌지를 결정하고 레이어마다 역할을 부여하고 테스크를 할당한다. ( 상당한 경험과 통찰력이 필요하다.)

# 서비스를 상세히 정의한다.
  레이어 패턴을 구현할때 가장 중요한 원칙은 레이어들을 서로 엄밀히 구분해야 한다는 점이다.  기반레이어는 가볍게 유지하는 대신 상위 레이어는 광범위한 적용석을 확보하도록 확장하는 것이 좋다. 이런 현상을 재사용의 역 피라미드 구조라고 한다.

# 레이어에 대한 정의를 개선해 간다.

# 각 레이어마다 인터페이스 하나씩을 정의한다.

# 개별 레이어를 구조화한다.

# 인접한 레이어들 간의 통신 방식을 정의한다.

# 인접한 레이어들을 서로 분리시키고 오류 핸들링 전략을 설계한다.

레이어 패턴을 사용하지만 부분적으로 터널을 사용하는 경우가 있는 일종의 변형코드를 사용하는 완화된 레이어 시스템을 사용하기도 하는데 유지보수성을 포기하는 대가로 유연성과 성능을 확보하게 된다. 어플리케이션보다 인프라 시스템에서 종종 이런 편법을 찾아볼수 있다. 이러한 편법이 종종 허용되는 이유는 인프라 시스템이 어플리케이션 시스템보다 변경이 빈번하지 않으며 대체로 성능을 유지보수성보다 중요하게 여기기 때문이다. 그리고 앞서 소개한 DB Framework에도 일종의 터널링이 존재하지만 일종의 예외이기 때문에 주의해서 사용해야 한다.


레이어 패턴은 아래와 같은 장단점이 있다.

장점
1. 레이어를 재사용할 수 있다.
2. 표준을 지원한다.
3. 종속성을 국지적으로 최소화한다.
4. 교환가능성이 확보된다.


단점
1. 동작이 변경될 경우 단계별로 재작업이 필요하다.
2. 효율이 낮다.
3. 불필요한 작업이 수행될 수 있다.
4. 레이어의 적절한 개수나 규모를 결정하는 것이 어렵다.



 

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

아키텍쳐 패턴 - Broker 패턴  (0) 2009.03.12
아키텍쳐 패턴 - Pipes and Filter 패턴  (0) 2009.03.11
Class Design  (0) 2009.03.07
나쁜 디자인의 징후  (0) 2009.02.22
Design Principle - SRP  (0) 2009.02.22
Posted by bleujin
Framework/예외처리2009. 3. 10. 02:25


예외 처리에 관해 알아야 할 중요한 사실 중 하나는 예외 처리는 비즈니스 로직이라는 것이다.
if (출금액() > 임금액) throw new 충분한돈이없음예외 와 같은 태생이 비즈니스 로직뿐 아니라 일반적으로 많이 나오는 IOException, SQLEXception등의 java Exception도 어떻게 예외 처리를 하는가는 프로그램마다 다르고 비지니스 로직마다 다르다.

그 전에 프로그래밍에서 예외란 에러와 달리 Alternative Path의 한 종류이고 이도 정상적인 하나의 프로그래밍 로직으로 인정해야 한다.

try {
    ....
} catch(IOException ex){
   log.warn(ex) ;
}

위 구문에서 예외 처리는 중복일까 아닐까? 프레임워크의 시야로 본다면 위 구문이 여러번 나온다면 그건 중복이다. 앞서 다른 글에서 밝힌바와 같이 두 줄이상의 코드가 동일할때만 중복이라고 하는게 아니다. 위와 같은 코드는 구조적인 중복의 2번째 케이스이다. 그런데 단 한줄인데 더 이상 어떻게 하라는 말인가? 한줄을 줄여봤자 한줄인데 이게 왜 중복이고 더 이상 어떻게 줄일 수 있다는 건지 의문을 가질 수 있다.

아마도 객체지향 책을 몇권 읽었다면 구체적인 것에 의지하지 말아라 라는 말을 듯었을 것이다. 구체적인 것에 의존하지 않기 위해 우리는 컨크리트 클래스보다는 추상클래스를 추상클래스보다는 인터페이스를 써야 한다는 것을 이미 알고 있다. 그리고 여기서의 log은 아마도 Apache의 common log같은걸 사용했다면 인터페이스 일테니 충분하지 않은가? 그리고 물론 충분하지 않다. 객체지향에서 구체적인 것이란 단순히 컨크리트 클래스만을 의미하는 것이 아니다. 처음에 언급한대로 예외처리는 비지니스 로직이다라는걸 인정한다면 log를 남긴다는 것 자체가 충분히 구체적이다.

보통의 경우 비지니스 로직과 일반 로직은 수명과 변화의 속도가 다르기 때문에 가능하면 분리해서 구성해야 한다. 잘 알다시피 프로그래머는 CPU가 아니기 때문에 멀티 테스킹의 CPU처럼 유연한 상태전환이 쉽지않고 인터럽트에 취약하기 때문에 로직 중간 catch 코드에 log를 쓸지 warn을 쓸지 info를 쓸지 LogLevel은 몇레벨을 쓸것인지 따위에 고민하게 만들어서는 안된다.

function xxx() throws IOException {
     ,,,,,,,
}

대부분의 코드에서 catch문을 작성하지 않는다면 어느순간 예외들을 던지는 구문이 메소드 내용보다 길어질 것이라고 걱정할지 모르지만 앞서의 조언들을 잘 따라서 인프라를 담당하고 있는 시스템 프레임워크들이 runtime exception을 던지게 하거나 걱정할 정도로 많은 예외를 던져야 한다면 코드를 다시한번 변화의 속도를 기준으로 나눠서 한번정도 Wrapping하는것도 고려한다면 그다지 큰 문제는 아니다.

그마저도 싫다면 혹은 어렵다면
try {
    ....
} catch(IOException ex){
   exManager.resolve(ex) ;
}
와 같이 처리 하는게 낫다 한다. resolve이란 메소드는 log 메소드 보다 더 추상적이고 확장과 구성의 포인트가 많다.

계획대로 모든 예외처리를 미루었다면 FrontController에서
,,,,,
catch (Throwable e) {
     Throwable ex = e ;
     if (e instanceof ServletException && ((ServletException) e).getRootCause() != null) {
        ex = ((ServletException)e).getRootCause() ;
     }
     getExceptionHandler().resolve(new HttpExceptionInfo(request, form, ex));
}
와 같이 한번에 처리해 버릴수 있다.

실제로 예외 처리의 예을 다이어그램으로 그려본다면



와 같이 Chain Of Responsibility 패턴 정도가 나올것이다. Chain Of Responsibility는 어떤 사람에게 요구가 들어왔을때 그 사람이 그것을 처리할 수 있으면 처리하고, 처리할 수 없으면 다음사람에게 넘긴다. 다음 사람이 그것을 처리할 수 있으면 처리하고, 처리할 수 없으면 또 다음 사람에게 넘긴다.

이 패턴은 요구를 하는 사람과 요구를 처리하는 사람을 느슨하게 연결한다. 만약 이 패턴을 사용하지 않으면 이 요구는 이 사람이 처리해야 한다. 라는 지식을 누군가 중앙 집중적으로 가지고 있거나 개별적으로 각각 처리해야한다. 이 지식을 요구를 하는 사람(Client)에게 맡기는 것은 현명하지 않다. 요구를 하는 사람이 처리자들의 역할 분담까지 상세하게 알아야 한다면 부품으로서의 독립성이 손상되고 자신의 일에 집중할 수 없게 되기 때문이다.

Client가 예외를 던지면 예외는 자신의 어떤 종류의 예외인지 예외 코드는 무엇인지 그리고 예외가 발생한 클래스와 메소드의 위치 등 예외와 관련한 모든 정보를 자체적으로 담고 있기 때문에 Handler들은 내가 담당할 예외인지를 판단해서 맞다면 자신이 처리하고 그렇지 않다면 다음 핸들러에 넘겨주는 방식을 취한다. 실제로 변할 가능성이 높은 - 혹은 변화의 속도가 다른 -  구체적으로 파일에 로그를 남길것인지 따라 중복된 예외들을 제외하고 에러데이타베이스를 통해 관리할 것인지 혹은 자고 있을 가엾은 개발자에게 문자를 날릴것인지는 각각의 Handler Class 들이 결정하게 된다. 그리고 컨피그 파일 등에서 아래와 같이 구성파일에서 핸들러의 종류와 순서를 지정할 수 있게 한다면 예외 처리의 변동에 유연하게 대처할 수 있다.

 <!--
  Exception Handler
 -->
 <exception-handler>
        <configured-object>
            <class-name>com.bleujin.webapp.exception.DupValOnIndexExceptionHandler</class-name>
            <constructor>
                <constructor-param>
                    <description>contact Message</description>
                    <type>java.lang.String</type>
                    <value> Please, check ID value</value>
                </constructor-param>
            </constructor>
        </configured-object>
        <configured-object>
            <class-name>com.bleujin.webapp.exception.InterMediaExceptionHandler</class-name>
        </configured-object>
        <configured-object>
            <class-name>com.bleujin.webapp.exception.ReferenceExceptionHandler</class-name>
        </configured-object>
        <configured-object>
            <class-name>com.bleujin.webapp.exception.LockExceptionHandler</class-name>
            <constructor>
                <constructor-param>
                    <description>contact Message</description>
                    <type>java.lang.String</type>
                    <value> Please, Confirm Lock Tab Of Admin's Menu</value>
                </constructor-param>
            </constructor>
        </configured-object>
        <configured-object>
            <class-name>com.bleujin.webapp.exception.LastOutExceptionHandler</class-name>
        </configured-object>
 </exception-handler>

'Framework > 예외처리' 카테고리의 다른 글

GUI TEST  (0) 2009.06.08
예외 수집기  (0) 2009.03.26
checked vs runtime  (0) 2009.03.07
checked exception의 문제  (0) 2009.02.09
예외 처리 격언  (2) 2009.02.09
Posted by bleujin
Framework/Database2009. 3. 8. 20:15

MDL을 구문을 실행하는 execUpdate와 Select를 실행하는 execQuery에 추가로 Select지만 Java의 일반 객체로 바꿔주는 execHandlerQuery가 있다.

앞서 서비스와 서비스의 교환은 중립적인 메시지 교환이라고 했는데 Rows 객체는 실질적으로 중립적인 객체라고 하기 어렵다. JIT 연결과 SQL Exception을 없에는 등 몇가지 점을 수정하긴 했지만 기본적으로 java.sql.ResultSet을 상속받았기 때문이다.

그래서 잠시 딴 생각을 해보자. ResultSet이 왜 필요한가? 클라이언트 서비스에서 DB서비스에 접근할때 response 될때 정말 필요한것은 Value 그 자체이지 특별히 ResultSet 이라는 인터페이스가 필요하지는 않다. 그리고 이러한 중립적인 Value 객체는 java.util에 많이 있다. 또는 EJB 처럼 POJO를 사용할 수도 있다.

그렇다고
bean.setName(rows.getString("name") ;
bean.setEmpNo(rows.getString("empNo") ;
bean.setSal(rows.getString("sal") ;
와 같이 반복적으로 직접 값을 셋팅하는 것은 불편하다.

개인적으로 어려운 걸 쉽게 만들기는 어렵다고 생각한다. 효율적인 SQL을 만드는 것은 어렵다. 이를 단순히 하이버네트식의 객체 인터페이스로 바꾼다고 해서 어려운 SQL을 쉽게 만들기는 아주아주 어렵다고 생각한다. 다만 복잡한걸 간결하게 만들기는 그렇게까지 어렵지 않다. 복잡하다는 것은 과정이 길다는 것을 말하고 이전의 여러 단계로 거쳐야 하는 과정을 몇단계를 줄여줄 수만 있으면 된다. 아직 이에 대한 연구가 행해진 걸 본적은 없지만 대부분의 버그는 어려운 로직보다는 반복적인 로직에서 좀더 많은 버그가 발생하지 않을까 생각한다. 인간은 기본적으로 반복적인 작업에 최적화 되어 있지 못하다. 그런 것은 컴퓨터에 맡기는 게 좀더 좋다.

ResultSet을 특정 Bean의 List로 바꾸는 것은 리플렉션을 이용하면 간단하다. 그리고 운이 좋게도 Apache Open source인 dbutils에 이미 대부분 구현되어 있어서 베껴왔다. 어느 것이나 마찬가지라고 생각하지만 기존의 사례를 살펴보는 것은 항상 도움되는 일이다.

예컨대 이런식으로 사용한다.
 public void testHandler(){
  IQueryable query = dc.createUserProcedure("employee@listBy(?)").addParam(10);
  ObjectWrapper wrapper = dc.getObjectWrapper(query, new BeanListHandler(Employee.class));
  List<Employee> list = (List<Employee>)wrapper.getObject() ;
  for(Employee e : list){
   System.out.println(e);
  }
 }



ResultSetHandle 인터페이스의 handle(ResultSet rs) 메소드 하나만 구현하면 되기 때문에 구조는 매우 간단하다. 앞에서 구조적 중복을 없에주는게 프레임워크라고했는데 여기서의 중복은 소스 증복에 가깝기 때문에 알아채는 것은 쉬운일이다.


ArrayHandler :
ResultSet의 첫번째 Row를 Object[]로 바꿔준다.

ArrayListHandler :
ResultSet의 모든 Row를 Object[]의 List로 바꿔준다.

BeanHandler :
첫번째 Row를 POJO Bean으로 바꿔준다.

BeanListHandler :
모든 Row를 POJO Bean의 List로 바꿔준다.

MapHandler :
첫번째 Row를 key(columnName), value의 Map으로 바꿔준다.

MapListHandler : 
모든 Row를 Map의 List로 바꿔준다.

LimitMapListHandler :
일부 범위의 Row만 Map의 List로 바꿔준다.

ScalarHandler :
첫번째 Row의 특정 column을 값으로 바꿔준다.

SimpleXMLStringBufferHandler :
모든 Row를 XML 형태의 StringBuffer로 바꿔준다.

CSVStoreHandler :
모든 Row를 CSV 파일 형태로 저장한다.


다시 말하자면 프레임워크는 어렵운걸 쉽게 만들어 주기 보단 복잡한걸 간결하게 만들어주는게 우선이다. 어려운걸 쉽게 만들기가 이론적으로 불가능은 아니지만 무척 어렵기 때문에 - 아이러니 하지만 쉽게 만들 수 있다는건 어려운게 아니다. 물론 혁신의 가능성을 부정하는 것은 아니지만.. - 그런것은 쉽게 도전할 성질의 것이 아니다. 복잡한걸 간결하기 만들기 위해서는 단계를 줄이고 순서를 없애야 한다.

'Framework > Database' 카테고리의 다른 글

Framework (DBController)  (0) 2009.03.12
Framework -커서(ForwardOnly, Sensitive(=Static))  (0) 2009.03.12
Framework (Rows)  (0) 2009.03.07
Framework (IQueryable)  (0) 2009.03.06
Framework (DBManager)  (0) 2009.03.04
Posted by bleujin