2009-09-29 3 views
7

이 질문은 핵심적인 디자인 질문입니다. 질문을 설명하기 위해 Java/Java EE 예제를 사용합니다.디자인 : 도메인 개체와 서비스 개체 사이의 선이 분명하지 않은 경우

지속성을 위해 JPA를 사용하고 서비스 계층을 위해 EJB를 사용하여 빌드 된 웹 메일 응용 프로그램을 생각해보십시오. 다음과 같이 EJB에 서비스 메소드가 있다고 가정 해 보겠습니다.

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    mb.addMessage(message); 
} 

이것은 합리적인 비즈니스 방법입니다. 아마도 사서함 개체는 계속 첨부되고 변경 내용을 데이터베이스에 원활하게 저장합니다. 결국 그것은 투명한 지속성에 대한 약속입니다.

사서함 개체는이 방법을 것이다 :

여기
public void addMessage(Message message) { 
    messages.add(message); 
} 

그것이 복잡해진다 어디 - 우리가 다른 사서함 유형을 갖고 싶어 가정합니다. 발신자에게 자동 응답하는 AutoRespondingMailbox와 수신 된 각 전자 메일과 함께 헬프 데스크 티켓을 자동으로 열리는 HelpDeskMailbox가 있다고 가정 해보십시오.

AutoRespondingMailbox이 방법이 사서함, 확장하는 것입니다 수행하는 자연적인 것 :

public void addMessage(Message message) { 
    String response = getAutoResponse(); 
    // do something magic here to send the response automatically 
} 

문제는 우리의 Maibox 객체와 그 서브 클래스는, "도메인 개체"(그리고이 예에 있다는입니다 JPA 엔티티). Hibernate 녀석 (그리고 많은 다른 사람들)은 비 의존적 도메인 모델, 즉 컨테이너/런타임 제공 서비스에 의존하지 않는 도메인 모델을 선전합니다. 이러한 모델의 문제는 AutoRespndingMailbox.addMessage() 메소드가 예를 들어 JavaMail에 액세스 할 수 없기 때문에 이메일을 보낼 수 없다는 것입니다.

HelpDeskMailbox에서 HelpDesk 시스템과 통신하기 위해 WebServices 또는 JNDI 삽입에 액세스 할 수 없기 때문에 똑같은 문제가 발생합니다.

은 그래서 당신은 다음과 같이 서비스 계층에서이 기능을 넣을 수밖에 :

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    if (mb instanceof AutoRespondingMailbox) { 
     String response = ((AutoRespondingMailbox)mb).getAutoResponse(); 
     // now we can access the container services to send the mail 
    } else if (mb instanceof HelpDeskMailbox) { 
     // ... 
    } else { 
     mb.addMessage(message); 
    } 
} 

가 그런 식으로 instanceof를 사용할 수있는 것은 문제의 첫 징후이다. Mailbox의 하위 클래스를 만들 때마다이 서비스 클래스를 수정하면 문제의 또 다른 징후입니다.

이러한 상황을 처리하는 방법에 대한 모범 사례가 있습니까? 어떤 사람들은 Mailbox 객체가 컨테이너 서비스에 액세스해야한다고 말하고 있지만, 이는 퍼지 (fudging)로 수행 할 수 있습니다.하지만 컨테이너가 엔티티를 제외한 모든 곳에서 의존성 주입을 제공하므로 명확하게 JPA의 용도와 싸우고 있습니다 이는 예상 된 유스 케이스가 아니라는 것을 의미합니다.

그래서 대신 무엇을해야합니까? 우리의 봉사 방법을 고치고 다형성을 포기 하시겠습니까? 우리의 객체는 자동적으로 C 스타일 구조체로 강등되고 OO의 이점 대부분을 잃어버린다.

Hibernate 팀은 비즈니스 로직을 도메인 계층과 서비스 계층으로 분리하고 컨테이너에 의존하지 않는 모든 로직을 도메인 엔티티에 넣고에 의존하는 모든 로직을 배치해야한다고 말합니다 서비스 레이어에 컨테이너. 만약 누군가가 다형성을 완전히 포기하고 instanceof 및 기타 불쾌감에 의존하지 않고서 어떻게해야하는지에 대한 예를 나에게 줄 수 있다면 받아 들일 수 있습니다.

