Based on this talk from Mauro Servienti called "All Our Aggregates are Wrong. Here is the link for reference: https://www.youtube.com/watch?v=KkzvQSuYd5I
We might have a few bounded contexts each containing information of a item.
e.g
Marketing Context - name, description, images
Sales Context - list price, product code?
Inventory - stock level
Each context might have their own name for it, e.g. inventory might call it sales item.
I think sales might be the owner of an item so when you go to create an item that is the place it will be created.
I have two questions.
Does the UI call these context's seperately?
If I have a UI to create a product/item and takes the data for both marketing and sales (list price, product code, name, description), I believe it is the case that the UI will make two separate calls.
The first call being to the sales context to create the item, which will publish a ItemCreated event or similar.
The second call will be to the marketing context to set the name, description, etc...
Is this correct?
Marketing's item
so my understanding of the inventory service, is that it will listen for the sales ItemCreated event and then create its own aggregate/stream for its representation of an item (Inventory item)
With marketing on the other hand, if I have a UI where I create the item with sales and then need to set the data in marketing, does the UI wait for an item to be created in marketing before then setting the title and description? Does the UI poll the marketing context until the item has been created and then performs the command to update the data?
I did something similar in the past.
The best approach I found is:
Firs you have to find the main owner of the item. In my case it was a product catalog.
Having a product in the catalog means the company intent to work with this product even if there is not stock, nor a marketing campaign, nor anything yet. This is an aggregate by its own, its operation (add product to catalog) is completly transactional because even if the event ProductAddedToCatalog fails to be applied in other Bounded Context (BC) (because infrastructure fails) I do not have to undo nor coordinate/orchestate anything. Just retry raising the event. The Catalog BC is kept in a consistent state.
For the others BC; applying the event means they create its entry with empty/default values. i.e. in Warehouse BC the entry is created with 0 stock and its correlated product catalog ID. In marketing the entry is created as a product with no marketing campaings so far. And so on with others BC.
How I modeled the UI taks for this? I created several screens like a wizard.
The first screen ask for product catalgo info. Its only task is insert a new product on the catalog.
The next screen ask for Warehouse info but it is not inserting; it is updating the entry with default values created by applying ProductAddedToCatalog event that was raised in the first step.
This way, in case warehouse step fails, the system keeps its consistent state and a user could use the UI task for updating product stock in Warehouse BC later.
Related
I will try to keep this as generic as possible using the “order” and “product” example, to try and help others that come across this question.
The Structure:
In the application we have 3 different services, 2 services that follow the event sourcing pattern and one that is designed for read only having the separation between our read and write views:
- Order service (write)
- Product service (write)
- Order details service (Read)
The Background:
We are currently storing the relationship between the order and product in only one of the write services, for example within order we have a property called ‘productItems’ which contains a list of the aggregate Ids from Product for the products that have been added to the order. Each product added to an order is emitted onto Kafka where the read service will update the view and form the relationships between the data.
The Problem:
As we pull back by aggregate Id for the order and the product to update them, if a product was to be deleted, there is no way to disassociate the product from the order on the write side.
This in turn means we have inconsistency, that the order holds a reference to a product that no longer exists within the product service.
The Ideas:
Master the relationship on both sides, which means when the product is deleted, we can look at the associated orders and trigger an update to remove from each order (this would cause duplication of reference).
Create another view of the data that shows the relationships and use a saga to do a clean-up. When a delete is triggered, it will look up the view database, see the relationships within the data and then trigger an update for each of the orders that have the product associated.
Does it really matter having the inconsistencies if the Product details service shows the correct information? Because the view database will consume the product deleted event, it will be able to safely remove the relationship that means clients will be able to get the correct view of the data even if the write models appear inconsistent. Based on the order of the events, the state will always appear correct in the read view.
Another thought: as the aggregate Id is deleted, it should never be reused which means when we have checks on the aggregate such as: “is this product in the order already?” will never trigger as the aggregate Id will never be repurposed meaning the inconsistency should not cause an issue when running commands in the future.
Sorry for the long read, but these are all the ideas we have thought of so far, and I am keen to gain some insight from the community, to make sure we are on the right track or if there is another approach to consider.
Thank you in advance for your help.
Event sourcing suites very well human and specifically human-paced processes. It helps a lot to imagine that every event in an event-sourced system is delivered by some clerk printed on a sheet of paper. Than it will be much easier to figure out the suitable solution.
What's the purpose of an order? So that your back-office personnel would secure the necessary units at a warehouse, then customer would do a payment and you start shipping process.
So, I guess, after an order is placed, some back-office system can process it and confirm that it can be taken into work and invoicing. Or it can return the order with remarks that this and that line are no longer available, so that a customer could agree to the reduced order or pick other options.
Another option is, since the probability of a customer ordering a discontinued item is low, just not do this check. But if at the shipping it still occurs - then issue a refund and some coupon for inconvenience. Why is it low? Because the goods are added from an online catalogue, which reflects the current state. The availability check can be done on the 'Submit' button click. So, an inconsistency may occur if an item is discontinued the same minute (or second) the order has been submitted. And usually the actual decision to discontinue is made up well before the information was updated in the Product service due to some external reasons.
Hence, I suggest to use eventual consistency. Since an event-sourced entity should only be responsible for its own consistency and not try to fulfil someone else's responsibility.
I'm trying to apply DDD in my latest project. Though I'm having a problem identifying where to put some business logic/calculation.
First I'm going to describe the business flow and then how I'm thinking of implementing this.
Business flow:
It's simply about adding a receipt item(s) to a receipt. However, the price of the item depends on the type of customer and the number of items.
i.e: Customer of type 'A' would like to purchase 2 meals. In his set up, he is allowed to have 1 lunch discounted, but because he is very hungry, he takes 2. The first meal should be for 4 eur (discounted), the second should be 6 eur (full price).
What I'm thinking is:
Product - an entity (of different bounded context, will only be used as an input param) - fields: code, name, ..., collection of prices each for a different customer type.
ReceiptItem - entity -fields: product code, product name, count, price, ...
Receipt as Aggregate root - fields:
customer (entity) - contains collection of ReceiptItems
On the Receipt, there will a method addItem(Product product, int count) and I plan to add the correct price calculation inside this method.
Returning to our example: receipt.addItem('menu1', 2)
So I would check the customer type and how many lunches he is allowed to have discounted. Given the example above, I would see, that he is allowed one. I then need to get the actual discounted price and I would get that directly from the product that exposes a method getCustomersPrice(customerid).
In the end, I will end up with two receipt items being added to Receipt. They will both have the same product code, but different prices. (In case the allowance would be 2 or more, I would only add one item to the collection with the discounted price, but count would the 2).
Would you say, that this is a good approach? I'm worried about passing the product as an input parameter to addItem. As it seems to create a tight coupling.
Thank you.
The Receipt aggregate root should be smaller: Receipt has ReceiptItems, which is fine, but Customer should be its own aggregate root. A customer exists independently of receipts, plus small aggregates are preferred.
Product is also a separate domain object within this bounded context, even though products also exist in a separate bounded context. Product also needs to be outside of the Receipt aggregate.
Given the fact that there are several aggregate roots, the logic to add a product to a receipt touches more than one aggregate. Thus, the logic cannot reside inside the Receipt aggregate nor any other aggregate. Such logic is best placed into a Domain service (which may reside in the same package as the Receipt aggregate), such as:
public class ReceiptService {
void addItem(ReceiptId aReceiptid, ProductId aProductId, int quantity) {
// business logic to determine price per item
// ...
// for each item, forward item creation to receipt:
receipt.addItem(productIdOrName, priceOfItem);
}
}
The Domain service combines data of various aggregates in order to determine the price per item. The internal logic to create a ReceiptItem and insert it into the collection of receipt items is best forwarded to the Receipt aggregate.
I am trying to refactor a legacy order handling and stock system with into a cleaner service oriented event-driven architecture. However, I am having some difficulty deciding what service should be responsible for the reservation/allocation of stock.
A brief overview of the current system
Sales orders are placed with us via third party system but we do not necessarily have all order lines in stock.
If an order item is in stock then we allocate/reserve the stock for that order straight away.
However, if we do not have enough stock then we procure the stock from our suppliers via a purchasing system.
When the item arrives from the supplier, the system will search through all open sales orders for the item and reserve/allocate the available stock to them, prioritising by sales order date. ***
I have already identified two services that I think need to be developed
Sales - Responsible for receiving the sales order and inserting into the database. Has domain entities such as Order, OrderLine etc.
Inventory - Responsible for keeping track of how much stock is available in our warehouse. Has domain entities such as StockItem.
However, as the allocation/reservation of stock concerns both inventory and sales I am not sure where the behaviour in point 2 above should be put.
I welcome any help or thoughts on this.
I think you have 2 BCs (bounded contexts): Inventory and Sales. For the integration between them I would probably go for domain events approach.
When a new item arrives at the warehouse, the Inventory BC increments the stock for the item, and publish an event.
Sales BC subscribes to the event, and it updates the opened sales that are waiting for the stock item.
So, behaviour of "point 2" are shared by both BC:
Sales BC search for opened orders waiting for that item. And then it asks Inventory BC to get the number of items it needs (this request is synchronous) and close the order.
Inventory BC receives the request and decrements the stock for the item.
However, as the allocation/reservation of stock concerns both inventory and sales I am not sure where the behaviour in point 2 above should be put.
I've been thinking about this problem (purely academically), and my current conclusion is that reservation management belongs with the inventory system. That keeps the stock source (the loading of items procured from your suppliers) and the stock sink (fulfillment of orders) together.
So the inventory system caches its own copy of the data required to fill the order (allowing it to work autonomously). It should be able to make progress as soon as it is informed that the suppliers have provided new inventory, even if the sales system happens to be down for maintenance.
You mentioned SOA and NServiceBus, so my initial thought was that you've attended Udi Dahan his ADSD training? I'll assume you have. With that, I'll try to answer your question.
So far I don't have a lot of information. But with what we have, I figured we need these properties to store all that you mentioned.
ProductId, one for each available product
InventoryTotal, attached to a ProductId. This number goes up and down
OrderId, to create an order
OrderDate, to make sure we can find the order that should receive incoming stock first.
If you have an OrderId, you can attach one or more ProductId to create an actual order. Different ways of storing this technically. Maybe in a relational database with Order and OrderLine tables, or possibly in a DocumentDb where everything is stored in a single document. That's totally irrelevant at this point.
Assuming we need 4 attributes, I'm not sure why we would create more than 1 service to split this up? This might change when we have more information, but at this moment I don't see the need.
If you want to discuss this, contact us at support#particular.net, mention my name and we can continue the conversation.
You are talking about loosely coupled domain apps, managing your sales orders, managing your inventory and managing your purchase orders.
Inventory must always be up to date, in order to not sell what you can't deliver. So PO en SO app should both talk to inventory via synchronous (inventory) services. To keep everything consistent, events on purchasing side, like receiving less than you expected for a PO, will have an impact on any SO already assigning quantity of that PO, as persisted in inventory. So the same PO pcs for example, in which the event of receiving less as expected, is registered, should synchronously update inventory, to update the quantity available for SOs to assign from, and publish an event, to be picked up, asynchronously, in the So app, so that the user can be notified and talk relevant action. Etc.
In my organization, I have a sales department. The users in the sales department have given leads. Daily They call to the different leads. Now I want that two Sales Persons are not calling the same lead. So how to prevent this situation in the CRM. Moreover we are giving the random leads to all the Sales Persons. There is a possibility that two or more sales persons have same leads.
The Privileges given to the Sales Persons are:
They are not able to see the leads of each other.
They are not able to see the accounts of each other.
They are not able to see the contacts of each other.
Now I want that two or more Sales Persons are not having the same leads so they are not calling the same person. So how to prevent this situation in the CRM?
The duplicate detection is limited to the records for which you are granted access. See Point 4 in this article: http://blogs.msdn.com/b/crm/archive/2008/04/29/duplicate-detection-security-model.aspx
All users with Read privilege on the base and duplicate records and Read privilege on System Job entity can view the duplicates. Every user will view the duplicates according to his access level on that entity. For example, if Tim has Basic read access on Accounts entity and Jack has Global read access, then for a duplicate detection job ran by Tim for all the account records in the system, Tim will see the duplicate account records that he owns but Jack will see duplicate account records, created by all users in the organization and detected in that run.
So you have to either run a duplicated detection with administrative rights peridically or you have to grant more rights.
The reason for this is pretty simple: how would you show a user that there is a possible duplicate record when he is not allowed to view it. So it will be handled like the records don't exist.
At last I have found the answer of the above question.
I have increased the privileges given to the sales persons. I have given access to see all the records of all the sales persons.
But In the Front End, I hide the system views like "All Leads", "Active Accounts", "Active Contacts" etc. and made custom views for the sales persons. Edit the filters of each view.
Because These are the views which able the sales persons to see all the records. So I hide these views from the sales persons and make custom views.
By this the two or more sales persons are not having the same leads.So they are not calling the same leads.
I will explain you how it helps: Suppose One sales person import the leads and after some time the other sales person also import some leads and if some leads are same, then the CRM will not import the duplicate leads and import the other leads which are not duplicate of the other.Moreover the sales person can see their leads which are not imported in their system by going in the Workplace and then to Imports.
Also I have made the custom Duplicate Detection Rules according to my requirement. These rules will check that the leads are not duplicate of each other according to my requirement.
I think you will need to increase the security privileges of each user, so they can see each others records. Or have an admin account for importing records.
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.