יצירת מודול של תשתית לנתונים

במסגרת Cortex Framework יש מודולים מוכנים מראש של data foundation למערכות ERP ארגוניות כמו SAP‏ (cortex.sap), אבל אפשר גם ליצור מודולים חדשים של data foundation בהתאמה אישית במרחב שמות בהתאמה אישית. כך אפשר להגדיר התנהגויות בנייה מותאמות אישית ולהרחיב את התמיכה למערכת מקור חדשה. יכול להיות שמדובר במערכות לניהול מסדי נתונים כמו PostgreSQL, ‏ MySQL וכו', שמבצעות שכפול של הנתונים הגולמיים שלהן ל-BigQuery.

במדריך הזה מוסבר איך ליצור מודול חדש של בסיס נתונים עבור מערכת כרטיסי תמיכה לשירות לקוחות, שהנתונים שלה (טבלאות customers, tickets, ticketlogitem) שוכפלו ממסד נתונים של PostgreSQL למערך נתונים גולמי ב-BigQuery בשם ticketing_data_raw.

כשיוצרים מודול מותאם אישית של שכבת נתונים, מומלץ להשתמש במרחב שמות מותאם אישית ייעודי כדי לשפר את ניהול מחזור החיים על ידי הפרדה בין תוספים והתאמות אישיות לבין ארטיפקטים של Cortex Framework.

סקירה כללית של תרחיש לדוגמה

במדריך הזה:

  1. יוצרים מרחב שמות ייעודי בהתאמה אישית ticketing כדי לבודד את נכסי בסיס הנתונים.
  2. מגדירים מודול חדש של שכבת נתונים בנתיב ticketing.ticketing.foundations.ticketing_system.
  3. מגדירים את הגדרות הטבלה (table_settings.default.yaml) עבור הטבלאות customers, tickets ו-ticketlogitem.
  4. יוצרים הערות מטא-נתונים ברמת העמודה והשדה לכל טבלה.
  5. רושמים את מקור הנתונים הגולמי (ticketing_data_raw), את מערך הנתונים המאוחד של היעד (data_foundation_ticketing) ואת מודול הבסיס החדש ב-config/config.yaml.

מבנה התיקיות והקבצים של מודול

כל הקבצים הפיזיים של מודול חדש של שכבת נתונים מאוחדת נמצאים במרחב השמות המותאם אישית שלכם בתיקייה src/data_modules/. בטבלה הבאה ובמבנה הספריות מפורט איפה צריך למקם כל קובץ:

config/
└── config.yaml                                           # Global configuration & module registration
src/data_modules/ticketing/ticketing/foundations/ticketing_system/
├── manifest.yaml                                         # Declares module category, type, and builder
├── table_settings.default.yaml                           # Table materialization, bigQueryLabels, dataformTags, and layouts
├── builder.py                                            # (Optional) Custom Dataform generator class for this module
└── annotations/                                          # Field and column-level schema descriptions
    ├── customers.yaml
    ├── tickets.yaml
    └── ticketlogitem.yaml

חשוב: לפני שמתחילים, צריך לוודא שטבלאות המקור שמתכננים לעבד קיימות במערך הנתונים של שכבת הנתונים הגולמיים.

נתיב לקבצים או לספריות מטרה ותיאור
config/config.yaml הפונקציה רושמת את מרחב השמות ticketing, את מקור הנתונים של PostgreSQL, את מערך היעד של BigQuery ואת מופע מודול הבסיס.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/manifest.yaml התג הזה מגדיר את המטא-נתונים של המודול, השם המוצג, הקטגוריה (לדוגמה, foundation), סוג המודול (לדוגמה, generic) ואת מחלקת ה-builder של הגנרטור שמשמשת במהלך הקומפילציה.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/table_settings.default.yaml הגדרה של טבלאות המקור מ-ticketing_data_raw שצריך להתאים, יחד עם נתוני האופטימיזציה של BigQuery‏: dataformTags,‏ bigQueryLabels, פרטי החלוקה ופרטי האשכול.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/annotations/*.yaml מכיל מטא-נתונים מפורטים בפורמט YAML שמתארים את ההגדרות של הטבלה והשדות. הן מתמזגות אוטומטית בהגדרות המהודרות של Dataform, כך שהתיאורים נשמרים במטא-נתונים של הטבלה ב-BigQuery.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/builder.py אופציונלי. אם מסד הנתונים של המקור דורש ניקוי נתונים מותאם אישית או טרנספורמציות של SQL שספציפיות לניב במהלך הקומפילציה, אפשר להגדיר כאן מחלקה של builder ברמת המודול.

שלב 1: רושמים את מרחב השמות, מקור הנתונים והיעד ב-config.yaml

לפני שיוצרים את הקבצים הפיזיים, פותחים את קובץ הגדרת הפריסה (config/config.yaml) ומצהירים על מרחב השמות המותאם אישית, על מערך הנתונים של מקור PostgreSQL הגולמי ועל מערך הנתונים של היעד שבו ייצרו טבלאות תואמות:

data:
  namespaces:
    - name: cortex
      path: ../src/data_modules/cortex
    - name: ticketing                              # <-- Name of custom namespace
      path: ../src/data_modules/ticketing          # <-- Points to subdirectory under 'src/data_modules/'

  datasets:
    - id: ticketing_data_raw                       # <-- Unique source ID
      projectId: "source_project_id"
      datasetId: ticketing_data_raw                # <-- Raw dataset containing PostgreSQL replication tables
    - id: data_foundation_ticketing                # <-- Unique target ID
      projectId: "target_project_id"
      datasetId: data_foundation_ticketing         # <-- Target dataset for conformed foundation tables

שלב 2: רושמים את מודול הנתונים הבסיסי ב-config.yaml

בקטע data.modules.foundations של config/config.yaml, רושמים את המופע החדש של מודול בסיס הנתונים, ומקשרים את מקור הנתונים (ticketing_data_raw) ליעד הנתונים (data_foundation_ticketing):

data:
  modules:
    foundations:
      - moduleId: ticketing_foundation
        modulePath: ticketing.ticketing.foundations.ticketing_system   # Format: {namespace}.{systemtype}.{module_type:foundations}.{subsystemtype}
        dataSourceId: ticketing_data_raw
        dataTargetId: data_foundation_ticketing
        # Custom table settings file relative to 'config/' directory
        # Recommended path: '{namespace_dir}/{system_type}/foundations/{system_sub_type}/table_settings.yaml'
        # If omitted, defaults to "../src/data_modules/ticketing/ticketing/foundations/ticketing_system/table_settings.default.yaml"
        tableSettings: "ticketing/ticketing/foundations/ticketing_system/table_settings.yaml"

שלב 3: יוצרים את קובץ המניפסט של המודול

יוצרים את קובץ המניפסט ומצהירים על המטא-נתונים של המודול: src/data_modules/ticketing/ticketing/foundations/ticketing_system/manifest.yaml.

displayName: Ticketing System Data Foundation
description: Conformed foundation tables for PostgreSQL raw ticketing database.
category: foundation
type: generic
builder: ticketing_foundation

שלב 4: יוצרים את קובץ הגדרות הטבלה (table_settings.default.yaml)

יוצרים את קובץ התצורה של טבלת ברירת המחדל src/data_modules/ticketing/ticketing/foundations/ticketing_system/table_settings.default.yaml. בקובץ הזה מוגדר איך הטבלאות המשוכפלות של PostgreSQL‏ (customers, tickets, ticketlogitem) ממומשות, מחולקות למחיצות ומקובצות בתוך BigQuery:

common:
  - source:
      tableName: customers
    target:
      bigQueryLabels:
        - key: data_class
          value: master
      dataformTags: [ticketing, foundation, masterdata]
      clusterDetails:
        columns: [customer_id]

  - source:
      tableName: tickets
    target:
      bigQueryLabels:
        - key: data_class
          value: transactional
      dataformTags: [ticketing, foundation, transactional]
      partitionDetails:
        column: created_at
        partitionType: time
        timeGrain: day
      clusterDetails:
        columns: [ticket_id, customer_id]

  - source:
      tableName: ticketlogitem
    target:
      bigQueryLabels:
        - key: data_class
          value: transactional
      dataformTags: [ticketing, foundation, transactional]
      partitionDetails:
        column: log_timestamp
        partitionType: time
        timeGrain: day
      clusterDetails:
        columns: [ticket_id, log_id]

שלב 5: יצירת הערות מטא-נתונים ברמת השדה

כדי לוודא שהטבלאות התואמות מכילות תיעוד ברור ב-BigQuery, יוצרים קובץ YAML של הערות לכל טבלה בתוך src/data_modules/ticketing/ticketing/foundations/ticketing_system/annotations/. שם הקובץ צריך להיות זהה בדיוק לשם טבלת המקור.

annotations/customers.yaml

description: "Customer master data conformed from PostgreSQL raw ticketing database."
fields:
  - name: "customer_id"
    description: "Unique customer identifier, PK"
  - name: "email"
    description: "Primary email address associated with the customer"
  - name: "full_name"
    description: "Customer full name or account contact name"
  - name: "created_at"
    description: "Timestamp when the customer record was originally created in PostgreSQL"

annotations/tickets.yaml

description: "Customer service tickets conformed from PostgreSQL raw ticketing database."
fields:
  - name: "ticket_id"
    description: "Unique ticket identifier, PK"
  - name: "customer_id"
    description: "Foreign key referencing customers.customer_id"
  - name: "subject"
    description: "Summary or subject line of the customer inquiry"
  - name: "status"
    description: "Current ticket lifecycle status (e.g., OPEN, IN_PROGRESS, RESOLVED, CLOSED)"
  - name: "priority"
    description: "Priority severity level (e.g., LOW, MEDIUM, HIGH, URGENT)"
  - name: "created_at"
    description: "Timestamp when the ticket was created"
  - name: "updated_at"
    description: "Timestamp when the ticket was last modified"

annotations/ticketlogitem.yaml

description: "Audit log history and activity events for customer service tickets."
fields:
  - name: "log_id"
    description: "Unique log event identifier, PK"
  - name: "ticket_id"
    description: "Foreign key referencing tickets.ticket_id"
  - name: "action"
    description: "Action or event performed on the ticket"
  - name: "description"
    description: "Notes and description on performed events on the ticket"
  - name: "performed_by"
    description: "User, agent, or automated system that performed the action"
  - name: "log_timestamp"
    description: "Exact timestamp when the activity log event occurred"

שלב 6: (אופציונלי) הגדרת כלי מותאם אישית ליצירת שכבת בסיס

אם נדרשת לוגיקה במהלך הקומפילציה של בסיס הנתונים של PostgreSQL (למשל, המרה אוטומטית של סוגי נתונים, המרת חותמות זמן או כללים לניקוי נתונים בכל הטבלאות), אפשר להגדיר כלי בנייה בהתאמה אישית בהיקף של המודול הזה.

יצירה src/data_modules/ticketing/ticketing/foundations/ticketing_system/builder.py:

import logging
import pathlib
import yaml
from common.builders.base import FoundationBuilder, Source
from common.registry import builder_registry
from common.schemas import config_schema, manifest_schema

logger = logging.getLogger(__name__)

@builder_registry.register("ticketing_foundation")
class TicketingFoundationBuilder(FoundationBuilder[config_schema.BaseModuleConfig]):
    """Custom Dataform generator for PostgreSQL ticketing data foundation."""

    def build(
        self,
        *,
        module_id: str,
        module_config: config_schema.BaseModuleConfig,
        global_config: config_schema.GlobalConfig,
        manifest: manifest_schema.ManifestConfig,
        base_dir: pathlib.Path,
        annotations_dir: pathlib.Path,
        output_dir: pathlib.Path,
        module_dir_name: str,
        sources_registry: set[Source],
        table_settings_file: pathlib.Path | None = None,
        required_tables: set[str] | None = None,
    ) -> None:
        logger.info("Building ticketing data foundation for module: %s", module_id)
        
        # 1. Load table settings
        if not table_settings_file or not table_settings_file.exists():
            logger.warning("No valid table settings found for %s", module_id)
            return

        with open(table_settings_file, encoding="utf-8") as f:
            settings = yaml.safe_load(f) or {}

        tables = settings.get("common", [])
        source_config = global_config.get_data_source(module_config.data_source_id)
        target_dataset = global_config.get_data_target(module_config.data_target_id)

        # 2. Generate Dataform .sqlx files for each table
        for table_item in tables:
            source_table = table_item["source"]["tableName"]
            if required_tables and source_table not in required_tables and not table_item.get("deployAlways"):
                continue

            # Register source table for centralized source generation
            sources_registry.add(Source(source_config.project_id, source_config.dataset_id, source_table))

            # Retrieve labels if configured
            bigquery_config = {}
            if "bigQueryLabels" in table_item["target"]:
                labels_dict = {label["key"]: label["value"] for label in table_item["target"]["bigQueryLabels"]}
                bigquery_config["labels"] = labels_dict

            dataform_tags = table_item["target"].get("dataformTags", ["ticketing", "foundation"])

            sqlx_content = f"""config {{
  type: "table",
  schema: "{target_dataset.dataset_id}",
  name: "{source_table}",
  tags: {dataform_tags}"""
            
            if bigquery_config:
                sqlx_content += f",\n  bigquery: {bigquery_config}"
                
            sqlx_content += f"""
}}

SELECT *
FROM `${{source_config.project_id}}.${{source_config.dataset_id}}.{source_table}`
"""
            out_file = output_dir / f"{source_table}.sqlx"
            out_file.write_text(sqlx_content, encoding="utf-8")
            logger.info("Generated %s", out_file)

אימות של מודול חדש של קרן

כדי לאמת ולפרוס את מודול בסיס הנתונים החדש שיצרתם:

  1. מריצים את הסקריפט של Cortex Framework לבנייה ולפריסה: bash uv run cortex-build-and-deploy --config "config/config.yaml"
  2. בודקים שההידור ב-Dataform הצליח בלי שגיאות ושנוצרו סקריפטים של .sqlx עבור customers, tickets ו-ticketlogitem.
  3. כדי להפעיל את פעולות צינור הנתונים של Dataform ולאמת את הרשומות התואמות בתוך מערך הנתונים data_foundation_ticketing ב-BigQuery, צריך לפעול לפי השלבים אחרי הפריסה.

כדי לוודא שמודול בסיס הנתונים בהתאמה אישית עובר קומפילציה ופריסה בהצלחה, אפשר לעיין בקטע אימות בדף ההרחבה של מוצר הנתונים.