답변

4

사서함은 사서함 ...

입니다 ...하지만, autoresponding 사서함에 연결된 몇 가지 규칙과 사서함입니다; 이것은 틀림없이 사서함의 하위 클래스는 아니지만 하나 이상의 사서함과 규칙 집합을 제어하는 ​​MailAgent입니다.

경고 : DDD에 대한 경험이 제한되어 있지만이 예는 허위 가정에 기반하여 나를 공격합니다. 규칙을 적용하는 동작이 사서함에 속한 것입니다. 메시지에 규칙을 적용하는 것이 메일 박스와 독립적이라고 생각합니다. 즉,받는 사람 사서함은 필터링/라우팅 규칙에 사용되는 기준 중 하나 일 수 있습니다. 따라서이 경우 ApplyRules (메시지) 또는 ApplyRules (사서함, 메시지) 서비스가 나에게 더 적합합니다.

+0

내 질문의 의도를 놓친 것 같아요. 예제의 특성을 잊어 버리고 Entity의 일부 하위 클래스가 일부 컨테이너 제공 서비스에 의존하는 동작을 필요로한다고 가정합니다. – TTar

+0

동의합니다. 문제는 도메인 개체가 더 이상 단순한 데이터 소유자가 아니지만 이제는 지속성에 영향을주는 동작을 연결 한 것입니다. 개인적으로 이것은 서비스 수준에서 처리되어야하는 것으로 느껴집니다. –

+0

도메인 객체가 단순한 데이터 보유자 인 경우 내 도메인 객체가 구조체가 아닌지? 데이터와 행동이 객체로 결합 된 객체 지향 디자인의 정의가 아닌가? – TTar

0

하나의 옵션 (그리고 아마도 최선의 선택이 아닐 수도 있습니다)은 오브젝트를 「executor」오브젝트 내에 랩합니다.실행자 객체는 서비스 계층 정보를 포함 할 것이고 내부화 된 데이터 객체는 도메인 정보를 포함 할 것이다. 그런 다음 팩토리를 사용하여 이러한 객체를 만들 수 있으므로 "instanceof"메서드 또는 유사한 요소의 범위가 제한되며 다른 객체는 데이터 객체에서 실행할 수 있도록 사용할 공용 인터페이스를 갖게됩니다. 명령 패턴 (실행자로 명령 오브젝트가 있음)과 상태 패턴 (상태가 데이터 오브젝트의 현재 상태 임) 중 하나가 혼합되어 있습니다. 둘 다 정확하게 맞지는 않지만.

+0

그러나 우리는 도메인 모델에서 컨테이너 의존성을 지키는 이름으로이 모든 작업을 수행합니다. 어떤 목적으로? 그냥 일을 더 어렵게 만드시겠습니까? – TTar

+0

다른 프런트 엔드 서비스에서 동일한 백 엔드 시스템을 사용할 수있게하려면 내 이해가 – aperkins

6

뭔가 빠졌습니다. Mailbox 개체가 런타임에 제공되는 인터페이스에 의존하는 것이 좋습니다. "런타임 서비스에 의존하지 말 것"은 정확합니다. 컴파일시 의존성이 없어야합니다.

인터페이스가 유일한 종속성 인 경우 StructureMap, Unity 등과 같은 IoC 컨테이너를 사용하여 런타임 인스턴스와 대조되는 테스트 인스턴스에 개체를 제공 할 수 있습니다. 결국

, AutoRespondingMailbox에 대한 코드는 다음과 같습니다이 클래스 뭔가에 의존 않지만, 반드시 런타임에서 제공하지 않는 것

public class AutoRespondingMailbox { 
    private IEmailSender _sender; 

    public AutoRespondingMailbox(IEmailSender sender){ 
     _sender = sender; 
    } 

