2011년 10월 31일 월요일

Castle.Windsor 를 이용한 Decorator 패턴

Castle.Windsor 는 IoC/DI 라이브러리 중에 .NET 계열에서는 유명하고, 많이 사용하는 라이브러리 중에 한가지입니다. NHibernate 초기에 DynamicProxy 를 Castle.DynamicProxy 를 사용하여, 많이 유명해졌지요.
지금은 NHibernate도 독립적으로 ProxyFactory를 운용하고, Microsoft 사까지 Unity 라는 IoC/DI 라이브러리를 제공하고 있어, 어떤 것이 더 좋냐? 이런 건 무의미하고, 각자 자신에게 맞는 걸 사용하면 되겠습니다.



 

  
   
    ${DataService.Northwind}
    ${RequestSerializer.Northwind}
    ${ResponseSerializer.Northwind}
   
  

  
   
    ${DataService.Pubs}
    ${RequestSerializer.Pubs}
    ${ResponseSerializer.Pubs}
   
  
  
  

  
   
    ${AdoRepository.Northwind}
   
  

  
   
    ${AdoRepository.Pubs}
   
  

  
  

  
   
    ${JsonSerializer.Request}
    ${Compressor.SharpBZip2}
   
  

  
   
    ${JsonSerializer.Response}
    ${Compressor.SharpBZip2}
   
  

  
   
    ${RequestSerializer.Compress}
    ${Encryptor.Aria}
   
  

  
   
    ${ResponseSerializer.Compress}
    ${Encryptor.Aria}
   
  


  
   
    ${BsonSerializer.Request}
    ${Compressor.SevenZip}
   
  

  
   
    ${BsonSerializer.Response}
    ${Compressor.SevenZip}
   
  
  
  

  
  

  
  

  
  

  
  
  
  
  

  
  

 
 

위의 코드는 Castle.Windsor 를 이용하여, Dependency Injection을 좀 복잡하게 사용한 예입니다. 뭐 이렇게 할 필요가 있나 싶겠지만, 제품을 만들다 보면, Customizing을 해야 할 게 아니라, 위와 같이 Configuration을 변경하도록 하는 것이 제품 완성도를 높인다고 믿고 있는 터라 꼭 이런 방식을 사용합니다.

아니 이런 Decorator 패턴에 대해 쓴다는 걸 서론만 쓰고, 그만 뒀네요... 코드 처리하는데 정신이 팔려서리... 쩝...
자 그럼 코드를 좀 볼까요? 상위에 있는 놈들은 모두 Component의 속성이나 인자값으로 다른 Component 를 주입하는 Dependency Injection이라 합니다.

자 여기서 Decorator 패턴을 사용한 예는?
Component 중에 "RequestSerializer.Pubs" 를 보십시요. 이 놈은 Serializer 로 "RequestSerializer.Compress" 를 Wrapping 하여 ARIA 암호화를 수행하는 Decorator 입니다.
"RequestSerializer.Compress" 는 "BsonSerializer.Request" 를 Wrapping 하고, SevenZip 알고리즘으로 압축을 수행합니다.
자 이제 정리하면 BSON->Compress->암호화 로 래핑되도록 되었습니다. 물론 순서는 바꿀 수 있고, Decorator를 더 추가할 수도 있습니다.

이렇게 환경설정에서 IoC/DI 기능 중에 Decorator 패턴을 활용하게 되면, 상당히 복잡한 구성도 쉽게 구성할 수 있고, 다양한 조합의 Component를 제공할 수 있습니다.

2011년 10월 30일 일요일

Silverlight 와 서버와의 객체 통신에서 압축 방식의 장점

통신 시 어떤 경우가 되었건, 같은 정보가 전달될 때에는 작은 양이 작은 횟수로 전달 되는 것이 가장 바람직합니다. 그래야 통신 비용이 절감되고, 통신에 따른 지연을 막을 수도 있습니다.

요즘 한창 Silverlight와 서버와의 통신 방식에 대해 개발하고 있습니다만, .NET이나 Microsoft 에서 제공하는 기본 방식의 경우는 브라우저와 같은 방식으로 Data 를 Client 로 다운로드 할 때에만, 옵션으로 압축을 제공합니다. 물론 WCF 에서 압축 모듈을 서로 사용하면 가능합니다만, 그게 바로 족쇄가 될 가능성이 있어, 자체적으로 일반적으로 많이 사용하는 Data 압축 방식을 이용한 통신을 수행하도록 해 봤습니다.

image

JSON 방식으로 객체를 Serialize 하고, 압축을 수행하였더니, 4MB 정보인 것이 246K 정도로 확 줄었습니다. 원본에 비해 5.67% 로 크기가 준 것이지요. 이렇게 되면 통신 속도가 엄청 좋아지겠죠?

BZip22 알고리즘을 사용하였으므로, GZip 보다는 압축률이 좋을 것입니다. LZMA 알고리즘의 경우는 더 좋을 수도 있지만, 수시로 압축/복원을 수행하는 통신 모듈에는 적당하지 않는 것 같습니다. 일반적으로 가장 적당한 방식은 GZip 이 되겠습니다.

이제 서버 단에서 압축을 풀고, 작업하고, 결과를 압축하여 반환하는 모듈을 제작하면, 통신 라이브러리가 완성됩니다.

2011년 10월 25일 화요일

왜 Silverlight 에는 압축관련 모듈이 없을까요?

Microsoft 관계자 분 중에 혹시 아시는 분 계신가요? Microsoft 사가 웹 통신시 압축이 유용하다는 것은 모를 리가 없을 텐데요… 그쵸?

어쩔 수 없이 찾다보니 다행히 Silverlight SharpZipLib 이 있군요.  이 라이브러리는 .NET 용으로 이미 유명한 #ZipLib 의 Silverlight 버전이라 할 수 있습니다.
이 놈이 있어서 다행이지, 기본적으로는 Silverlight에서 압축 관련 기능을 제공하지 않는 건가요?

이게 단순 통신만의 문제가 아니라, Silverlight 자체적으로 Isolated File Storage 에 뭔가를 저장할 때 압축해서 저장하고 싶을 때, 기본 Framework에서 제공해준다면, 상당히 편리한 기능인데, 아쉽군요…

Silverlight와 WCF 통신 시 압축 이용 글을 보면, 이건 뭐 그냥 WebClient 사용하라는 얘기고… 이거 말고… 개발자가 특정 암호화를 부가한다던가, 여러 가지 조작을 할 수 있도록 Class 로 제공해 줄 수는 없나요?

Silverlight와 통신용 WCF에 압축 기능 넣기 에도 결국 서버 쪽 IHttpHandler 나 Web Services, WCF 등에서 자체적으로 Data 를 압축해서 보내는 것인데… 그걸 Silverlight에서 어떻게 압축을 풀어서 사용하지요?

대부분의 글이 Silverlight 와 WCF 와의 통신 시에 WS-Compression 을 이용하던가, WebCliient 에서 HttpHeader에 Accept-Encoding=gzip, deflate 를 넣어서 데이터를 받으라고 되어있는데, 문제는 대용량 데이타를 Upload 할 때입니다…
이 때는 어쩔 수 없이 일반 데이타를 보내야 한다는 소리 아닌가요?

