DDD (또는 의미가있는)와의 모델 관계?


9

간단한 요구 사항은 다음과 같습니다.

사용자 Question는 여러 개의를 사용하여 를 만듭니다 Answer. Question하나 이상이 있어야합니다 Answer.

설명 : 생각 Question하고 테스트Answer같이 : 하나의 질문이 있지만 몇 가지 대답이 있습니다. 사용자는이 테스트를 준비하는 행위자이므로 질문과 답변을 만듭니다.

이 간단한 예제를 모델링하여 1) 실제 모델과 2) 코드로 표현하여 잠재적 인 오용 및 오류를 최소화하고 모델 사용 방법에 대한 힌트를 개발자에게 제공하려고합니다.

질문은 실체 이며, 답변은 가치 객체 입니다. 질문에 답이 있습니다. 지금까지 가능한 해결책이 있습니다.

[A] 공장 내부Question

Answer수동으로 생성하는 대신 다음을 호출 할 수 있습니다.

Answer answer = question.createAnswer()
answer.setText("");
...

그 대답을 만듭니다 그리고 그것은 질문에 추가 할 수 있습니다. 그런 다음 속성을 설정하여 답변을 조작 할 수 있습니다. 이런 식으로 질문 만 답을 만들 수 있습니다. 또한 우리는 질문없이 답변을하지 않습니다. 그러나 답변은 하드 코딩되어 있으므로 답변 작성을 제어 할 수 없습니다 Question.

위 코드의 '언어'에는 한 가지 문제가 있습니다. 사용자는 질문이 아니라 답변을 만드는 사람입니다. 개인적으로, 나는 우리가 가치 객체를 생성하는 것을 좋아하지 않으며 개발자에 따라 가치를 채울 필요가 없습니다-어떻게 추가 해야하는지 확신 할 수 있습니까?

[B] 공장 내부 질문, # 2

일부에서는 다음과 같은 방법을 사용해야한다고 말합니다 Question.

question.addAnswer(String answer, boolean correct, int level....);

위의 솔루션과 마찬가지로이 방법은 답변에 필수 데이터를 가져 와서 질문에 추가 할 데이터를 만듭니다.

여기서 문제는 우리가 아무 이유없이 생성자를 복제 한다는 것 Answer입니다. 또한 질문은 실제로 답을 만들어 줍니까?

[C] 생성자 의존성

우리 스스로 스스로 두 물건을 자유롭게 만들자. 또한 생성자에서 종속성을 표현해 봅시다 :

Question q = new Question(...);
Answer a = new Answer(q, ...);   // answer can't exist without a question

질문없이 대답을 만들 수 없으므로 개발자에게 힌트를줍니다. 그러나 질문에 답이 '추가'되었다는 '언어'는 보이지 않습니다. 반면에, 우리는 정말로 그것을 볼 필요가 있습니까?

[D] 생성자 의존성, # 2

우리는 반대로 할 수 있습니다 :

Answer a1 = new Answer("",...);
Answer a2 = new Answer("",...);
Question q = new Question("", a1, a2);

이것은 위와 반대되는 상황입니다. 여기서 대답은 질문 없이는 존재할 수 있지만 (의미가없는) 질문은 대답 없이는 존재할 수 없습니다 (의미가 있음). 또한, '언어'여기 것 그 질문에 더 분명하다 답변을.

[E] 일반적인 방법

이것이 내가 일반적인 방법이라고 부르는 것입니다. ppl이 가장 먼저하는 일은 다음과 같습니다.

Question q = new Question("",...);
Answer a = new Answer("",...);
q.addAnswer(a);

답변과 질문이 서로없이 존재할 수 있기 때문에 위의 두 답변 중 '느슨한'버전입니다. 당신은 아무 특별한 힌트가 없다 함께 바인딩은.

[F] 결합

또는 C, D, E를 결합하여 관계를 만드는 방법을 모두 다루어야 개발자가 자신에게 가장 적합한 것을 사용할 수 있습니다.

질문

