본문 바로가기

Homo Faber/Concepts

Domain Driven Design(2) - Entity [Reference Object]

많은 객체들은 이들이 가진 속성이 아니라 일련의 지속성(continuity)과 식별자(identity)를 통해서 기본적으로 정의된다.

어떤 집주인이 자신의 재산에 중대한 손해를 입혔다고 주장하면서 나를 고소했다. 내가 받았던 고소장에는 아파트에 벽에 구멍이 나있으며, 카펫트에는 얼룩이 묻어 있고, 싱크대에 부엌 벽지가 벋겨지게 만든 부식성의 가스를 풍기는 유해한 용액을 있었다고 되어 있다. 법원 서류는 나를 이름과 그 당시의 주소를 근거로 손해를 입힌 세입자로 명기하고 있다. 이 사건은 나로 하여금 혼란으로 주었는데 왜냐하면 그러한 황폐한 장소에 가 본적이 결코 없었기 때문이다.

얼마 후, 나는 잘못된 식별인 경우임에 틀림없다고 생각이 들었다. 나는 고소인에게 전화해서 이러한 사실을 이야기했지만, 나를 믿으려고 하지 않았다. 이전 세입자는 수개월동안 교묘히 집주인을 피했다. 어떻게 내가 집주인에게 그와 같이 많은 돈을 지불했던 동일한 사람이 아니라는 것을 증명할 것인가? 내가 전화번호부에 있는 유일한 Eric Evans였다.

결국 전화번호부가 나를 구원해주었다. 2년 동안 나는 계속해서 같은 아파트에서 살았었기 때문에 집주인에게 작년의 전화번호부가 있는지를 물었다. 집주인은 전화번호부를 찾고 내이름과 같은 것이 있는지를(내 이름 바로 옆에) 확인한 후에 내가 집주인이 고소하기를 원하는 사람이 아니라는 것을 알고, 용서를 구하고, 소송을 취하했다.

컴퓨터는 자원이 많은 장치가 아니다. 소프트웨어에서 잘못된 식별자의 경우 데이터를 망가뜨리고 프로그램 에러를 유발시킨다.

여기에는 특별한 기술적인 문제가 있지만, 먼저 근본적인 문제에 대해서 살펴보자. 많은 사물들은 어떤 속성이 아닌 사물들이 가진 식별자에 의해서 정의된다. 전형적인 개념에서 사람 (비기술적인 예)은 생애 동안과 심지어 사후까지도 걸쳐서 식별자를 가진다. 사람의 물리적인 속성들은 변형되며 궁극적으로 사라진다. 이름은 바뀔 수 있다. 재무적인 관계가 생기고 사라진다. 변할 수 없는 사람의 속성은 단일하지 않다. 하지만 식별자는 영구적이다. 나는 5살때의 동일한 사람인가? 이런 종류의 형이상학적인 질문은 효과적인 도메인 모델을 찾는데에 중요하다. 조금 다르게 표현하면, 어플리케이션의 사용자는 내가 5살 떄의 동일한 사람인지에 관심이 있는가?

계좌 수수료를 추적하는 소프트웨어 시스템에서 가장 신중한 "고객" 객체는 더 많은 다양한 측면을 가질 수 있다. 고객 객체는 즉시불에 의한 상태를 가지고 있거나 지불에 대한 실패에 대해서 청구서 수집 에이전시로 변화한다. 판매 촉진 시스템이 접촉 관리 시스템으로 고객 데이터를 추출해서 보낼때 다른 시스템에서 생명을 더 연장할 수도 있다. 어떤 경우이든, 고객 객체는 형식에 얽매이지 않고 데이터베이스 테이블에 저장되는 짓이겨진 평면이다. 새로운 업무가 해당 자원으로부터의 흐름을 멈출때 고객 객체는 이전의 것에 대한 투영으로 보관소로 생을 마치게 될 것이다.

고객에 대한 이러한 각각의 형태들은 서로 다른 프로그래밍 언어와 기술에 기반하여 서로 다른 구현체이다. 하지만 전화로 주문했을 때에 다음과 같은 사항을 아는 것이 중요하다. 이 고객은 미지불 계좌를 가지고 있는가? 잭(특정 판매 응대자)이 수주 동안 같이 작업해오던 고객인가? 완전하게 새로운 고객인가?