어떻게 외부 통신 시 WCF 를 사용하게 되면, 압축 및 암호화가 가능하다 하더라도, 로컬 저장소에 저장하기 위해서는 필히 압축 기능이 기본으로 제공되었으면 합니다.

Silverlight Data 통신 시의 암호화

Silverlight 등 Client 모듈과의 통신 시에 고려해야 할 사항을 살펴보면,

  1. 통신 프로토콜 및 포맷을 결정해야 합니다.
    HTTP 통신이 대세므로 논외로 하고, 포맷은 XML, JSON, Byte Array 등을 결정해야 합니다.
  2. 그 다음으로 서버 쪽 통신 Daemon 을 IHttpHandler, Web Services, WCF 등을 선택해야 합니다.
    1. WCF 의 경우는 Binding 방식, Protocol 등의 설정 방법이 무지 많으므로, 더 많은 확장성이 있습니다.
    2. Data 처리 방식으로 동기/비동기 방식을 결정해야 합니다.
  3. 압축을 지원할 것인가? 한다면, 어떤 압축 알고리즘을 지원할 것인가?
    1. GZip (NET 기본)
    2. Deflate (NET 기본)
    3. ISharpCode.SharpZipLib.dll 에 있는 GZip, BZip2 알고리즘
    4. 7Zip 알고리즘
  4. 암호화를 지원할 것인가? 어떤 암호 알고리즘을 지원할 것인가?
    1. .NET에는 대칭형 알고리즘 중에 상당히 많은 알고리즘을 제공합니다. DES, RC2, TripleDES, Rijndael 등
    2. Silverlight 에서 제공하는 대칭형 알고리즘은 AES 클래스로 대응되는 것 달랑 한 개?

자 이제 실버라이트의 통신 관련 결정 사항이 많다는 것을 아실 것입니다.
뭐 닥치고, OData 나 Microsoft 의 Dynamic Data 등 DataContext 사용 방식이 있겠습니다만, 내부적으로는 HTTP 웹서비스에 JSON 포맷의 데이터 전송 방식이라 하겠습니다.

오늘은 위의 여러 가지 결정사항 중 가장 늦게 결정해도 되는 (SSL 이 있으니, 안 해도 됩니다!!! 라고 주장하시면 할 말 없습니다.) 암호화 관련 사항에 대해 짚어보겠습니다.

위에서 보셨듯이 .NET Framework 2.0 이상부터 상당히 다양한 암호 알고리즘을 제공하고, 양방향 통신 시에 가장 많이 쓰이는 대칭형 알고리즘은 AES 기반으로 발전된 많은 알고리즘을 제공합니다. 근데 왜!!! 실버라이트에서는 AesManaged 라는 클래스 달랑 하나만 지원하냐구요? Rijndael 은 내부에 숨어서 사용도 못하고 (물론 사용하려면 하겠지만)

그래서 암호화 지원을 할 수 있는 방법은 .NET에서 제공하지 않지만, 한국에서는 꼭 써야 할 ARIA 알고리즘을 적용하기로 했습니다.
뭐 결론이 이미 있는데, 뭐하러 다른 걸 시도하지? 라고 하실지 모르지만, 누가 알겠습니까… 제가 만든 제품이 외국에도 팔리고, 암호화 알고리즘을 미국 규격인 AES 를 따라야 한다라고 한다면… ㅋㅋ

어쨌든 ARIA 알고리즘을 이용하여, 제품에 적용하기 위해, 사전에 단위 테스트를 수행해 봤습니다.


AriaEncryptor_for_Silverlight

AriaEncryptor_for_Silverlight_Log

제대로 Silverlight 에서도 암호화 및 복호화가 제대로 되는군요^^. 이제 Silverlight 와의 통신 시에는 ARAI 알고리즘 을 이용한 데이타 암호화를 기본으로 사용하려고 합니다.


더 좋은  방안이 있는 분께서는 의견 주시기 바랍니다.

NHibernate 학습 및 개발 속도 높이기

저도 처음 NHibernate를 접했을 때, HBM 작성하는데, 상당히 많은 시간을 투자해야 했고, 많은 시행착오를 겪었습니다. 특히, NHibernate 공식 자료에도, Class – HBM – Table 이런 식의 전체 정보가 존재하지 않아 애를 많이 먹었죠.

그래서 시작한 것이, NHibernate 소스의 테스트 코드를 보기 시작했습니다. 물론 실행도 해보고요. 이 때 제가 “뭔가 잘못하고 있구나” 를 깨닳았습니다. 테스트 코드를 보면, DB는 미리 만들어야 하지만, hbm2ddl 을 이용하여, DB Schema 생성용 script가 생성되고, 이를 DB에 실제 적용해서, 테스트를 위한 테이블을 모두 만든 후 테스트를 수행하더군요.

이 것을 알기 전에는 매핑 정보 하나 바꿀 때마다 DB도 바꾸고, 클래스도 바꾸고, 참 무식한 짓을 했습니다.
이제는 아예, NHibernate 테스트 프로젝트를 뜯어보다가, 하나의 DB에 대해서 테스트용 Schema 를 생성하는 것이 아니라, 설정에 따라, 다양한 DB에 대해서 테스트 할 수 있는 테스트용 Framework을 갖추게 되었습니다.

이 후 제게는 큰 변화가 일어났습니다. 그 동안 시도할 생각조차 못해왔던 복잡한 매핑이나, 개념이 잡히지 않던 매핑 속성 값에 대해, 여러 개의 매핑을 제작하여, 상호 비교할 수 있는 그런 체계가 제게 주어진 것이죠.

이렇게 되니, NHibernate에 대한 학습속도가 가파르게 오르더군요. 더군다나, IUserType, Interceptor, DynamicProxy, Listener, 2nd Cache Provider 등을 직접 제작하고, 테스트 하면서, 내부 구조를 빠르게 이해할 수 있게 되어, 이제는 왠만한 경우에는 자체 해결할 정도가 되었습니다.

그럼, 이제 막 NHibernate 를 시작하거나, 너무 힘들어서 포기하신 분들이라면, 다음과 같이 해보시기 바랍니다.

  1. NHibernate Test 프로젝트의 테스트 구조 파악 및 DB 생성 (hbm2ddl 활용) 방법을 파악
  2. 위의 테스트 방식으로 자신만의 Test Framework을 만들 것
  3. 다양한 매핑 방식에 대해 테스트 해 볼 것.
  4. 가능하면, FluentNHibernatePersistenceSpecification 을 활용할 것 (Fluent 방식의 매핑이 아니더라도 가능)
  5. DDD (Domain Driven Develoment), ORM 관련 지식을 쌓을 것
  6. Proxy에 대한 개념을 잡을 것 ( CastleProjectDynamicProxy 추천)

결론적으로 자신만의 Test Framework을 만들지 않고, NHibernate 를 습득하기는 상당히 힘듭니다.
남이 만든 것을 약간 고쳐서 사용해도 무방할 것입니다. 이런 예는 구글링으로 찾아보시면 될 듯 합니다.

2011년 10월 21일 금요일

Hadoop 관련 정보

