Imagine, we have test modules with large number of questions and we can create tests which will contain exactly 30 questions.
public class Module
{
public long Id { get; set; }
public List<Question> Questions { get; set; } // all questions
}
public class Test
{
public long Id { get; set; }
public List<Question> Questions { get; set; } // exactly 30 per attempt
}
That is what I would usually I do:
public class QuestionsRepository
{
public Question[] GetRandomQuestions(int count)
{
return context.Set<Question>()
.Where(x => x.Status == QuestionStatus.Active)
.OrderBy(x => Guid.NewGuid())
.Take(count)
.ToArray();
}
}
public class TestService
{
public Test StartNewTest()
{
// take random 30 and build a new test
return new Test
{
Questions = questionsRepository.GetRandomQuestions(30)
};
}
}
However, this means that we have anemic domain models and domain logic is leaking into repository, it knows what Status must be Active, that we take only limited number of questions etc.
That is what I expect DDD to look like:
public class Module
{
public long Id { get; set; }
public List<Question> Questions { get; set; } // all questions
public Test StartNewTest(int questionsCount)
{
return new Test
{
Questions = Questions
.OrderBy(x => Guid.NewGuid())
.Take(questionsCount)
.ToArray();
};
}
}
public class TestService
{
public Test StartNewTest()
{
var module = moduleRepository.GetDefaultModule();
return module.StartNewTest();
}
}
Now, domain logic is contained in Domain Models.
However, this means that all questions will be loaded into memory eagerly and then will be filtered as IEnumerable
which may be very inefficient if we have large questions number.
How do you implement persistence-ignorant Domain Model while making all SQL queries efficient and optimal? I cannot understand how to design DDD without leaking domain logic into repositories.
1 Answer 1
How do you implement persistence-ignorant Domain Model while making all SQL queries efficient and optimal? I cannot understand how to design DDD without leaking domain logic into repositories.
To start, review your understanding of "repository"; here's what Evans originally wrote:
create an object that can provide the illusion of an in-memory collection of all objects of that type.
The motivation being that the implementation details of how the collection is actually stored are hidden behind an implementation agnostic interface. See Parnas 1971.
Here's the important trick: the interface itself does not have to be domain agnostic.
One interpretation of the repository pattern is that the interface describes a contract between the implementation details of the collection and the consumers. As Greg Young explains, there are advantages to making the details of that contract explicit. The interface documents the capabilities that the implementation needs to provide.
Now, I agree that you are right - in common cases, you shouldn't need a repository implementation that understands a domain concept like "active". What you probably want is that the domain model can choose what active means, and let the repository figure out how to implement the query.
So your Repository interface might look like
Repository {
Question [] getRandomQuestions(Status s, int count);
}
It wouldn't be wrong to wrap the repository in a domain service with a more explicit API.
Active Questions {
Question [] getRandom(int count) {
return questionRepository.get(QuestionStatus.Active, count);
}
}
That is what I expect DDD to look like...
Creation patterns are weird.
public Test StartNewTest()
{
var module = moduleRepository.GetDefaultModule();
return module.StartNewTest();
}
One of the important things to notice here is that starting a new test doesn't modify the module; it just happens that module is responsible for the state that you want to copy.
cqrs is a pattern that follows from this observation: reads and writes have different requirements, so maybe they should have different code paths. Here, a consequence of that idea might be that you don't create Tests from Modules, but rather from some simpler view of the model. The advantage being that you can use a lazy loaded view in those circumstances where loading the entire module isn't necessary.
-
the link to codebetter.com is broken. Here's an archive.org link: web.archive.org/web/20180430194023/http://codebetter.com/… (Greg Young)DarkTrick– DarkTrick2024年09月30日 09:09:21 +00:00Commented Sep 30, 2024 at 9:09
Explore related questions
See similar questions with these tags.
public class Test
andTestService
. As I understand they are not too important for the question. Maybe you could even remove theModule
class. Reason: You main question (as I understand) is theActive
inside the repository, which is domain knowledge. I know this question is quite old, but improving it (even not) will help people in the future. And the content in question itself is very important, I think.