Knowledge-based authentication using Domain-driven Design in C#
$begingroup$
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
Below are before and after screen shots of the Data Protection part of the Add Call view:
The current system was written five years ago and we did not attempt to use any design patterns or modern approaches (such as Domain-Driven Design) because we lacked the time and understanding.
We now have the opportunity to re-write this software and would like to follow a Domain-driven approach.
First draft of a DataProtectionQuestion entity class:
public class DataProtectionQuestion
{
public string Question { get; set; }
public IEnumerable<string> Answers { get; set; }
public bool IsRequired { get; set; }
public string Answer { get; set; }
public bool IsAnswered => !string.IsNullOrWhiteSpace(Answer);
public bool AnswerIsCorrect => Answers?.Contains(Answer) ?? false;
}
Questions that arose from this design:
- Is this an anaemic model?
- Is the DataProtectionQuestion entity the 'right' place to validate the answer?
- Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
- Should the setters be private and should clients supply data through a constructor?
- Should an Answer be an entity in its own right with a property for 'IsValid'?
- How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'? (I realise the UI is not the concern of the Domain, but having the ability to choose these as answers is)
Second draft (in an attempt to answer some of the above):
public class DataProtectionAnswer
{
public DataProtectionAnswer(string answer, bool isValid)
{
Answer = answer;
IsValid = isValid;
}
public string Answer { get; private set; }
public bool IsValid { get; private set; }
}
public class DataProtectionQuestion
{
public DataProtectionQuestion(string question, bool isRequired, IEnumerable<DataProtectionAnswer> answers)
{
// validate non-null parameters?
Question = question;
IsRequired = isRequired;
Answers = answers;
}
public string Question { get; private set; }
public bool IsRequired { get; private set; }
public IEnumerable<DataProtectionAnswer> Answers { get; private set; }
public DataProtectionAnswer SelectedAnswer { get; private set; }
public bool IsAnswered => SelectedAnswer != null;
public bool AnswerIsCorrect => SelectedAnswer?.IsValid ?? false;
public void SetSelectedAnswer(DataProtectionAnswer answer)
{
// Should validate that answer is not null and contained in Answers?
SelectedAnswer = answer;
}
}
Some answers..leading to more questions?:
- Q. Should the entity have methods like 'SetAnswer' or
'SetAnswerIsValid'? - A. I have added a 'SetSelectedAnswer' method but I still don't know if this 'feels' right?
- Q. Should the setters be private and should clients supply data through a constructor?
- A. I don't know but that's what I've done in draft 2.
- Q. Should an Answer be an entity in its own right with a property for 'IsValid'?
- A. As per previous question, this is what I've done but should I have?
- Q. How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
- A. I can now do this by adding an 'Unanswered' and 'Incorrect Answer' DataProtectionAnswer to the DataProtectionQuestion, but this
'feels' wrong. Isn't this the responsibility of the Presenter?
As you can probably tell, I'm floundering and really struggling to get my head around how to model this scenario using a DDD approach. Perhaps DDD isn't right for this situation. I don't know and I feel very stupid right now!
Can anyone please offer some guidance / suggestions on a way forward / better approach?
c# ddd
New contributor
$endgroup$
add a comment |
$begingroup$
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
Below are before and after screen shots of the Data Protection part of the Add Call view:
The current system was written five years ago and we did not attempt to use any design patterns or modern approaches (such as Domain-Driven Design) because we lacked the time and understanding.
We now have the opportunity to re-write this software and would like to follow a Domain-driven approach.
First draft of a DataProtectionQuestion entity class:
public class DataProtectionQuestion
{
public string Question { get; set; }
public IEnumerable<string> Answers { get; set; }
public bool IsRequired { get; set; }
public string Answer { get; set; }
public bool IsAnswered => !string.IsNullOrWhiteSpace(Answer);
public bool AnswerIsCorrect => Answers?.Contains(Answer) ?? false;
}
Questions that arose from this design:
- Is this an anaemic model?
- Is the DataProtectionQuestion entity the 'right' place to validate the answer?
- Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
- Should the setters be private and should clients supply data through a constructor?
- Should an Answer be an entity in its own right with a property for 'IsValid'?
- How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'? (I realise the UI is not the concern of the Domain, but having the ability to choose these as answers is)
Second draft (in an attempt to answer some of the above):
public class DataProtectionAnswer
{
public DataProtectionAnswer(string answer, bool isValid)
{
Answer = answer;
IsValid = isValid;
}
public string Answer { get; private set; }
public bool IsValid { get; private set; }
}
public class DataProtectionQuestion
{
public DataProtectionQuestion(string question, bool isRequired, IEnumerable<DataProtectionAnswer> answers)
{
// validate non-null parameters?
Question = question;
IsRequired = isRequired;
Answers = answers;
}
public string Question { get; private set; }
public bool IsRequired { get; private set; }
public IEnumerable<DataProtectionAnswer> Answers { get; private set; }
public DataProtectionAnswer SelectedAnswer { get; private set; }
public bool IsAnswered => SelectedAnswer != null;
public bool AnswerIsCorrect => SelectedAnswer?.IsValid ?? false;
public void SetSelectedAnswer(DataProtectionAnswer answer)
{
// Should validate that answer is not null and contained in Answers?
SelectedAnswer = answer;
}
}
Some answers..leading to more questions?:
- Q. Should the entity have methods like 'SetAnswer' or
'SetAnswerIsValid'? - A. I have added a 'SetSelectedAnswer' method but I still don't know if this 'feels' right?
- Q. Should the setters be private and should clients supply data through a constructor?
- A. I don't know but that's what I've done in draft 2.
- Q. Should an Answer be an entity in its own right with a property for 'IsValid'?
- A. As per previous question, this is what I've done but should I have?
- Q. How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
- A. I can now do this by adding an 'Unanswered' and 'Incorrect Answer' DataProtectionAnswer to the DataProtectionQuestion, but this
'feels' wrong. Isn't this the responsibility of the Presenter?
As you can probably tell, I'm floundering and really struggling to get my head around how to model this scenario using a DDD approach. Perhaps DDD isn't right for this situation. I don't know and I feel very stupid right now!
Can anyone please offer some guidance / suggestions on a way forward / better approach?
c# ddd
New contributor
$endgroup$
add a comment |
$begingroup$
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
Below are before and after screen shots of the Data Protection part of the Add Call view:
The current system was written five years ago and we did not attempt to use any design patterns or modern approaches (such as Domain-Driven Design) because we lacked the time and understanding.
We now have the opportunity to re-write this software and would like to follow a Domain-driven approach.
First draft of a DataProtectionQuestion entity class:
public class DataProtectionQuestion
{
public string Question { get; set; }
public IEnumerable<string> Answers { get; set; }
public bool IsRequired { get; set; }
public string Answer { get; set; }
public bool IsAnswered => !string.IsNullOrWhiteSpace(Answer);
public bool AnswerIsCorrect => Answers?.Contains(Answer) ?? false;
}
Questions that arose from this design:
- Is this an anaemic model?
- Is the DataProtectionQuestion entity the 'right' place to validate the answer?
- Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
- Should the setters be private and should clients supply data through a constructor?
- Should an Answer be an entity in its own right with a property for 'IsValid'?
- How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'? (I realise the UI is not the concern of the Domain, but having the ability to choose these as answers is)
Second draft (in an attempt to answer some of the above):
public class DataProtectionAnswer
{
public DataProtectionAnswer(string answer, bool isValid)
{
Answer = answer;
IsValid = isValid;
}
public string Answer { get; private set; }
public bool IsValid { get; private set; }
}
public class DataProtectionQuestion
{
public DataProtectionQuestion(string question, bool isRequired, IEnumerable<DataProtectionAnswer> answers)
{
// validate non-null parameters?
Question = question;
IsRequired = isRequired;
Answers = answers;
}
public string Question { get; private set; }
public bool IsRequired { get; private set; }
public IEnumerable<DataProtectionAnswer> Answers { get; private set; }
public DataProtectionAnswer SelectedAnswer { get; private set; }
public bool IsAnswered => SelectedAnswer != null;
public bool AnswerIsCorrect => SelectedAnswer?.IsValid ?? false;
public void SetSelectedAnswer(DataProtectionAnswer answer)
{
// Should validate that answer is not null and contained in Answers?
SelectedAnswer = answer;
}
}
Some answers..leading to more questions?:
- Q. Should the entity have methods like 'SetAnswer' or
'SetAnswerIsValid'? - A. I have added a 'SetSelectedAnswer' method but I still don't know if this 'feels' right?
- Q. Should the setters be private and should clients supply data through a constructor?
- A. I don't know but that's what I've done in draft 2.
- Q. Should an Answer be an entity in its own right with a property for 'IsValid'?
- A. As per previous question, this is what I've done but should I have?
- Q. How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
- A. I can now do this by adding an 'Unanswered' and 'Incorrect Answer' DataProtectionAnswer to the DataProtectionQuestion, but this
'feels' wrong. Isn't this the responsibility of the Presenter?
As you can probably tell, I'm floundering and really struggling to get my head around how to model this scenario using a DDD approach. Perhaps DDD isn't right for this situation. I don't know and I feel very stupid right now!
Can anyone please offer some guidance / suggestions on a way forward / better approach?
c# ddd
New contributor
$endgroup$
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
Below are before and after screen shots of the Data Protection part of the Add Call view:
The current system was written five years ago and we did not attempt to use any design patterns or modern approaches (such as Domain-Driven Design) because we lacked the time and understanding.
We now have the opportunity to re-write this software and would like to follow a Domain-driven approach.
First draft of a DataProtectionQuestion entity class:
public class DataProtectionQuestion
{
public string Question { get; set; }
public IEnumerable<string> Answers { get; set; }
public bool IsRequired { get; set; }
public string Answer { get; set; }
public bool IsAnswered => !string.IsNullOrWhiteSpace(Answer);
public bool AnswerIsCorrect => Answers?.Contains(Answer) ?? false;
}
Questions that arose from this design:
- Is this an anaemic model?
- Is the DataProtectionQuestion entity the 'right' place to validate the answer?
- Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
- Should the setters be private and should clients supply data through a constructor?
- Should an Answer be an entity in its own right with a property for 'IsValid'?
- How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'? (I realise the UI is not the concern of the Domain, but having the ability to choose these as answers is)
Second draft (in an attempt to answer some of the above):
public class DataProtectionAnswer
{
public DataProtectionAnswer(string answer, bool isValid)
{
Answer = answer;
IsValid = isValid;
}
public string Answer { get; private set; }
public bool IsValid { get; private set; }
}
public class DataProtectionQuestion
{
public DataProtectionQuestion(string question, bool isRequired, IEnumerable<DataProtectionAnswer> answers)
{
// validate non-null parameters?
Question = question;
IsRequired = isRequired;
Answers = answers;
}
public string Question { get; private set; }
public bool IsRequired { get; private set; }
public IEnumerable<DataProtectionAnswer> Answers { get; private set; }
public DataProtectionAnswer SelectedAnswer { get; private set; }
public bool IsAnswered => SelectedAnswer != null;
public bool AnswerIsCorrect => SelectedAnswer?.IsValid ?? false;
public void SetSelectedAnswer(DataProtectionAnswer answer)
{
// Should validate that answer is not null and contained in Answers?
SelectedAnswer = answer;
}
}
Some answers..leading to more questions?:
- Q. Should the entity have methods like 'SetAnswer' or
'SetAnswerIsValid'? - A. I have added a 'SetSelectedAnswer' method but I still don't know if this 'feels' right?
- Q. Should the setters be private and should clients supply data through a constructor?
- A. I don't know but that's what I've done in draft 2.
- Q. Should an Answer be an entity in its own right with a property for 'IsValid'?
- A. As per previous question, this is what I've done but should I have?
- Q. How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
- A. I can now do this by adding an 'Unanswered' and 'Incorrect Answer' DataProtectionAnswer to the DataProtectionQuestion, but this
'feels' wrong. Isn't this the responsibility of the Presenter?
As you can probably tell, I'm floundering and really struggling to get my head around how to model this scenario using a DDD approach. Perhaps DDD isn't right for this situation. I don't know and I feel very stupid right now!
Can anyone please offer some guidance / suggestions on a way forward / better approach?
c# ddd
c# ddd
New contributor
New contributor
New contributor
asked 3 hours ago
Nick AdkinsonNick Adkinson
241
241
New contributor
New contributor
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
To start with, you're not doing DDD here.
DDD (Domain-Driven Design / Development) is based around the idea that we start with the domain. We don't touch code yet—we develop the domain models on-paper (or whiteboard, whatever is preferred). Once that is done, we build the code as closely to the domain as possible. The point of DDD is that the code should mirror the domain design.
Before we get going, I highly, highly, highly recommend this book, by Scott Wlaschin, a prominent F# developer who brings DDD into a very easy-to-understand view (the examples are F#, but they apply to C# as well): Domain Modeling made Functional
DDD is about:
Define the domain, the inputs, and the outputs. That is, as a user of the system, what does the domain need to do. Here it sounds like we have part of the domain defined:
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
From there, we define our types. Generally, I do DDD with F#, but it's just as applicable to C#. We model the physical domain, so here we're not modeling the questions, we're modeling the validation. That is: the user must answer various questions and prove they are knowledgeable on the claim.
This is the root of our domain model: we need to validate some information. You have mixed multiple pieces here, so we're going to separate them a bit.
After building the types, we build the work. That is, the functions. We build the types as just data-structures, then we build the functions next to encapsulate the domain rules.
So, you've defined the domain (at least, as far as I see it) via the quoted-blurb, so what I want to do is move that into some types.
To start with, we'll define a DataProtectionResponse
(we're going to use the exact language from the domain model, the purpose of DDD is to translate the human-language into code).
class DataProtectionResponse {
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
}
Now, we need to come up with a model for DataProtectionQuestion
:
class DataProtectionQuestion {
public string Question { get; set; }
public bool Required { get; set; }
}
As you see, we are ONLY modeling two components of the question: the actual question, and whether or not it's required. The questions themselves are a different part of the domain, they're generated as a question, and using this is how we get into building a flexible model. We can now take these same questions somewhere else, and use them as a whole other tool, assuming it needs to interact with our current domain.
Next, we have ValidQuestionAnswer
. This is going to be the answer that are valid for this particular claim:
class ValidQuestionAnswer {
public Response Response { get; set; }
}
We made this a class as we absolutely want to consider a situation where an answer might have more data to it.
Finally, the Response
. You might say, "Der Kommissar, why does that need to be a class, it's always a string?" Again, we might need to add more to this model, including functionality, so we do that by using a class.
class Response {
public string Value { get; set; }
}
So now, our domain will consume an IEnumerable<DataProtectionResponse>
, but not directly.
class DataProtection {
public IEnumerable<DataProtectionResponse> Questions { get; set; }
}
Why another class? Well, let's start talking functionality.
First and foremost, the primary component of our design is that DataProtection
must validate. For this to work, we need a IsValid
function or property there:
public bool IsValid => Questions.All(x => x.IsSufficient);
Alright, so we have some concepts now. We have a IsValid
that indicates if our DataProtection
is valid or not, and we have decided that all of the questions must be sufficiently answered.
Next, we need to prove that a question is sufficiently answered.
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
Again, we are going to encode our actual logic: this DataProtectionResponse
is sufficient if any of the ValidQuestionAnswers
are acceptable with the question and response.
Next, how do we prove they're acceptable?
Well, the first rule is that if it's not required and there is no response, then it's valid:
if (!question.Required && response?.IsEmpty ?? false == false)
{
return true;
}
And of course, Response.IsEmpty
:
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
Otherwise, we want to prove that this response and the provided response are acceptable:
return response.Satisfies(Response);
And this is why we made it a class right-off-the-bat: we might have more logic that goes into Satisfies
that might do heuristic analysis. I.e. if you provide an address, the logic might say 123 Main St.
and 123 Main St
and 123 Main Street
are all the same.
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
Next, our Response.Satisfies
:
public bool Satisfies(Response response) => Value = response.Value;
And viola, we're done. We've encoded the entire domain, concisely, and using the actual terms the domain considers. Only 37 lines of code:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
We don't have any odd logic, we don't have any conflated values: each model concerns itself with it's own work, no one else's.
Additionally, when our domain changes now (or we have to change the satisfaction logic) we have built the flexibility in-place without needing major infrastructure rewrites. If we need to override a response, we encode that in DataProtectionResponse
and modify IsSufficient
.
Finally, you could even shorten Acceptable
to a single statement, since the logic is relatively straightforward:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response) =>
(!question.Required && response?.IsEmpty ?? true) || response.Satisfies(Response);
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
Finally, you asked a few questions about your implementation: as you see, I ignored them, purposefully. With DDD, those questions are not things to ask about the implementation but things to ask about the domain. With DDD we do iterations of "design", "implement", "design", "implement"—all the questions you have should go in the design stage, which is where you (and the other domain experts) gather and hash-out the principles of the project.
But, suppose I were to answer them:
Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
A: That's actually a design question, do you need to override whether an answer is valid or not? (I briefly touched on that in the first part of the answer.)
Should the setters be private and should clients supply data through a constructor?
A: This seems like an implementation question at first glance, but if we reword it things are different: can a client change an answer? If the answer is yes, the proposed design is fine. If not, model for immutability.
Should an Answer be an entity in its own right with a property for 'IsValid'?
A: In my humble opinion, yes. Answers aren't a string, they're a concept. Additionally, with a base-class, you can override that for answers that are bool
, DateTime
, etc.
How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
A: That's a design question, but my suggestion is to ditch the Valid
checkbox and provide a red/green "Needs Answered", "Incorrect Answer", or "Correct Answer" state. Again, do what the domain calls for. You're mixing some concerns here, and with DDD we create a clear separation. When you have a question like this, we go back into the design stage and hash it out. (It might turn out that you must not indicate if an answer is incorrect. Compliance laws are weird.)
$endgroup$
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Nick Adkinson is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216727%2fknowledge-based-authentication-using-domain-driven-design-in-c%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
To start with, you're not doing DDD here.
DDD (Domain-Driven Design / Development) is based around the idea that we start with the domain. We don't touch code yet—we develop the domain models on-paper (or whiteboard, whatever is preferred). Once that is done, we build the code as closely to the domain as possible. The point of DDD is that the code should mirror the domain design.
Before we get going, I highly, highly, highly recommend this book, by Scott Wlaschin, a prominent F# developer who brings DDD into a very easy-to-understand view (the examples are F#, but they apply to C# as well): Domain Modeling made Functional
DDD is about:
Define the domain, the inputs, and the outputs. That is, as a user of the system, what does the domain need to do. Here it sounds like we have part of the domain defined:
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
From there, we define our types. Generally, I do DDD with F#, but it's just as applicable to C#. We model the physical domain, so here we're not modeling the questions, we're modeling the validation. That is: the user must answer various questions and prove they are knowledgeable on the claim.
This is the root of our domain model: we need to validate some information. You have mixed multiple pieces here, so we're going to separate them a bit.
After building the types, we build the work. That is, the functions. We build the types as just data-structures, then we build the functions next to encapsulate the domain rules.
So, you've defined the domain (at least, as far as I see it) via the quoted-blurb, so what I want to do is move that into some types.
To start with, we'll define a DataProtectionResponse
(we're going to use the exact language from the domain model, the purpose of DDD is to translate the human-language into code).
class DataProtectionResponse {
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
}
Now, we need to come up with a model for DataProtectionQuestion
:
class DataProtectionQuestion {
public string Question { get; set; }
public bool Required { get; set; }
}
As you see, we are ONLY modeling two components of the question: the actual question, and whether or not it's required. The questions themselves are a different part of the domain, they're generated as a question, and using this is how we get into building a flexible model. We can now take these same questions somewhere else, and use them as a whole other tool, assuming it needs to interact with our current domain.
Next, we have ValidQuestionAnswer
. This is going to be the answer that are valid for this particular claim:
class ValidQuestionAnswer {
public Response Response { get; set; }
}
We made this a class as we absolutely want to consider a situation where an answer might have more data to it.
Finally, the Response
. You might say, "Der Kommissar, why does that need to be a class, it's always a string?" Again, we might need to add more to this model, including functionality, so we do that by using a class.
class Response {
public string Value { get; set; }
}
So now, our domain will consume an IEnumerable<DataProtectionResponse>
, but not directly.
class DataProtection {
public IEnumerable<DataProtectionResponse> Questions { get; set; }
}
Why another class? Well, let's start talking functionality.
First and foremost, the primary component of our design is that DataProtection
must validate. For this to work, we need a IsValid
function or property there:
public bool IsValid => Questions.All(x => x.IsSufficient);
Alright, so we have some concepts now. We have a IsValid
that indicates if our DataProtection
is valid or not, and we have decided that all of the questions must be sufficiently answered.
Next, we need to prove that a question is sufficiently answered.
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
Again, we are going to encode our actual logic: this DataProtectionResponse
is sufficient if any of the ValidQuestionAnswers
are acceptable with the question and response.
Next, how do we prove they're acceptable?
Well, the first rule is that if it's not required and there is no response, then it's valid:
if (!question.Required && response?.IsEmpty ?? false == false)
{
return true;
}
And of course, Response.IsEmpty
:
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
Otherwise, we want to prove that this response and the provided response are acceptable:
return response.Satisfies(Response);
And this is why we made it a class right-off-the-bat: we might have more logic that goes into Satisfies
that might do heuristic analysis. I.e. if you provide an address, the logic might say 123 Main St.
and 123 Main St
and 123 Main Street
are all the same.
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
Next, our Response.Satisfies
:
public bool Satisfies(Response response) => Value = response.Value;
And viola, we're done. We've encoded the entire domain, concisely, and using the actual terms the domain considers. Only 37 lines of code:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
We don't have any odd logic, we don't have any conflated values: each model concerns itself with it's own work, no one else's.
Additionally, when our domain changes now (or we have to change the satisfaction logic) we have built the flexibility in-place without needing major infrastructure rewrites. If we need to override a response, we encode that in DataProtectionResponse
and modify IsSufficient
.
Finally, you could even shorten Acceptable
to a single statement, since the logic is relatively straightforward:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response) =>
(!question.Required && response?.IsEmpty ?? true) || response.Satisfies(Response);
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
Finally, you asked a few questions about your implementation: as you see, I ignored them, purposefully. With DDD, those questions are not things to ask about the implementation but things to ask about the domain. With DDD we do iterations of "design", "implement", "design", "implement"—all the questions you have should go in the design stage, which is where you (and the other domain experts) gather and hash-out the principles of the project.
But, suppose I were to answer them:
Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
A: That's actually a design question, do you need to override whether an answer is valid or not? (I briefly touched on that in the first part of the answer.)
Should the setters be private and should clients supply data through a constructor?
A: This seems like an implementation question at first glance, but if we reword it things are different: can a client change an answer? If the answer is yes, the proposed design is fine. If not, model for immutability.
Should an Answer be an entity in its own right with a property for 'IsValid'?
A: In my humble opinion, yes. Answers aren't a string, they're a concept. Additionally, with a base-class, you can override that for answers that are bool
, DateTime
, etc.
How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
A: That's a design question, but my suggestion is to ditch the Valid
checkbox and provide a red/green "Needs Answered", "Incorrect Answer", or "Correct Answer" state. Again, do what the domain calls for. You're mixing some concerns here, and with DDD we create a clear separation. When you have a question like this, we go back into the design stage and hash it out. (It might turn out that you must not indicate if an answer is incorrect. Compliance laws are weird.)
$endgroup$
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
add a comment |
$begingroup$
To start with, you're not doing DDD here.
DDD (Domain-Driven Design / Development) is based around the idea that we start with the domain. We don't touch code yet—we develop the domain models on-paper (or whiteboard, whatever is preferred). Once that is done, we build the code as closely to the domain as possible. The point of DDD is that the code should mirror the domain design.
Before we get going, I highly, highly, highly recommend this book, by Scott Wlaschin, a prominent F# developer who brings DDD into a very easy-to-understand view (the examples are F#, but they apply to C# as well): Domain Modeling made Functional
DDD is about:
Define the domain, the inputs, and the outputs. That is, as a user of the system, what does the domain need to do. Here it sounds like we have part of the domain defined:
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
From there, we define our types. Generally, I do DDD with F#, but it's just as applicable to C#. We model the physical domain, so here we're not modeling the questions, we're modeling the validation. That is: the user must answer various questions and prove they are knowledgeable on the claim.
This is the root of our domain model: we need to validate some information. You have mixed multiple pieces here, so we're going to separate them a bit.
After building the types, we build the work. That is, the functions. We build the types as just data-structures, then we build the functions next to encapsulate the domain rules.
So, you've defined the domain (at least, as far as I see it) via the quoted-blurb, so what I want to do is move that into some types.
To start with, we'll define a DataProtectionResponse
(we're going to use the exact language from the domain model, the purpose of DDD is to translate the human-language into code).
class DataProtectionResponse {
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
}
Now, we need to come up with a model for DataProtectionQuestion
:
class DataProtectionQuestion {
public string Question { get; set; }
public bool Required { get; set; }
}
As you see, we are ONLY modeling two components of the question: the actual question, and whether or not it's required. The questions themselves are a different part of the domain, they're generated as a question, and using this is how we get into building a flexible model. We can now take these same questions somewhere else, and use them as a whole other tool, assuming it needs to interact with our current domain.
Next, we have ValidQuestionAnswer
. This is going to be the answer that are valid for this particular claim:
class ValidQuestionAnswer {
public Response Response { get; set; }
}
We made this a class as we absolutely want to consider a situation where an answer might have more data to it.
Finally, the Response
. You might say, "Der Kommissar, why does that need to be a class, it's always a string?" Again, we might need to add more to this model, including functionality, so we do that by using a class.
class Response {
public string Value { get; set; }
}
So now, our domain will consume an IEnumerable<DataProtectionResponse>
, but not directly.
class DataProtection {
public IEnumerable<DataProtectionResponse> Questions { get; set; }
}
Why another class? Well, let's start talking functionality.
First and foremost, the primary component of our design is that DataProtection
must validate. For this to work, we need a IsValid
function or property there:
public bool IsValid => Questions.All(x => x.IsSufficient);
Alright, so we have some concepts now. We have a IsValid
that indicates if our DataProtection
is valid or not, and we have decided that all of the questions must be sufficiently answered.
Next, we need to prove that a question is sufficiently answered.
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
Again, we are going to encode our actual logic: this DataProtectionResponse
is sufficient if any of the ValidQuestionAnswers
are acceptable with the question and response.
Next, how do we prove they're acceptable?
Well, the first rule is that if it's not required and there is no response, then it's valid:
if (!question.Required && response?.IsEmpty ?? false == false)
{
return true;
}
And of course, Response.IsEmpty
:
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
Otherwise, we want to prove that this response and the provided response are acceptable:
return response.Satisfies(Response);
And this is why we made it a class right-off-the-bat: we might have more logic that goes into Satisfies
that might do heuristic analysis. I.e. if you provide an address, the logic might say 123 Main St.
and 123 Main St
and 123 Main Street
are all the same.
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
Next, our Response.Satisfies
:
public bool Satisfies(Response response) => Value = response.Value;
And viola, we're done. We've encoded the entire domain, concisely, and using the actual terms the domain considers. Only 37 lines of code:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
We don't have any odd logic, we don't have any conflated values: each model concerns itself with it's own work, no one else's.
Additionally, when our domain changes now (or we have to change the satisfaction logic) we have built the flexibility in-place without needing major infrastructure rewrites. If we need to override a response, we encode that in DataProtectionResponse
and modify IsSufficient
.
Finally, you could even shorten Acceptable
to a single statement, since the logic is relatively straightforward:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response) =>
(!question.Required && response?.IsEmpty ?? true) || response.Satisfies(Response);
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
Finally, you asked a few questions about your implementation: as you see, I ignored them, purposefully. With DDD, those questions are not things to ask about the implementation but things to ask about the domain. With DDD we do iterations of "design", "implement", "design", "implement"—all the questions you have should go in the design stage, which is where you (and the other domain experts) gather and hash-out the principles of the project.
But, suppose I were to answer them:
Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
A: That's actually a design question, do you need to override whether an answer is valid or not? (I briefly touched on that in the first part of the answer.)
Should the setters be private and should clients supply data through a constructor?
A: This seems like an implementation question at first glance, but if we reword it things are different: can a client change an answer? If the answer is yes, the proposed design is fine. If not, model for immutability.
Should an Answer be an entity in its own right with a property for 'IsValid'?
A: In my humble opinion, yes. Answers aren't a string, they're a concept. Additionally, with a base-class, you can override that for answers that are bool
, DateTime
, etc.
How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
A: That's a design question, but my suggestion is to ditch the Valid
checkbox and provide a red/green "Needs Answered", "Incorrect Answer", or "Correct Answer" state. Again, do what the domain calls for. You're mixing some concerns here, and with DDD we create a clear separation. When you have a question like this, we go back into the design stage and hash it out. (It might turn out that you must not indicate if an answer is incorrect. Compliance laws are weird.)
$endgroup$
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
add a comment |
$begingroup$
To start with, you're not doing DDD here.
DDD (Domain-Driven Design / Development) is based around the idea that we start with the domain. We don't touch code yet—we develop the domain models on-paper (or whiteboard, whatever is preferred). Once that is done, we build the code as closely to the domain as possible. The point of DDD is that the code should mirror the domain design.
Before we get going, I highly, highly, highly recommend this book, by Scott Wlaschin, a prominent F# developer who brings DDD into a very easy-to-understand view (the examples are F#, but they apply to C# as well): Domain Modeling made Functional
DDD is about:
Define the domain, the inputs, and the outputs. That is, as a user of the system, what does the domain need to do. Here it sounds like we have part of the domain defined:
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
From there, we define our types. Generally, I do DDD with F#, but it's just as applicable to C#. We model the physical domain, so here we're not modeling the questions, we're modeling the validation. That is: the user must answer various questions and prove they are knowledgeable on the claim.
This is the root of our domain model: we need to validate some information. You have mixed multiple pieces here, so we're going to separate them a bit.
After building the types, we build the work. That is, the functions. We build the types as just data-structures, then we build the functions next to encapsulate the domain rules.
So, you've defined the domain (at least, as far as I see it) via the quoted-blurb, so what I want to do is move that into some types.
To start with, we'll define a DataProtectionResponse
(we're going to use the exact language from the domain model, the purpose of DDD is to translate the human-language into code).
class DataProtectionResponse {
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
}
Now, we need to come up with a model for DataProtectionQuestion
:
class DataProtectionQuestion {
public string Question { get; set; }
public bool Required { get; set; }
}
As you see, we are ONLY modeling two components of the question: the actual question, and whether or not it's required. The questions themselves are a different part of the domain, they're generated as a question, and using this is how we get into building a flexible model. We can now take these same questions somewhere else, and use them as a whole other tool, assuming it needs to interact with our current domain.
Next, we have ValidQuestionAnswer
. This is going to be the answer that are valid for this particular claim:
class ValidQuestionAnswer {
public Response Response { get; set; }
}
We made this a class as we absolutely want to consider a situation where an answer might have more data to it.
Finally, the Response
. You might say, "Der Kommissar, why does that need to be a class, it's always a string?" Again, we might need to add more to this model, including functionality, so we do that by using a class.
class Response {
public string Value { get; set; }
}
So now, our domain will consume an IEnumerable<DataProtectionResponse>
, but not directly.
class DataProtection {
public IEnumerable<DataProtectionResponse> Questions { get; set; }
}
Why another class? Well, let's start talking functionality.
First and foremost, the primary component of our design is that DataProtection
must validate. For this to work, we need a IsValid
function or property there:
public bool IsValid => Questions.All(x => x.IsSufficient);
Alright, so we have some concepts now. We have a IsValid
that indicates if our DataProtection
is valid or not, and we have decided that all of the questions must be sufficiently answered.
Next, we need to prove that a question is sufficiently answered.
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
Again, we are going to encode our actual logic: this DataProtectionResponse
is sufficient if any of the ValidQuestionAnswers
are acceptable with the question and response.
Next, how do we prove they're acceptable?
Well, the first rule is that if it's not required and there is no response, then it's valid:
if (!question.Required && response?.IsEmpty ?? false == false)
{
return true;
}
And of course, Response.IsEmpty
:
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
Otherwise, we want to prove that this response and the provided response are acceptable:
return response.Satisfies(Response);
And this is why we made it a class right-off-the-bat: we might have more logic that goes into Satisfies
that might do heuristic analysis. I.e. if you provide an address, the logic might say 123 Main St.
and 123 Main St
and 123 Main Street
are all the same.
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
Next, our Response.Satisfies
:
public bool Satisfies(Response response) => Value = response.Value;
And viola, we're done. We've encoded the entire domain, concisely, and using the actual terms the domain considers. Only 37 lines of code:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
We don't have any odd logic, we don't have any conflated values: each model concerns itself with it's own work, no one else's.
Additionally, when our domain changes now (or we have to change the satisfaction logic) we have built the flexibility in-place without needing major infrastructure rewrites. If we need to override a response, we encode that in DataProtectionResponse
and modify IsSufficient
.
Finally, you could even shorten Acceptable
to a single statement, since the logic is relatively straightforward:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response) =>
(!question.Required && response?.IsEmpty ?? true) || response.Satisfies(Response);
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
Finally, you asked a few questions about your implementation: as you see, I ignored them, purposefully. With DDD, those questions are not things to ask about the implementation but things to ask about the domain. With DDD we do iterations of "design", "implement", "design", "implement"—all the questions you have should go in the design stage, which is where you (and the other domain experts) gather and hash-out the principles of the project.
But, suppose I were to answer them:
Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
A: That's actually a design question, do you need to override whether an answer is valid or not? (I briefly touched on that in the first part of the answer.)
Should the setters be private and should clients supply data through a constructor?
A: This seems like an implementation question at first glance, but if we reword it things are different: can a client change an answer? If the answer is yes, the proposed design is fine. If not, model for immutability.
Should an Answer be an entity in its own right with a property for 'IsValid'?
A: In my humble opinion, yes. Answers aren't a string, they're a concept. Additionally, with a base-class, you can override that for answers that are bool
, DateTime
, etc.
How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
A: That's a design question, but my suggestion is to ditch the Valid
checkbox and provide a red/green "Needs Answered", "Incorrect Answer", or "Correct Answer" state. Again, do what the domain calls for. You're mixing some concerns here, and with DDD we create a clear separation. When you have a question like this, we go back into the design stage and hash it out. (It might turn out that you must not indicate if an answer is incorrect. Compliance laws are weird.)
$endgroup$
To start with, you're not doing DDD here.
DDD (Domain-Driven Design / Development) is based around the idea that we start with the domain. We don't touch code yet—we develop the domain models on-paper (or whiteboard, whatever is preferred). Once that is done, we build the code as closely to the domain as possible. The point of DDD is that the code should mirror the domain design.
Before we get going, I highly, highly, highly recommend this book, by Scott Wlaschin, a prominent F# developer who brings DDD into a very easy-to-understand view (the examples are F#, but they apply to C# as well): Domain Modeling made Functional
DDD is about:
Define the domain, the inputs, and the outputs. That is, as a user of the system, what does the domain need to do. Here it sounds like we have part of the domain defined:
As part of an insurance claims system we have created, the claims managers can log incoming telephone calls relating to a claim.
The claims manager must validate the caller by asking a number of 'Data Protection' questions that are generated dynamically from information stored against the claim in a database. I believe this type of security is known as 'knowledge-based authentication'.
Notes about Data Protection Questions:
- Some questions are mandatory and some are not.
- All mandatory questions must be answered in order to validate the caller.
- At least one non-mandatory question must be answered in order to validate the
caller. - Additional non-mandatory questions can remain unanswered.
- Each question may have multiple correct answers.
From there, we define our types. Generally, I do DDD with F#, but it's just as applicable to C#. We model the physical domain, so here we're not modeling the questions, we're modeling the validation. That is: the user must answer various questions and prove they are knowledgeable on the claim.
This is the root of our domain model: we need to validate some information. You have mixed multiple pieces here, so we're going to separate them a bit.
After building the types, we build the work. That is, the functions. We build the types as just data-structures, then we build the functions next to encapsulate the domain rules.
So, you've defined the domain (at least, as far as I see it) via the quoted-blurb, so what I want to do is move that into some types.
To start with, we'll define a DataProtectionResponse
(we're going to use the exact language from the domain model, the purpose of DDD is to translate the human-language into code).
class DataProtectionResponse {
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
}
Now, we need to come up with a model for DataProtectionQuestion
:
class DataProtectionQuestion {
public string Question { get; set; }
public bool Required { get; set; }
}
As you see, we are ONLY modeling two components of the question: the actual question, and whether or not it's required. The questions themselves are a different part of the domain, they're generated as a question, and using this is how we get into building a flexible model. We can now take these same questions somewhere else, and use them as a whole other tool, assuming it needs to interact with our current domain.
Next, we have ValidQuestionAnswer
. This is going to be the answer that are valid for this particular claim:
class ValidQuestionAnswer {
public Response Response { get; set; }
}
We made this a class as we absolutely want to consider a situation where an answer might have more data to it.
Finally, the Response
. You might say, "Der Kommissar, why does that need to be a class, it's always a string?" Again, we might need to add more to this model, including functionality, so we do that by using a class.
class Response {
public string Value { get; set; }
}
So now, our domain will consume an IEnumerable<DataProtectionResponse>
, but not directly.
class DataProtection {
public IEnumerable<DataProtectionResponse> Questions { get; set; }
}
Why another class? Well, let's start talking functionality.
First and foremost, the primary component of our design is that DataProtection
must validate. For this to work, we need a IsValid
function or property there:
public bool IsValid => Questions.All(x => x.IsSufficient);
Alright, so we have some concepts now. We have a IsValid
that indicates if our DataProtection
is valid or not, and we have decided that all of the questions must be sufficiently answered.
Next, we need to prove that a question is sufficiently answered.
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
Again, we are going to encode our actual logic: this DataProtectionResponse
is sufficient if any of the ValidQuestionAnswers
are acceptable with the question and response.
Next, how do we prove they're acceptable?
Well, the first rule is that if it's not required and there is no response, then it's valid:
if (!question.Required && response?.IsEmpty ?? false == false)
{
return true;
}
And of course, Response.IsEmpty
:
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
Otherwise, we want to prove that this response and the provided response are acceptable:
return response.Satisfies(Response);
And this is why we made it a class right-off-the-bat: we might have more logic that goes into Satisfies
that might do heuristic analysis. I.e. if you provide an address, the logic might say 123 Main St.
and 123 Main St
and 123 Main Street
are all the same.
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
Next, our Response.Satisfies
:
public bool Satisfies(Response response) => Value = response.Value;
And viola, we're done. We've encoded the entire domain, concisely, and using the actual terms the domain considers. Only 37 lines of code:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response)
{
if (!question.Required && response?.IsEmpty ?? true)
{
return true;
}
return response.Satisfies(Response);
}
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
We don't have any odd logic, we don't have any conflated values: each model concerns itself with it's own work, no one else's.
Additionally, when our domain changes now (or we have to change the satisfaction logic) we have built the flexibility in-place without needing major infrastructure rewrites. If we need to override a response, we encode that in DataProtectionResponse
and modify IsSufficient
.
Finally, you could even shorten Acceptable
to a single statement, since the logic is relatively straightforward:
class Response
{
public string Value { get; set; }
public bool IsEmpty => String.IsNullOrWhiteSpace(Value);
public bool Satisfies(Response response) => Value == response.Value;
}
class ValidQuestionAnswer
{
public Response Response { get; set; }
public bool Acceptable(DataProtectionQuestion question, Response response) =>
(!question.Required && response?.IsEmpty ?? true) || response.Satisfies(Response);
}
class DataProtectionQuestion
{
public string Question { get; set; }
public bool Required { get; set; }
}
class DataProtectionResponse
{
public DataProtectionQuestion Question { get; set; }
public IEnumerable<ValidQuestionAnswer> ValidQuestionAnswers { get; set; }
public Response Response { get; set; }
public bool IsSufficient => ValidQuestionAnswers.Any(x => x.Acceptable(Question, Response));
}
class DataProtection
{
public IEnumerable<DataProtectionResponse> Questions { get; set; }
public bool IsValid => Questions.All(x => x.IsSufficient);
}
Finally, you asked a few questions about your implementation: as you see, I ignored them, purposefully. With DDD, those questions are not things to ask about the implementation but things to ask about the domain. With DDD we do iterations of "design", "implement", "design", "implement"—all the questions you have should go in the design stage, which is where you (and the other domain experts) gather and hash-out the principles of the project.
But, suppose I were to answer them:
Should the entity have methods like 'SetAnswer' or 'SetAnswerIsValid'?
A: That's actually a design question, do you need to override whether an answer is valid or not? (I briefly touched on that in the first part of the answer.)
Should the setters be private and should clients supply data through a constructor?
A: This seems like an implementation question at first glance, but if we reword it things are different: can a client change an answer? If the answer is yes, the proposed design is fine. If not, model for immutability.
Should an Answer be an entity in its own right with a property for 'IsValid'?
A: In my humble opinion, yes. Answers aren't a string, they're a concept. Additionally, with a base-class, you can override that for answers that are bool
, DateTime
, etc.
How do I display answers in the UI to include 'Unanswered' and 'Incorrect Answer'?
A: That's a design question, but my suggestion is to ditch the Valid
checkbox and provide a red/green "Needs Answered", "Incorrect Answer", or "Correct Answer" state. Again, do what the domain calls for. You're mixing some concerns here, and with DDD we create a clear separation. When you have a question like this, we go back into the design stage and hash it out. (It might turn out that you must not indicate if an answer is incorrect. Compliance laws are weird.)
edited 2 hours ago
answered 2 hours ago
Der KommissarDer Kommissar
15.6k250135
15.6k250135
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
add a comment |
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
$begingroup$
Nice one, take my vote
$endgroup$
– Heslacher
2 hours ago
add a comment |
Nick Adkinson is a new contributor. Be nice, and check out our Code of Conduct.
Nick Adkinson is a new contributor. Be nice, and check out our Code of Conduct.
Nick Adkinson is a new contributor. Be nice, and check out our Code of Conduct.
Nick Adkinson is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216727%2fknowledge-based-authentication-using-domain-driven-design-in-c%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown