1

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.

  • In your second example where did knowledge that we want 30 test questions go? – candied_orange May 5 '18 at 14:21
  • 1
    Depends on the database, but it most cases you can probably create a materialized view (an indexed view in MS SQL Server) of active questions, map a new read-only repository to that view, and simply take N top entities out of it. – yegodm May 5 '18 at 17:08
3

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.

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.

0

IEnumerable/IQueryable will not execute until you explicitly invoke the collection

To avoid storing millions of questions in memory, use a database

Can look into an Object Relational Model library like Entity Framework

  • Honestly don't know why each question can't be a file. – candied_orange May 5 '18 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.