'Framework'에 해당되는 글 84건

  1. 2012.06.19 작업중인 Project
  2. 2010.04.04 DBFSlayers 1
  3. 2010.03.09 DB Framework 2.0 Short Explain 3
  4. 2010.02.24 Framework Cursor (cursor in MySql) 1
  5. 2009.08.14 언젠가...
  6. 2009.07.24 일곱번째 계 - 호출의 빈도(1)
  7. 2009.07.17 Second Program Effect
  8. 2009.07.15 XUL 단상
  9. 2009.07.14 여섯번째 계
  10. 2009.07.08 호출의 빈도
Framework2012. 6. 19. 22:18



ionFramework

Home : https://github.com/bleujin/ionframework

gitURL : https://github.com/bleujin/ionframework.git

doc : http://bleujin.springnote.com/pages/11948044


mongoNode

Home : https://github.com/bleujin/mongoNode

mongoSearchHome : https://github.com/bleujin/mongoSearch


gitURL : https://github.com/bleujin/mongoNode.git

mongoGitURL : https://github.com/bleujin/mongoSearch.git


doc : http://bleujin.springnote.com/pages/8134200



ISearcher
Home : https://github.com/bleujin/ISearcher
gitURL : https://github.com/bleujin/ISearcher.git
doc : http://bleujin.springnote.com/pages/11944056

 


Aradon

ServerHome : https://github.com/bleujin/aradon 

ClientHome : https://github.com/bleujin/aradonClient

ExtendHome : https://github.com/bleujin/aradonExtend

AgentHome : ...


ServerGitURL : https://github.com/bleujin/aradon.git.git

ClientGitURL : https://github.com/bleujin/aradonClient.git

ExtendGitURL : https://github.com/bleujin/aradonExtend.git


doc : http://bleujin.springnote.com/pages/8011148





Posted by bleujin
Framework/Database2010. 4. 4. 01:01
Database Framework Scripting Layers is

a lightweight database abstraction layer suitable for high-load websites where you need the scalable advantages of connection pooling. DBFSlayer talks to clients via JSON over HTTP, meaning it's simple to monitor and can swiftly interoperate with any web framework you choose.

Features At A Glance
  • Simple HTTP interface
  • JSON-format messages
  • Multiple DB adapter (currently tested oracle 9 higher, mssql 2000 higher, mysql 5 higher)
  • Connection pooling
  • Multithreaded
  • Straight-forward configuration
  • Simple yet powerful access

Download
https://sourceforge.net/projects/dbfslayers/files/
dbfs_layers_fat.jar include following lib
     - jetty 6.1
     - servlet 2.5, jsp 2.5
     - struts 1.2.9
     - json-lib 2.3
     - bleujin framework_core_fat.jar(http://sourceforge.net/projects/clientcursordbf/)
     - Database JDBC client libraries(offical lib of oracle 9i ,mssql 2000 , mysql 5)


Install
   1. download dbfs_layer_web.zip(https://sourceforge.net/projects/dbfslayers/files/)
   2. extract dbfs_layer_web.zip
   5. download dbfs_layers_fat.jar at 2.dir (https://sourceforge.net/projects/dbfslayers/files/)
   6. configure webapps/simple/WEB-INF/default-config.xml
   6. execute java -jar dbfs_layer_fat.jar
   7. connect http://localhost:8080/simple/index.htm


Configuring Your Database
view webapps/simple/WEB-INF/default-config.xml


Example Usage

run : java -jar dbfs_layers_fat.jar
view : http://localhost:8080/simple/index.htm

<script  language="JavaScript">
    var dc = new Database(new Session('http://localhost:8080/simple/db.do', 'john'));

    var ins = dc.createUserCommand("insert into update_sample values(2, '222')") ;
    $('result').innerHTML = ins.execUpdate() + '<br/>\n' ;

    var cmd = dc.createUserCommand('select * from update_sample where a < :a') ;
    cmd.setPage(3, 1) ;   // 1 page per 3 unit
    cmd.setParam('a', '3') ;

    // var result = cmd.execQuery() ;   // excute. return JSON format
   
    $('result').innerHTML += cmd.execQuery()  + '<br/>\n';

    /*  execute procedure example
    var upt = dc.createUserProcedure('sample@selectEmpBy()') ;
    upt.setPage(3, 1) ;
    $('result').innerHTML = upt.execQuery() ;
   
    */
   
    /*  multiple query(same transaction) example
    var upt1 = dc.createUserCommand('select * from copy_tblc where no1 < :no1') ;
    upt1.setPage(3, 1) ;
    upt1.setParam('no1', '05') ;
   
    var upt2 = dc.createUserProcedure('sample@selectEmpBy()') ;
    upt2.setPage(3, 1) ;
   
    var upts = dc.createUserProcedures('multi query') ;
    upts.add(upt1).add(upt2) ;
   
    $('result').innerHTML = upts.execQuery() ;
    */
</script>



UTF8 Issues

The DBFSlayers JSON requires your data to be in UTF-8 format. This means your database should be in UTF-8 and it should return UTF-8 for queries.



Future Work
  • Batch MDL - batch insert, update, delete
  • Composite Query - select + mdl execute at same transaction
  • LOB Datatype support
  • JTA support at multiple DB
  • Security - add support for HTTPS / HTTP Auth as a basic access control mechanism (security is still primarily handled in the database).
  • Round-Robin Dispatching
  • Automatic Failover

  • Documentation - improvements to the documentation as suggested by the community.
  • Testing - better unit tests are in the works
  • Feeds/Get-if-modified-since Support - some cumulative stats mechanisms could support mechanisms to only download new log messages or if there is new log messages.
  • Language Bindings - the DBSlayer just speaks JSON + HTTP, so many languages should be able to talk to it. We welcome well-written client libraries for any language if you want to share yours.







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

DB Framework 2.0 Short Explain  (3) 2010.03.09
Framework Cursor (cursor in MySql)  (1) 2010.02.24
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Database2010. 3. 9. 12:46

framework_src.zip의 com.bleujin.framework.db.sample에 기본 적인 예제가 있으며 특히 com.bleujin.framework.db.sample에 가장 기초적인 예제가 있다.

해당 예제들을 실행하기 위해서는 sample_mysql.sql(mssql.sql, oracle.sql 역시 모두 같은 역할을 한다.) 의 예제 테이블 등의 내용을 실행시킨다 . 모든 테스트를 실행하기 위해서는 com.bleujin.framework.db.sample.SampleAllTest.java 를 실행한다.


주요 클래스

DBManager

     - 가장 먼저 생성해야 할 클래스로 대부분 JDBC URL과 userID, password를 생성자로 받는다.
        DB별로 혹은
            MySQLDBManger, MySQLPoolDBManager
            MSSQLDBManger, MSSQLPoolDBManager
            OracleDBManger, OracleDBManager, OracleCacheDBManger, Oracle9iCacheDBManger
            H2EmbedDBMangaer, HSQLDBManager 등등

DB별, 접속형태별(Pool여부, Cache여부)로 여러개의 구현체를 가지고 있다.

ex)
DBManager dbm = new MySQLPoolDBManager("jdbc:mysql://novision/test", "bleu", "redf") ;



IDBController


      - DBManger를 생성자로 받는 IQueryable Impl의 Factory 역할을 맡는다.
      - 현재는 IDBController의 구현체로 DBController 한개만 있다.
      - initSelf() 메소드로 dbm의 Pool 초기화 등의 작업을 실행하고 destroySelf() 메소드로 DBManager Pool을 해제시킨다. initSelf()는 생성후 꼭 실행시켜 주어야 한다. 보통의 경우 initSelf()는 프로그램 시작시에 한번, destorySelf()는 프로그램 종료시에 한번 실행한다. Static 변수로 참조하여 사용하는 경우가 일반적이지만 꼭 그럴필요는 없다.

ex)
DBController dc = new DBController(dbm) ;
dc.initSelf() ;
....
dc.destroySelf() ;

     - DBManger와 IDBController를 생성시키고 initSelf()를 실행했다면 이제 쿼리를 실행시킬 준비는 모두 완료되었다.
        가장 간단한 쿼리 실행은
        Rows rows = dc.execQuery(String select) ;
        int result = dc.execUpdate(String mdl) ; 이다.
       



IQueryable
    - 인터페이스이고 execQuery()와 execUpdate()의 주 메소드 2개를 가지고 있다. 모든 실행할수 있는 쿼리는 IQueryable를 상속받는다. 가장 많이 알려진 구현체로 UserCommand, UserCommandBatch, UserProcedure, UserProcedureBatch, UserPorcedures 등이 있다.




IUserCommand

     - 굳이 비슷한 걸 찾자면 JDBC의 PreparedStatement의 역할과 비슷하며 사용방법도 비슷하다.

ex)
IUserCommand cmd = dc.createUserCommand("select * ... where a = ? and b = ?") ;
cmd.addParam(1).addParam("abc") ;
// cmd.addParam(0, 1).addParam(1, "abc") ; // 위구문과 의미가 같다. param index는 0부터 시작한다.