개념적인 식별자는 해당 객체에 대한 여러 구현체들과, 그 저장 형태들, 송화자와 같은 실세계 액터들 사이에서 일치되어야 한다. 속성들은 일치하지 않을 것이다. 판매 응대자는 방금 계좌 수수료를 지불했다고 접촉 소프트웨어로 주소 수정을 입력할 수도 있다. 두개의 고객 주소는 동일한 이름을 가질 수도 있다. 분산 소프트웨어에서 다중 사용자들은 서로 다른 원천지로부터 데이터를 입력할 수도 있으며, 이는 서로 다른 동기화되지 않은 데이터베이스에서 조정하는 시스템을 통해 update 트랜잭션을 유발시킨다.

객체 모델링은 객체의 속성에 초점을 맞추도록 이끄는 경향이 있지만 ENTITY에 대한 기본적인 개념은 생명주기에 걸쳐 있고 심지어 여러 형태를 통해 전달되는 추상적인 지속성이다.

어떤 객체들은 이들이 가진 속성에 의해서 대부분 정의되지 않는다. 이들은 시간을 통해 혹은 다른 형태를 통해서 실행되는 일련의 식별자를 표현한다. 때로는 그러한 객체가 서로 속성이 다르더라도 또 다른 객체와 일치되어야 한다. 객체는 다른 객체들이 동일한 속성을 가지고 있다고 하더라도 구별되어야만 한다. 잘못된 식별자는 데이터를 망가뜨리는 원인이 될 수 있다.

식별자에 의해서 주로 정의도는 객체를 ENTITY라고 한다. ENTITY는 특별한 모델링과 설계 고려사항이 있다. ENTITY는 형태와 내용이 급격하게 바뀔 수 있는 생명주기를 가지지만, 일련의 지속성은 유지되어야 한다. ENTITY의 식별자들은 효과적으로 추적될 수 있게 하기 위해 정의되어야 한다. 이들의 클래스 정의, 책임성, 속성, 및 연관들은 ENTITY들이 포함하고 있는 특정 속성대신에 ENTITY들이 누구인지를 결정해야만 한다. 심지어 급격하게 변형되지 않거나 그렇게 복잡한 생명 주기를 가지지 않는 ENTITY에 대해서도 이들을 의미론적인 분류를 하는 것은 더 명료한 모델과 내구성이 강한 구현을 가능하게 한다.

모델의 ENTITY는 자바의 "엔티티 빈"과 동일한 것이 아니다. 엔티티 빈은 어느 정도는 ENTITY에 대한 구현에 대한 프레임워크로 의미되었지만, 그러한 방식으로 처리되지 않았다. 대부분의 ENTITY들은 일반적인 객체로 구현된다. 이들이 어떻게 구현되었는지에 상관없이 ENTITY들은 도메인 모델에서 기본적인 차별적인 요소이다.

물론, 소프트웨어 시스템에서 대부분의 "ENTITY"들은 평범한 의미의 단어로 사람이나 개체들이 아니다. ENTITY는 생명주기를 관통하는 지속성을 가지며 어플리케이션의 사용자에게 중요한 속성과 무관한 차별적인 요소를 가지는 것이다. 사람이나, 도시, 자동차, 혹은 복권, 은행 트랜잭션이 될 수도 있다.

반면에 모델에서 모든 객체들은 의미있는 식별자를 가지는 ENTITY가 아니다. 이러한 사실은 객체지향 언어가 모든 객체로 "식별자" 오퍼레이션을 만든다는 사실(예를 들어 자바에서는 "==" 오퍼레이터) 때문에 혼돈스럽다. 이러한 오퍼레이션들은 메모리에 객체들의 위치를 비교하거나 다른 메커니즘을 통해 동일한 객체를 두개의 참조가 가리키고 있는지를 결정한다. 이러한 의미에서 모든 객체 인스턴스는 식별자를 가진다. 즉, 자바 실행 환경이나 로컬에 원격 객체를 캐싱하는 기술적인 프레임워크에 대한 도메인에서 모든 객체 인스턴스는 실제로 ENTITY가 될 수 있다. 하지만 이러한 식별 메커니즘은 다른 어플리케이션 영역에서 거의 의미가 없다. 식별자는 언어의 자동적인 성질로 변경될 수 없는 ENTITY의 미묘하고 의미있는 속성이다.

