Tutorial
3. Structuring Business Logic

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_AMOUNT and replaced it with ORDER_VALUE which groups Amount and Currency.

Validate the updated model:

daana-cli check model

The 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_orders

Step 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 execute

The Result

Query the new structure:

docker exec -it daana-customerdb psql -U dev -d customerdb
SELECT
  "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

  1. Semantic Clarity: ORDER_VALUE is now a complete concept.
  2. Data Quality: You explicitly defined that an amount requires a currency.
  3. Standardization: You can reuse UNIT types for consistent handling of measurements across your warehouse.

Next: Chapter 4 - Loading Strategies