// 혹은 아래와 같이 같은 변수를 여러개 사용하거나 편이성을 위해 named parameter를 사용할수 있다.
IUserCommand cmd = dc.createUserCommand("select * ... where a = :a and b = :b and c >= :a") ;
cmd.addParam("a", 1).addParam(b, "abc") ;

// 만약 특별한 형을 지정하고 싶다면. cmd.addParam("a", 1, Types.LONG) ; 와 같이 명시적으로 지정한다.


      - IUserCommand 실행
Rows rows = cmd.execQuery() ;  // select
int result = cmd.execUpdate() ;     // mdl

       - 실행계획 보기
//실행계획의 해당 DB의 기능을 이용하기 때문에 Format은 DB마다 다른데, MySQL은 표 형태로, MSSQL은 XML Graph형태로, Oracle은 문자열로 보여준다.
cmd.viewPlan(OutputSteam output) ;


       - Page 설정
// 쿼리를 실행하기 전에 Page를 설정하여 해당 Page의 결과값만을 가져올수 있다.
cmd.setPage(Page.create(10, 2)) ;     // 10개씩 했을때 2페이지 즉 11-20번째 row를 가져온다.
cmd.execQuery() ;

이전의 cursor 관련글에서도 확인하였듯이 가장 좋은 방법은 원하는 집함만을 억세스 해서 원하는 집합형태로 가져오는 쿼리를 사용하는 것이 Page 설정에 있어서의 가장 좋은 답이다. 그렇게 생각했기 때문에 예전의 버전에는 Page 관련 기능을 넣지 않았다. 다만 그것은 MySQL의 limit나 오라클의 rownum의 단순한 사용방법보다는 훨씬 더 고난이도이기 때문에 DB별로 자유자재로 할 수 있는 사람이 극히 드물고 또한 매우 번거롭다. 또한 그 효과를 볼수 있는 곳이 제한적이기 때문에 번거로움을 무시하고 모든 곳에 그와 같은 쿼리 방식을 사용하는것은 불필요하다는 아니지만 비효율적이다라는 생각이 들었다.


Rows


   - 쿼리를 실행할수 있는 Interface인 IQueryable의 하위 구현체는 IUserCommand, IUserProcedure, IUserCommmandBatch, IUserProcedureBatch, UserProceudres, TimeoutQuery ... 등등 아주 많은데 그중의 모든 Select 문의 결과값으로 ResultSet의 인터페이스를 구현한 Rows를 반환한다. ResultSet Interface를 구현하였기 때문에 기존의 메소드를 거의 모두 지원하며 몇가지 추가 유틸리티 메소드를 제공한다.

  - 기존의 일반 JDBC에서 반환하는 ResultSet의 구현체와는 다르게 Rows는 Value Object 기반의 클라이언트 커서를 사용한다. 따라서 쿼리의 실행후 pstmt.close(), rs.close(), freeConnection()의 과정은 전부 내부에서 이루어지고 호출자에게 노출되지 않는다. 일단 IDBControler가 초기화된후 dc.getRows() 혹은 cmd.execQuery() 실행후 사용자는 Resource의 반환등에 전혀 신경쓸 필요 없으며 내부적으로 상황에 맞는 결과 캐쉬를 통해 실행속도를 보장한다. rows는 인터페이스 규약상 close()가 있지만 close()를 하지않아도 단순 ValueObject이므로 가비지 컬렉터에 의해 자동으로 정리가 된다.

   - Rows는 거의 순수하게 Value 객체이기 때문에 Framework에서 자동으로 반환한 Resource와 상관없이 Clob 억세스와 쿼리 재실행 등이 가능하며 Serialized XML 형태로 언제든 변환이 가능하다.


rows.getNextPage() ; // 다음 페이지의 rows를 가져온다.
rows.getPrePage() ;; // 이전 페이지의 rows를 가져온다.

   - Clob Access
     JDBC에는 Clob과 Blob의 표준 datetype이 있지만 DB마다 지원여부와 사용방법이 조금씩 다르다. 또한 그 사용방법이 대부분 번거롭게 때문에 varchar의 글자수 제한으로 어쩔수 없이 Clob을 사용해야 하는 경우 매우 불편하다. 그래서 Datatype이 Clob이더라도 DB에 따라 자동 형변환을 통해 rows.getString()을 통해 Access한다. (Blob은 getBinaryStream()) 만약에 Clob에 들어가는 데이타가 100M가 넘는다면 이 방법을 사용하는 것을 신중히 생각해야 할지도 모른다. 그러나 텍스트 1M를 넣는 경우도 매우 흔치 않을뿐더러 그경우에도 Blob으로 다루는 방법을 선택하는게 좋다.

    # insert 혹은 update시 parameter는 DB 벤더에 상관없이 setClob(String str), setBlob(InputStream input) 메소드를 사용하면 된다.




IUserCommandBatch

   - 특정 테이블에 10건의 insert를 해야 한다면 10개의 UserCommand를 실행시키는 것보다 10개의 Arrary를 parameter에 set한후 실행시키는 Batch를 사용하는게 좋다. 평균적으로 최소한 Batch는 건당 insert 속도가 최소 1ms 이하를 보장하며 이는 UserCommand의 기준 속도인 50ms보다 약 50배가 더 빠르다.

ex)
IUserCommandBatch cmd = dc.createUserCommandBatch("insert into update_sample values(?, ?)") ;
int max = 1000 ;
for (int i = 0; i < max; i++) {
        cmd.addBatchParam(0, i) ;
        cmd.addBatchParam(1, i + "th ..") ;
 }
int count = cmd.execUpdate() ;
와 같이 설정하거나..

int[] a = new int[max];
String[] b = new String[max] ;
for (int i = 0; i < max; i++) {
    a[i] = i ;      b[i] = i + "th .." ;
}
cmd.addParam(a).addParam(b) ;
int count = cmd.execUpdate() ;
등과 같이 직접 Array를 인자로 설정한다.


    - 하나의 Batch문에 에 가장 효율적인 건수는 JVM에서 관리되는 Array가 차지하는 메모리에도 밀접한 관련이 있긴 하지만 대략 10,000-100,000 사이가 가장 좋다. row의 평균사이즈를 150-250 byte로 했을때 대부분은 만건당 최대 10초이내의 효과를 보인다.




IUserProcedure

   - 가장 많은 오해를 받지만 가장 효율적이며 많이 사용되는 객체이다. IUserProcedure는 오라클이나 MSSQL의 Procedure와는 직접적인 관계는 없다. 프로그램에 직접 ANSI SQL를 사용했을 경우 이후 DB에 변동이 있다면 재앙수준의 변경을 요구한다. 특히 SQL이 String 변수로 관리되기 때문에 직접 모든 프로그램을 열어서 수정해야 한다. 컴파일 오류가 아닌 런타임 오류가 나기 때문에 수정에 대한 확신도 매우 어렵다.

    이러한 문제로 몇년전부터는 IBatis등에서 XML 형태로 Key Value형태로 관리한후 Key 형태로 Access 하는 방식이 좀더 많이 퍼지게 되었다. 비슷하게 UserProcedure는 앞에서 말한 DB 벤더에 디펜던트한 Procedure와는 직접적 상관없이 그냥 IDName을 가진 Message 객체에 불과하다. 그 Message를 어떻게 해석할 것인가는 DB Manager에 달려 있으며