은행 어플리케이션의 트랜잭션을 생각해보자. 동일한 날짜의 동일한 계좌에 대한 동일한 금액의 두개의 예금은 여전히 다른 트랜잭션이며, 따라서 이 예금들은 식별자를 가지는 동시에 ENTITY이다. 반면에 두개의 트랜잭션의 금액 속성은 금액 객체에 대한 인스턴스가 될 것이다. 이러한 값들은 식별자를 가지지 않는데, 이는 이들을 구별하는 것이 아무런 의미가 없기 때문이다. 사실, 두개의 객체는 동일한 속성을 가지지 않거나 심지어 동일한 클래스일 필요없이 동일한 식별자를 가질 수 있다. 은행 고객이 수표 등록표라는 트랜잭션과 함께 은행 계산이라는 트랜잭션을 대조할 때 업무는 구체적으로 서로 다른 날짜에 서로 다른 사람을 통해 기록되었다고 하더라도(은행은 수표의 날짜보다 이후의 날짜는 삭제함) 동일한 식별자를 가지고 있는 트랜잭션을 일치하게 된다. 수표번호의 목적은 문제가 컴퓨터 프로그램이든 수기로든 처리되든지 간에 이러한 목적에 대해서 유일한 식별자로 사용된다. 번호를 식별하지 못하는 예금과 현금인출은 더 까다롭지만 동일한 원리로 적용된다. 즉, 각각의 트랜잭션은 ENTITY이며, 최소한 두가지 형태로 나타난다.

은행 업무 트랜잭션과 아파트 세입자의 경우에서와 같이 식별자는 특정 소프트웨어 시스템 외부에서 중요시 되는 것이 보통이다. 하지만 경우에 따라 식별자가 컴퓨터 처리의 식별자와 같이 시스템의 영역에서만 중요한 경우도 있다.

따라서,
속성 대신에 식별자로 객체가 구별될 때 모델에서 이러한 정의를 가장 중요시하라. 클래스 정의를 간결하게 유지하고 생명주기 지속성과 식별자에 초점을 유지하라. 객체의 형태나 이력에 상관없이 각각의 객체를 식별하는 의미를 정의하라. 속성에 의해서 객체를 일치를 요구하는 사항에 대해서 주의하라. 가능한 유일함을 보장하는 식별을 추가함으로써 각각의 객체에 대한 유일한 결과를 만들어내는 것을 보장하는 오퍼레이션을 정의하라. 이러한 식별 방법은 외부로부터 만들어질 수도 있거나, 시스템을 위해 생성된 임의의 식별자일 수도 있지만, 모델에서 식별자 구분과 대응되어야 한다. 모델은 동일한 것을 의미하는 것이 무엇인지를 정의해야 한다.

식별자는 실세계의 사물에 고유한 것이 아니다. 유용하기 때문에 의미있게 만든 것이다. 사실, 동일한 실세계의 사물은 도메인 모델의 ENTITY로써 표현될 수도 있고 그렇지 않을 수도 있다.

경기장 예약에 대한 어플리케이션은 좌석과 관중을 ENTITY로 처리할 수 있다. 할당된 좌석의 경우, 개개의 표는 좌석 번호를 가지고 있으며, 좌석은 ENTITY이다. 식별자는 좌석 번호가 되며, 이는 경기장 내에서 유일하다. 좌석은 위치나, 전망이 좋은지 여부, 가격 등과 같은 많은 다른 속성들을 가지고 있을 수 있지만, 유일한 행과 위치인 좌석 번호만이 조석을 식별하고 구별하는데 사용된다.

반면에, 만일 좌석을 정하는 것이 표를 가진 사람이 비어있는 좌석을 발견할 때마다 앉는 "임의적인 발생"이라고 하면 개개의 좌석을 구별할 방법이 없다. 단지 전체 좌석의 수만이 중요하다. 비록 좌석 번호가 물리적인 좌석위에 여전히 새겨져있다고 하더라도 좌석을 추적하는 소프트웨어가 필요치 않다. 사실, 임의적인 발생에서 아무런 제약이 없기 때문에 특정 좌석 번호와 표를 연결하는 모델은 에러가 발생될 것이다. 그런 경우에, 좌석은 ENTITY가 아니며, 아무런 식별자가 필요하지 않다.

ENTITY 모델링