나는 사람들이 위의 답변 중 하나를 선택하여 위의 답변 중 하나를 선택할 수 있다는 것을 알고 있습니다. 그러나 위의 변형 중 하나가 다른 이유보다 나은지 궁금합니다. 또한 위의 질문에서 생각하지 마십시오. 대부분의 경우에 적용될 수있는 몇 가지 모범 사례를 짜고 싶습니다. 동의하면 대부분의 사용 사례는 유사합니다. 또한 여기서 기술과 무관하게하자. ORM 사용 여부를 생각하고 싶지 않습니다. 좋은 표현 모드를 원하십시오.

이것에 대한 지혜?

편집하다

Question및의 다른 속성은 무시하십시오 Answer. 질문과 관련이 없습니다. 위의 텍스트를 편집하고 필요한 경우 대부분의 생성자를 변경했습니다. 이제 필요한 속성 값을 수락합니다. 그것은 단지 질문 문자열이거나 다른 언어, 상태 등의 문자열 맵 일 수 있습니다-전달 된 속성에 관계없이 이것에 초점이 맞지 않습니다. 고맙습니다!

답변:


6

업데이트되었습니다. 설명이 고려되었습니다.

이것이 다중 선택 도메인 인 것처럼 보이며 일반적으로 다음 요구 사항이 있습니다.

  1. 질문은 두 가지 중 하나를 선택해야합니다.
  2. 적어도 하나의 올바른 선택이 있어야합니다
  3. 질문이없는 선택은 없어야합니다

위의 내용을 기반으로

[A] 는 지점 1의 불변을 보장 할 수 없습니다. 선택하지 않아도 질문이 생길 수 있습니다

[B][A] 와 동일한 단점이 있습니다 .

[C][A][B] 와 동일한 단점이 있습니다 .

[D] 는 유효한 접근 방법이지만 선택 항목을 개별적으로 전달하는 것보다 목록으로 전달하는 것이 좋습니다.

[E][A] , [B][C] 와 동일한 단점이 있습니다 .

따라서 포인트 1, 2 및 3의 도메인 규칙을 준수 할 수 있기 때문에 [D] 로 이동합니다 . 질문이 오랜 시간 동안 아무런 선택없이 남아있을 가능성이 거의 없다고해도 코드를 통해 도메인 요구 사항을 전달하는 것이 항상 좋습니다.

또한 이 도메인에서 나에게 더 의미가 있는 Answerto Choice를 이름을 바꿉니다 .

public class Choice implements ValueObject {

    private Question q;
    private final String txt;
    private final boolean isCorrect;
    private boolean isSelected = false;

    public Choice(String txt, boolean isCorrect) {
        // validate and assign
    }

    public void assignToQuestion(Question q) {
        this.q = q;
    }

    public void select() {
        isSelected = true;
    }

    public void unselect() {
        isSelected = false;
    }

    public boolean isSelected() {
        return isSelected;
    }
}

public class Question implements Entity {

    private final String txt;
    private final List<Choice> choices;

    public Question(String txt, List<Choice> choices) {
        // ensure requirements are met
        // 1. make sure there are more than 2 choices
        // 2. make sure at least 1 of the choices is correct
        // 3. assign each choice to this question
    }
}

Choice ch1 = new Choice("The sky", false);
Choice ch2 = new Choice("Ceiling", true);
List<Choice> choices = Arrays.asList(ch1, ch2);
Question q = new Question("What's up?", choices);

공책. 당신이 할 경우 Question개체를 집계 루트와 Choice값 객체 같은 집합의 일부를, 하나는 저장할 수있는 기회가 없습니다 Choice그것이 할당하지 않고 Question당신이에 대한 직접 참조를 통과하지 않더라도 ( Question받는 인수로 Choice저장소는 루트에서만 작동하고 일단 빌드 Question하면 생성자에서 모든 선택 사항이 지정됩니다.

도움이 되었기를 바랍니다.

최신 정보

질문에 앞서 선택이 어떻게 만들어 지는지를 실제로 귀찮게한다면 유용한 몇 가지 트릭이 있습니다.

1) 질문 이후 또는 적어도 동시에 생성 된 것처럼 보이도록 코드를 재 배열하십시오.

Question q = new Question(
    "What's up?",
    Arrays.asList(
        new Choice("The sky", false),
        new Choice("Ceiling", true)
    )
);

2) 생성자를 숨기고 정적 팩토리 메소드를 사용하십시오.

public class Question implements Entity {
    ...

    private Question(String txt) { ... }

    public static Question newInstance(String txt, List<Choice> choices) {
        Question q = new Question(txt);
        for (Choice ch : choices) {
            q.assignChoice(ch);
        }
    }

    public void assignChoice(Choice ch) { ... }
    ...
}

3) 빌더 패턴 사용

Question q = new Question.Builder("What's up?")
    .assignChoice(new Choice("The sky", false))
    .assignChoice(new Choice("Ceiling", true))
    .build();

그러나 모든 것은 도메인에 따라 다릅니다. 대부분의 경우 객체 생성 순서는 문제 도메인 관점에서 중요하지 않습니다. 더 중요한 것은 클래스의 인스턴스를 얻는 즉시 논리적으로 완성되어 사용할 수 있다는 것입니다.


시대에 뒤쳐진. 아래의 모든 내용은 설명 후 질문과 관련이 없습니다.

우선, DDD 도메인 모델에 따르면 현실에서는 의미가 있어야합니다. 따라서 몇 점

  1. 질문에 답이 없을 수 있습니다
  2. 질문이 없으면 답이 없어야합니다
  3. 답은 정확히 하나의 질문에 해당해야합니다
  4. "빈"답변은 질문에 답변하지 않습니다

위의 내용을 기반으로

[A] 는 텍스트를 잘못 사용하고 설정하는 것을 잊기 때문에 포인트 4와 모순 될 수 있습니다.

[B] 는 유효한 접근 방법이지만 선택적인 매개 변수가 필요합니다

[C] 는 텍스트가없는 답변을 허용하기 때문에 포인트 4와 모순 될 수 있습니다.

[D] 는 포인트 1과 모순되며 포인트 2와 3과 모순 될 수 있습니다.

[E] 포인트 2, 3 및 4와 모순 될 수 있음

둘째, 도메인 로직을 적용하기 위해 OOP 기능을 사용할 수 있습니다. 즉, 필요한 매개 변수에 생성자를 사용하고 선택적 매개 변수에 설정자를 사용할 수 있습니다.

세번째로, 나는 도메인에보다 자연스러운 유비쿼터스 언어를 사용할 것입니다.

마지막으로 집계 루트, 엔터티 및 값 개체와 같은 DDD 패턴을 사용하여 모든 것을 디자인 할 수 있습니다. 질문을 집계의 근본으로 만들고 답변을 그 일부로 만들 수 있습니다. 대답은 질문의 맥락 밖에서 의미가 없기 때문에 이것은 논리적 결정입니다.

따라서 위의 모든 내용은 다음 디자인으로 요약됩니다.

class Answer implements ValueObject {

    private final Question q;
    private String txt;
    private boolean isCorrect = false;

    Answer(Question q, String txt) {
        // validate and assign
    }

    public void markAsCorrect() {
        isCorrect = true;
    }

    public boolean isCorrect() {
        return isCorrect;
    }
}

public class Question implements Entity {

    private String txt;
    private final List<Answer> answers = new ArrayList<>();

    public Question(String txt) {
        // validate and assign
    }

    // Ubiquitous Language: answer() instead of addAnswer()
    public void answer(String txt) {
        answers.add(new Answer(this, txt));
    }
}

Question q = new Question("What's up?");
q.answer("The sky");

추신 귀하의 질문에 대한 답변 귀하의 도메인에 대한 가정이 옳지 않을 수 있으므로 위의 내용을 자유롭게 조정하십시오.