현재 Framework에 포함되어 있는 OracleDBManger는 패키지와 프로시저, MSSQL은 Procededure로, MySQL은 Procedure 혹은 Function으로 H2DBManager는 특정 XML 파일의 Key-Value 형식의 SQL name으로 해석하고 있다. 즉 UserProcedure는 DB Maanger를 어떻게 구현하는가에 따라 해석방법이 정해지며 일단 같은 name을 가진다면 DB 벤더의 프로시저 혹은 패키지 지원여부와 상관없이 동일한 결과값을 보장해야 한다.


ex)
IUserProcedure upt = dc.createUserProcedure("Sample@selectBy(:a)") ;
upt.addParam("a", 1); 
Rows rows = upt.execQuery() ;

만약 DBManager가 MySQLPoolDBManager라면 위 구문을 Sample_selectBy라는 procedure라고 해석하며
해당 MySQL에는 아래와 같은 Procedure가 있다면 해당 프로시저를 실행한후 그 결과 Rows를 반환한다.

CREATE PROCEDURE sample_selectBy(v_a int)
BEGIN
    SELECT * FROM update_sample WHERE a > v_a ;
END//



만약 DBManger가 오라클이라면 아래와 같이 Sample Package의 selectBy function을 실행시킨후 결과셋을 반환한다.

CREATE OR REPLACE PACKAGE BODY Sample
    is
        function selectBy (v_a number) return Types.cursorType
        is
            rtn_cursor Types.cursorType ;
        begin
            open rtn_cursor For
            select * from update_sample where a > v_a ;
           
            return rtn_cursor ;
        end ;

         ................
       
End Sample ;


     - 이와 같이 DB가 Procedure를 지원하는가 혹은 지원하지 않는가와 상관없이 IUserProcedure는 쓰이며 그 해석방법은 DBManager에게 달려있다. IUserCommand보다 IUserProcedure의 사용을 더 권장하는 이유는 DB 벤더의 장점을 최대한 활용할 수 있고 개발과 유지보수에 있어서의 장점때문이다. 물론 기타 성능이나 보안등의 자잘한 이유는 제껴두고서라도 말이다.


IUserProcedureBatch

    - IUserCommandBatch 처럼 UserProcedure의 Batch 버전이다. 다만 Proceudre 구조상 Batch라서 해서 UserProcedure와 비교해 커다란 성능차이는 없다. 다만 하나의 Procedure에 여러개의 SQL문을 담을 수 있다는 걸 생각하면 복잡한 배치처리 작업에나 쓸만하다.



UserProcedures
    - s가 뒤에 하나 붙어 있다. UserProcedure는 자신이 IQueryable의 구현체이면서 IQueryable의 구현체를 담을수 있는 composite 형태로 되어 있다. UserProcedures에 담긴 모든 MDL문은 자동으로 하나의 Transaction으로 처리가 된다. 그리고 물론 각각의 개별적인 실행보다는 조금 더 빠르다.

ex)
 IUserCommand cmd1 = dc.createUserCommand("insert into update_sample values(?, ?)") ;
 cmd1.addParam(1).addParam("abc") ;
 IUserCommand cmd2 = dc.createUserCommand("delete from update_sample where a = ?") ;
 cmd2.addParam(1);
       
 UserProcedures upts = dc.createUserProcedures("Multi MDL") ;
 upts.add(cmd1).add(cmd2) ;

 int result = upts.execUpdate() ;
 assertEquals(2, result) ;
    
    - 만약 UserProcedures에 여러개의 Select Query를 집어넣고 execQuery()를 실행하면 어떻게 될까? 이 경우에 여러개의 결과값을 반환하게 되는데 Rows.getNextRows()를 통해 다음 결과셋을 얻을수 있다. UserProcedures에 담긴 IQueryable 객체는 add된 순서대로 실행된다.

ex)        
  IUserCommand cmd1 = dc.createUserCommand("select * from copy_sample") ;
  cmd1.setPage(Page.create(10, 1)) ;
  IUserCommand cmd2 = dc.createUserCommand("select 3 from dept_sample") ;
       
  UserProcedures upts = dc.createUserProcedures("Multi Query") ;
  upts.add(cmd1).add(cmd2) ;
       
  Rows first = upts.execQuery() ;  // first query result ;
  assertEquals(true, first.getRowCount() == 10) ;
       
  Rows second = first.getNextRows() ; // second query result ;
  assertEquals(3, second.firstRow().getInt(1)) ;




XAUserProcedure

    - 앞의 UserProcedures는 하나의 DB에 대한 Transaction 보장이며 이기종 DB 즉 멀티 DB에 대한 멀티 MDL문의 분산 트랜잭션은 XAUserProcedure를 통해 할수 있다.


    MSSQL의 접속관리자인 mdc와 오라클의 접속관리자인 odc가 아래와 같이 이미 생성되어 있다고 하자.
// prepare
MSSQLDBManager mManager = new MSSQLDBManager("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=test", "bleu", "redf");
DBController mdc = new DBController(mManager) ;
mdc.initSelf() ;

OracleDBManager oManager = new OracleDBManager("jdbc:oracle:thin:@novision:1521:al", "al", "redf") ;
DBController odc = new DBController(oManager) ;
odc.initSelf() ;
       

// tx1 : MSSQL용 쿼리 집합을 만든다.        
TxTransaction tx1 = new MSSQL2000TxTransaction(mManager, "mquery") ;
tx1.add((Queryable)mdc.createUserCommand("insert into copy_tblc values('111', 111)")) ;
       
// tx2 : oracle용 쿼리 집합을 만든다.
TxTransaction tx2 = new Oracle9iTxTransaction(oManager, "oquery") ;
tx2.add((Queryable)odc.createUserCommand("insert into copy_tblc values('111', 111)")) ;
       

// add tx : 두개의 쿼리 집합을 XAUserProcedure에 넣는다.
TxTransaction[] txs = new TxTransaction[]{tx1, tx2} ;
XAUserProcedure upts = new XAUserProcedure(txs) ;

// 실행한다.
upts.execUpdate() ;


// clear : 더이상 사용되지 않을 DB 접속정보라면 정리한다.
mdc.destroySelf() ;
odc.destroySelf() ;


      - 보통의 XaTransaction 보다 훨씬 더 간편하게 실행할 수 있다. 결국 인터페이스의 간결화로 호출자는 자세한 XaTransaction 과정에 대해 알 필요가 없다.

MSSQL에서 XaTransaction을 사용하기 위해서는
     * Be sure that you have copied your sqljdbc.dll file from C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\SQLServer JTA\ to your SQL Server's "binn" directory (most likely C:\Program Files\Microsoft SQL Server\MSSQL\Binn).
     * Then open your instjdbc.sql script from C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\SQLServerJTA\ within Query Analyzer and run the script. This will install the extended stored procedures into SQL Server so that it can reference the sqljdbc.dll file.

     의 과정을 실행해야 하며 MS DTC가 기동중이어야 한다. MySQL은 얼마전까지 XaTransaction을 지원하지 않았고 현재도 InnoDB storage engine에서만 지원하는 걸로 알고 있다.



CombinedQuery

     - CombinedQuery는 UserProcedure와 조금 비슷한데 단지 UserProcedures와는 달리 MDL문과 Query문을 동시에 실행시킬수 있다는 점이 다르다.


ex)
IUserCommand ins = dc.createUserCommand("insert into update_sample values(?,?)") ;
ins.addParam(1).addParam("abc") ;
       
IUserCommand sel = dc.createUserCommand("select * from update_sample") ;
       
CombinedUserProcedures upts = dc.createCombinedUserProcedures("combined") ;
upts.add(ins, "ins", IQueryable.UPDATE_COMMAND).add(sel, "sel", IQueryable.QUERY_COMMAND) ;

// 타입이 다른 ins문과 sel문을 동시에 실행시킨다.       
upts.execUpdate() ;
       
Map result = upts.getResultMap() ; // for access sel'result
       