    public void addMessage(Message message){ 
     String response = getAutoResponse(); 
     _sender.Send(response); 
} 

하는 것으로

- 단위 테스트를 위해, 당신은 콘솔에 글을 쓰는 가짜 IEmailSender를 쉽게 제공 할 수 있습니다. 또한 플랫폼이 변경되거나 요구 사항이 변경되면 원본과 다른 방법론을 사용하는 다른 IEmailSender를 쉽게 제공 할 수 있습니다. 은 "제한 종속성"태도의 이유입니다.

+0

나는 Hibernate 녀석들이 다르게 논할 것이라고 생각한다. 그들은 정말로 비즈니스 논리가 분할 될 수 있거나 분할되어야한다고 믿습니다. 모든 것을 혼란스럽게 만들지 않고 어떻게 분리 될 수 있는지 나는 생각하지 않습니다. – TTar

+0

이것은 어떻게 비즈니스 로직을 분할하지 않습니까? AutoRespondingMailbox의 유일한 논리는 어딘가에서 응답을 받아 자동으로 보내야한다는 것입니다. 특정 인터페이스의 존재 이외의 다른 것에 의존하지 않습니다. 이 인터페이스는 여러 가지 방법으로 제공 될 수 있으며, 그 중 많은 부분이 컴파일 된 코드와 전혀 관련이 없습니다. –

1

저는 DDD에 경험이 없지만 해결 방법에 대한 제안이 하나 있습니다.

나는 MailBox 클래스 추상을 작성한 다음 MailBox를 사용하여 3 개의 구현을 수퍼 클래스로 만들었을 것이다.

addMessage (...) 메서드의 이름을 더 잘 지정할 수 있다고 생각합니다. 이 이름 - add는 제공된 메시지를 setter처럼 사서함에 추가하면되지만 기존 값을 바꾸는 대신 제공된 메시지를 일종의 저장소에 추가하는 것이 좋습니다.

하지만 당신이 찾고있는 것은 오히려 행동입니다. 추상 사서함에서 모든 하위 클래스가 public void handleIncommingMessage(Message message); 메서드를 구현하도록 강요하면 어떻게됩니까?

그런 다음 방법 findMailBox(destination);은 검색해야하는 사서함 인스턴스를 이미 결정한 것으로 결정합니다.

사서함의 다른 하위 클래스를 인스턴스화 할 때 각 하위 클래스는 통합 메시지 처리 방법에 대한 다양한 요구를 가질 수 있습니다.하지만이 일을 분리 할 수있는 다음과 같은 :

기능 인터페이스 :

public interface MessageHandler { 
    void handleMessage(Message message); 
} 

추상 클래스 :

public abstract MailBox{ 
    private MessageHandler handler; 

    protected MailBox(MessageHandler handler){ 
     this.handler = handler; 
    } 

Instanciation :

MailBox mb1 = new MailStorage(new DefaultMessageHandler()); 
MailBox mb2 = new AutoreplyingMailBox(new AutoReplyingMessageHandler()); 
MailBox mb3 = new HelpDeskMailBox(new HelpDeskMessageHandler()); 

당신이 원한다면, 당신도 할 수 MailBox의 모든 다른 하위 클래스를 없애고 대신 다른 구현을 수행하십시오. MessageHandler 인터페이스의

findMailBox- 메서드에 제공되는 대상에 따라 MailBox (이 경우 비 추상)를 인스턴스화하고 올바른 MessageHandler 구현을 제공하면됩니다. 만들 것

MailBox.handleIncommingMessage(...) 단 한 가지 (또는 2) 수행

public class MailBox { 

    private MessageHandler messageHandler; 

    public MailBox(MessageHandler messageHandler){ 
     this.messageHandler = messageHandler; 
    } 

    public void handleIncommingMessage(Message message){ 
     addMessage(message); 
     this.messageHandler.handleMessage(message); 
    } 
} 

귀하의 예제의 마지막 코드는 다음이 방법은 필요가 없을 것이

public void incomingMail(String destination, Message message) { 
    Mailbox mb = findMailBox(destination); // who cares how this works 
    mb.handleIncommingMessage(message); 
} 

같은 것을 새로운 유형의 MailBox 또는 MessageHandler가 도입되면 편집 할 수 있습니다. 로직이 데이터와 분리되면 메시지가 추가 될 때 발생하는 논리 (addMessage/handleIncommingMessage)가 MailHandler 구현에 보관됩니다.

관련 문제