1
요약하면 : 이것은 B와 C의 혼합입니다. 요구 사항에 대한 설명을 참조하십시오. 요점 1. 질문을 작성하는 동안 '짧은'기간 동안 만 존재할 수 있습니다. 그러나 데이터베이스에는 없습니다. 그런 의미에서 4.는 절대 일어나지 않아야합니다. 나는 이제 요구 사항이 명확
해지기를 바란다

BTW, 정화와, 나에게 그런 것 addAnswer또는 assignAnswer단지보다 더 나은 언어 것 answer, 난 당신이 동의 바랍니다. 어쨌든, 내 질문은-당신은 여전히 ​​B로 가고 예를 들어 답 방법에 대부분의 인수 사본을 가지고 있습니까? 그것이 중복되지 않습니까?
lawpert

불명확 한 요구 사항에 대해 죄송합니다. 답변을 업데이트 해 주시겠습니까?
lawpert

1
내 가정이 틀렸다는 것이 밝혀졌습니다. QA 도메인을 stackexchange 웹 사이트의 예로 취급했지만 객관식 테스트처럼 보입니다. 물론 대답을 업데이트하겠습니다.
zafarkhaja

1
@lawpert Answer는 값 객체이며 집계의 집계 루트와 함께 저장됩니다. 값 개체를 직접 저장하거나 개체가 집계의 근본이 아닌 경우 개체를 저장하지 않습니다.
zafarkhaja

1

요구 사항이 너무 단순하여 여러 가능한 솔루션이 존재하는 경우 KISS 원칙을 따라야합니다. 귀하의 경우에는 옵션 E입니다.

무언가를 표현하는 코드를 만드는 경우도 있습니다. 예를 들어, 질문에 대한 답변 (A 및 B)을 작성하거나 질문에 대한 답변 (C 및 D)을 제공하면 도메인에 필요하지 않고 혼동 될 수있는 동작이 추가됩니다. 또한 귀하의 경우 질문은 대부분 답변과 함께 집계되며 답변은 값 유형이됩니다.


1
왜 [C]가 불필요한 행동입니까? 내가 알다시피, [C]는 Answer가 질문 없이는 살 수 없다고 말하고 그것이 바로 그 질문입니다. 또한 응답에 필수 플래그 (예 : 응답 유형, 범주 등)가 더 필요한지 상상해보십시오. KISS에 들어가기 위해 우리는 필수 사항에 대한 지식을 잃어 버렸습니다. 개발자는 정답을 맞추기 위해 답변에 추가 / 설정해야하는 내용 을 미리 알아야 합니다 . 여기서 질문은이 간단한 예제를 모델링하는 것이 아니라 OO를 사용하여 유비쿼터스 언어를 작성하는 더 나은 방법을 찾는 것이 었습니다.
igor

@igor E는 이미 답변을 질문에 할당하도록하여 답변이 질문의 일부임을 저장소에 저장합니다. 질문을로드하지 않고 답변 만 저장할 수있는 방법이 있다면 C가 더 좋습니다. 그러나 그것은 당신이 쓴 것에서 분명하지 않습니다.
Euphoric

@igor 또한, Answer with Question 작성을 묶으려면 A가 더 좋을 것입니다. C를 사용하면 응답이 질문에 할당 될 때 숨겨지기 때문입니다. 또한 A에서 텍스트를 읽으면 "모델 동작"과이 동작을 시작한 사람을 구별해야합니다. 질문은 어떤 식 으로든 답변을 초기화해야 할 때 답변을 생성해야 할 책임이 있습니다. "사용자 생성 답변"과 관련이 없습니다.
Euphoric

기록을 위해, C & E 사이에서 찢어진 것 :) 이제 : "... 저장할 질문에 대한 답변을 반드시 할당하도록하는 것은 저장소입니다." 이것은 '필수'부분은 우리가 저장소에 올 때만 온다는 것을 의미 합니다. 따라서 필수 연결은 컴파일 타임에 개발자에게 '표시되지'않으며 비즈니스 규칙은 저장소에서 누출됩니다. 그래서 여기서 [C]를 테스트하고 있습니다. 어쩌면 이 이야기 는 C 옵션이 무엇이라고 생각하는지에 대한 자세한 정보를 제공 할 수 있습니다.
igor

