DDD approach where to enforce business rules without aggregate?
New to DDD I have a simple case a I would like to model using DDD approach
2 entities Student
and Course
Relevant property for Student are StudentId and Budget
Relevant property for Course are CourseId and Price
Student and Course are entities that can exists on its own and have their own life cycle
Business requirements:
1) Student can book one course (CourseId is fk for Student table)
2) Student can book the course only if the user's budget is higher or equal to the course price.
3) Changes of course price doesn’t affect the students have already booked the course.
4) When the student book the course the his budget remains unchanged (maybe changes later at the end of the course)
5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked.
Setting a lower amount should throw a runtime error.
What the way to model this simple case following domain driven design? Where to enforce the two busines rules (points 2 and 5)?
As a Course can exist without a Student I can’t define the aggregate where Student is the root entity and Course its child entity. Can I?
But at the same time the business rule defined at point 5 seems to me be an invariants. Is it?
So where and how to apply this rules?
I tried a service approach, can work for the first simple rule (point 2) but fail for the rule described at point 5
var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)
var studentService = new StudentService();
studentService.SubScribeStudentToCourse(student, course);
studentRepository.Update(student);
StudentService.ChangeStudentBudget(student, 100000);
studentRepository.Update(student);
when I update the student with the new budget someone else can change the course price making the student budget inconsistent
public class StudentService
{
SubScribeStudentToCourse(Studen student, Course course)
{
if (studentt.Budget >= course.Price)
{
student.CourseId = course.CourseId
}
}
ChangeStudentBudget( Student student, decimal budgetAmount)
{
if (student.CourseId != null)
{
var studentCourse = courseRepository.Get(student.CourseId);
if ( studentCourse.Price <= budgetAmount)
{
student.Budget = budgetAmount;
}
else
{
throw new Exception("Budget should be higher than studentCourse.Price");
}
}
}
}
oop design-patterns architecture domain-driven-design
add a comment |
New to DDD I have a simple case a I would like to model using DDD approach
2 entities Student
and Course
Relevant property for Student are StudentId and Budget
Relevant property for Course are CourseId and Price
Student and Course are entities that can exists on its own and have their own life cycle
Business requirements:
1) Student can book one course (CourseId is fk for Student table)
2) Student can book the course only if the user's budget is higher or equal to the course price.
3) Changes of course price doesn’t affect the students have already booked the course.
4) When the student book the course the his budget remains unchanged (maybe changes later at the end of the course)
5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked.
Setting a lower amount should throw a runtime error.
What the way to model this simple case following domain driven design? Where to enforce the two busines rules (points 2 and 5)?
As a Course can exist without a Student I can’t define the aggregate where Student is the root entity and Course its child entity. Can I?
But at the same time the business rule defined at point 5 seems to me be an invariants. Is it?
So where and how to apply this rules?
I tried a service approach, can work for the first simple rule (point 2) but fail for the rule described at point 5
var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)
var studentService = new StudentService();
studentService.SubScribeStudentToCourse(student, course);
studentRepository.Update(student);
StudentService.ChangeStudentBudget(student, 100000);
studentRepository.Update(student);
when I update the student with the new budget someone else can change the course price making the student budget inconsistent
public class StudentService
{
SubScribeStudentToCourse(Studen student, Course course)
{
if (studentt.Budget >= course.Price)
{
student.CourseId = course.CourseId
}
}
ChangeStudentBudget( Student student, decimal budgetAmount)
{
if (student.CourseId != null)
{
var studentCourse = courseRepository.Get(student.CourseId);
if ( studentCourse.Price <= budgetAmount)
{
student.Budget = budgetAmount;
}
else
{
throw new Exception("Budget should be higher than studentCourse.Price");
}
}
}
}
oop design-patterns architecture domain-driven-design
1
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31
add a comment |
New to DDD I have a simple case a I would like to model using DDD approach
2 entities Student
and Course
Relevant property for Student are StudentId and Budget
Relevant property for Course are CourseId and Price
Student and Course are entities that can exists on its own and have their own life cycle
Business requirements:
1) Student can book one course (CourseId is fk for Student table)
2) Student can book the course only if the user's budget is higher or equal to the course price.
3) Changes of course price doesn’t affect the students have already booked the course.
4) When the student book the course the his budget remains unchanged (maybe changes later at the end of the course)
5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked.
Setting a lower amount should throw a runtime error.
What the way to model this simple case following domain driven design? Where to enforce the two busines rules (points 2 and 5)?
As a Course can exist without a Student I can’t define the aggregate where Student is the root entity and Course its child entity. Can I?
But at the same time the business rule defined at point 5 seems to me be an invariants. Is it?
So where and how to apply this rules?
I tried a service approach, can work for the first simple rule (point 2) but fail for the rule described at point 5
var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)
var studentService = new StudentService();
studentService.SubScribeStudentToCourse(student, course);
studentRepository.Update(student);
StudentService.ChangeStudentBudget(student, 100000);
studentRepository.Update(student);
when I update the student with the new budget someone else can change the course price making the student budget inconsistent
public class StudentService
{
SubScribeStudentToCourse(Studen student, Course course)
{
if (studentt.Budget >= course.Price)
{
student.CourseId = course.CourseId
}
}
ChangeStudentBudget( Student student, decimal budgetAmount)
{
if (student.CourseId != null)
{
var studentCourse = courseRepository.Get(student.CourseId);
if ( studentCourse.Price <= budgetAmount)
{
student.Budget = budgetAmount;
}
else
{
throw new Exception("Budget should be higher than studentCourse.Price");
}
}
}
}
oop design-patterns architecture domain-driven-design
New to DDD I have a simple case a I would like to model using DDD approach
2 entities Student
and Course
Relevant property for Student are StudentId and Budget
Relevant property for Course are CourseId and Price
Student and Course are entities that can exists on its own and have their own life cycle
Business requirements:
1) Student can book one course (CourseId is fk for Student table)
2) Student can book the course only if the user's budget is higher or equal to the course price.
3) Changes of course price doesn’t affect the students have already booked the course.
4) When the student book the course the his budget remains unchanged (maybe changes later at the end of the course)
5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked.
Setting a lower amount should throw a runtime error.
What the way to model this simple case following domain driven design? Where to enforce the two busines rules (points 2 and 5)?
As a Course can exist without a Student I can’t define the aggregate where Student is the root entity and Course its child entity. Can I?
But at the same time the business rule defined at point 5 seems to me be an invariants. Is it?
So where and how to apply this rules?
I tried a service approach, can work for the first simple rule (point 2) but fail for the rule described at point 5
var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)
var studentService = new StudentService();
studentService.SubScribeStudentToCourse(student, course);
studentRepository.Update(student);
StudentService.ChangeStudentBudget(student, 100000);
studentRepository.Update(student);
when I update the student with the new budget someone else can change the course price making the student budget inconsistent
public class StudentService
{
SubScribeStudentToCourse(Studen student, Course course)
{
if (studentt.Budget >= course.Price)
{
student.CourseId = course.CourseId
}
}
ChangeStudentBudget( Student student, decimal budgetAmount)
{
if (student.CourseId != null)
{
var studentCourse = courseRepository.Get(student.CourseId);
if ( studentCourse.Price <= budgetAmount)
{
student.Budget = budgetAmount;
}
else
{
throw new Exception("Budget should be higher than studentCourse.Price");
}
}
}
}
oop design-patterns architecture domain-driven-design
oop design-patterns architecture domain-driven-design
edited Jan 3 at 0:08
Ghini Antonio
asked Jan 2 at 22:25