객체를 모델링할때 속성에 대해서 생각하는 것은 당연하며, 그 행위에 대해서 생각하는 것도 매우 중요하다. 하지만, ENTITY의 가장 기본적인 책임성은 지속성을 유지함으로써 행위가 명확하고 예상가능해야 한다. 식별자들을 예비의 장소로 유지시킨다면 최선일 것이다. 속성이나 행위에 초점을 맞추는 대신에 ENTITY 객체의 정의를 가장 본질적인 성질, 특히 식별하거나 검색이나 일치하는데 공통적으로 사용되는 것으로 분해한다. 개념에 필수적인 행위와 이러한 행위에 의해서 필요한 속성만을 추가한다. 그외의 것은 핵심 ENTITY에 연관된 다른 객체들로 행위와 속성들을 제거한다. 이러한 다른 객체들 중의 어떤 것들은 다른 ENTITY일 것이다. 어떤 것들은 Value Object일 것이다. 식별자 문제를 제외하고, ENTITY는 자신들이 소유한 객체들의 오퍼레이션들을 조정함으로써 책임성을 수행하려고 한다.

customerID는 Customer ENTITY의 유일무이한 식별자이지만, 전화번호와 주소는 Customer를 검색하거 일치하는데 사용될 수도 있다. 이름은 사람의 식별자를 정의할 수 없지만, 사람을 결정하는 방법의 일부분으로 사용되기도 한다. 아래 예제에서 전화와 주소 속성은 Customer로 옮겨졌지만, 실제 프로젝트에서 그러한 결정은 도메인의 고객들이 얼마나 전형적으로 일치되거나 구별되는지에 달려있을 것이다. 예를 들어, 만일 Customer가 다른 목적으로 많은 접촉 전화 번호를 가지고 있다면, 전화 번호는 식별자와 관련이 없으며 Sales Contact에 위치해야 한다.

사용자 삽입 이미지


식별자 오퍼레이션 설계

각각의 ENTITY는 다른 객체와 구별되는 식별자를 만드는 운영적인 방법을 가지고 있어야만 한다. - 동일한 속성을 갖는 다른 객체와 구별되는. 속성의 식별은 해당 시스템이 어떻게 정의되든지 간에 - 심지어 분산이든 객체가 저장될 때이든 - 시스템 내에서 유일함을 보장해야 한다.

앞에서 언급했듯이 객체지향 언어는 두개의 참조가 메모리의 객체의 위치를 비교함으로써 동일한 객체를 가리키고 있는지를 결정하는 "식별자" 오퍼레이션을 가진다. 이러한 종류의 식별자 추적은 DDD 목적으로는 너무 위험하다. 객체의 영구적인 저장에 대한 대부분의 기술들에서 매번 객체가 DB에서 조회되고, 새로운 인스턴스가 생성되며, 따라서 초기의 식별자는 잃어버린다. 매번 객체는 네트워크를 통해 전달되며, 새로운 인스턴스가 도착지에 생성되며, 다시 한번 식별자는 잃어버린다. 문제는 업데이티가 분산된 DB에서 발생될 때처럼 시스템에 존재하는 동일한 객체가 여러개 있는 경우에 더욱 심각해진다.

이러한 기술적인 문제를 간소화시키는 프레임워크를 가지고 해도, 근본적인 문제는 남아있는다. 즉, 두개의 객체가 동일한 개념의 ENTITY를 대표하는지를 어떻게 알 수 있는가? 식별자에 대한 정의는 모델로부터 불거졌다. 식별자를 정의하는 것은 도메인에 대한 이해를 필요로 한다.

특정 데이터 속성이나, 속성들의 조합으로 시스템 내에서 유일성을 보장받거나 단순하게 제약을 두는 경우도 있다. 이러한 방법은 ENTITY에 대한 유일한 키를 제공한다. 예를 들어, 신문은 신문의 이름, 도시, 발행일을 통해 식별될 수 있다. (하지만 호외와 이름 변경에 주의하라)

객체의 속성으로 구성되는 유일한 키가 없다면 또 다른 공통적인 해결책은 클래스 내에서 유일한 상징(숫자나 문자열과 같은)을 각각의 인스턴스에 추가하는 것이다. 일단 이러한 ID 상징이 생성되고 ENTITY의 속성으로 저장되었다면 변경되지 못하게 해야 한다. 심지어 개발 시스템이 이러한 규칙을 직접적으로 강제할 수 없다고 하더라도 결코 변경해서는 안된다. 예를 들어, ID 속성은 객체가 DB로 입력되거나 조회될 때 보존된다. 때로는 기술적인 프레임워크가 이러한 처리를 도와주지만, 그렇지 않다면 공학적인 원리를 취하면 된다.