int rowcount = (Integer)result.get("ins") ;
Rows rows = (Rows)result.get("sel") ;
       
assertEquals(1, rowcount) ;
assertEquals("abc", rows.firstRow().getString("b")) ;

    - 역시 하나의 Transaction으로 처리된다. 주로 마이그레이션 작업이나 session scope temporary table을 가지고 작업할때 쓰인다. 혹은 Session Level Configuration 명령문과 select문을 혼합할때 쓰이기도 한다. 즉 특정경우에 Set Ansi_null Off 시키고자 할때 session 명령문은 Update로 Select문은 Query로 실행시킨다. 앞어 거듭 말했듯 Rows는 클라이언트 커서이기 때문에 한건을 insert 하고 해당 건을 select하고 해당 건을 지우는 3개의 명령문을 combinedProcedure에 넣고 실행시켜도 제대로 된 Rows 결과물을 볼 수 있다.




ExtraServant


    - ExtraServant는 IQueryable의 구현 클래스가 아니며 이름 그대로 IDBController의 하인 역할를 수행한다. 예컨데 모든 쿼리의 실행시간을 System.out으로 확인하고 싶다고 하자.

ex)
dc.addServant(new StdOutServant(StdOutServant.All)) ;

를 실행시켜 주면 해당 dc로부터 생성된 모든 IQueryable 객체는 실행후 StdOutServant를 통해 쿼리 이름과 실행시간을 System.out에 출력한다. 그와 동시에 300ms 이상의 모든 쿼리는 따로 기록해 두고 싶다고 한다면.

ex)
dc.addServant(new TraceOfLateProcServant(300)) ;
를 추가시키면 된다.

dc는 1개 이상의 ExtraServant를 가지며 모든 쿼리는 해당 쿼리 작업을 완료후 Chain된 ExtranServant를 통해 임의의 추가 행동을 지정할 수 있다. ExtraServant는 별도의 Thread로 관리되며 ExtraServant의 동작시간과 행동은 기존의 작업에 영향을 주지 않는다.

별도의 IDBController를 새로 만들어서 Connection만 공유한채 Servant 정보는 달리 유지시킬수 있다.

ex)
DBController newDc = new DBController("newDC", dc.getDBManager()) ;
newDc.addServant(new StdOutServant(StdOutServant.All)) ; // 모든 IQueryable를 화면에 프린트하는 Servant를 추가한다.
newDc.addServant(new StdOutServant(StdOutServant.All)) ; // 한개 더 추가한다.
newDc.initSelf() ;
       
newDc.getRows("select 1 from copy_sample") ;
newDc.destroySelf() ;

위의 경우 newDC는 기존 dc.getDBManger의 Owner가 아니기 때문에 destroySelf()를 실행시켜도 해당 DBManger의 Pool은 파괴되지 않는다.

     - ExtraServant는 다양한 상황에 새롭게 상속받아 구현함으로써 다양한 역할을 수행할 수 있다. 이를테면 특정 사용자가 접속했을때 MSN으로 메시지를 보내주는 Servant를 구현할 수도 있고 특정 쿼리들이 실행되었을때 Log를 남기는 Servant를 구현할수도 있다.



   Handler

   - Rows는 여러종류의 Handler를 사용하여 다양한 타입으로 변신이 가능하다.


Rows rows = dc.getRows("select * from copy_sample order by no1", 10, 1) ;

첫번째 row를 Map 형태로 바꿀 수도 있고
ex)
Map results = (Map)rows.toHandle(new MapHandler()) ;
assertEquals(2, results.size()) ;   // column 수 확인

모든 row를 Map의 List 형태로 바꿀 수도 있고.
List<Map> results = (List<Map>)rows.toHandle(new MapListHandler()) ;
assertEquals(10, results.size()) ; // row수 확인

모든 row를 Bean List 형태로 바꿀 수도 있고
List<TestBean> results = (List<TestBean>)rows.toHandle(new BeanListHandler(TestBean.class)) ;
TestBean row = results.get(0);

assertEquals(1, row.getNo1()) ;
assertEquals("01", row.getNo2()) ;

assertEquals(2, results.get(1).getNo1()) ;
assertEquals("02", results.get(1).getNo2()) ;


특정 컬럼의 값만을 얻어올수도 있다.
Object value = rows.toHandle(new ScalarHandler(2)) ; // 첫번째 row의 2번째 컬럼의 값
assertEquals("1", value.toString()) ;




6년전에 해당 Framework를 처음 개발했을때 목적은 4가지이다.

1. 풀링을 구현하되 프레임워크 사용자가 실수로라도 Connectionless를 만들 수 있는 방법을 원천적으로 차단한다.
    -> Connectio은 외부에서 보이지 않으며 Client Cursor 사용으로 결과셋 전달후 Resource는 자동으로 바로 반환한다. 즉 사용자는 JDBC의 Resource에 대해 알 필요도 없으며 알 수도 없다.

2. 프로그램에 SQL문을 심지 않는다.
    -> UserProcedure의 적극적인 사용으로 Query문은 모두 중앙집중 관리 되며 이는 개별적인 DB 벤더의 장점은 최대한 활용함과 동시에 DB 벤더에 종속되지 않는다는 상이한 목표를 동시에 추구한다. IQueryable의 모든 구현체는 단순히 Message역할만을 수행하기 때문에 JDBC의 PreparedStatement와 달리 정리해줘야 할 Resource가 아니다.

3. LOB 핸들링을 쉽게 한다.
   -> 벤더마다 그리고 버전마다 Lob Handling에 있어서의 사용방법이 다른걸 간단한 인터페이스로 통합시고 사용자는 byte 단위의 Access Handling을 하지 않아도 되게 한다.

4. 사용방법은 쉬워야 함과 동시에 여러가시 발생할 수 있는 상황에 쉽게 적응 가능해야 한다. 동시에 불필요한 퍼포먼스 감소를 일으켜서는 안된다.



이후.. 한동안 추가 기능이 없다가 최근에

  - MySQL을 기존의 MSSQL, Oracle과 더블어 기본 테스트 항목및 지원DB로 함.
  - 인터페이스의 소폭 수정
  - viewPlan(Query의 실행계획보기)
  - Paging (쿼리의 Page 설정, nextPage, prePage Access)
  - NamedParameter

등등의 잡다한 기능을 넣었다. 가장 큰 이유는 Paging 때문인데 JDBC에 Client cursor에 Page 관련 메소드가 있는데 제대로 implement한  JDBC가 거의 없어서 그냥 만들었다. -ㅅ-. Page는 DB 마다 처리 방법이 다르지만 해당 DB에 가장 효율적인 방법을 사용하려고 했다.








사용자는 framework_core_fat.jar를 추가하고 해당 DB의 JDBC jar만 추가시켜서 사용하면 된다.

아래와 같은 Apache Common Jar를 사용한다.
<fatjar.jarsource file="lib\jericho-html-2.5.jar" relpath=""/>  // 얘는 parser framework에 쓰임
<fatjar.jarsource file="lib\jmock-core-1.2.0.jar" relpath=""/>
<fatjar.jarsource file="lib\junit.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-pool-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-beanutils-1.8.0.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-collections-3.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-dbcp-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-digester.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-fileupload-1.1.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-io-1.2.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-lang-2.3.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-logging-1.0.4.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-net-1.3.0.jar" relpath=""/>
<fatjar.jarsource file="lib\apache-common\commons-validator-1.1.4.jar" relpath=""/>



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

DBFSlayers  (1) 2010.04.04
Framework Cursor (cursor in MySql)  (1) 2010.02.24
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Database2010. 2. 24. 09:55


이전글에서 커서의 일반 지식과 오라클과 MSSQL의 커서 방식에 대해 얘기 했으니 3번째로 MySQL의 커서 방식을 이야기해보자. 사실 개인적으로 MySQL은 상대적으로 자주 사용하는 DB가 아니다. MySQL은 "거의" 무료지만 다른 2개의 디비에 비해 기능과 지원이 부족하고 다른 '완전한' 무료에 비해 그리 많이 좋지도 않는 어정쩡한 위치에 있기 때문이다.