Ghini AntonioGhini Antonio
1,5161628
1,5161628
1
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31
add a comment |
1
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31
1
1
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31
add a comment |
3 Answers
3
active
oldest
votes
Point 5 is another validation rule. But if course price can be modified, you will have to check the rule there too, not just when student budget is modified.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates.
This question sounds to me like I already answered it.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
add a comment |
Hypothetical scenarios are typically tricky to comment on but I'll add my ZAR0.02 :)
You will have both a Student
and a Course
aggregate root. If you need the relationship to the other defined then store either a list of ids or a value object that represents the other side.
To enforce certain rules that cannot overlap it may be simpler to have a state regarding the budget on the Student
. For instance, if the course is not in the BudgetApproved
state then you cannot add to course. In order to change the budget you would first need to change the state to, say, 'Budgeting'. In this way you introduce more distinct steps that allow better control of your invariants.
Just another note on changing prices. These things would probably work on a "quote" basis in any event. Once you "Accept" the quote any changes in price are irrelevant unless there is an "Error" or "Omission" that could, and should, be dealt with using some business process or, if not defined in the system, out-of-band. An Order
may be Cancelled
or 'Abandoned` and then some other process such as a reimbursement kicks in.
add a comment |
Your aggregates must be eventually consistent, not strongly, unless it's a really really important scenario. If it is, then consider using Saga, or update them in one transaction. What you should do here is very simple: StudentService.SubscribeTo() and CourceService.Enroll(). This 2 methods should happen in 2 diffent transactions. First, inside StudentService.SubscribeTo you get student and course models from persistence, then you call student.SubscribeTo(course). After the operation, you raise student assignedToCourse Domain Event and StudentDomainEventsHandler catches it, and calls CourceService.Enroll() which gets student and course models from persistence, then calls course.Enroll(student).
add a comment |
Your Answer
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: "1"
};
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: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
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
});
}
});
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%2fstackoverflow.com%2fquestions%2f54013963%2fddd-approach-where-to-enforce-business-rules-without-aggregate%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
Point 5 is another validation rule. But if course price can be modified, you will have to check the rule there too, not just when student budget is modified.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates.
This question sounds to me like I already answered it.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
add a comment |
Point 5 is another validation rule. But if course price can be modified, you will have to check the rule there too, not just when student budget is modified.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates.
This question sounds to me like I already answered it.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
add a comment |
Point 5 is another validation rule. But if course price can be modified, you will have to check the rule there too, not just when student budget is modified.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates.
This question sounds to me like I already answered it.
Point 5 is another validation rule. But if course price can be modified, you will have to check the rule there too, not just when student budget is modified.
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates.
This question sounds to me like I already answered it.
answered Jan 3 at 8:10
choquero70choquero70
1,45421732
1,45421732
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
add a comment |
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
It isn't an invariant. Invariant is regarding just one aggregate. You have two aggregates. - Invariants can also be between multiple aggregates, i.e make sure customer with the same code is unique in the system.
– DmitriBodiu
Feb 5 at 7:24
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
@DmitriBodiu "customer with the same code is unique in the system" just affects to the Customer aggregate. From what I know, an invariant is a business rule concerning to the state of an aggregate type, a rule that must be transactionally consistent. It just affects to an aggregate type, which defines a transactional boundary. See pages 205 and 353 of the book "Implementing domain driven design"
– choquero70
Feb 5 at 7:54
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
"customer with the same code is unique in the system" just affects to the Customer aggregate. No way. Its an invariant affecting all customers in the system. Unless you create a huge Company aggregate with Customers, then it becomes an invariant inside Company aggregate. (enterprisecraftsmanship.com/2016/09/22/…) is a great article explaining across-aggreagte invariant.
– DmitriBodiu
Feb 5 at 7:58
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
@DmitriBodiu I mean it just affects to Customer aggregate type. Anyway I do uniqueness checking in the repository of the aggregate, when you try to persist a customer you check that there's no other customer already stored having the same code. No other aggregate type is checked
– choquero70
Feb 5 at 16:44
add a comment |
Hypothetical scenarios are typically tricky to comment on but I'll add my ZAR0.02 :)
You will have both a Student
and a Course
aggregate root. If you need the relationship to the other defined then store either a list of ids or a value object that represents the other side.
To enforce certain rules that cannot overlap it may be simpler to have a state regarding the budget on the Student
. For instance, if the course is not in the BudgetApproved
state then you cannot add to course. In order to change the budget you would first need to change the state to, say, 'Budgeting'. In this way you introduce more distinct steps that allow better control of your invariants.
Just another note on changing prices. These things would probably work on a "quote" basis in any event. Once you "Accept" the quote any changes in price are irrelevant unless there is an "Error" or "Omission" that could, and should, be dealt with using some business process or, if not defined in the system, out-of-band. An Order
may be Cancelled
or 'Abandoned` and then some other process such as a reimbursement kicks in.
add a comment |
Hypothetical scenarios are typically tricky to comment on but I'll add my ZAR0.02 :)
You will have both a Student
and a Course
aggregate root. If you need the relationship to the other defined then store either a list of ids or a value object that represents the other side.
To enforce certain rules that cannot overlap it may be simpler to have a state regarding the budget on the Student
. For instance, if the course is not in the BudgetApproved
state then you cannot add to course. In order to change the budget you would first need to change the state to, say, 'Budgeting'. In this way you introduce more distinct steps that allow better control of your invariants.
Just another note on changing prices. These things would probably work on a "quote" basis in any event. Once you "Accept" the quote any changes in price are irrelevant unless there is an "Error" or "Omission" that could, and should, be dealt with using some business process or, if not defined in the system, out-of-band. An Order
may be Cancelled
or 'Abandoned` and then some other process such as a reimbursement kicks in.
add a comment |
Hypothetical scenarios are typically tricky to comment on but I'll add my ZAR0.02 :)
You will have both a Student
and a Course
aggregate root. If you need the relationship to the other defined then store either a list of ids or a value object that represents the other side.
To enforce certain rules that cannot overlap it may be simpler to have a state regarding the budget on the Student
. For instance, if the course is not in the BudgetApproved
state then you cannot add to course. In order to change the budget you would first need to change the state to, say, 'Budgeting'. In this way you introduce more distinct steps that allow better control of your invariants.
Just another note on changing prices. These things would probably work on a "quote" basis in any event. Once you "Accept" the quote any changes in price are irrelevant unless there is an "Error" or "Omission" that could, and should, be dealt with using some business process or, if not defined in the system, out-of-band. An Order
may be Cancelled
or 'Abandoned` and then some other process such as a reimbursement kicks in.
Hypothetical scenarios are typically tricky to comment on but I'll add my ZAR0.02 :)
You will have both a Student
and a Course
aggregate root. If you need the relationship to the other defined then store either a list of ids or a value object that represents the other side.
To enforce certain rules that cannot overlap it may be simpler to have a state regarding the budget on the Student
. For instance, if the course is not in the BudgetApproved
state then you cannot add to course. In order to change the budget you would first need to change the state to, say, 'Budgeting'. In this way you introduce more distinct steps that allow better control of your invariants.
Just another note on changing prices. These things would probably work on a "quote" basis in any event. Once you "Accept" the quote any changes in price are irrelevant unless there is an "Error" or "Omission" that could, and should, be dealt with using some business process or, if not defined in the system, out-of-band. An Order
may be Cancelled
or 'Abandoned` and then some other process such as a reimbursement kicks in.
answered Jan 3 at 4:48