종종 ID가 시스템에 의해서 자동으로 생성된다. 생성 알고리즘은 시스템 내애서 유일성을 보장해야 하며, 이는 동시 처리와 분산 시스템에서 해결될 수 있다. 그러한 ID를 생성하는 것은 특별한 기술을 필요로 할 수도 있다. 핵심은 식별자는 모델의 특정 부분에 중요한 지점을 영향을 준다는 것을 인식하는 것이다. 종종 식별 방법은 도메인에 대한 주의깊은 연구를 필요로 한다.

ID가 자동으로 생성될 때 사용자는 이를 살펴볼 필요가 없을 수도 있다. ID는 사용자가 사람의 이름으로 검색하게 하는 접촉 관리 시스템에서와 같이 내부적으로만 필요로 할 것이다. 프로그램은 단순하고 애매하지 않은 방법으로 정확하게 같은 이름을 가진 두개의 접촉 정보를 구별할 수 있으면 된다. 유일하고 내부적인 ID는 시스템으로 하여금 이와같이 처리하게 한다. 두개의 다른 항목을 조회한 후, 시스템은 사용자에게 두개의 분리된 접촉 정보를 보여줄 것이지만, ID는 보여주지 않을 수 있다. 사용자는 이들의 회사, 위치 등을 통해서 구별할 수 있다.

마지막으로, 생성된 ID가 사용자에게 의미있는 경우가 있다. 택배회사를 통해 물건을 보낼때 택배회사의 소프트웨어로부터 발생된 추적 번호를 받았으며, 내 물건을 식별하고 추적하는데 사용할 수 있다. 항공편을 예약하거나 호텔을 예약할 때 트랜잭션에 대한 유일한 식별자인 확인 번호를 받는다.

어떤 경우에는 ID의 유일성이 컴퓨터 시스템의 영역 외부에 영향을 미쳐야만 한다. 예를 들어, 만일 의료 기록이 서로 다른 컴퓨터 시스템을 가진 두 병원간에 교환된다면 이상적으로 각각의 시스템은 동일한 환자 ID를 사용할 수 있지만, 각각의 시스템의 자체 상징을 생성한다면 이는 어렵다. 그러한 시스템은 종종 보통 정부 기관가 같은 다른 조직을 통해 발급되는 식별자를 사용한다. 미국에서는 사회보장 번호가 사람에 대한 식별자로 병원에 의해서 종종 사용된다. 그러한 방법은 그렇게 확실한 것은 아니다. 모든 사람이 사회보장 번호를 가지는 것이 아니며(특히, 어린아이나 이민자들), 많은 사람 객체는 개인적인 이유로 사용한다.

덜 공식적인 상황에서(예를 들어, 비디오 대여) 전화번호가 식별자로 사용된다. 하지만 전화는 공유될 수 있다. 번호는 바뀔 수 있다. 이전 번호는 다른 사람에게 할당될 수도 있다.

이러한 이유로, 특별하게 할당된 식별자가 종종 사용되며, 전화번호와 사회보장 번호와 같은 다른 속성들은 검색이나 인식할 때 사용된다. 어떤 경우이든, 어플리케이션이 외부 ID를 필요로 할때 시스템의 사용자는 유일한 ID를 제공하는 것을 담당해야 하며, 시스템은 예외 발생에 대한 처리를 위해 적절한 도구를 제공해야 한다.

이러한 모든 기술적인 문제로 인해서, 다음과 같이 내재된 개념적인 문제의 통찰을 잃어버리기 쉽다. 두개의 객체가 동일하다는 것은 무엇을 의미하는가? 각각의 객체에 ID를 부여하거나 두 인스턴스를 비교하는 오퍼레이션을 작성하는 것은 쉽지만, 이러한 ID나 오퍼레이션이 도메인에서 의미있는 상이함에 대응되지 않는다면, 더 혼란만 줄 뿐이다. 이것이 왜 식별자 부여 오퍼레이션이 종종 사람의 입력과 관련이 있는지를 나타낸다. 예를 들어, 수표책 비교 소프트웨어는 유사하게 일치를 제공할 수 있지만, 사용자가 최종 결정을 하도록 한다.
반응형

'Homo Faber > Concepts' 카테고리의 다른 글

자바에서 상속  (1) 2008.03.04
Domain Driven 과 Model Driven  (1) 2008.02.28
Domain Driven Design(1) - Association  (0) 2008.02.25
Hibernate에서 Equals 와 HashCode  (0) 2008.02.21
객체지향 어플리케이션에서의 Persistence  (0) 2008.02.15