제가 Windows 계열에서 개발하는 관계로, Java 보다는 .NET 으로 개발이 되면 좋겠는데, Hadoop 은 그런 자료가 드물군요…
Hadoop 관련 설치 운용 관련 글들을 좀 모아봤습니다. 나중에 참고해서 꼭 적용해 봐야겠습니다.

등이 있네요^^ 언젠가는 참고해서 적용해 볼 날이 올려나?

Microsoft SQL Server 와 Apache Hadoop 의 결합

Microsoft SQL Server 2012 (Code name ‘Denali’) 에서는 BI 와 Big Data에 중점을 뒀다고 하네요. 흠 당연히 BI 하려면 Big Data를 처리해야 하니 Hadoop 같은 대용량 처리가 가능한 기능이 있어야겠지요. 근데, 왜 자체적으로 안 만들고,  Hadoop 인지 좀 의아스럽긴 합니다.

여기에 더해서 Apache Hadoop Connector for SQL Server 2008 R2 도 출시되었네요.
또 Azure 기반의 Hadoop 서비스도 2011년 말에 제공한다고 하니, Hadoop 은 이제 대용량 데이타 처리 저장소로서 여러 군데서 인정을 받는 수준에 이르렀군요.

더 자세한 정보는 ‘Denali’ No More: SQL Server 2012 announced, Focuses on BI and Big Data 를 참고하세요.

Microsoft Roslyn CTP 소개

마이크로소프트에서, 드디어 C# Script 엔진을 내놓았네요. 정식명칭은 아직 정해지지 않았고, 코드명이 Roslyn 입니다.

“The Roslyn CTP previews the next generation of language object models for code generation, analysis, and refactoring, and the upcoming support for scripting and interactive use of VB and C#.”

이라고 하네요^^

즉 C# 이나 VB의 컴파일러 코드를 공개해서, 사용자가 직접 사용할 수 있도록 했다 뭐 이건데… 간단하게 말해서 걍 Python Script 랑 비슷하다고 생각하시면 되겠습니다. (이러면 Roslyn 개발자들이 섭섭하려나?)

Roslyn2
저도 아직 분석 단계라 더 이상 자세한 설명은 힘들구요. 그래도 언제가는 나오겠지 하던 그런 기능들이 모두 포함되어서 나왔으면 합니다. 아마 그렇게 해야 Microsoft 사에서 Cloud 컴퓨팅의 PaaS 를 적용할 수 있을 것이라 생각됩니다.

구글에서는 현재 Python으로 PaaS 를 제공하는데, MS는 자신들의 언어로 제공해야 체면이 서겠죠?

참고: Roslyn Project Overview
참고: Introducing the Microsoft "Roslyn" CTP
참고: Microsoft previews compiler-as-a-service software

2011년 10월 16일 일요일

FluentNHibernate 을 이용한 IUserType 매핑

NHibernate의 기능 중에 가장 큰 장점 중에 하나가 IUserType, ICompositeUserType 을 이용하여, RDBMS 저장소의 컬럼과 속성을 1:1 매핑이라던지, Component 로 직접 매핑이 아닌, 무언가 2차적인 처리를 수행할 수 있는 장치를 두었다는 것입니다.

제가 가장 많이 쓰는 IUserType

  1. 암호화, 압축 등을 수행하여 저장소에 저장하고, 로드 시에는 원본 데이터로 복원하여 제공해 주고, WHERE 절도 자동으로 되는 그런 방식
  2. 여러 속성을 가지는 class 나 struct 를 하나의 Property 처럼 사용할 수 있도록 해주는 ICompositeUserType 을 이용하는 것읍니다. 에를 들면, 주차 (WeekOfYear)는 Year 와 WeekOfYear 두 값이 같이 저장되어야 하는데, 따로 작업하면 안되므려 YearAndWeek 라는 struct 로 정의하여 사용하고, 기간을 나타내는 TimeRange 같은 경우에는 StartTime, EndTime 을 항상 같이 가지고 다녀야 하기 때문에, Component 로 표현하기 보다 ICompositeUserType으로 표현하면 훨씬 좋습니다.

자 그럼 IUserType 으로 NHiberante용 사용자 정의 수형을 만드는 것은 여기 설명을 참고하는 것으로 하고, 이런 사용자 수형을 FluentNHibernate 으로 매핑하는 방법에 대해 설명 드리겠습니다.

우선 사용자 정의 수형을 가지는 엔티티 정의를 보시면,

[Serializable]
public class FUserTypeEntity : DataEntityBase<Int32>
{
public virtual string Name { get; set; }
public virtual string Password { get; set; }
public virtual string Password2 { get; set; }

public virtual string CompressedString { get; set; }

public virtual byte[] CompressedBlob { get; set; }

private TimeRange _activePeriod;

public virtual TimeRange ActivePeriod
{
get { return _activePeriod ?? (_activePeriod = new TimeRange()); }
set { _activePeriod = value; }
}

public virtual YearAndWeek ActiveYearWeek { get; set; }

public virtual Type LanguageType { get; set; }

public virtual DateTime? UpdateTimestamp { get; set; }

public override int GetHashCode()
{
if(IsSaved)
return base.GetHashCode();

return HashTool.Compute(Name, Password);
}
}


과 같습니다. 보시면, CompressedString, CompressedBlob는 IUserType 을 구현한 문자열이나 byte[] 을 압축하여 문자열로 저장하는 UserType 으로 매핑됩니다. 다음으로   ActivePeriod 나   ActiveYearWeek 는 ICompositeUserType 을 구현했습니다. ActivePeriod는 StartTime, EndTime 이라는 속성을 가지고 있는 class 이고, YearAndWeek 는 Year 와 WeekOfYear 값을 가지는 struct 입니다.



이제 Fluent 방식으로 FUserTypeEntity 를 매핑을 하면, 다음과 같습니다.



public class FUserTypeEntityMapping : ClassMap<FUserTypeEntity>
{
public FUserTypeEntityMapping()
{
Table("FUserTypeEntity");
DynamicInsert();
DynamicUpdate();
LazyLoad();

Id(x => x.Id).GeneratedBy.Native();

Map(x => x.Name);

Map(x => x.Password)
.CustomType<RijndaelEncryptStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.Password2)
.CustomType<AriaEncryptStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.CompressedString)
.CustomType<GZipStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.CompressedBlob)
.CustomType<SevenZipBlobUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.ActivePeriod)
.CustomType<TimeRangeUserType>()
.Columns.Clear()
.Columns.Add("ACTIVE_FROM_DATE")
.Columns.Add("ACTIVE_TO_DATE");

Map(x => x.ActiveYearWeek)
.CustomType<YearAndWeekUserType>()
.Columns.Clear()
.Columns.Add("ACTIVE_YEAR")
.Columns.Add("ACTIVE_WEEK");

Map(x => x.UpdateTimestamp).CustomType("Timestamp");
}
}



우선, Password, Password2 를 보시게 되면,  둘 다 암호화를 해서 저장하는 UserType 을 지정하고, 길이를 CLOB나 VARCHAR(MAX)를 할 수 있도록 합니다.  RijndaelEncryptStringUserType 은 미국 암호화 AES 규격을 따른 것이고,   AriaEncryptStringUserType 은 한국 암호화 규격을 따른 것입니다.



