NHibernate 엔티티를 외부에 서비스하기 위해서는 전통적 방식 중에 하나가 DTO (Data Transfer Object) 패턴을 사용하는 것입니다. 물론 다른 ORM도 마찮가지구요.
다만 문제는 NHibernate 의 Association 이 유지되느냐의 문제인데, 유지를 원할 수도 있고, 안 원할 수도 있습니다.
Entity Framework 처럼 WCF DataService를 이용하면, Domain Layer의 모든 정보를 간단하게 외부에 서비스 (노출) 할 수 있습니다. 정말 환상이긴 합니다만, OOP 의 객체 안정성을 위해, 대부분의 정보를 은폐하는 것을 추천하듯이, 있는 그대로 노출하는 것보다 DTO 패턴을 사용하여, 원하는 내용만 서비스(노출) 하는 것을 더 추천하고 싶습니다.
이 예를 DB 간의 Data 공유 시에 Table 들을 OPEN 해주는 것과 VIEW 를 만들어서 제공하는 것은 다른 의미가 있습니다. 실제 Data를 쓸 수 없다하더라도, 구조를 알게되면 보안에 문제가 생기겠죠.
또 한가지 문제점은 서비스마다 VIEW 를 만든다던가, 공통의 큰 VIEW 하나를 만든다면, 유지보수나 성능상에 문제가 큽니다.
여기 NHibernate 엔티티를 DTO로 만들 때, 단순 엔티티가 아니라 Association 된 부분도 같이 자동으로 Mapping 해주는 기능을 가진 AutoMapper 를 사용해 봅시다.
원문 : AutoMapper 의 Nested Mappings
원문을 보시면 아 하시겠지만, NHIbernate의 Lazy Loading 을 위한 Proxy에 대해서도 과연 될까요? 그럼 함 해보지요^^
우선 NHibernate 엔티티로서 Parent-Child 를 구성했습니다.
[Serializable]
public class Parent : DataEntityBase<Guid>, IParent
{
public Parent()
{
Id = Guid.NewGuid();
Version = -1;
}
public virtual int Version { get; set; }
public virtual int Age { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
private IList<Child> _children;
public virtual IList<Child> Children
{
get { return _children ?? (_children = new List<Child>()); }
protected set { _children = value; }
}
public override int GetHashCode()
{
if(IsSaved)
return base.GetHashCode();
return HashTool.Compute(Id, Version);
}
}
[Serializable]
public class Child : DataEntityBase<Guid>
{
public Child()
{
Id = Guid.NewGuid();
Version = -1;
Name = "Some Child";
}
public Child(string name, Parent parent):this()
{
Name = name;
Parent = parent;
}
public Child(Guid id, string name, Parent parent): this(name, parent)
{
Id = id;
}
public virtual int Version { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Parent Parent { get; set; }
public override int GetHashCode()
{
return IsSaved ? base.GetHashCode() : HashTool.Compute(Parent, Name);
}
}
============================================================
자 그럼 Parent – Child 에 대응되는 DTO 들을 정의해보면,
[Serializable]
public class ParentDTO : ValueObjectBase
{
public ParentDTO(string name, int age)
{
Name = name;
Age = age;
}
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
private IList<ChildDTO> _children;
public IList<ChildDTO> Children
{
get { return _children ?? (_children = new List<ChildDTO>()); }
set { _children = value; }
}
public override int GetHashCode()
{
return HashTool.Compute(Name);
}
public override string ToString()
{
return string.Format("ParentDTO# Id=[{0}], Name=[{1}], Age=[{2}]", Id, Name, Age);
}
}
[Serializable]
public class ChildDTO : ValueObjectBase
{
public Guid Id { get; set; }
public int Version { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ParentDTO Parent { get; set; }
public override int GetHashCode()
{
return HashTool.Compute(Id);
}
public override string ToString()
{
return string.Format("ChildDTO# Id=[{0}], Name=[{1}], Description=[{2}], Parent=[{3}]", Id, Name, Description, Parent);
}
}
두 매핑 클래스들이 유사하지요? 다만 NHibernate 엔티티들은 DataEntityBase<T> 라는 NHIbernate용 클래스를 상속 받고, DTO 들은 일반 Persistent Object를 나타내는 ValueObjectBase 클래스를 상속받습니다. 그리고 둘 간의 매핑 정보는 양방향 관계를 가지도록 했습니다.
자 그럼 이제 Mapping 이 제대로 되는지 확인해 봅시다.
/// <summary>
/// AutoMapper 를 이용하여, Association 을 가진 엔티티도 DTO로 매핑이 가능합니다.
/// 즉 Parent-Child 를 ParentDTO-ChildDTO 로 매핑이 가능합니다.
/// </summary>
[TestFixture]
public class DtoMappingFixture : NHRepositoryTestFixtureBase
{
#region << logger >>
private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
#endregion
protected override void OnTestFixtureSetUp()
{
base.OnTestFixtureSetUp();
Mapper.CreateMap<Parent, ParentDTO>();
Mapper.CreateMap<Child, ChildDTO>();
Mapper.AssertConfigurationIsValid();
}
[Test]
public void MapToDto()
{
UnitOfWork.CurrentSession.Clear();
var parent = Repository<Parent>.Get(parentsInDB[0].Id);
Assert.IsNotNull(parent);
Assert.AreEqual(parentsInDB[0].Name, parent.Name);
Assert.AreEqual(2, parentsInDB[0].Children.Count);
Assert.AreEqual(2, parent.Children.Count);
var parentDTO = Mapper.Map<Parent, ParentDTO>(parent);
Assert.IsNotNull(parentDTO);
Assert.AreEqual(parentsInDB[0].Name, parentDTO.Name);
Assert.AreEqual(2, parentDTO.Children.Count);
foreach(var childDto in parentDTO.Children)
Console.WriteLine(childDto);
}
}
보시다시피 NUnit 을 이용하여 테스트 코드를 작성하였습니다.
우선 테스트 시작 시에 OnTestFixtureSetUp()에서 CreateMap 을 이용하여 각각 Parent – ParentDTO, Child – ChildDTO 에 대한 매핑을 정의하고, 매핑이 유효한지 확인 합니다.
이제 엔티티를 로드하고, Mapper.Map 을 통해 Parent 엔티티를 매핑해서 ParentDTO 를 만들었습니다. 단지 그것만 했는데, parentDTO.Children 에 값이 제대로 들어오는군요.
이제 DTO를 이용한 외부 서비스가 상당히 간단해질 것입니다. 물론 WCF DataService용으로 NHibernateDataContext를 만들어도 되지만, 전 DTO 방식이 노가다가 좀 더 들어가서 고생은 되지만, 유연성이나 보안등에서 더 땡기네요^^
울 회사 PMS 팀에서 이거 보면 뒤집어지겠군요^^
PS. Assertion을 해야 하는데, 어제 밤에 갑자기 해보는 바람에 Console.WriteLine 을 써 버렸네요… 여러분은 Assertion을 하세요. 꼭!!!
댓글 없음:
댓글 쓰기