이 : "... ... 질문과 대답의 생성을 묶어 싶다". _creation 자체 를 묶고 싶지 않습니다 . 강제적 인 관계 를 표현하고 싶을 뿐이다 . 제 생각에 이것은 창조에 관한 것이 아니기 때문에 곧 A와 B를 버립니다. 질문이 답변 작성을 담당한다는 것을 알 수 없습니다.
igor

1

[C] 나 [E]로 갈 것입니다.

첫째, 왜 A와 B가 아닌가? 내 질문에 관련 값을 생성하는 것을 원하지 않습니다. Question에 다른 많은 가치 객체가 있다고 상상해보십시오 create. 또는 복잡한 집계가 있으면 같은 경우입니다.

왜 [D]? 그것은 우리가 실제로 가진 것과 반대이기 때문입니다. 먼저 질문을 만듭니다. 이 모든 것을 만드는 웹 페이지를 상상할 수 있습니다. 사용자가 먼저 질문을 만들 것입니다. 따라서 D가 아닙니다.

[E]는 @Euphoric과 같이 KISS입니다. 하지만 최근에는 [C]도 좋아하기 시작합니다. 보이는 것처럼 혼란스럽지 않습니다. 또한 질문이 더 많은 것에 의존하는지 상상해보십시오. 개발자는 질문을 올바르게 초기화하기 위해 질문에 넣을 내용 을 알아야 합니다 . 당신이 옳지 만-실제로 대답이 질문에 추가되었다는 것을 설명하는 '시각적'언어는 없습니다.

추가 자료

이와 같은 질문은 컴퓨터 언어가 모델링에 너무 일반적인지 궁금합니다. (나는 그들이 이해할 수 있는 모든 프로그래밍 요구 사항에 대한 답변을 포괄적으로). 최근에 나는 유창한 인터페이스를 사용하여 비즈니스 언어를 표현하는 더 좋은 방법을 찾으려고 노력하고 있습니다. 이와 같은 것 (sudo 언어) :

use(question).addAnswer(answer).storeToRepo();

즉, 큰 * Services 및 * Repository 클래스에서 더 작은 비즈니스 로직 덩어리로 이동하려고합니다. 그냥 생각이야


애드온에서 도메인 특정 언어에 대해 이야기하고 있습니까?
lawpert

이제 당신이 언급했을 때, 그것은 그렇게 보입니다 :) 구매 나는 그것에 대해 아무런 경험이 없습니다.
igor

2
나는 IO 엔티티 (storeToRepo)에 의해 처리되어서는 안된다, 따라서 직교 resposibility 인 것을 지금 쯤은 합의 있다고 생각
Esben SKOV 페데르센

본인은 @Esben Skov Pedersen에 실체 자체가 repo를 호출해서는 안된다는 점에 동의합니다. 그러나 AFAIU로서 우리는 명령을 호출하는 일종의 빌더 패턴을 가지고 있습니다. 여기서 엔티티에서 IO가 수행되지 않습니다. 적어도 이것이
내가

@lawpert 맞습니다. 나는 그것이 어떻게 작동 해야하는지 알지 못하지만 흥미로울 것입니다.
Esben Skov Pedersen

1

나는 당신이 여기서 요점을 놓쳤다 고 생각합니다. 귀하의 집계 루트는 테스트 엔터티가되어야합니다.

그리고 그것이 사실이라면 TestFactory가 귀하의 문제에 대답하는 데 가장 적합 할 것이라고 생각합니다.

질문과 답변 건물을 팩토리에 위임하면 하위 엔티티를 인스턴스화하는 방식으로 클라이언트에 숨겨져 있기 때문에 기본적으로 모델을 손상시키지 않고 생각한 솔루션을 사용할 수 있습니다.

TestFactory가 테스트를 인스턴스화하는 데 사용하는 유일한 인터페이스 인 경우에만 가능합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.