다음으로 압축 저장을 수행해주는 CompressString, CompressedBlob 를 보시면, GZip 으로 압축하던가, 7Zip 알고리즘으로 압축하여 저장해 줍니다. 물론, 로드 시에는 압축을 풀어서 엔티티에서는 Plain Text 나 Byte가 됩니다.



위 두 가지 방식은 모두 IUserType 을 구현한 Custom UserType 이므로, 속성:컬럼이 1:1입니다. 그러므로 CustomType<XxxxUserType>() 을 지정해 주면 알아서 됩니다. 물론 컬럼 명을 변경하고자 한다면,  .Column(“xxxx”) 를 사용하면 되겠지요^^



그럼 이번에는 ICompositeUserType을 사용한 속성에 대한 매핑을 보시죠. 위의 예에서는 ActivePeriod, ActiveYearWeek 입니다. ActivePeriod 는 컬럼 명에서도 유추할 수 있듯이, 기간을 나타냅니다. TimeRangeUserType 으로 수형을 지정하고, 자동으로 매핑되는 컬럼 명을 무시하고 (Columns.Clear()) 순서대로 컬럼 명을 새로 지정했습니다. (한 엔티티에 여러 개의 TimeRangeUserType 이 존재할 수 있으니까요)



FluentNHibernate 1.3 에서는 ICompositeUserType에 대한 매핑 방식이 달라졌지만, 그래도 아직은 FNH-1.2 가 주류이므로, 이렇게 작성했습니다.

FluentNHibernate 으로 CustomType 에 대한 매핑에 대한 설명이 별로 없는 것 같아 작성해 봤습니다…

2011년 10월 14일 금요일

HttpMultiFileHandler 소개

원본 : Http handler to combine multiple files, cache and deliver compressed output for faster page load
참고 : http://developer.yahoo.com/performance/rules.html

아이디어는 이렇습니다. 일반적으로 웹 페이지를 구성하는 것은 페이지 자체의 HTML과 각종 이미지, 그 다음에 부가적으로 CSS 등의 Style 관련 파일과 Javascript 파일들입니다.
이 때 브라우저가 특정 페이지를 로드할 때, 다음과 같은 일이 벌어집니다.

clip_image001
일반적인 Page Load 시의 통신 Timeline

자 보시다시피, Page의 본문이 다 다운되고 나면, 관련된 리소스들 (linked resources) 을 다운받기 위해 network round-trip을 수행합니다. 이때, 관련 리소스들이 아주 많을 경우, 한번에 최대 2개씩 다운로드를 받습니다. 그러니 여러 파일을 다운로드 받아야 할 경우에는 페이지 로딩 속도가 저하되는 것은 너무나 당연합니다.
이를 해결하기 위한 방안은 여러가지가 있습니다. (회사 도서 중에 Ultra-Fast ASP.NET 에 많은 방법이 있습니다.)
DNS를 모두 적어준다던가 하는 간단한 방법도 있습니다만,
여기서는 HttpHandler를 통해, 필요한 파일들을 한꺼번에 묶어서 보내는 방식을 써보기로 합시다. 즉 다운로드 받을 파일의 갯 수를 줄여서, round-trip을 최소화 하자는 얘기입니다. (이 것만으로도 속도가 빨라지는데, 압축까지 하게 되면 더욱 빨라질 것입니다.)
아래 그림에서 보듯이, CSS 두 개가 한꺼번에 내려 받고, 3개의 Javascript 파일도 동적으로 하나로 묶여서 다운로드가 됩니다.
이렇게 되면, Page Download시간이 기존보다 2배 이상 빨라질 것입니다.

clip_image002

HttpMultiFileHandler를 이용하여, 여러파일을 하나로 묶어 전송한다.
자 그럼 본격적으로 어떻게 사용하는지부터 살펴봅시다.
원 저작자는 일반적으로 다운로드 할 파일들을 묶음으로 정의하는 것은 환경설정에서 수행하였습니다. 대부분의 Page가 거의 같은 파일들을 다운로드 받으므로 그렇게 해도 무방합니다.

clip_image003

환경설정에서 appSettings 에 CSS 파일 묶음, Javascript 묶음을 설정합니다.
다음으로 실제로 위의 파일들을 하나로 묶어서 다운로드 할 수 있는 HttpHandler 를 등록합니다.

clip_image004

자 이제 모든 환경 설정은 끝났습니다.
예제 페이지를 작성하고, 페이지 구동이 어떻게 되는지 살펴봅시다.

clip_image005
clip_image006

Page 소스를 보면, 3가지 멀티 파일 묶음 다운로드가 있습니다.
1번은 web.config에서 설정된 CSS 파일들을 묶어서 다운로드 합니다. 그래서 웹페이지 화면이 노란 바탕에 빨강 글씨가 나타나게 된 것이구요.
2번은 web.config에서 설정된 javascript 파일 묶음에 추가로 필요한 파일을 "F" 인자에 할당하여 (Js3.js, Js4.js) 총 5개의 javascript 파일을 하나로 묶어서 다운로드 받습니다.
3번은 좀 특이하게 리소스 위치가 현재 웹 응용프로그램이 아닌, 외부에 있을 경우에도 리소스 들을 다운로드 받아서 하나의 파일로 묶어서 제공해 준다는 것입니다.
이제 대강 어떻게 돌아가는지는 아시겠죠?
그럼 원저작자가 성능을 위해 두 가지를 더 했는데 다음과 같은 기능입니다.
  1. 전송할 리소스를 압축하여 전송한다.
  2. 서버 메모리 캐시 (HttpContext.Current.Cache)에 파일 통합본 정보를 저장해 놓고, 다음 요청 시에 사용한다.
    물론 유효기간을 두어, 일정시간이 지나면, 폐기되도록 한다. - 이렇게 해야 어느 정도 최신 정보를 볼 수 있습니다.
위 두 가지 일은 서버 모듈을 개발하는 개발자라면 꼭 공부해 두고, 자기 것으로 만들어 보시기 바랍니다.
그럼 RCL에서는 원작자와 달리 뭘 더 추가했을까요?
  1. 비동기 IO 처리를 수행처리할 내용이 파일을 읽어서 응답 스트림에 쓰는 것이므로, 파일 읽기를 비동기 방식으로 수행한다면, 확장성이 보장됩니다.
    특히 외부 리소스에 대해서는 WebClient 를 이용하여, 비동기 방식으로 리소스를 다운로드 받도록 하여, 확장성 및 속도를 향상 시켰습니다.
  2. 병렬 처리처리할 파일이 복수 개이므로, 병렬로 파일을 읽게 하여, 응답 스트림에 쓴다면, 속도 향상이 클 것입니다. 특히 CPU가 많은 서버라면 속도 향상에 많은 기여를 할 것입니다.
  3. HttpHandler 자체를 IHttpAsyncHandler를 구현하여, 웹 응용프로그램의 확장성을 보장했습니다.
  4. 파일 묶음 정의를 고정시키지 않고, 추가할 수 있도록 했습니다. (원작자는 예제니까 그런거고)
  5. 멀티바이트 언어에 대한 대처원저자는 영어권이라 문제가 안되지만, 멀티바이트 언어를 사용하는 환경에서는 여러 파일을 하나의 Stream으로 묶을 때 스트림의 선두번지에 멀티바이트임을 나타내는 prefix 를 제거해줘야 합니다. 이 것 때문에 한 두 시간 헤맸습니다.http://developer.yahoo.com/performance/rules.html

