Model relationships with DDD (or with sense)?

Here is a simplified requirement:

User creates a Question with multiple Answers. Question must have at least one Answer.

Clarification: think Question and Answer as in a test: there is one question, but several answers, where few may be correct. User is the actor who is preparing this test, hence he creates question and answers.

I am trying to model this simple example so to 1) match the real life model 2) to be expressive with the code, so to minimize potential misuse and errors, and to give hints to developers how to use the model.

Question is an entity, while answer is value object. Question holds answers. So far, I have these possible solutions.

[A] Factory inside Question

Instead of creating Answer manually, we can call:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Answer answer = question.createAnswer()
answer.setText("");
...
</code>
<code>Answer answer = question.createAnswer() answer.setText(""); ... </code>
Answer answer = question.createAnswer()
answer.setText("");
...

That will create an answer and add it to the question. Then we can manipulate answer by setting it’s properties. This way, only questions can crete an answer. Also, we prevent to have an answer without a question. Yet, we do not have control over creating answers, as that is hardcoded in the Question.

There is also one problem with the ‘language’ of above code. User is the one who creates answers, not the question. Personally, I don’t like we create value object and depending on developer to fill it with values – how he can be sure what is required to add?

[B] Factory inside Question, take #2

Some says we should have this kind of method in Question:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>question.addAnswer(String answer, boolean correct, int level....);
</code>
<code>question.addAnswer(String answer, boolean correct, int level....); </code>
question.addAnswer(String answer, boolean correct, int level....);

Similar to above solution, this method takes mandatory data for the answer and creates one that will be also added to the question.

Problem here is that we duplicate the constructor of the Answer for no good reason. Also, does question really creates an answer?

[C] Constructor dependencies

Let’s be free to create both objects by our selves. Let’s also express the dependency right in constructor:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Question q = new Question(...);
Answer a = new Answer(q, ...); // answer can't exist without a question
</code>
<code>Question q = new Question(...); Answer a = new Answer(q, ...); // answer can't exist without a question </code>
Question q = new Question(...);
Answer a = new Answer(q, ...);   // answer can't exist without a question

This gives hints to developer, as answer can’t be created without a question. However, we do not see the ‘language’ that says that answer is ‘added’ to the question. On the other hand, do we really need to see it?

[D] Constructor dependency, take #2

We can do the opposite:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Answer a1 = new Answer("",...);
Answer a2 = new Answer("",...);
Question q = new Question("", a1, a2);
</code>
<code>Answer a1 = new Answer("",...); Answer a2 = new Answer("",...); Question q = new Question("", a1, a2); </code>
Answer a1 = new Answer("",...);
Answer a2 = new Answer("",...);
Question q = new Question("", a1, a2);

This is opposite situation of above. Here answers can exist without a question (which does not make sense), but question can not exist without answer (which make sense). Also, the ‘language’ here is more clear on that question will have the answers.

[E] Common way

This is what I call the common way, the first thing that ppl usually do:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Question q = new Question("",...);
Answer a = new Answer("",...);
q.addAnswer(a);
</code>
<code>Question q = new Question("",...); Answer a = new Answer("",...); q.addAnswer(a); </code>
Question q = new Question("",...);
Answer a = new Answer("",...);
q.addAnswer(a);

which is ‘loose’ version of above two answers, since both answer and question may exist without each other. There is no special hint that you have to bind them together.

[F] Combined

Or should I combine C, D, E – to cover all the ways how relationship can be made, so to help developers to use whatever is best for them.

Question

I know people may choose one of the answers above based on the ‘hunch’. But I wonder if any of above variant is better then the other with a good reason for that. Also, please don’t think inside the above question, I would like to squeeze here some best practices that could be applied on most cases – and if you agree, most use cases of creation some entities are similar. Also, lets be technology agnostic here, eg. I do not want to think if ORM is going to be used or not. Just want good, expressive mode.

Any wisdom on this?

EDIT

Please ignore other properties of Question and Answer, they are not relevant for the question. I edited above text and changed the most of the constructors (where needed): now they accept any of necessary property values needed. That may be just a question string, or map of strings in different languages, statuses etc. – whatever properties are passed, they are not a focus for this 😉 So just assume we are above passing necessary parameters, unless said different. Thanx!

Updated. Clarifications taken into account.

Looks like this is a multiple choice domain, which usually has the following requirements

  1. a question must have at least two choices so that you could choose among
  2. there must be at least one correct choice
  3. there should not be a choice without a question

Based on the above

[A] can’t ensure the invariant from point 1, you may end up with a question without any choice

[B] has the same disadvantage as [A]

[C] has the same disadvantage as [A] and [B]

[D] is a valid approach, but it’s better to pass the choices as a list rather than passing them individually

[E] has the same disadvantage as [A], [B] and [C]

Hence, I would go for [D] because it allows to ensure domain rules from points 1, 2 and 3 are followed. Even if you say that it’s very unlikely for a question to remain without any choice for a long period of time, it’s always a good idea to convey domain requirements through the code.