거의 1년만이므로..
다시 기본조건을 상기해보자.

JDBC로 stmt.execQuery("select * from millonRow_tblc") ; 를 실행했을때..


Large ResultSet의 Handling에 관한 문제이다.


우리가 먼저 시작해야 할 점은 사용자는 과연 모든 결과가 필요할까? 라는 점이다.

물론 100만개의 Row가 모두 필요한 프로그램일수도 있다. 하지만 그 1%의 확률로 모든 Row가 필요한 프로그램이라고 하더라도 그 중에 다시 99% 이상은 DB에서 연산이 가능하다. DB는 다른 어떤 통계 프로그램보다 많은 연산자와 빠른 속력을 지원해주기 때문에 정말 화면에 모든 Rows를 뿌려야 하는 0.01%의 확률의 프로그램이 아니라면 연산을 DB에서 하는게 좋다.

어쨌거나 지금 그 0.01%의 확률의 프로그램을 만들어야 한다고 가정하자.
즉 당신은 이유야 어쨌거나 "select * from millonRow_tblc" 모든 결과 셋이 필요하다.


DB가 MySQL일때
pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
rs = pstmt.execQuery() ;
를 실행해보자.

아마도 특별히 Java의 Heap 메모리를 늘리지 않았다면 Out of Memory 예외를 보게 될것이다.
단 한행도 rs.next()를 하지 않았음에도 불구하고 말이다.

한행이 약 100byte라고 가정했을때 0.1k * 1000000 = 100M로
Select의 모든 결과셋의 크기가 기본 Java Heap Memory의 64M를 초과하기 때문이다.


MySQL에서 - 비록 공식문서에는 나와 있지 않지만
당신이 특별한 다른 옵션을 사용하지 않았을때 Client Cursor Location에 Static Cursor Type을 사용한다.

즉 JDBC에서 위 예제를 실행했을때
해당 쿼리의 모든 결과셋을 Client(여기서는 Application Server가 Client)에 전송하고 Client에서는 그 결과셋을 모두 받아서 Client 메모리안에 Table 형의 결과셋을 만들어 채우는 과정을 한다. 보통의 다른 Set 알고리즘과 마찬가지로 기본 Set이 거의 채워지면 해당 Set의 기존 Size를 1.5배 늘리고 이 용량이 다시 채워지면 다시 1.5배를 늘리는 동작을 반복한다.
다시 말해서 위의 경우 DB에 던지는 결과셋을 받아서 계속 Set의 용량을 늘리다가 어느순간 한계 Heap Size에 도달하게 되는 것이다.

따라서 정말 millonRow_tblc의 Table의 모든 Row가 필요하다면 Heap Size를 늘려야 한다. 그러나 근본적인 해결책은 아니다. 왜냐하면 Heap의 Size를 10배로 늘렸다고 해도 10명이 동시에 해당 프로그램을 실행하면 역시 Out of Memory Exception을 보게 된다. 백만 Row를 모두 받아서 Clinet에 Set을 구축하기까지는 꽤 오랜(아마도 컴퓨터 성능에 따라 1 - 10분 사이) 시간이 걸리기 때문에 10명이라는 수치는 매우 자주 일어나는 경우가 될것이다. 물론 사용자가 단 1명이더라도 결과를 보기위해서는 1-10분을 기다려야 한다.



공식적인 해결방법은 아니지만 MySQL에서는 일종의 꽁수같은 방법이 있다. 이것은 일종의 버그이므로 앞으로도 계속 지원되는 방법이라고는 보장할수 없다.

pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
psmt.execQuery() ;

위와 같이 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY를 사용하고
FetchSize에 Integer.MIN_VALUE를 설정하면
MySQL은 Client에 더이상 Static Cursor를 사용하지 않는다. 위와 같이 작성할경우 MySQL은 FireHouse Cursor방식을 사용한다. FIreHouse Cursor는 앞에서 나왔던 MSSQL의 기본 cursor이다.

공식적인 cursor Type에서는 언급하지 않지만 인터넷 환경에서는 묵시적으로 자주 사용된다. FireHouse 커서 동작방식에 대해 말하기 전에 MYSQL의 JDBC는 Server Cursor Location을 지원하지 않는다. 물론 근본적으로 JDBC의 API에 Cursor Location을 설정할수 있는 메소드가 없기 때문이기도 하지만 그럼에도 MSSQL에서는 CursorType에 따라 묵시적으로 바뀌는 것과 달리 MySQL에서는 아예 Server Cursor Locatiion을 공식적으로 지원하지 않는다.

따라서 MSSQL의 FireHouse 커서와는 조금 다른 동작을 한다.

pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
rs = psmt.execQuery() ;
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}

와 같이 실행하였다면 반갑게도 바로 결과가 나올것이다. Connection Pool을 사용했다면 0.1초 이내에 말이다.

유레카~ 라고 소리지르기 전에..알아야 할 것이 있다.
FireHouse 커서방식은 Static Cursor Type과 달리 모든 결과셋을 Client에 구축하지 않고 Row Level 단위로 Access할수 있는 장점이 있긴 하지만 단점이 2가지가 있다.
첫번째는 rs는 더이상 previons()나 relative/absolute move를 지원하지 않는다. 이는 FireHouse의 동작방식을 생각해보면 당연한 것이다. FireHouse방식에서는 rs.next()를 호출하는 순간에 더 이상 이전 previous의 커서의 위치나 정보를 보관하지 않는다. next()를 호출할때마다 이전 record의 정보는 메모리에서 날림으로서 Out Of Memory를 피하는 방식이다. 그리고 당연히 rs.getRowCount() 같은 메소드도 사용할수 없다. 만약 RowCount를 알고 싶다면 while(rs.next()) i++ 와 같이 구할수 밖에 없다.

첫번째는 사실 그다지 큰 문제는 아니다. 보다 중요한 것은 두번째 단점이다.
두번째 단점은 MYSQL의 Client Location의 FireHouse Cursor는 멈추지 않는다는 것이다.
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}
와 같이 단지 10행만을 사용한다고 하더라도 MYSQL DB는 그걸 인지할수 없으며 여전히 남은 99만 9990개의 남은 row를 Client에 전송한다.

