I am designing a guest check-in system for a residency hall. Residents can check-in guests, but only if these two constraints are met.
1. A resident can only have 2 guests checked-in at once
2. A guest can only be checked in with one resident
The check-in process results in a Visit.
I'm having trouble figuring out where these rules should be implemented. I started with this
var visit = resident.checkin(guest);
But that means I am modifying (or creating) three aggregates in one action:
Resident (increment # of checked in guests)
Guest (set them as checked in)
Visit = created
I don't see a concept in the domain to model as aggregate to hold these rules. Residents and guests exist outside of visits (or across other visits), so can't be wrapped in another aggregate.
I thought about a Saga, but that ends up in steps that don't make sense in the domain (like checking in the student and guest separately to see if one fails).
I could use some guidance? Is my modelling just off?
Creating a new aggregate B as part of existing aggregate A's transaction is usually not a problem in terms of concurrent access and consistency (since no one knows B yet). Here you have an additional invariant on the number of Visits which could be an exception to that, but it's enforced in Resident and Resident is the only entry point to creating a Visit, so everything boils down to a modification of Resident which is safe concurrency wise.
Then you have a global constraint on how many visits a guest can be in at a time. The issue has been answered multiple times on SO, but I would first of all recommend getting back to your domain experts to discuss the rule and possible variations of it, because there's a world of possibilities (eventual consistency, manual intervention, etc.) between "the constraint must be enforced and immediately consistent" and "the rule is useless".
In my mind the checked in state of the Resident and Guest should be viewed as a part of a 3rd AR and the incremental field in the Resident should just be incremented when a Visit is confirmed.
Approach 1:
I don't see a concept in the domain to model as aggregate to hold these rules. Residents and guests exist outside of visits (or across other visits), so can't be wrapped in another aggregate.
This does not mean that your Visit cannot hold references to the Resident and Guest.
If this 3rd aggregate is the Visit and it has states (for e.g. pending, confirmed, etc.) you can validate the state in a synchronous way and go through the states as the rule commands.
Approach 2 - Use Events for this:
There many ways to integrate events architecturally. Event Sourcing and CQRS could be a good fit here as it will change the way that you think of ARs, Entities and especially state changes.
Short CQRS definition:
Capture all changes to an application state as a sequence of events
Whatever you choose between 1 and 2 you should think of the Visit validation as a separate thing. The Resident should not validate how many guests there are at once, the Guest should not validate if 'he' is at two places at once. Those are all Business Rules related to Visits.
Some links:
I've recently answered a DDD question in a similar domain
CQRS's "official site" by Greg Young
Docs on CQRS by Greg Young
Martin Fowler's notes on Event Sourcing
When to avoid CQRS?
Tons of videos and presentations on the subject
Residence Hall - Aggregate Root (AR)
Guest - entity
Resident - entity
Visits - Value Object (list of Visit)
Commands for AR:
CheckInGuestCmd(guest,resident)
CheckOutGuestCmd(guest)
First command will check visits and enforce invariants than add visit
Second command will remove existing visit if it exists
Related
This question is more of a general question about how to model simple one-to-many relations using collections: should a change in a list item be reflected in the version of the aggregate containing it?
The domain is about meeting scheduling (like in Outlook).
I have a Meeting entity, which can have multiple Participants.
A participant can accept/decline meeting requests.
Rescheduling a meeting nullifies all of the participants confirmations.
I thought of two ways to model this.
Option 1
The Meeting aggregate will contain a list of Participants where each Participant has a ParticipantId and a Status (accepted/denied).
The problem here is that every Accept or Deny command, for a specific participant, increments the Meeting's version, which means two participants will enter a race condition if trying to Accept the meeting request based on the same original version.
Although this could be solved by re-reading the Meeting's document and retrying the Accept command, it's quite annoying considering how often this could happen.
Another approach is to ignore the meeting's version when executing the Accept command, but this introduces a new problem: what happens if, after sending the meeting requests, the meeting has been rescheduled? In this case we can't afford to ignore the Meeting's version, because this time the version DOES represent a real version that should be considered.
BTW, is it at all a good practice to ignore the version in some of the commands and not in others?
Option 2
Extract a Participation aggregate out of Meeting.
Participation will have MeetingId, ParticipantId, and Status.
It will also have its own version.
This way, when participant X Accepts the meeting request, only the relevant Participation will be modified, and the rest will be left intact.
And, when rescheduling the meeting, a "Meeting Rescheduled" event will be published and an event handler will respond to it by resetting all of the Participations' statuses to "NotAccepted" regardless of their current version.
On the one hand this sounds logical in the sense that a meeting's version shouldn't be incremented just because someone accepted/denied its request.
On the other hand, modeling Participation as a standalone aggregate doesn't sound quite right to me, because it is has no meaning outside of the context of the meeting.
Anyway, would love to get feedback on this and see the various approaches to this problem.
Although this could be solved by re-reading the Meeting's document and retrying the Accept command, it's quite annoying considering how often this could happen.
This looks like a modeling error. You should keep in mind that the meeting aggregate is not the book of record for the participants availability - the real world is. So the message shouldn't be AcceptInvitation, but instead InvitationAccepted. There shouldn't be a conflict about this, because the domain model doesn't get to veto events outside of its authority boundary.
You might, depending on your implementation, end up with a concurrent modification exception in your plumbing, but that's something that you should be handling automatically (ie: expected version any, or a retry).
Another approach is to ignore the meeting's version when executing the Accept command, but this introduces a new problem: what happens if, after sending the meeting requests, the meeting has been rescheduled?
The solution here is to model more carefully. Yes, sometimes you will get a message that accepts or declines an invitation that has expired.
Put another way: race conditions don't exist.
A microsecond difference in timing shouldn’t make a difference to core business behaviors.
What happens to Alice, who replied instantly to the invitation, when the meeting is rescheduled? Why wouldn't the same thing happen to Bob, when his reply arrives just after the meeting is rescheduled?
Participation as a standalone aggregate doesn't sound quite right to me, because it is has no meaning outside of the context of the meeting.
I find that heuristic isn't particularly effective. It's much more important to understand whether entities can change state independently, or if their changes need to be coordinated.
Actually, the Meeting aggregate is used to track the participants availability. That's what it purpose is. Unless I didn't fully understand you...
It's a bit subtle, and I didn't spell it out very well.
Suppose the model says that I'm available, but an emergency in the real world calls me away. What happens? Am I blocked from going to the hospital because the model says I have to go to a meeting? Can somebody cancel my emergency by changing the invitation I've submitted?
Furthermore, if I'm away on an emergency, are you available for a meeting that is scheduled for the same time as the meeting you and I were going to have?
In this space, the real world is the authority for whether or not somebody is available. The model is just looking at a cached copy of a message describing whether or not somebody was available in the past.
The cached information being used by the model is not guaranteed to be complete. See Greg Young on warehouse systems and exception reports.
which makes me think that perhaps the Meeting aggregate should have two version fields: one will be a strong version which, when incremented, represents a breaking change, and another soft version for non-breaking changes. Does this make any sense?
Not really. Version is not, as far as I know, a term taken from the ubiquitous language of scheduling meetings. It's meta data, if it exists at all, and the business rules in your model should not depend upon meta data.
I agree, but a Meeting ID (or any ID for that matter) is also not part of the ubiquitous language, yet I might pass it back and forth between my domain world and external worlds.
How can we rotate CUSTOMER NUMBER values in CICS?
For eg. If customer number is c52063
How can i get onto next value ie, c52064(say) in CICS?
This is a very broad question, essentially you're asking what persistence mechanisms are available in CICS.
Please understand there is a big difference between...
what is technically possible
what is allowed in your shop
what is likely to provide a robust and maintainable solution given your requirements
These are three very different things. Some of us answering questions here on StackOverflow have life experiences that make us reticent about answering questions regarding what is technically possible absent any mention of what is allowed in your shop or what the actual business requirement that is being solved.
Mainframes have been around for over half a century, and many shops have standard solutions to technical problems. Sometimes the solution is "don't do that, and here's what we do instead." Working against the recommendations of your technical staff, or your shop standards, is career limiting.
A couple of options, not intended to be an exhaustive list...
SELECT and UPDATE the value in a DBMS (such as DB2). You must code your SELECT SQL with FOR UPDATE.
READ and REWRITE the value in a VSAM file. You must code your READ with the UPDATE option.
In either case you are holding a lock on the resource until you hit either an explicit (EXEC CICS SYNCPOINT) or implicit (end of transaction) syncpoint or rollback (EXEC CICS SYNCPOINT ROLLBACK or abend condition). Holding such a lock means all other instances of your transaction will wait until the syncpoint or rollback has occurred.
If you know for certain your application will be limited to a single CICS region... Other options would include having a transaction initiated as part of region initialization processing that would obtain and populate a shared resource such as a temporary storage queue with a name known to your application with the last known customer number. This initialization transaction would have to obtain the highest used customer number from somewhere, probably a DBMS or a VSAM file. Applications would have to be coded to ENQ and DEQ their access to the temporary storage queue. You could do this without using a temporary storage queue but with shared memory and storing the address of that memory in the CICS CWA for your region. Again, ENQ and DEQ logic would have to be coded in the applications.
You could use a named counter as defined by your CICS Systems Programmer. Be certain to read and understand the recovery requirements for your application as documented in the IBM Knowledge Center.
Again, this is not an exhaustive list, it is just to give an overview of some of the options available. Talk to your technical staff, they likely have either a standard solution as employed by your shop or a preference based on their experience and your requirements.
I am working on DDD project and I am currently focused on two bouned contexts, Orders and Warehouse.
What confuses me is the following situation:
Order keep track of all the placed orders, and warehouse keeps track about all the available inventory. If user places one order for certain product item, that would mean one less item of that product in a warehouse. I am oversimplifying this process, so please bear with me.
Since two domain models are placed inside of a different BC, i am currently relying on eventual consistency ie. after one item has been sold, it would eventually be removed from the warehouse.
That situation unfortunately leads to the problem scenario where another user could simultaneously make another order of the same item, and it would appear as available until eventual consistency kicks is. That is something it is unacceptable by the domain expert.
So IMO I am stuck with two options
merge order and warehouse (at least the part regarding product
inventory, units available in warehouse) into one BC
have Order BC (or microservice if you wish) to be dependent of Warehouse BC (microservice) in order to pull a live product units
available
Which option does actually follows DDD patern? Is there another way out?
You could use a reservation system with a timeout.
Using a messaging analogy: With a broker-style queuing mechanism (such as RabbitMQ) you get a message from the queue and you have control over it until you either acknowledge that it can be removed from the queue or you release it back to the queue.
You could do the same thing in your ordering process. You reserve any items on your order. SO when you add them they have a status of, say, reserving and upon sending some message to reserve the items. If the response comes back you can decide how to proceed. Perhaps you could add any items that cannot be reserved onto a back order or try again later.
There are going to be different ways to approach this. Depending on your business case it may be acceptable to only check availability when someone really accepts the order.
If you domain expert reckons it is totally unacceptable that having this resolved at the end of the process then you could move it to the start. The issue is of course that user A could reserve and never buy thereby losing user B as a customer; whereas only applying the real "taking" of the item at the end of the process is closer to ensuring a purchase. But that is a business decision.
This issue is a really great example of where reality actually is eventually consistent. Is it really the best thing to decline an order if there is no inventory currently in the warehouse - even if there was a replenishment due in the next 20 minutes?
What if the item was actually on the shelf, but the operator hadn't yet keyed it into the system?
Sometimes designers and domain experts assume that people want 100% consistency, when really, users might be willing to accept a delay in confirmation of their order, if it increased the chance that their order would be accepted rather than rejected.
In the case above, why make it the user's job to retry their order N minutes later? In an eventually consistent system, you can accommodate such timing flexibility by including a timeout to retry the attempt to fulfill the order for a period of time before confirming to the client that it really wasn't possible.
There are technical solutions that will give you 100% consistency, but I think really this is not a technical challenge but a cultural/mindset one, changing people's minds about what is possible & acceptable to achieve an what is actually a better outcome.
IMO you can build a PlaceOrderSaga which will ask for inventory availability before placing the order.
I'm new to the CQRS/ES world and I have a question. I'm working on an invoicing web application which uses event sourcing and CQRS.
My question is this - to my understanding, a new command coming into the system (let's say ChangeLineItemPrice) should pass through the domain model so it can be validated as a legal command (for example, to check if this line item actually exists, the price doesn't violate any business rules, etc). If all goes well (the command is not rejected) - then the appropriate event is created and stored (for example LineItemPriceChanged)
The thing I didn't quite get is how do I keep this aggregate in memory to begin with, before trying to apply the command. If I have a million invoices in the system, should I playback the whole history every time I want to apply a command? Do I always save the event without any validations and do the validations when constructing the view models / projections?
If I misunderstood any part of the process I would appreciate your feedback.
Thanks for your help!
You are not alone, this is a common misunderstanding. Let me answer the validation part first:
There are 2 types of validation which take place in this kind of system. The first is the kind where you look for valid email addresses, numeric only or required fields. This type is done before the command is even issued. A command which contains these sorts of problems should not be raised as commands (for belt and braces you can check at the domain side but this is not a domain concern and you are better off just preventing this scenario).
The next type of validation is when it is a domain concern. It could be the kind of thing you mention where you check prices are within a set of specified parameters. This is a domain concept the business people would understand, do and be able to articulate.
The next phase is for the domain to apply the state change and raise the associated events. These are then persisted and on success, published for the rest of the app.
All of this is can be done with the aggregate in memory. The actions are coordinated with a domain service which handles the command. It loads the aggregate, apply's all it's past events (or loads a snapshot) then issues the command. On success of the command it requests all the new uncommitted events and tries to persist them. On success it publishes the new events.
As you see it only loads the events for that specific aggregate. Even with a lot of events this process is lightning fast. If performance is a problem there are strategies such as keeping aggregates in memory or snapshotting which you can apply.
To your last point about validating events. As they can only be generated by your aggregate they are trustworthy.
If you want more detail check out my overview of CQRS and ES here. And take a look at my post about how to build aggregate roots here.
Good luck - I hope they help!
It is right that you have to replay the event to 'rehydrate' the domain aggregate. But you don't have to replay all events for all invoices. If you store the entity id of the root aggregate in the events, you can just select and replay the events that with the relevant id.
Then, how do you find the relevant aggregate root id? One of the read repositories should contain the relevant information to get the id, based on a set of search criteria.
So, not really sure if this is the right place for this but I have this current Context level data flow diagram for the bellow specification extract and I have never done one before so I was wondering if it was correct or if it needs fixing? any help appreciated
This is a link to a screen of my current one http://i.imgur.com/S4xvutc.png
SPECIFICATION
Currently the office staff operate the following processes:
Add/Amend/Delete Membership
This is run on-demand when a new membership application is received or when a member indicates that he/she wishes to make amendments to their details. It is also run in those rare instances when a membership is terminated at the discretion of the manager. A new member has an ID number allocated (simply incremented from the previous membership accepted). A membership balance is also maintained for accounting purposes.
Another process operates in a similar fashion on data associated with transfer partners.
Monthly Maintenance
This is run on the last day of each month to issue requests and reminders for subscriptions due, and to remove memberships where fees remain outstanding. Standard letters are also generated. Membership balances are updated as appropriate.
Payment Updates
This is run prior to the Monthly Maintenance, with membership balances being updated accordingly.
Payments to partners are also disbursed at this time.
New Member Search
This is run whenever a new member has been added to the database. The partners are partitioned in terms of vehicle category and location. Normally, there is a limited choice of partner in a particular location (if, indeed, there is any choice) but for some popular destinations, several partners are involved in providing the airport transfer. Thus, a search is then made through the appropriate section for potential matches in the following manner:
A search is then made on the grounds of sex (many female passengers in particular prefer a driver of their own sex, especially if travelling alone or in couples).
Matches are then selected according to factors such as cost (if available), availability of extra requested facilities (such as child seats, air-conditioning etc.)
Existing Member - Additional Searches
These are run on-demand in the same fashion as for a new member's search. Members may of course request any number of such searches, but a separate payment is due for each.
All financial transactions (payments) are also posted to the separate Accounts file, which also stores other financial details relating to running costs for the consideration of the firm's accountants at the end of the financial year.
Thanks for any help, regarding this level 0 Context only DFD
It needs some fixing.
The most obvious flaw is that you use verbs in your dataflows. In some cases this can be fixed easily by just discarding the verb. Return balance and status is not a datflow, but balance and status is.
In others cases it is not so easy. Check Balance, is it outstanding? sounds more like a Process than a dataflow. It looks like Accounting is responsible for doing that job. So will Accounting produce a list of outstanding balances? Or will it return a single balance and status, and if so, based on what input? Will your Airpot Transport System send a list of balances to check to Accounting?
Take for example Monthly Maintenance. What matters is that you want
requests and reminders for subscriptions due
Standard letters
These need to be visible in your DFD
The fact that you want to remove memberships where fees remain outstanding, probably has not place in the toplevel diagram, because that looks like an internal affair.
In general, focus on what the System produces. Maintaining internal state is secondary, is is a necessity to produce the desired output.