.NETFRAMEWORK 3.0의 가장 큰 변화는 LINQ(통합언어쿼리)의 포함입니다.
LINQ의 목적은 데이터 소스 독립적인 일관성 있는 접근, 컴파일 타임에 디버깅을 지원하는 것입니다.
LINQ 사용의 핵심은 제공되는 QueryProvider를 사용하거나 IQueryable을 핸들링 하는 것입니다.
여기서 우리가 궁금해 하는 IQueryable, IQueryable<T>가 등장합니다.
이 낯설은 IQueryable이 도대체 무엇이며, 왜 필요한가 어떻게 사용해야 하나 하는 숙제를 던져 주는 것이지요.
IQueryable을 어떻게 사용해야 하나 라는 것은 개발자들에게 도전 입니다.
어디에 위치해야 하나, 얼마만큼 노출 해야 하나 는 Layered Architecture를 구현함에 있어 고민 꺼리 입니다.
IQueryable, IQueryable<T> 어느 별에서 왔니?
IQueryable 은 Query Objects [Fowler PoEAA] 의 개념이 .NET FRAMEWORK에 구현된 것입니다.
"
Query Object
An object that represents a database query.
A Query Object is an interpreter [Gang of Four], that is, a structure of objects that can form itself into a SQL query. You can create this query by refer-ring to classes and fields rather than tables and columns. In this way those who write the queries can do so independently of the database schema and changes to the schema can be localized in a single place.
"
Query Objects [Fowler PoEAA] http://martinfowler.com/eaaCatalog/queryObject.html
파울러가 정의한 Query Objects의 핵심은 특정 데이터 소스 Query Lanaguge 와 소비자 사이에서 통역하는 역할을 하며, 클래스와 필드로 작업할 수 있게해 주며, 데이터 소스와 독립적인 관리코드의 한 장소에서 관리할 수 있게 지원해 주는 객체를 의미합니다. Criteria 를 지원해 주는 것은 필수 기능이죠. 클래스와 필드를 이용한 Criteria 를 의미합니다.
MSDN IQueryable 인터페이스 http://msdn.microsoft.com/ko-kr/library/system.linq.iqueryable.aspx 의 정의는 아래와 같습니다.
데이터 형식이 지정되지 않은 특정 데이터 소스에 대한 쿼리를 실행하는 기능을 제공합니다.
네임스페이스: System.Linq
어셈블리: System.Core(System.Core.dll)
public interface IQueryable : IEnumerable
MSDN IQueryable<T> 인터페이스
http://msdn.microsoft.com/ko-kr/library/bb351562(v=VS.100).aspx
네임스페이스: System.Linq
어셈블리: System.Core(System.Core.dll)
데이터 형식이 알려진 특정 데이터 소스에 대한 쿼리를 실행하는 기능을 제공합니다.
public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
IQueryable, IQueryable<T>를 노출시키는 범위를 정하는 것은 구현하는 비즈니스 도메인에 의존적 입니다. 다만, 가능한 한 장소에 Encapsulation 되어 존재 시키는 것이 Loose Coupling , Higher Cohesion 과 SRP를 완성시키는 것이죠.
Query Objects 의 고려 사항
1. 통역비용이 발생한다.
2. 일관성 있는 쿼리를 지원 할때 데이터 소스 특정 파워를 잃을 수 있다.
하지만 , 전체적으로는 비용 감소를 기대할 수 있으며, 레이어 아키텍처 도입의 장점과 단점으로 비교해 볼 수 있습니다.
참조 :
Cohesion And Coupling
C-Thinker 님의 많은 포스트를 읽고 매우 상쾌해지는 느낌과 동시에 많은 도움이 되었습니다.
하지만
Entity Framework 4.0 - 아직도 헤메는 MS 라는 포스트는 많은 부분에 동의를 하지 못하였고, 이렇게 EF에 대한 변론(?) 을 하는 포스트를 작성하게 되었습니다.
일단, 포스트 내용을 반박하게 되는 글이 될 것과
C-Thinker님의 글에서 EF에 대한 문제의식을 제대로 집어 내지 못한 것이 포스트를 작성하는 것에 대해 조심 스럽게 만듭니다. 하지만 논쟁을 통해서 서로가 발전할 수 있다는 변증법 사고에 기반을 두고 EF에 대한 저의 의견을 적어 봅니다.
Table Module Pattern 의 개발 모델 문제에 대해 말 해 보도록 하죠.
이상한 모습도 그렇지만 아직도 테이블 기반 (Table Module Pattern) 의 애플리케이션 개발 모델을 버리지 못하고 있는 느낌이다.
우선 EF가 테이블 기반인가? 테이블로 부터 EDM을 생성하므로 테이블 기반이라고 말 할 수 있겠네요. 하지만 마틴 파울러가 말한 Table Module Pattern이냐? 라는 것에는 이견이 있군요. 파울러의 Table Module Pattern은 Data Access Layer를 작성할 때 1:1 테이블로 사상된 일명 DataSet을 통해 작업을 하느냐. 다시 말하면 직접적인 RecordSet을 통하여 작업 하느냐 라고 봅니다. 개발자가 이용하는 관점에서는 EF는 Domain Model 이며, Data Mapper를 구현 할때 Table의 메타 데이터를 이용한다. 라고 볼수 있네요.
매퍼 자체가 매핑을 하기 위해서 Gateway를 이용해야 하는데 Gateway는 Table Data Gateway, Row Data Gateway, Active Record를 사용할 수 있으나, 이것은 매퍼를 구현하는 방법에서의 적절한 것을 선택하는 것의 문제이지 Table을 기반으로 생성된 EDM이 문제일 수는 없다고 봅니다.
ORM과 Persistence Ignorance를 아래의 글로 언급하셨는데
ORM 의 근본 목적이라면 OO (Object Orientation) 프로그램 상에서 생성되고 관리되는 객체들과 관계형 (Relation) 으로 구성된 데이타베이스와의 구조적 차이 (Impedance Mismatch) 를 극복하기 위한 반복되는 코드를 줄이고 어떻게 저장되는 지에 대해서는 상관하지 않겠다 (Persistence Ignorance) 는 관점에 있다고 볼 수 있다.
저는 "ORM 이라는 것은 Object Oriented 객체와 RDBMS와의 임피던스 미스매치(Impedance Mismatch)를 조절하며 데이터 영속화(Data Persistence)를 자동화(Automate) 함으로써 비지니스 로직을 작성하는 개발자에게 Domain Model로 작업하도록 지원해 주는 것이 목표 라는 것"이라고 정리하고 있는데, artofsoftwaredev님의 의견과 거의 같으면서도 다름이 느껴집니다.
Persistence Ignorance는 Jimmy Nilsson 의 책 Applying Domain-Driven
Design and Patterns: With Examples in C# and .NET (Addison-Wesley Professional, 2006)에서 제시한 용어로써 "keep your domain model decoupled from your persistence layer " 로써 정확한 정의가 모호합니다.
Eric Evans의 DDD 방법론과 마틴 파울러의 생각을 정의해 보자면 "도메인 모델이 저장 장소에 무지하도록 한다." 라고 할 수 있습니다. 저장장소가 인-메모리 가 되었건 , 파일 시스템이 되었건, SQL Server가 되었건 몰라도 영속화를 할 수 있어야 함을 의미 하는 것으로 , "어떻게" 보다는 "어디"에 또는 "무엇"에 영속화 되는가에 관심이 있겠다 말할 수 있습니다.
영속화 매체가 파일 시스템, 또는 오라클이냐, MySql이냐, MS SQL 이냐를 도메인 모델이 인지를 하지 않고 특정 영속화 인터페이스를 사용한다면 영속화 할 수 있다는 개념으로 Repository 패턴을 생각해 볼 수 있습니다.
객체와 테이블이 1:1의 관계이냐?
데이타베이스로 부터 모델을 생성하고 코드만 자동으로 생성을 하지 않은 후, 그 모델에 맞는 객체를 만들어야 한다. 예를 들어 주문 테이블이 있다고 하면 이 테이블로 부터 주문이라는 모델을 만들고 이 주문이라는 모델이 갖고 있는 필드들을 갖고 있는 객체를 만들어야 한다는 얘기다. 여기서 다시 객체와 테이블의 1 : 1 관계를 벗어나지 못한다. 그래도 여기까지는 큰 문제가 없다고 봐 줄 수도 있다. 정작 문제는 이 객체들을 데이타베이스에 넣고 빼는 과정을 자동화 해 주는 ObjectContext 이다.
우선 EF는 상속받아 구현되는 컴플렉스 타입을 지원하며 1:1 관계라는 것 자체가 사실과 다릅니다.
OO의 객체를 영속화 하기 위해서 테이블을 모델링 할 때 상속받아 구현된 모든 객체를 테이블과 1:1로 구현 할 수도 있고, 타입으로 구분하여 상속받아 구현된 모든 객체를 한개의 테이블에 저장할 수도 있습니다. 이 두가지 모두 전략에 따라 적절한 패턴의 선택 문제입니다.
그럼 문제가 된다고 말씀하시는 ObjectContext의 POCO 지원시에 의존성 문제를 볼까요
객체들을 자동으로 생성하지 않았으므로 POCO 를 사용할 경우 ObjectContext 는 어떤 객체들이 어떤 모델들과 어떤 관계를 갖고 있는지 알 수가 없게 된다. 따라서 ObjectContext 를 상속받는 객체를 만들어 사용을 해야 하는데 문제는 각 POCO 객체를 Entity Framework 에서 Entity 로 인식을 하고 처리를 하기 위해 이 POCO 객체타입으로 EntitySet 을 만들어야 한다는 것이다. 다시 말해서 도메인 객체를 데이타 엑세스 레이어에서 알고 있어야 한다는 얘기다. 어라... 이건 뭐가 거꾸로 되도 한참 거꾸로 됐다. 프로젝트를 UI, 도메인, 데이타 레이어로 나누었을 경우 UI -> 도메인 -> 데이타 순서가 아니라 UI -> 데이타 -> 도메인 순서의 의존성 (Dependency) 가 생긴다.
객체를 영속화 하기 위해서는 상태 관리, CRUD의 인터페이스를 제공하는 그 어떤것이 필요하게 되는데 ObjectContext가 그 역할을 하는 것입니다. POCO의 영속성을 지원하기 위해서는 지원하는 '그 무엇'이 있어야 합니다. 그것이 Transaction Script가 되었건, Table Module이 되었건 말이죠. EF 4에서는 POCO를 지원하기 위해 POCO를 EDM에 추가하는 것으로 해결하는 것이죠.
도메인에서 만들어진 객체를 데이터 레이어에서 알아야 하는 것이 문제라고 말씀하셨는데, 언어의 Primative 타입만을 데이타 레이어에 전송하지 않고 Object로 전달하기로 결정하는 순가. DTO라고 하는 객체가 필요하게 됩니다. DTO는 도메인 모델과 데이터 모델에서 알지 않고서는 작업이 되지 않기 때문에 EDM의 엔터티 자체가 DTO 역할을 하면서 엔터티의 상태 관리와 CRUD 및 데이터 매핑 작업을 ObjectContext가 해 주는 구조 입니다.
다시 말해서 DTO가 필요해 지는 순간 도메인 레이어와 데이터 레이어가 동시에 알아야 할 필요성이 생기고, 이것을 의존성이라고 말한다면 Primative Type 만으로 작업하는 것 이 외에는 방법이 없다고 생각합니다.
의존성의 순서는 둘째 치고, 의존성이 있다는 얘기는 변경발생시 두군데의 변경이 불가피 하나는 얘기이고 굳이 두개로 나눌 이유가 없다는 말일 수도 있다. 이는 다시말해 도메인 로직과 데이타 엑세스가 분리 될 수 없다는 것을 말하고 결국 데이타베이스에 대한 의존성은 줄지 않았다는 얘기가 된다.
의존성 문제는 개발자의 코드 자체가 레이어 간의 의존성을 가지냐의 관점으로 봐야 옳으며, 중간자 (Mediator)가 의존성을 알고 있느냐의 관점으로 보는 것이 아니라고 생각합니다. 데이터베이스의 변경 마다, 데이터 레이어와 도메인 레이어의 수직 변경을 메터데이터(EDM)에 위임하고 , EDM의 업데이트로 변경을 반영함으로써 , 수직 변경을 최소화하는 것은 옳은 관점이라고 생각합니다.
artofsoftwaredev 님의 말씀대로 모든 레이어가 Ignore 하고 서로의 변경에 전혀 관심 갖지 않아도 된다면 얼마나 좋겠습니까 만은 , 그 개념은 아직 이상적이며 완벽한 솔루션이 없습니다.
현재로써는 "관심의 분리 , Seperate of Concern"로 "한 곳에서만 관리하도록 하자 Single Point Management "의 관점으로 디자인 하는 것이 옳다고 볼수 있습니다.
EF가 완벽하냐? 라고 묻는다면 대답할 수 없습니다만, 디자인 관점이 옳으냐 라고 묻는다면 "예" 라고 하겠습니다.
Repository에 대한 체크 리스트
Do you use a Generic Repository that can work for any entity, or a Specialised one for each entity?
Where are queries constructed?
How do repositories move data in and out of the data store?
What query language are you using (HQL, LINQ, SQL etc)?
Where are transactions handled?
Do entities know about the Repositories?
What do your domain services do?
What do your application services do?
How do you test your repositories?
Do ASPX pages talk to the domain too, or do they always go through application services?
위의 체크 리스트는 Repository를 구현할 때 고려되는 사항을 뽑은 것입니다.
들어가기에 앞서 다시 한번 마틴파울러의 Repository의 정의를 다시 살펴 보면,
리파지터리란 도메인 객체에 접근하기 위해 컬렉션과 같은 인터페이스를 사용하는 , 데이터 매핑 레이어와 도메인 사이의 중재자 이다.
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
더 자세한 정의 및 Repository pattern에 대해서는 이전 포스트 http://smack.kr/300 에서 다루고 있습니다.
위의 정의로 보아 리파지터리의 조건은
도메인이 클라이언트가 되고, 데이터 매핑 레이어에 대해서 컬렉션 형태의 인터페이스와, Persistance Ignorance를 지원한다.
1. 어떤 entity에서도 동작하는 제네릭 리파지터리를 사용 하느냐, 아니면 각각의 entity에 특화된 리파지터리를 사용할 것인가?
(Do you use a Generic Repository that can work for any entity, or a Specialised one for each entity? )
각각의 특화된 리파지터리를 사용하는 것은 Jimmy Nilsson의 Applying Domain-Driven Design and Patterns: With Examples in C# and .NET의 예를 들 수 있다.
특화된 리파지터리는
class Product
class Order
interface IRepository
class ProductRepository : IRepository
class OrderRepository: IRepository
형태를 가지며,
interface IRepsitory는 기본 CRUD만을 정의 하며, 상속 받은 ProductRepository , OrderRepository가 ProductRepository.GetProductByKey(int itemKey)와 같은 확장된 메소드를 구현 할 수 있다.
사실 IRepository를 상속 받지 않아도 된다. 특화된 Repository는 거의 LSP를 지킬 수가 없고, 이는
Product->IRepository = new ProductRepository의 형태를 사용 할 수 없기 때문이다. 하지만, SRP를 구현하기 에는 명확하다.
제네릭 리파지터리는 interface IRepsotory<T> 와 같은 형태를 가지게 되는데 , class Repository<T> : IRepostory<T>로 구성되며,
Product -> IRepository = new Repository<Product>();
Order -> IRepository = new Repository<Order>();
와 같이 구상 클래스가 아닌 , 인터페이스를 가지고 프로그래밍을 할 수 있다.
또한 class InMemoryRepository<T> : IRepository<T> 형태의 In-Memory Repository를 추가 할 수 있으며, 이는
Product->IRepsitory = new InMemoryRepository<Product>();
Order -> IRepository = new InMemoryRepository<Order>();
와 같은 형태로 Persistence-Ignorance의 개념을 명확하게 구현 할 수 있다.
하지만, 잘 정의된 제네릭 리파지터를 구현하는 것은 쉬운 일이 아니다.
2. 쿼리는 어디에 존재 해야 하나?(Where are queries constructed?)
쿼리는 일반적으로 Repository 내부에 정의 되는데, 특화된 리파지터리와 제네릭 리파지터리는 약간 다른 형태를 띄게 된다.
3.데이타 저장소로 부터 데이터를 어떻게 꺼내고 집어 넣나? (How do repositories move data in and out of the data store? )
4. 어떤 쿼리 언어를 사용하나? What query language are you using (HQL, LINQ, SQL etc)?
5. 트랜잭션은 어디에서 핸들링 해야 하나? (Where are transactions handled? )
6. 엔터티가 리파지터리를 알아야 하나?( Do entities know about the Repositories?)
7. 도메인 서비스는 어떤 일을 하는가? ( What do your domain services do?)
8. 어플리케이션 서비스는 어떤 일을 하는가? (What do your application services do?)
9. 리파지터리에 대한 테스트는 어떻게 하는가? ( How do you test your repositories?)
10. ASPX 페이지는 도메인과 이야기 하는가? 아니면 항상 어플리케이션 서비스를 통하는가? (Do ASPX pages talk to the domain too, or do they always go through application services?)
위의 질문에 대한 Collin Jack의 아키텍처는
DDD Repositories in the Wild: Colin Jack
http://www.tobinharris.com/past/2008/8/22/ddd-repositories-in-the-wild/
에서 확인 할 수 있습니다.
MSDN Magazine 2009년 2월호 에서는 DDD에 대해서 다루고 있으며
DDD(Domain Driven Design) 소개
David Laribee : http://msdn.microsoft.com/ko-kr/magazine/dd419654.aspx
위의 링크는 도메인 드리븐에 대한 설명과 참조 링크들을 제공하고 있습니다.