그래서
pstmt = conn.prepareStatement("Select * ....", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(Integer.MIN_VALUE) ;
rs = psmt.execQuery() ;
for( int i = 0 ; i < 10 ; i++) {
   out(rs.getString(1)) ;
}
까지 0.1초가 걸리더라도..

rs.close() ;
이 문장을 실행하는데 1분 - 10분 정도가 걸린다. FireHouse는 일종의 Data Stream이기 때문에 모든 결과셋을 받기 전까지는 rs를 close 할수 없다. 그렇다고 rs.close()를 하지 않으면 해당 Connection은 미아가 되어 얼마지나지 않아 Connection Pool Exception을 보게 된다.

가끔 특정 사이트를 보면 화면에 내용은 거의 다 뿌려졌는데 브라우저 하단의 Progress Bar는 여전히 진행중인 사이트를 본다면 반이상은 JSP로 위의 방식으로 코드를 작성했기 때문이다. response.flush()로 일단 화면에 보여지지만 JSP코드 하단의 rs.close()를 하는데 오래오래 걸리고 있기 때문이다.

MVC가 머고간에 일단 JSP에서 JDBC를 억세스 해서 close()를 하기전에 일단 Access한것만이라도 flush로 먼저 보낸다면 화면에는 빨리나온것처럼 보이기에 일단 급한 마음에 그렇게 한것이다.

Lazy Initialize 패턴같은 사용방식처럼 보일수도 있지만 이런다고 문제는 해결되지 않는다. 일단 Client에서 빨리 보여지느냐 늦게 보여지느냐에 상관없이 FireHouse Cursor Mode에서 Server인 DB는 여전히 남은 Row 들을 보내고 있다. 그 말은 귀중한 자원인 DB는 Network Channel에 사용하지 않을 Row를 쓰고 있고 여전히 Application Servers는 사용하지 않을 Row을 받고 있다는 얘기이다.

최근의 대부분의 하드는 100Mb read/sec 이상의 속도를 가진다. 그럼에도 대부분의 Network Channel은 고작해야 10Mb / sec이기 때문에 필요도 없는 Row를 Network로 보내느라 DB는 계속 바쁘다.

처음에 언급한바와 같이 상황에 따라 DB에 따라 심지어는 버전에 따라 이와 같은 문제를 해결하는 방식은 다르기에 이전의 Mssql의 방식을 사용할수는 없다.(다시 말하지만 MySQL의 JDBC에서는 Server Cursor Location이 없다.) 해결방법은 다음글에서.. =ㅅ=


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

DBFSlayers  (1) 2010.04.04
DB Framework 2.0 Short Explain  (3) 2010.03.09
Framework - client cursor  (0) 2009.03.26
Framework - 커서의 선택 .. and  (0) 2009.03.24
프로시저 vs SQL  (0) 2009.03.23
Posted by bleujin
Framework/Another Lore2009. 8. 14. 15:49

AL을 처음 시작할때인 2007년에
AL의 최종 진화 모습은 DB를 사용하는게 아니라 DB 그 자체라고 얘기를 한적이 있는데..

결국 DB를 만들고 있다 -ㅅ-;

험난하다. -ㅅ-; 후. 

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

XUL 단상  (0) 2009.07.15
레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin


대부분의 책에서 인터페이스와 추상 클래스에 기반한 프로그래밍이란 얘기가 너무 많이 나와서 흔히 인터페이스를 사용하는게 목적처럼 되어 버리는 경우가 있다. 

인터페이스를 사용하는 것은 수단일뿐 그 자체로 목적이 아니다. 그럼 인터페이스를 사용하는 목적은 무엇일까에 대해 의문을 가져야 한다. 그 답을 알기전에 먼저 인터페이스는 절대선이 아니라는 것을 이해해야 한다. 오히려 인터페이스는 프로그램의 문서화처럼 존재 자체의 손해 보다 더 많은 이득을 얻기 위한 필요악같은 존재이다. 

아이러니하게도 인터페이스는 복잡함을 감소시키기 위한 추가적인 복잡함의 장치이다. 인터페이스를 사용한다는 것은 관리해야 할 파일 하나가 늘어다는 것이므로 그 자체로 복잡함이 증가한다. 다만 인터페이스를 사용함으로서 구현자는 "인터페이스의 책임"의 구현에만 집중할 수 있고 호출자는 별로 관심없는 상세 구현보다 책임 그 자체에 집중할 수 있게 함으로서 "구현자과 호출자 사이의 복잡함"을 감소시킨다. 

여기서 ~사이의 복잡함이란 시간과 공간이라는 차원을 포함하고 있다. 애초에 구현자와 호출자가 같은 시간과 공간에 존재한다면 얻는 복잡함의 감소보다 잃는 복잡함의 증가가 더 크다. 


일단 "공간"에 대해서 말하자면 서울과 LA의 거리라는 물리적 감각보다는 나와 나 이외의 생각의 거리라는 의미가 강하다. 만약 혼자 하는 프로젝트라면 혹은 아주 마음이 잘 맞는 팀이라면 공간 사이의 거리가 크지 않을 수도 있다. 

그러나 "시간"의 거리는 시간이 갈수록 엔트로피처럼 항상 늘어나고 혼자하는 프로젝트라도 시간의 거리는 생긴다. 그래서 나는 인터페이스의 가장 큰 목적은 이 시간의 거리 복잡함을 줄이는 것이라고 생각한다. 다시 말해서 복잡함이란 현재의 특정 상황 혹은 시점에서의 복잡함만을 뜻하는 것이 아니라 지속적으로 변함으로써 생기는 복잡함이 더욱 많다는 말이다. 



시간의 거리로 생기는 복잡함이 보다 복잡한 이유는 이렇게 생긴 복잡함은 지속적인 파동의 다른 변화를 일으키고 이로인해 더욱더 복잡해지기 때문이다.. 게다가 앞날이라는 것은 본질적으로 예측하기 어렵기 때문에 미래를 대비하는 것 작체가 불필요한 복잡합이 야기될수 있다. 


특정 객체가 변해야 할 이유는 그 자체가 아니라 그가 사용하는 객체가 변해야 할때도 일어나고 이때문에 객체는 그 자신보다 보다 덜 변하는 것에 의존하여야 한다. (보다 덜 이라는 의미는 <=(작거나 같다) 의미인데 당연히 < (작다) 로 할수있으면 하는게 더 낫다. ) 

쉬워보이지만 이 원칙은 의외로 지키기 어렵다. 

이에 관해서 첫번째 알려진 것은 벽돌형 layer 아키텍쳐에서 상위 레이어는 하위 레이어에 의존해야 하고 하위 레이어는 상위 레이어에 의존해서는 안된다 라는 것인데 종종 이를 위반하는 것은 상위 레이어와 하위 레이어를 같은 시간에 혹은 같은 사람이 만들기 때문인데 주의하지 않으면 처음 생각보다 쉽지 않다. 

보통의 프로그램에서 하나의 새로운 레이어를 만드는 일은 그리 많지 않지만 하나의 레이어에서도 "보다 덜 변하는 것에 의존해야 한다는 원칙"은 중요한 것은 이것이 단순히 레이어간의 이야기만이 아니기 때문이다.


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

Second Program Effect  (0) 2009.07.17
여섯번째 계  (0) 2009.07.14
호출의 빈도  (0) 2009.07.08
한줄짜리 함수  (0) 2009.07.06
예언?  (0) 2009.06.12
Posted by bleujin

" 설계자의 첫번째 작업은 절제되고 깔끔한 편이 많다. 그는 자신이 하는 일을 아직 잘 모르는 상태라는 것을 인식한다. 그래서 매우 조심스럽고 절제하는 태도를 가진다. 

그가 첫번째 작업을 설계하는 과정에서 다양한 꾸밈과 장식이 떠오르지만 이것들은 "다음번"에 사용할 것으로 일단 옆으로 제쳐놓는다. 머지않아 첫번째 시스템은 끝이 나고 일정한 수준의 시스템을 달성했다는 것을 입증한 설계자는 자신감이 충만한 상태로 두번째 시스템을 구축할 준비에 나서게 된다. 

이 두번째 시스템이 누구에게나 가장 위험한 시스템이다. ~ 두번째 시스템에서는 첫번째 시스템에서 옆으로 제쳐두었던 모든 아이디어와 치장들을 사용하여 과도하게 설계하는 경향이 나타나게 된다.  "


브룩스는 두번째 시스템 효과에 대해 말하면서 성공한 제품의 두번째 제품은 과도한 설계로 과도하게 복잡한 시스템이 될 가능성이 많음을 경고하였다. 여기서 과도하게 복잡하다는 것은 시스템을 세련되게 만들기 위한 장치가 시스템의 기본 장치보다 더 앞서가게 되는 것을 말한다. 

보통의 경우 두번째 시스템은 첫번째의 그것보다 기능이 더 많다. 그리고 이것들은 적절히 분류 관리되거나 절제되어 있지 않음으로서 UI가 복잡해지고 옵셔널한 기능이 많아지면서 설정이 많아지는 등이다. 

솔류션 시스템의 경우 첫번째 시스템에서는 직접적으로 고객과 맞닿는 일이 많지 않다. 대부분 실험실에서 소수 인원의 설계로 인해 통일되고 간결한 형태를 지닌다. 첫번째 버전이 고객에게 팔리게 된 이후에 많은 추가 요구사항이 생기게 되고 그것들의 일부는 서로 이율배반적이다. 

설계자는 그것들을 잘 조절하려고 노력하겠지만 그러함에도 많은 요구사항을 어쩔수없이 받아들임으로서 두번째 시스템은 500라인짜리 XML 설정 파일을 가지게 되는 경우는 흔하다. 아마도 그 설정파일의 대부분 사용되지 않을 옵션 기능을 설정하고 활성화 시키는 역할들을 담당하지만  너무도 길고 복잡해서 그에 관심을 쏟는 사용자는 거의 없다. 

이후 이것들은 문제가 생겼을 경우에 문제의 해결에 장애가 되고 덧데기와 IF 코드로 빠르게 부패되는 시스템이 된다. 

이러한 것이 근본적으로 피할수 없는 문제는 아니다. 브룩스는 그 해결방법의 하나로 두개 이상의 시스템 작업을 한, 노련한 설계자를 선택하는 것을 들었지만 사실 최근 대부분의 프로젝트는 과거와 같이 오랜 시간동안 초기 설계자가 이후에도 꾸준히 참여하는 경우는 극히 드물다. 프로젝트는 대부분 1년 이내이며 그동안 인원이 바뀐다. 첫번째 시스템이 성공하든 실패하든 말이다. 

개인적으로 시스템의 발전 방향은 기능의 개수를 늘리는데 집착할 것이 아니라 좀더 똑똑한 시스템이 되어야 한다고 생각한다. 기존의 기능과 상충되는 요구사항이 들어왔을 경우 혹은 목적은 같지만 처리가 달라질 경우에 이후에 개발자도 까먹을 유연성(?)을 이유로 XML 설정 파일을 늘리는 것은 해결책이 될 수 없다. 

똑똑한 시스템이란 사용자의 행동에 주목하는 시스템을 말한다. 사용자가 말하는 것이 아니라 원하는 것을 하는 시스템을 만들어라 라는 말이 있는데 아웃룩의 메일관리와 지메일의 메일관리의 차이이다. 아웃룩은 메일을 분류하기 위해 4단계 이상의 설정을 거치지만 지메일은 달랑 제목을 파싱해서 링크해주는 것 하나이지만 앞의 관점에서 지메일의 관리방식이 좀더 똑똑하다. 


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

일곱번째 계 - 호출의 빈도(1)  (0) 2009.07.24
여섯번째 계  (0) 2009.07.14
호출의 빈도  (0) 2009.07.08
한줄짜리 함수  (0) 2009.07.06
예언?  (0) 2009.06.12
Posted by bleujin
Framework/Another Lore2009. 7. 15. 02:26

AL Client를 XUL(ThinLet)를 사용해서 약 한달간 만들었는데. 브라우저가 아닌 Java에서의 XUL은 2% 부족하다는 느낌이다. 

XUL(XML User-Interface Language)은 단순히 IDL 이므로 다양한 Client에서(물론 이전에 클라이언트가 XUL을 인지해야 한다. Firefox와는 달리 대표적인 브라우저인 ie는 XUL을 지원하지 않는다. ) 별도의 UI 로직를 고심할 필요가 없다는 것은 크나큰 장점이다. (https://developer.mozilla.org/Ko/The_Joy_of_XUL 모질라의 XUL Page)

그러나 XUL이 세상에 나온지 5년이 넘게 지났지만 생각보다 그 영향력은 매우 미비하다. 개인적으로는 XUL을 써보면서 lightweight Framework는 lightweight 하다라는 사실을 새삼스럽게 느꼈다. 

XUL의 Java OpenSource인 ThinLet(http://thinlet.sourceforge.net/component.html)는 매우 훌륭한 프레임워크였다. 무엇보다 놀란것은 그 간결한 구조였다. ThinLet은 XML을 Object Array Tree 형태로 관리하여 개별적인 컴포넌트의 paint를 구현한다는 것은 복잡한 머리속을 순식간에 관통하게 만드는 Simple 미학을 보여준다. Java의 Swing에 의존하지 않기 때문에 오히려 역으로 확장이 쉬운 아키텍쳐이다. 

XUL의 장점은 대표적으로 
    Based on existing standards, 
    Platform portability, 
    Separation of presentation from application logic, 
    Easy customization, localization, or branding 
등을 들 수 있다. 특히나  HTML 처럼 XUL은 플랫폼 중립적(platform-neutral)하기 때문에 비교적 단순하게 portable하고 cross platform application UI를 만들수 있다는 점에 매료되었다. 

문제가 됐던것은 바로 Lightweight 하다는 점이다. 어느정도 익숙해지고 난후 ThinLet은 Draggable한 Panel을 지원하지 않았기 때문에 ThinLet의 단순한 아키텍쳐에 감탄하면서 ThinBean 형태로 확장해서 만들었다. 얼마 지나지 않아서 다시 Draggable한 Table이 필요했고 역시 또 만들었다. 이 과정에서 ThinLet의 이벤트 처리 과정을 대폭 수정해야 했다. 왜냐하면 기본적인 Action Handler로는 너무 부족함이 많았고 결정적으로 Drag할수 있도록 Table의 Cell을 프로그램이 인지해야 했다. ThinLet에서 모든 객체는 paint된 pixel에 불과했기 때문에 Mouse Event가 발생한 곳의 객체를 인지하게 만드는 과정은 매우 복잡했고(zindex가 있음을 생각해보자) 어려웠다. 

나를 결국 손을 들게 만든건 Draggabl한 TreeTable이다. AL은 저장 구조상 TreeTable UI가 꼭 필요했다. 1주일이 넘게 삽질하면서 XUL에서 독립적인 Draggable한 TreeTable UI를 만들기는 아주아주 어렵다는 사실을 알았고 더 큰 문제는 설사 만든다고 하더라도 더 이상 그것은 XUL이 아니라는 사실을 깨닫게 됐다는 점이다. 

이른바 추상화의 함정에 빠진것이다. 

XUL은 일종의 새로운 추상층(Abstraction Layer)이다. 소프트웨어는 마치 양파와도 같이 겹겹이 층을 이루고 있는데, 각층은 그 아래충 위에 조심스럽지만 다소 불안정하게 쌓여있다. 새로운 층이 쌓일때마다 뭔가 복잡하고 지엽적인 것이 좀더 직관적이고 보편적인 것으로 대체되기 때문에 컴퓨터 공학에서는 추상을 여러개의 조그만 프로세스를 묶어 하나의 큰 프로세스로 만드는 과정이라고 정의한다. 

조엘의 말에 따르면 모든 추상화는 완벽하지 않고 함정이 있다고 하는데 예컨데 우리가 Java나 C#을 쓰지만 여전히 point에 대해서 알아야 한다는 것이다. VC++의 MFC 혹은 Delphi의 VCL만 쓰면서 Win32 API에 대해 전혀 알 필요가 없다면 정말 좋겠지만 조금만 더 깊이 들어가려 하면 바로 포인터와 관련된 지식을 만나게 되는 것과 같다. 사실 나는 기꺼이 Open된 ThinLet 코드를 볼수 있고, 가지고 있었기 때문에 사다리를 오르락 내리락 하며 문제를 수정했고 이러한 구멍에 대해서 충분히 이해를 하고 있었다.

그러나 결정적으로 수정에 수정을 거듭하다보니 수정된 프로그램은 XUL이라고 부르기에는 부끄러울 정도로 너덜너덜 해졌다. ThinLet의 마지막 버전은 2005년이고 코드를 작성하는 내내 왜 아무도 Draggabe한 Panel이나 Table을 만들지 않았을까가 궁금했었다. 그 의문은 그동안 겪었던 많은 문제들처럼 오랜동안 헤딩을 하고서야 깨달았는데, 쉽게 말하면 HTML에서 Draggable한 Table을 만들지 않는 이유와 같다. HTML처럼 XUL은 View를 하기 위한 Markup Language인데 Drag Action은 View가 아닌 영역에도 발을 걸칠수 밖에 없다. 즉 이전처럼 Model를 단지 View하기만 했던 구조와 달리 Drag등의 Action이 들어가는 순간 VIew는 Model을 인지하지 않으면 View를 할 수 없게 되었다. 그리고 Model과 Controller가 혼합되면서 Draggable한 TreeTable을 만든다고 하더라도 이게 그건 VIew역할을 하는 XUL이 아니고 오히려 더 복잡해진 정체를 알수 없는 혼합물이 되어버린 것이다. 

사실 프로그래밍 언어에 한해서는 이제 거의 이런 추상화의 문제로부터 벗어나 보인다. 우리는 수십년전의 어셈블리 개발자의 포트란 개발자에 대한 비난을 이젠 역사서에서나 읽을 수 있으니 말이다. 그러나 아직 개별적인 프레임워크에 있어서 그러한 함정은 너무나 많다. 

물론 이것이 XUL은 단지 VIew이기 때문에 XUL의 잘못이라고는 할 수 없다. 그러나 Helloworld를 만드는 것은 버튼 하나로 만들수 있지만 10만줄 짜리 프로그램을 만드는 데는 2배의 시간이 걸린다면 아마도 내가 잘못 선택을 한것이다. 


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

언젠가...  (0) 2009.08.14
레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin

여섯번째는 인터페이스에 대한 이야기이다. 

나는 오래전부터 추상클래스와 인터페이스의 문법적인 차이 이상의 문제에 대해 궁금해 왔다.

기본적으로 인터페이스는 다중 상속이 가능하고 추상클래스는 속성을 가질수 있다는 차이점 말고도 무언가 더 많은 차이가 있을것만 같았다. 

앞서 말한바와 같이 일단 속성을 가진다는 특성때문에 명사형 객체는 추상클래스가, 동사나 형용사는 인터페이스로 만들 확률이 높다. 물론 Thread와 Runnable처럼 두가지 형태를 모두 가질수도 있다. 보통 이정도 기준으로도 70%정도는 맞을 수 있다. 

다만 명사형임에도 인터페이스를 할 때가 있는데 그건 레이어의 바깥에 노출되는 객체 즉 구현에 있어서 매우 많은 유연성을 필요로 하기 때문에 - 여기서 더 유연이라는 의미는 인터페이스의 다중상속에 의지하는 바가 크다. - 명사형임에도 인터페이스를 고려해야 할 때가 있다. 

여기서 상속의 의미를 구분해야 할 것다. 인터페이스의 implements와 추상 클래스의 extends 모두 보통 상속이라고 부르고. 보통 부모 자식 관계라고 하는데 이 말은 명사형 추상클래스에 적합하고 인터페이스는 미묘하게 다른 의미를 갖는다. (영어 서적을 읽으면서 느껴지는 이 미묘한 뉘앙스의 차이가 번역을 거치면 일괄적인 부모-자식으로 변역되어 버린다.)

concrete 클래스에 있어서 인터페이스는 부모라기 보다는 일종의 이름이다. 흔한 수수께끼처럼 자기꺼지만 자기가 사용하지 않는걸 이름이라고 하는데 인터페이스는 이 수수께끼처럼 자기것이지만 자기가 사용하지 않는 것이고 우리가 사용하는 부모라는 의미로 보기 힘들다. (이게 영어와 한국어의 뉘앙스 차이일수도 있다.)

인터페이스와의 관계를 부모-자식 관계로  보지않고 이름 혹은 별명으로 보면 인터페이스의 의미에 대해 더 이해하기 쉬워진다. 인터페이스라는 의미 그 자체로 보면 접점-통로의 의미이므로 이름처럼 자기것이지만 불려지는 것이 되고 따라서 존재적 의미보다는 보여지는 의미가 더 중요해진다. (별로 상관없는 이야기지만 김춘수의 꽃이라는 시를 보면 존재적 의미보다 선언적 의미가 더 앞선다.)

추상클래스는 앞서 말한대로 명사형에 치우쳐 있고 속성을 가지기 때문에 상속이라는 용어와 부모자식관계라는 번역에 위화감은 없지만 인터페이스와 구현체는 좀 달리 이해할필요가 있다. 하지만 이러한 관계에 새로운 용어를 여기서 다시 만든다는 것은 더 많은 혼란을 야기할수 있기 때문에 그냥 외부에 불려지는 이름정도라고 넘어가자. 

따라서 인터페이스는 추상클래스보다 외부에 보여지는 모습이 중요하다. 아니 그게 전부라고 해도 좋을 정도이다. 이제 여섯번째 계를 말할때가 왔다. 잘 알려진 구현은 숨기고 대신 의도는 드러내라가 여섯번째 계이다. 본질과 작동원리에 대한 이해보다 인터페이스는 표층이 더 중요하고  특히나 다른 객체에게 더 중요하다. 자신의 것이지만 남에게 불리는 이름처럼 인터페이스는 객체가 다른 객체에게 불리어지는 이름이기 때문에 자신의 구현과는 상관이 없다. 다만 구현 객체들은 이런 이름으로 불리어야 한다는 걸 제시할 뿐이다. 

6계는 이미 다른 객체지향 책에서 많이 언급하고 있으므로 좀더 자세한 예제가 필요하다. 



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

일곱번째 계 - 호출의 빈도(1)  (0) 2009.07.24
Second Program Effect  (0) 2009.07.17
호출의 빈도  (0) 2009.07.08
한줄짜리 함수  (0) 2009.07.06
예언?  (0) 2009.06.12
Posted by bleujin

이전글대로 변화의 속도에 다르기 때문에 소스포지나 코드서치를 통해서 얻은 Lib를 직접 사용하는 것보다 중개자 클래스(이것이 좀더 확장되면 새로운 Layer가 된다.)를 도입하는 것은 현명하다. 그런데 변화의 속도만으로 판단한다면 아마도 모든 Java Core Lib에 대해 중개자 클래스를 도입해야 할지도 모른다. 

그래서 변화의 속도 못지않게 중요한게 한가지 더 있다. 언젠가 다시 나올 7계인 호출의 빈도를 말하는 - 객체는 객체자체로서의 아닌 다른 객체에서 어떻게 그리고 얼마나 자주 보이는가가 더 중요하다.이다. 이전에서 말한바와 같이 양적인 차이는 질적인 차이를 유발한다. 만약에 단지 몇번 호출하고 말것이라면 굳이 번거로운 길을 갈 필요는 없다. 그러나 아주 많다면 그것이 new ArrayList() 라도 중개자 클래스(혹은 메소드)를 만들어서 하는게 도움이 된다. 


인간은 분류하는 동물

호출 빈도의 증가는 단순히 양적인 증가 이상의 의미가 있다. 서랍안에 물건이 단 6개만 있다면 책장에 책이 7권정도라면 그럭저럭 괜찮다. 하지만 하나의 서랍안에 물건이 37개라거나 한칸에 책이 76권이라면 서랍과 책장이 어질러져 있다고 느낀다. 그래서 Brian Buchanan은 Theory of library classification = 문헌 분류 이론의 한국어판 서문에서 “아마도 패턴과 순서에 대한 관심은 우리 인간들이 타고 나는 모양이다. 이러한 의미에서 인간을 분류하는 동물 (classifying animal) 이라고 정의할 수 있을 것이다. 이것은 카오스(혼돈) 에 대한 두려움을 바탕으로 하고있고, 또한 통제할 수 없는 환경을 통제함으로서 불안정한 세계에서 안전하다는 망상을 갖고자 하는 욕망에서 생겨나는 듯 하다.”라고 그의 분류관을 개관해 주고 있다. 


양이 많아지면 우리는 분리하고 싶어한다. 사실 System.out.println의 중개 클래스를 만드는 게 좋은 이유는 첫째의 변화의 속도 보다 우리가 그 메소드를 너무나 자주 쓰기 때문에 의존하는 바가 더 크다. 인터페이스를 만드는 이유를 생각해봐도 이는 당연한 말이다. 주로 인터페이스가 되는 객체는 인터페이스라는 말 그대로 Front Object 즉 바깥에서 가장 많이 노출되는 객체일 경우가 많고 핵심 알고리즘이 담겨서 있어서나 단지 양이 길어서가 아닌 가장 많이 불리는 객체에 대해 객체이름과 인터페이스 설계를 가장 고민해야 한다. 

사실 객체는 어떻게 보면 보여지는 모습 (인터페이스)이 바로 그것의 본질이다. 현상을 이해하는데 본질과 작동원리 (구현)에 대한 이해보다 표면의 관찰, 즉 통신수단으로서의 인터페이스가 중요하다.

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

Second Program Effect  (0) 2009.07.17
여섯번째 계  (0) 2009.07.14
한줄짜리 함수  (0) 2009.07.06
예언?  (0) 2009.06.12
패키지의 설계 원칙  (0) 2009.06.08
Posted by bleujin