Eben RouxEben Roux
9,69921538
9,69921538
add a comment |
add a comment |
Your aggregates must be eventually consistent, not strongly, unless it's a really really important scenario. If it is, then consider using Saga, or update them in one transaction. What you should do here is very simple: StudentService.SubscribeTo() and CourceService.Enroll(). This 2 methods should happen in 2 diffent transactions. First, inside StudentService.SubscribeTo you get student and course models from persistence, then you call student.SubscribeTo(course). After the operation, you raise student assignedToCourse Domain Event and StudentDomainEventsHandler catches it, and calls CourceService.Enroll() which gets student and course models from persistence, then calls course.Enroll(student).
add a comment |
Your aggregates must be eventually consistent, not strongly, unless it's a really really important scenario. If it is, then consider using Saga, or update them in one transaction. What you should do here is very simple: StudentService.SubscribeTo() and CourceService.Enroll(). This 2 methods should happen in 2 diffent transactions. First, inside StudentService.SubscribeTo you get student and course models from persistence, then you call student.SubscribeTo(course). After the operation, you raise student assignedToCourse Domain Event and StudentDomainEventsHandler catches it, and calls CourceService.Enroll() which gets student and course models from persistence, then calls course.Enroll(student).
add a comment |
Your aggregates must be eventually consistent, not strongly, unless it's a really really important scenario. If it is, then consider using Saga, or update them in one transaction. What you should do here is very simple: StudentService.SubscribeTo() and CourceService.Enroll(). This 2 methods should happen in 2 diffent transactions. First, inside StudentService.SubscribeTo you get student and course models from persistence, then you call student.SubscribeTo(course). After the operation, you raise student assignedToCourse Domain Event and StudentDomainEventsHandler catches it, and calls CourceService.Enroll() which gets student and course models from persistence, then calls course.Enroll(student).
Your aggregates must be eventually consistent, not strongly, unless it's a really really important scenario. If it is, then consider using Saga, or update them in one transaction. What you should do here is very simple: StudentService.SubscribeTo() and CourceService.Enroll(). This 2 methods should happen in 2 diffent transactions. First, inside StudentService.SubscribeTo you get student and course models from persistence, then you call student.SubscribeTo(course). After the operation, you raise student assignedToCourse Domain Event and StudentDomainEventsHandler catches it, and calls CourceService.Enroll() which gets student and course models from persistence, then calls course.Enroll(student).
answered Feb 5 at 8:04
DmitriBodiuDmitriBodiu
13611
13611
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- 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.
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%2fstackoverflow.com%2fquestions%2f54013963%2fddd-approach-where-to-enforce-business-rules-without-aggregate%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
1
what have you tried? your question looks like homework
– Adam Siemion
Jan 2 at 22:29
I'm approaching DDD and I'm imagining in a hypothetical case like the one described where to define two simple business rules (the first probably a simple validation rule, the other an invariant to be respected whenever you change the value of Student.Budget
– Ghini Antonio
Jan 2 at 23:47
Setting a lower amount should throw a runtime error. That's the issue #1. I will follow up with the answer later, but for now just want to point that SetAmount is a "Crud" based thinking. You student should have Deposit and Withdraw methods. Not SetAmount. The actual 'SetAmount' logic would be incapsulated inside those methods, based on invariants.
– DmitriBodiu
Feb 5 at 7:31