Chapter 3: Structuring Business Logic
Goal: Move from flat tables to "Atomic Questions" using Group Attributes.
Prerequisites: You must have completed Chapter 2: The Building Blocks.
The Concept: Atomic Questions
In Chapter 1, we created a flat model where every attribute was independent. In the real world, some pieces of information belong together inextricably.
Take ORDER_TOTAL_AMOUNT. Is "60.33" meaningful? No. You need "60.33 USD" or "60.33 EUR". The Amount and the Currency together answer the atomic question: "What is the value of this order?"
Daana allows you to group attributes to represent these atomic concepts. This improves data quality (you can't have an amount without a currency) and organization.
Step 1: Update Your Model
We will modify model.yaml to group the order amount with its currency.
Open model.yaml and modify the ORDER entity attributes:
- id: "ORDER"
name: "ORDER"
definition: "Purchase order"
description: "Orders placed by customers"
attributes:
- id: "ORDER_ID"
name: "ORDER_ID"
definition: "Unique order identifier"
type: "STRING"
effective_timestamp: true
- id: "ORDER_STATUS"
name: "ORDER_STATUS"
definition: "Current order status"
type: "STRING"
effective_timestamp: true
- id: "ORDER_PURCHASE_TS"
name: "ORDER_PURCHASE_TS"
definition: "When order was placed"
type: "START_TIMESTAMP"
# NEW: Grouped Attribute for Order Value
- id: "ORDER_VALUE"
name: "ORDER_VALUE"
definition: "Total monetary value of the order"
description: "Combined amount and currency"
effective_timestamp: true
group:
- id: "ORDER_VALUE_AMOUNT"
name: "ORDER_VALUE_AMOUNT"
definition: "Monetary amount"
type: "NUMBER"
- id: "ORDER_VALUE_CURRENCY"
name: "ORDER_VALUE_CURRENCY"
definition: "Currency code (ISO 4217)"
type: "UNIT"Note: We removed the standalone
ORDER_TOTAL_AMOUNTand replaced it withORDER_VALUEwhich groups Amount and Currency.
Validate the updated model:
daana-cli check modelThe model validates successfully, but if you run daana-cli check workflow, you'll notice the checker finds that the mapping references ORDER_TOTAL_AMOUNT which no longer exists. Let's fix that next.
Step 2: Update the Order Mapping
Now we need to map our source data to this new structure. We'll map the numeric amount and currency from our stage.sales_orders table.
Open mappings/order-mapping.yaml and update the attributes section:
attributes:
- id: ORDER_ID
transformation_expression: order_id
- id: ORDER_STATUS
transformation_expression: order_status
- id: ORDER_PURCHASE_TS
transformation_expression: order_date
# NEW: Mapping for the Grouped Attribute
# Notice: We map each group child as a flat attribute (not nested)
- id: ORDER_VALUE_AMOUNT
transformation_expression: total_amount
- id: ORDER_VALUE_CURRENCY
transformation_expression: currency # Maps to 'USD' from stage.sales_ordersStep 3: Deploy and Verify
Re-deploy and execute your workflow to apply the changes:
# Deploy the updated workflow
daana-cli deploy
# Execute the transformation
daana-cli executeThe Result
Query the new structure:
docker exec -it daana-customerdb psql -U dev -d customerdbSELECT
"ORDER_ID",
"ORDER_STATUS",
"ORDER_VALUE_AMOUNT",
"ORDER_VALUE_CURRENCY"
FROM daana_dw.view_order
LIMIT 5;You'll see that ORDER_VALUE is now represented by two columns working in concert:
ORDER_ID | ORDER_STATUS | ORDER_VALUE_AMOUNT | ORDER_VALUE_CURRENCY
----------+--------------+--------------------+----------------------
1 | Delivered | 60.33 | USD
10 | Processing | 65.77 | USD
2 | Delivered | 24.49 | USD
3 | Shipped | 71.39 | USD
4 | Processing | 44.03 | USD
(5 rows)You query data just like normal, but under the hood there's a special relationship tying ORDER_VALUE_AMOUNT to ORDER_VALUE_CURRENCY - which guarantees the amount and currency always stay together and change together in history tracking.
Why This Matters
- Semantic Clarity:
ORDER_VALUEis now a complete concept. - Data Quality: You explicitly defined that an amount requires a currency.
- Standardization: You can reuse
UNITtypes for consistent handling of measurements across your warehouse.