I would also rename the Answer to Choice as it makes more sense to me in this domain.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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);
</code>
<code>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); </code>
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);

A note. If you make the Question entity an aggregate root and the Choice value object a part of the same aggregate, there’s no chances one can store a Choice without it being assigned to a Question (even though you don’t pass a direct reference to the Question as an argument to the Choice‘s constructor), because the repositories work with roots only and once you build your Question you have all your choices assigned to it in the constructor.

Hope this helps.

UPDATE

If it really bothers you how the choices are created ahead of their question, there are few tricks you might find useful

1) Rearrange the code so that it looks like they are created after the question or at least at the same time

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Question q = new Question(
"What's up?",
Arrays.asList(
new Choice("The sky", false),
new Choice("Ceiling", true)
)
);
</code>
<code>Question q = new Question( "What's up?", Arrays.asList( new Choice("The sky", false), new Choice("Ceiling", true) ) ); </code>
Question q = new Question(
    "What's up?",
    Arrays.asList(
        new Choice("The sky", false),
        new Choice("Ceiling", true)
    )
);

2) Hide constructors and use a static factory method

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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) { ... }
...
}
</code>
<code>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) { ... } ... } </code>
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) Use the builder pattern

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Question q = new Question.Builder("What's up?")
.assignChoice(new Choice("The sky", false))
.assignChoice(new Choice("Ceiling", true))
.build();
</code>
<code>Question q = new Question.Builder("What's up?") .assignChoice(new Choice("The sky", false)) .assignChoice(new Choice("Ceiling", true)) .build(); </code>
Question q = new Question.Builder("What's up?")
    .assignChoice(new Choice("The sky", false))
    .assignChoice(new Choice("Ceiling", true))
    .build();

However, everything depends on your domain. Most of the times the order of objects creation is not important from the problem domain perspective. What is more important is that as soon as you get an instance of your class it is logically complete and ready to use.

Outdated. Everything below is irrelevant to the question after clarifications.

First of all, according to DDD domain model should make sense in the real world. Hence, few points

  1. a question may have no answers
  2. there should not be an answer without a question
  3. an answer should correspond to exactly one question
  4. an “empty” answer doesn’t answer a question

Based on the above

[A] may contradict point 4 because it’s easy to misuse and forget to set the text.

[B] is a valid approach but requires parameters that are optional

[C] may contradict point 4 because it allows an answer with no text

[D] contradicts point 1 and may contradict points 2 and 3

[E] may contradict points 2, 3 and 4

Secondly, we can make use of OOP features to enforce domain logic. Namely we can use constructors for the required parameters and setters for the optional ones.

Thirdly, I would use the ubiquitous language which is supposed to be more natural for the domain.

And finally, we can design it all using DDD patterns like aggregate roots, entities and value objects. We can make the Question a root of its aggregate and the Answer a part of it. This is a logical decision because an answer has no meaning outside of a question’s context.

So, all the above boil down to the following design

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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");
</code>
<code>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"); </code>
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");

P.S. Answering to your question I made few assumptions about your domain which might not be correct, so feel free to adjust the above with your specifics.

10

In case where requirements are so simple, that multiple possible solutions exist, then KISS principle should be followed. In your case, that would be option E.

There is also case of creating code that expresses something, that it should not. For example tying creation of Answers to Question (A and B) or giving Answer reference to Question (C and D) add some behavior that is not necessary to the domain and might be confusing. Also, in your case, Question would most probably be aggregate with Answer and Answer would be a value type.

9

I would go either [C] or [E].

First, why not A and B? I don’t want my Question be responsible for creating any related value. Imagine if Question has many other value objects – would you put create method for every one? Or if there are some complex aggregates, the same case.

Why not [D]? Because it is opposite to what we have in nature. We first do create a Question. You can imagine a web page where you create all this – user would first create a question, right? Therefore, not D.

[E] is KISS, like @Euphoric said. But I also start to like [C] recently. This is not so confusing as it seems. Moreover, imagine if Question depends on more things – then developer must to know what he needs to put inside the Question to have it properly initialized. Although you are right – there is no ‘visual’ language explaining that answer is actually added to the question.

Additional reading

Questions like this makes me wonder if our computer languages are too generic for modeling. (I understand they have to be generic to answer on all programming requirements). Recently I am trying to find a better way to express the business language using fluent interfaces. Something like this (in sudo language):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>use(question).addAnswer(answer).storeToRepo();
</code>
<code>use(question).addAnswer(answer).storeToRepo(); </code>
use(question).addAnswer(answer).storeToRepo();

i.e. trying to move away from any big *Services and *Repository classes to smaller chunks of business logic. Just an idea.

5

I believe you missed a point here, your Aggregate root should be your Test Entity.

And if it’s really the case I believe a TestFactory would be best suited to answer your problem.

You would delegate the Question and Answer building to the Factory and therefore you could basically use any solution you thought of without corrupting your model because you are hiding to the client the way you instantiate your sub entities.

This is, as long as the TestFactory is the only interface you use to instantiate your Test.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật