-
Notifications
You must be signed in to change notification settings - Fork 8
by code
Backend transaction logic is a significant component of a system - as much as half the code.
This page contrasts the same requirements implemented by:
-
declarative logic (5 rules)
-
a conventional procedural approach using Python code (200 lines of legacy code - a factor of 40x)
The specification addresses around a dozen transactions. Let's focus on just two:
-
Add Order (Check Credit) - enter an order/orderdetails, and rollup to AmountTotal / Balance to check CreditLimit
-
Ship / Unship an Order (Adjust Balance) - when an Order's DateShippped is changed, adjust the Customers balance
For more information, including the data model, see Check Credit.
The logic "cocktail napkin" requirements are implemented with the following 5 rules (which note exactly match the requirements):
![](../raw/main/images/overview/cocktail-logic-bank.png)
The same "cocktail napkin spec" requires over 200 lines of legacy code (not counting shareable logic in utils):
![](../raw/main/images/overview/rules-vs-code.png)
You can review the code here:
- 45 in
examples/nw/logic/legacy/setup.py
to set up listeners (code here) - 90 in
examples/nw/logic/legacy/order_code.py
for Order code (code here) - 90 in
examples/nw/logic/legacy/order_detail_code.py
for OrderDetail code (code here) - 27 in
examples/nw/logic/legacy/customer_code.py
for OrderDetail code (code here)
To facilitate comparison, the legacy code is strictly focused on the 5 requirements above:
-
The by-hand code does not address the cascade of ShippedDate into OrderDetails, nor its adjustment to Products.
-
test
asserts fail since counts are not implemented in the legacy code
Here we focus on placing an order, and checking credit. The focus here is on multi-level roll-ups, to compute the balance and check it against the credit.
Execution begins in examples/nw/tests/test_add_order.py
.
The import statement from nw.nw_logic import session
runs
examples/nw/logic/__init__
,
which opens the database and registers the listeners.
When add_order.py
issues commit, sqlalchemy invokes the
listeners
.
These forward before_flush
events to
examples/nw/logic/legacy/order_details_code.py
and examples/nw/logic/legacy/order_code.py
.
Adjustments require we access/update rows that were not in the dirty list. Observations
- for attached parents (like customer, arising
from the
upd_order_shipped
test), updates are automatic. They do not, however, trigger flush logic. - for non-attached parents (like customer, arising
from the
add_order
test)- you need to read them
- add them to the session
- and explicitly invoke their update logic
(
customer_update(customer, old_customer, a_session)
)
Execution begins in examples/nw/tests/test_upd_order_shipped.py
,
and follows the same paths described above.
Here we explore old values - we need
to see what the ShippedDate
was, and
what it is changed to:
- If it changed from empty to shipped, we need to decrease the balance.
- If it changed from shipped to empty, we decrease the balance.
Observe the service to get an old_row, so you can code
things like if a_row.ShippedDate != an_old_row.ShippedDate:
Flow is as described above, reaching nw_logic/order_logic.py
Note the call to logic_engine/util.get_old_row
.
In a declarative approach, the following are automated:
-
Re-use - like a spreadsheet, the rules are automatically applied to different operations (insert, update, delete)
-
Dependency Management - it requires a good deal of manual code to check for
what attributes are changed,
to prune the paths that require SQL commands -
Persistence - manual code is required to issue sql commands to read and iterate through data
The declarative approach is not only more concise, it ensures higher quality, and is easier to maintain
For more information, see Declarative.
![](../raw/main/images/jetbrains-variant-4.png)
User Project Operations
Logic Bank Internals