NHibernate Code Mapping 비교 – Join

NHibernate 사용 시 매핑은 필수이고, 그 동안 HBM 만을 사용했었습니다.
이유는 없고, 많은 참고 자료와 익숙해져 있어서였습니다만, 회사 팀원들이 이제 NHibernate 자체에 많이 익숙해져서, 새로운 시도를 해 봤습니다.
물론 그 시도라는 것은 FluentNHibernate 를 이용한 Code Mapping 입니다.
FluentNHibernate 소개를 보시면 알겠지만, 가장 큰 특징은 Code로 Mapping 을 정의하는 것이고, 부가적으로 자동으로 Mapping 테스트도 할 수 있다는 장점이 있습니다.
그럼 오늘은 HBM, FluentNHibernate, NH 3.2 Build-In Code Mapping 을 간단한 예를 가지고 비교해 보겠습니다.
아주 간단한 예 중에 하나의 entity를 두 개의 Table 의 1:1 매핑으로 표현하는 방식인 <join /> 이라는 방식에 대한 구현을 보겠습니다.
엔티티 클래스는 다음과 같습니다.
 
using System;

namespace RCL.Data.NH.DomainModel.Mappings.JoinTable
{
 public class JoinMaster : DataEntityBase<Int32>
 {
  public virtual string Name { get; set; }
 
  public virtual string NickName { get; set; }
 
  public virtual string Description { get; set; }
 }
}



보시다시피, 3개의 Property가 있습니다. Name 속성은 “JoinMaster” 테이블에 저장되고, NickName과 Description은 “JoinDetail” 테이블에 저장되고, 1:1 매핑이 되게 합니다.

기본 HBM 방식으로 Mapping 을 정의한다면 다음과 같습니다.

결과를 보시면, 뭐 별거 아니잖아 하실 것입니다…




  
   
  

  

  
   
   
   
   
  
     
 

NHibernate 에 대해 초급자 수준이라도 쉽게 이해가 되실 것입니다. 

그럼 이런 매핑을 FluentNHibernate 를 이용하여 매핑한다면 다음과 같게 정의할 수 있습니다.
using FluentNHibernate.Mapping;

namespace RCL.Data.NH.DomainModel.Fluent.Mappings
{
 public class FJoinMasterMap : ClassMap
 {
  public FJoinMasterMap()
  {
   Table("FJoinMaster");
   LazyLoad();
   DynamicInsert();
   DynamicUpdate();

   Id(x => x.Id).Column("MasterId").GeneratedBy.Native();

   Map(x => x.Name);

   Join("FJoinDetail", m =>
                       {
                        m.Map(x => x.NickName);
                        m.Map(x => x.Description);
                       });
  }
 }
}

FluentNHibernate 를 직접 사용해보면, Lambda Expression을 사용하게 되어, 손 쉽게 Proeprty 를 매핑할 수 있도록 Intellisense에 나타나게 되어 매핑이 손쉽게 됩니다.
(이 매핑에서 “F” 라는 접두사를 둔 것은 HBM 방식의 매핑과 같은 DB에 생성하기 위해 구분을 위해 넣었습니다.)

다음은 NH-3.2에 새로 제공되는 Built-In Code Mapping 방식을 살펴보겠습니다.


using NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;

namespace RCL.Data.NH.DomainModel.Loquacious
{
 public class CJoinEntityMap : ClassMapping
 {
  public CJoinEntityMap()
  {
   Table("CJoinEntity");
   Lazy(true);
   DynamicInsert(true);
   DynamicUpdate(true);

   Id(x => x.Id, c => c.Generator(Generators.Native));

   Property(x => x.Name);

   Join("CJoinEntityDetail",
        jm =>
        {
         jm.Property(x => x.NickName);
         jm.Property(x => x.Description, c => c.Length(9999));
        });
  }
 }
}


NH-3.2 Code Mapping 방식은 Lambda Expression을 사용했지만, Fluent 방식 (Method Chain) 을 사용하지 않아, 코드가 좀 길어진 느낌입니다. 다만 메소드 명칭이 HBM의 XML Element 요소명과 같아, 쉽게 기억할 수 있는 점이 장점입니다.

HBM 방식의 장점은 기존 매핑에 대한 많은 방식이 HBM으로 되어 있어, 공부하기에는 좋습니다. 즉 기초를 닦기 위해서는 HBM을 꼭 학습해야 합니다.

FluentNHibernate 방식은 실행 시 HBM을 생성하여, NHibernate가 Deserialize를 수행하는 방식이라, 첫 실행 시에 성능 문제가 제기될 수 있지만, 웬만한 시스템에서는 별 의미 없는 단점이라 할 수 있습니다. 코드 매핑의 장점으로는 “Magic String” 을 사용하지 않아, 유지보수나 Refactoring 시에 상당히 유용하다고 볼 수 있습니다.

NH-3.2 Code Mapping 방식은 Code Mapping의 장점과 내부 API 라는 장점으로 XML Deserialize 단계를 생략하게 됩니다. 결국 NHibernate 초기화 비용이 가장 적게 든다고 볼 수 있습니다. 다만 매핑 방식이 Fluent 방식이 아니라, 코드가 길어지고, 가독성이 떨어진다는 점이 단점이라 볼 수 있습니다. 또한 첫 번째 버전이라 그런지, MySQL 이나 PostgreSQL 에서는 매핑이 제대로 안 되는 경우가 있습니다. (앞으로 나올 NH 3.2.1 도 마찮가지로 안됩니다)

여러분은 어떤 방식을 쓰시겠습니까?
저희 회사 내부에서는 압도적으로 FluentNHibernate가 인기가 있더군요

2011년 10월 13일 목요일

코드 테스트


코드 예
static void Main(string[] args)
{
    _path = AppDomain.CurrentDomain.BaseDirectory;
    if (!File.Exists(Path.Combine(_path, RedisServer)))
        Exit("Couldn`t find " + RedisServer);

    if (!File.Exists(Path.Combine(_path, RedisCLI)))
        Exit("Couldn`t find " + RedisCLI);

    if (Environment.UserInteractive)
    {
        SetConsoleCtrlHandler(ConsoleCtrlCheck, true);
        //Console.CancelKeyPress += (sender, eventArgs) => StopRedis();
        StartRedis(args.Length == 1 ? args[0] : null);
    }
    else
        Run(new Program());
}

이건데요… 제대로 되나요?
이제 제대로 됩니다. Syntax Highlight 땜시 항상 글 쓰기 힘들었는데, 이제 좀 편해지려나?

2011년 10월 12일 수요일

여러 가지 NoSQL DB 비교 자료 및 의견

요즘 한창 인기가 있다 보니 너무도 다양한 방식의 NoSQL DB들이 나옵니다. 대표적인 RDBMS 업체인 Oracle에서도 NoSQL을 한다니 말 다했죠. 그만큼 현재 기술 흐름에서 NoSQL이 꼭 필요한 기술임에는 틀림 없습니다… 다만… 처음 접하시는 분들은 “아니 그게 뭔데?” 부터 시작하겠죠?

