The key to understanding business rules is knowing that each rule is defined on a business entity (Table) and perhaps an attribute (Column) and the rule is'triggered' when state change occurs (e.g. PUT/POST/DELETE) on the entity and/or attribute. The other key to Reactive Design is that it may be necessary to introduce new tables and columns as part of the design to enact specific design results.
These rule types are used to alter or 'derive' the value of a specific attribute. Much like a spreadsheet cell, these derivations will be part of a chain of events that are called during the processing of a data transaction. These derivations are applied to a specific attributes on a table. For a more details see the Live Logic page. In this exercise we are going to use the classic Customer/Order/Invoice to describe each of the derivation rules and how they are applied to an invoice.
The typical invoice contains company information, customer billing and shipping information, order header (invoice specific) and line item details. Using this template as our guide - the invoice line item has an item#, quantity, details, unit price ($) and Total price ($).
The TotalPrice is derived using a formula (row.unitprice * row.quantity). But we do not want to enter the unit price by hand, so where does this value live? It is assigned to the Parts Inventory system. The next rule we add is called a parent copy of unitprice from the Parts table when we enter the part item#. The parent copy makes a replication of the value at the time of entry (non-maintained) so future price changes do not impact existing orders. Not shown is the lineitem Discount calculations for this customer using a formula which applies quantity discounts based on customer loyalty. We can also use the Parent Copy from Part to populate the Details on the line item.
The Invoice SubTotal uses a sum on the Invoice (order header) itself and we will use a derivation called
sum(lineitem.TotalPrice). The invoice discount is the sum(lineitem.discount). The Tax/VAT is another formula (row.subtotal * row.taxrate). Finally - we calculate the grand total using a formula ((row.subtotal -row.discount) - (row.tax + row.shipping)).
Notice that the building of rules we did not specify the order of operation or what events were needed. In other words, state change means inserting a new line item, deleting a line item, changing a quantity or even an item# will trigger every one of these rules to recalculate.
Behind the actual data entry of an order or shopping cart is some sort of validation logic. The business requirements determine the level of open and unpaid orders a customer may have at any moment in time. For example, we may create a validation that will REJECT an order when the balance of unpaid orders exceed the maximum credit limit. This sounds like we need some additional derivation rules before we can determine the state of the unpaid balance. If we create a validation reject if (row.totalUnpaidOrders > row.creditLimit) on the Customer entity we see that we need to derive totalUnpaidOrders as the sum(OrderTotal where paid= false). We have added a qualification paid=false. This paid flag is on our invoice (order) and when the state change occurs - this will increase or decrease or totalUnpaidOrders on the Customer entity.
One of the often overlooked rules is Count. The count is used like a sum to calculate the number of child objects. And like the sum - it can have a filter (where) clause. So you can use the state change (increase or decrease) in a child row to 'trigger' an event action. For example, if the count of the child transactions is greater than 100, you could archive to MongoDB the older rows.
When using a Resource with nested children (Customer contains Orders, contains Items) - the ability to POST (insert) a new Customer, Order, and Items in one transaction can only be done when the primary key of the parent (Customer or Orders) can be propagated down to the child (e.g. Orders or Items).