그런 분들은 우선 대표적인 MongoDB 나 Membase 를 사용해보시던가, 자료를 읽어보시기 바랍니다. 기존 RDBMS 와의 차이를 보실 수 있습니다.

여기서는 다들 NoSQL 이라고 하는데 (Wikipedia 참고하시면 더 많습니다만…), 너무 많아 특징을 분석하기가 힘듭니다. 그리고, 자신이 필요로 하는 기능을 가진 제품이 자신이 적용하고자 하는 환경을 지원하는지도 다 파악하려면, 지레 겁 먹을 수 있겠죠?

여러 NoSQL DB 비교 (http://kkovacs.eu/cassandra-vs-mongodb-vs-couchdb-vs-redis) 사이트를 참고하시면, 어렴풋이나마, 난 어떤 것을 사용해 보면 좋을 것 같다고 느끼실 수 있습니다. (물론 NoSQL DB의 필요성에 공감을 해야지요)

대략 보시면 CouchDB 는 MDM 에 알맞고, Redis 는 Log, Cache, ETL 등에 알맞고, MongoDB 는 팔방미인이고, Membase는 메모리 기반이므로, 속도는 빠르고, 용량이 작은 분야에 적용하고… 뭐 그런 내용들입니다…

제가 실전에 쓴 것은 현재까지 Membase와 MongoDB 두 가지 입니다만, 앞으로 Redis 를 실전에 적용하기 위해, 테스트 중입니다.

Membase 는 소규모 웹 사이트의 ViewState, OutputCache, Session 정보 및 Data Cache 용으로 주로 사용하였고,
MongoDB는 위의 Data Cache 뿐 아니라, 대용량 파일 저장소로도 사용하였고, ETL 변환 작업에서도 사용하였습니다. (내부에서는 javascript 로)
Redis 는 실시간 Data 전송이 아주 많은 곳에 사용하려고 합니다. 실시간 Data 수집 및 분석 시에는 Redis 처럼 메모리에서 처리하고, 용량이 큰 파일 같은 것은 따로 처리 할 수 있으면 좋겠지요…

좀 다른 얘기지만, 2011년 8월 기준으로 NoSQL Job Trends (http://css.dzone.com/news/nosql-job-trends) 라고, 어떤 NoSQL을 다룰 줄 아는 사람에 대한 일자리 비율입니다. 3군데 자료가 좀 다르지만, 대부분 MongoDB 가 압도적이고, Canssandra와 Redis 가 뒤를 쫒고 잇는 형국입니다.

물론 제가 아예 관심을 두지 않는 HBase, Hadoop 관련은 논외로 했습니다. 이쪽 분야에는 제가 문외한이라서요^^

ASP.NET 비동기 프로그래밍

비동기 프로그래밍 방식은 여러 분야에서 처리량(throughput) 증대, 확장성 향상에 좋은 방식임에는 틀림없습니다.

다만 사용 방법이 직관적이지 못해, 개발자들이 쉽게 수용하지 못하는 경우가 많습니다. 이에 몇 가지 자료와 함께 비동기 프로그래밍을 아주 쉽게 적용할 수 있는 방법을 알려 드리도록 하겠습니다.

우선 참고자료로는

ASP.NET에서 비동기 프로그래밍을 활용하여 확장성이 우수한 응용프로그램 작성

MSDN : PageAsyncTask

를 보시면 좋습니다. 다만 위의 예제는 사용하지 않을 것이고, 방법도 전혀 다르므로, 개념만 이해하시면 됩니다.

  1. ASP.NET Web Form 에서 비동기 프로그래밍

ASP.NET에서 비동기 프로그래밍의 개념을 가장 잘 나타낸 그림입니다.

보시다시피, 1번 요청 스레드에서 비동기로 작업을 시작하면, 스레드가 없어지고, 비동기 IO 작업으로 DB에 대한 처리를 담당합니다. 그 후 작업이 완료되면, 새로운 스레드가 만들어져, 비동기 작업을 마무리 합니다.

 

clip_image001

화면 캡처: 2010-08-19 오후 1:39

 

위의 방식은 일반적인 Computed-bounded 비동기 방식과는 달리 IO-bounded 비동기 방식이라 합니다. IO-bounded 비동기 방식은 처리율이 현저히 떨어지는 파일, Network, DB등의 IO 작업 시에 작업 스레드가 멍청히 기다리면서, 계속된 context switching에 대한 부담을 가지지 않도록 해주고, 동시에 여러 요청이 오더라도, 동시 활성화된 스레드의 수가 작으므로, 멀티스레드 관리에 드는 부담이 현저히 줄어들게 됩니다.

이에 Data-Centric 한 응용프로그램이거나 메신저 서버 등 IO 작업이 많은 서버 쪽에서도 비동기 방식으로 구현하게 되면, 많은 수의 동시 요청을 수용할 수 있게 됩니다.(이런 걸 확장성이라 합니다.)

그럼 기존 ASP.NET 2.0에서는 Web Form에 대해 두 가지 방식의 비동기 프로그래밍을 지원합니다. 첫 번째가 Page.AddOnPreRenderCompleteAsync 이고, 두 번째가 PageAsyncTask 입니다.

근데, 두 방식 모두 다 상당히 복잡합니다…

그래서 .NET 4.0 TPL (Task Parallel Library)의 TaskFactoryFromAsync 메소드를 이용하여 아주 쉽게, 비동기 Web Form을 구현하는 방법에 대해 알려드리겠습니다.

 

clip_image002

비동기 방식의 Web Form 수행

 

예제 코드는 보시다시피 비동기 작업에 대한, 시뮬레이션을 위해 Thread.Sleep() 함수를 이용합니다.

실제 작업은 DoProcessTask() 로 기존 개발과 똑같이 구현하시면 됩니다. 그 후 비동기 방식으로 실행하기 위해, TPL의 TaskFactory.FromAsync() 메소드를 이용하는 것입니다. Line 21에서 실제 작업하고자 하는 함수의 인스턴스를 받아, line 24 처럼 FromAsync 메소드를 호출하기만 하면 됩니다.

물론 FromAsync 메소드는 overload가 상당히 많으므로, 작업함수(DoProcessTask())의 다양한 signature를 수용할 수 있습니다.

응용을 위해서는, 위의 DoProcessTask() 메소드 내에서, DB에 접속하여 정보를 가져오는 작업을 구현하면 됩니다.

TPL을 활용해서 더 좋은 장점은 FromAsync로 만드는 작업을 여러 개 만들면, 병렬로 수행할 수 있다는 것입니다.

즉 여러 개의 비동기 IO-Bounded 작업을 병렬로 수행할 수 있다는 뜻입니다.

clip_image003

복수의 비동기 작업을 병렬로 수행하는 예

이렇게 사용한다면, Bottleneck도 걸리지 않고, 병렬로 작업을 수행하게 되므로, 처리율과 속도 모두 만족스러운 향상을 가져올 것입니다.

TPL 사용이 .NET 4.0이상에서만 가능하다는 편견은 버리십시요. Reactive Extensions for .NET 3.5.1에 System.Threading.dll 이 TPL을 제공하니, .NET 3.5.1에서도 병렬 프로그래밍이 가능합니다. (RCL도 마찮가지구요)

  1. IHttpAsyncHandler를 구현한 비동기 프로그래밍

우선 MSDN의 비동기 HTTP 처리기 만들기 를 보십시요. 설명서대로 구현하려면, 여러 Http 처리기를 만들어야 하는 사람의 입장에서, 상당히 부담이 될 것입니다.

 

<<HttpAsyncHandlerBase.cs>>

clip_image004

이에 IHttpAsyncHandler 를 구현한 기본클래스를 제작하여, 일반 개발자는 Web Form 비동기 방식 구현과 마찮가지로, 전혀 비동기에 대한 어떠한 구현도 하지 않도록 해줍니다. (첨부파일 참고)

예제는 다음과 같습니다.

clip_image005

IHttpAsyncHandler 를 구현한 예제

 

위 예제 코드에서는 비동기 관련 코드는 전혀 보이지 않습니다. 다만, 개발자는 DoProcessRequest() 메소드를 재 정의하여, 자신이 원하는 코드를 작성하면 됩니다. DB 작업을 기존 방식대로 작업해도 됩니다.

어떻습니까? MSDN 에 나온 방식은 제가 봐도, 구현 자체가 문제가 아니라 확산이 문제였는데, 위의 두 가지 방식은 아주 손 쉽게 작업할 수 있도록 했습니다.

물론 HttpAsyncHandlerBase의 경우에는, 상속 체계를 바꾸는 문제가 있을 수 있지만, 많은 개발자들이 HttpHandler 를 도입하는 단계라면, 도입 효과가 상당하리라 예상됩니다.^^

  1. 참고 자료
    1. CLR via C# 2nd Ed. Chapter 23, 24
    2. CLR via C# 3rd Ed. Chapter 25 ~ 29
    3. Pro .NET Parallel Programming in C#
    4. Pattern Of Parallel Programming (svn/public/research 에 parallel 관련 )

FluentNHibernate 으로 ManyToMany 매핑하기

HBM으로 매핑하는 것은 많은 예제가 있는데, FluentNHibernate으로 매핑하는 예제는 FluentNHibernate 사이트에 달랑 한 줄의 코드로 설명이 끝납니다.
아니 ManyToMany가 얼마나 복잡한 건데 달랑 한 줄에 끝내는 겨? 특히나 양방향 (bi-directional) 인 경우에는 둘 중 한 군데에 inverse=true 를 줘야 하는데, 그런 예도 없고 말야…
어쩔 수 없이 테스트를 해봤습니다… 우선은 상세한 매핑 설정을 통해 제대로 작동하는지 검증해 봤습니다.

Many-To-Many 상세 설정
   1: public class FDepartmentMap : ClassMap<FDepartment>
   2: {
   3:     public FDepartmentMap()
   4:     {
   5:         Table("FDepartment");
   6:         DynamicInsert();
   7:         DynamicUpdate();
   8:  
   9:         Id(x => x.Id).Column("DepartmentId").GeneratedBy.Native();
  10:  
  11:         Map(x => x.Code);
  12:         Map(x => x.Name);
  13:  
  14:         References(x => x.Parent)
  15:             .Column("ParentId")
  16:             .Access.Property()
  17:             .Cascade.SaveUpdate()
  18:             .Fetch.Select()
  19:             .LazyLoad(Laziness.Proxy);
  20:  
  21:         HasMany(x => x.Children)
  22:             .Access.CamelCaseField(Prefix.Underscore)
  23:             .Cascade.AllDeleteOrphan()
  24:             .Inverse()
  25:             .LazyLoad()
  26:             .AsSet(SortType.Natural);
  27:  
  28:  
  29:         Component<TreeNodePosition>(x => x.NodePosition,
  30:                                     p =>
  31:                                     {
  32:                                         p.Map(x => x.Order).Column("TreeOrder");
  33:                                         p.Map(x => x.Level).Column("TreeLevel");
  34:                                     });
  35:  
  36:         HasManyToMany(x => x.Users)
  37:             .Table("FDepartmentMember")
  38:             .ParentKeyColumn("DepartmentId")
  39:             .ChildKeyColumn("UserId")
  40:             .Inverse()
  41:             .LazyLoad()
  42:             .AsSet();
  43:     }
  44: }



보시다시피 many-to-many  매핑을 수행하는 부분은 소속 부서원들과의 매핑이고, 겸직을 고려한 설계입니다.

관계 table 명은 “FDepartmentMember” 이고, DepartmentId, UserId 의 값 설정으로 됩니다. 그리고, 중복을 방지하기 위해 ISet<FUser> 이구요…



이 방법은 제가 그 동안 HBM으로 매핑하는 방식에 익숙해서 모두 설정해 준 것이고, 실제로 다음과 같이만 해줘도




   1: HasManyToMany(x => x.Users)
   2:     .Table("FDepartmentMember")
   3:     .Inverse()
   4:     .AsSet();

똑 같은 결과를 얻는다는 것입니다.



FluentNHibernate의 AutoMapping 기능이 상당히 막강하여, 왠만한 정보는 Entity 클래스로부터 추출해 내므로, 굳이 매핑 시 지정하지 않아도 된다는 점입니다.

보면 볼 수록 FluentNHibernate은 매력적인 놈이라 생각됩니다.

Newtonsoft.Json.Net.dll 을 사용하여 직렬화/역직렬화하기

 
객체를 Json 형식으로 직렬화/ 역직렬화를 수행하여, 다른 형식의 수형으로 매핑을 수행합니다.
보통의 경우 Binary 방식의 경우는 객체가 SerializableAttribute가 있어야 하고, Xml 방식은 성능이 안나옵니다.
이럴 때, Json 또는 Bson 방식을 사용하게 되면 상당히 유리합니다.
 
   1: using System;
   2: using Newtonsoft.Json;
   3:  
   4: namespace RCL.Core
   5: {
   6:     /// <summary>
   7:     /// JSON 포맷으로 객체 Mapping을 수행합니다.
   8:     /// </summary>
   9:     public static partial class JsonUtil
  10:     {
  11:         /// <summary>
  12:         /// 객체를 JSON 직렬화/역직렬화를 통해, T 수형의 인스턴스를 빌드합니다.
  13:         /// </summary>
  14:         /// <typeparam name="T">대상 수형</typeparam>
  15:         /// <param name="source">원본객체</param>
  16:         /// <returns>매핑된 T 수형의 객체</returns>
  17:         public static T Mapping<T>(object source)
  18:         {
  19:             return Mapping<T>(source, DefaultJsonSerializerSettings);
  20:         }
  21:  
  22:         /// <summary>
  23:         /// 객체를 JSON 직렬화/역직렬화를 통해, T 수형의 인스턴스를 빌드합니다.
  24:         /// </summary>
  25:         /// <typeparam name="T">대상 수형</typeparam>
  26:         /// <param name="source">원본객체</param>
  27:         /// <param name="serializerSettings">JSON 직렬화 설정 정보</param>
  28:         /// <returns>매핑된 T 수형의 객체</returns>
  29:         public static T Mapping<T>(object source, JsonSerializerSettings serializerSettings)
  30:         {
  31:             source.ShouldNotBeNull("source");
  32:  
  33:             T target;
  34:  
  35:             if(TryMapping(source, serializerSettings, out target))
  36:                 return target;
  37:  
  38:             return default(T);
  39:         }
  40:  
  41:         /// <summary>
  42:         ///  객체를 JSON 직렬화/역직렬화를 통해, T 수형의 인스턴스를 빌드합니다.
  43:         /// </summary>
  44:         /// <typeparam name="T">대상 수형</typeparam>
  45:         /// <param name="source">원본객체</param>
  46:         /// <param name="additionalMapping">부가 매핑 정보</param>
  47:         /// <returns>매핑된 T 수형의 객체</returns>
  48:         public static T Mapping<T>(object source, Action<object, T> additionalMapping)
  49:         {
  50:             source.ShouldNotBeNull("source");
  51:             var result = Mapping<T>(source);
  52:  
  53:             if(additionalMapping != null)
  54:             {
  55:                 additionalMapping(source, result);
  56:             }
  57:             return result;
  58:         }
  59:  
  60:         /// <summary>
  61:         /// 객체를 JSON 직렬화/역직렬화를 통해, <paramref name="targetType"/> 수형의 인스턴스를 빌드합니다.
  62:         /// </summary>
  63:         /// <param name="source">원본 객체</param>
  64:         /// <param name="targetType">대상 수형</param>
  65:         /// <returns>매핑된 대상 수형의 객체</returns>
  66:         public static object Mapping(object source, Type targetType)
  67:         {
  68:             return Mapping(source, targetType, DefaultJsonSerializerSettings);
  69:         }
  70:  
  71:         /// <summary>
  72:         /// 객체를 JSON 직렬화/역직렬화를 통해, <paramref name="targetType"/> 수형의 인스턴스를 빌드합니다.
  73:         /// </summary>
  74:         /// <param name="source">원본 객체</param>
  75:         /// <param name="targetType">대상 수형</param>
  76:         /// <param name="serializerSettings">JSON 직렬화 설정 정보</param>
  77:         /// <returns>매핑된 대상 수형의 객체</returns>
  78:         public static object Mapping(object source, Type targetType, JsonSerializerSettings serializerSettings)
  79:         {
  80:             source.ShouldNotBeNull("source");
  81:             targetType.ShouldNotBeNull("targetType");
  82:  
  83:             object target;
  84:  
  85:             if(TryMapping(source, targetType, serializerSettings, out target))
  86:                 return target;
  87:  
  88:             return null;
  89:         }
  90:  
  91:         /// <summary>
  92:         /// 원본 객체를 JSON 포맷으로 직렬화를 수행하고, 대상 형식으로 역직렬화를 수행합니다. 두 수형이 달라도 상관없습니다.
  93:         /// </summary>
  94:         /// <param name="source">원본 객체</param>
  95:         /// <param name="target">대상 객체</param>
  96:         /// <returns>Mapping 성공 여부</returns>
  97:         /// <see cref="ObjectMapper"/>
  98:         public static bool TryMapping<T>(object source, out T target)
  99:         {
 100:             return TryMapping<T>(source, DefaultJsonSerializerSettings, out target);
 101:         }
 102:  
 103:         /// <summary>
 104:         /// 원본 객체를 JSON 포맷으로 직렬화를 수행하고, 대상 형식으로 역직렬화를 수행합니다. 두 수형이 달라도 상관없습니다.
 105:         /// </summary>
 106:         /// <param name="source">원본 객체</param>
 107:         /// <param name="target">대상 객체</param>
 108:         /// <param name="serializerSettings">JSON 직렬화 설정 정보</param>
 109:         /// <returns>Mapping 성공 여부</returns>
 110:         /// <see cref="ObjectMapper"/>
 111:         public static bool TryMapping<T>(object source, JsonSerializerSettings serializerSettings, out T target)
 112:         {
 113:             if(source == null)
 114:             {
 115:                 target = default(T);
 116:                 return false;
 117:             }
 118:  
 119:             Type targetType = typeof(T);
 120:             target = default(T);
 121:             object targetObject;
 122:  
 123:             var result = TryMapping(source, targetType, serializerSettings, out targetObject);
 124:             if(result)
 125:                 target = (T)targetObject;
 126:  
 127:             return result;
 128:         }
 129:  
 130:         /// <summary>
 131:         /// 원본 객체를 JSON 포맷으로 직렬화를 수행하고, 대상 수형으로 역직렬화를 수행합니다. 두 수형이 달라도 상관없습니다.
 132:         /// </summary>
 133:         /// <param name="source">원본 객체</param>
 134:         /// <param name="targetType">대상 객체의 수형</param>
 135:         /// <param name="target">대상 객체</param>
 136:         /// <returns>Mapping 성공 여부</returns>
 137:         /// <see cref="ObjectMapper"/>
 138:         public static bool TryMapping(object source, Type targetType, out object target)
 139:         {
 140:             return TryMapping(source, targetType, DefaultJsonSerializerSettings, out target);
 141:         }
 142:  
 143:         /// <summary>
 144:         /// 원본 객체를 JSON 포맷으로 직렬화를 수행하고, 대상 수형으로 역직렬화를 수행합니다. 두 수형이 달라도 상관없습니다.
 145:         /// </summary>
 146:         /// <param name="source">원본 객체</param>
 147:         /// <param name="targetType">대상 객체의 수형</param>
 148:         /// <param name="serializerSettings">JSON 직렬화 설정 정보</param>
 149:         /// <param name="target">대상 객체</param>
 150:         /// <returns>Mapping 성공 여부</returns>
 151:         /// <see cref="ObjectMapper"/>
 152:         public static bool TryMapping(object source, Type targetType, JsonSerializerSettings serializerSettings, out object target)
 153:         {
 154:             var result = false;
 155:             target = null;
 156:  
 157:             try
 158:             {
 159:                 source.ShouldNotBeNull("source");
 160:                 targetType.ShouldNotBeNull("targetType");
 161:                 serializerSettings.ShouldNotBeNull("serializerSettings");
 162:  
 163:                 if(IsDebugEnabled)
 164:                 {
 165:                     log.Debug("원본 객체를 JSON 포맷으로 직렬화를 수행하고, 대상 수형[{0}]으로 역직렬화를 수행합니다.", targetType.FullName);
 166:                     log.Debug("source=[{0}], targetType=[{1}], serializerSettings=[{2}]", source, targetType, serializerSettings);
 167:                 }
 168:  
 169:                 var jsonText = SerializeAsText(source, serializerSettings);
 170:                 target = DeserializeFromText(jsonText, targetType, serializerSettings);
 171:  
 172:                 result = true;
 173:  
 174:                 if(IsDebugEnabled)
 175:                     log.Debug("원본 객체로부터 대상 객체로 매핑을 수행했습니다. target=[{0}]", target);
 176:             }
 177:             catch(Exception ex)
 178:             {
 179:                 if(log.IsWarnEnabled)
 180:                 {
 181:                     log.Warn("JSON 포맷을 이용한 객체 Mapping 작업에 실패했습니다.");
 182:                     log.Warn(ex);
 183:                 }
 184:             }
 185:             return result;
 186:         }
 187:     }
 188: }