Criação do módulo de base de dados

Embora o Cortex Framework forneça módulos de base de dados prontos para uso para sistemas ERP empresariais, como o SAP (cortex.sap), também é possível criar novos módulos personalizados em um namespace personalizado. Isso permite definir comportamentos de build personalizados e estender o suporte a um novo sistema de origem. Eles podem incluir sistemas de gerenciamento de bancos de dados como PostgreSQL, MySQL etc., que replicam os dados brutos no BigQuery.

Este guia orienta você em um exemplo completo de criação de um novo módulo de base de dados para um sistema de emissão de tickets de atendimento ao cliente cujos dados (tabelas customers, tickets, ticketlogitem) foram replicados de um banco de dados PostgreSQL para um conjunto de dados brutos do BigQuery chamado ticketing_data_raw.

Ao criar um módulo de base de dados personalizado, recomendamos o uso de um namespace personalizado dedicado para melhorar o gerenciamento do ciclo de vida, separando extensões e personalizações de artefatos do Cortex Framework.

Visão geral do cenário de exemplo

Neste tutorial, vamos:

  1. Criar um namespace personalizado dedicado ticketing para isolar os recursos da base de dados.
  2. Definir um novo módulo de base de dados do caminho ticketing.ticketing.foundations.ticketing_system.
  3. Configurar as definições de tabela (table_settings.default.yaml) para as tabelas customers, tickets e ticketlogitem.
  4. Criar anotações de metadados de coluna e campo para cada tabela.
  5. Registrar a fonte de dados bruta (ticketing_data_raw), o conjunto de dados de destino em conformidade (data_foundation_ticketing) e o novo módulo de base em config/config.yaml.

Estrutura de pastas e arquivos do módulo

Todos os arquivos físicos do novo módulo de base de dados residem no namespace personalizado em src/data_modules/. A tabela e a árvore de diretórios a seguir descrevem onde colocar cada arquivo:

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

Importante: antes de começar, verifique se as tabelas de origem que você planeja processar existem no conjunto de dados da camada bruta.

Caminho do arquivo ou diretório Finalidade e descrição
config/config.yaml Registra o namespace ticketing, a fonte de dados bruta do PostgreSQL, o conjunto de dados de destino do BigQuery e a instância do módulo de base.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/manifest.yaml Declara os metadados do módulo, o nome de exibição, a categoria (por exemplo, foundation), o tipo de módulo (por exemplo, generic) e a classe do builder do gerador usada durante a compilação.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/table_settings.default.yaml Configura quais tabelas de origem de ticketing_data_raw precisam estar em conformidade, juntamente com as tags de otimização do Dataform, bigQueryLabels, detalhes de partição e detalhes do cluster do BigQuery.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/annotations/*.yaml Contém metadados YAML avançados que descrevem definições de tabela e campo. Eles são mesclados automaticamente nas definições compiladas do Dataform para que as descrições persistam nos metadados da tabela do BigQuery.
src/data_modules/ticketing/ticketing/foundations/ticketing_system/builder.py Opcional. Se o banco de dados de origem exigir limpeza de dados personalizada ou transformações SQL específicas do dialeto durante a compilação, você poderá definir uma classe de builder no nível do módulo aqui.

Etapa 1: registrar o namespace, a fonte de dados e o destino em config.yaml

Antes de criar os arquivos físicos, abra o arquivo de configuração de implantação (config/config.yaml) e declare o namespace personalizado, o conjunto de dados de origem bruto do PostgreSQL e o conjunto de dados de destino em que as tabelas em conformidade serão criadas:

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

Etapa 2: registrar o módulo de base de dados em config.yaml

Na seção data.modules.foundations de config/config.yaml, registre a nova instância do módulo de base de dados, vinculando a fonte de dados (ticketing_data_raw) ao destino de dados (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"

Etapa 3: criar o arquivo de manifesto do módulo

Crie o arquivo de manifesto declarando os metadados do módulo: 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

Etapa 4: criar o arquivo de configurações de tabela (table_settings.default.yaml)

Crie o arquivo de configuração de tabela padrão src/data_modules/ticketing/ticketing/foundations/ticketing_system/table_settings.default.yaml. Esse arquivo define como as tabelas replicadas do PostgreSQL (customers, tickets, ticketlogitem) são materializadas, particionadas e agrupadas no 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]

Etapa 5: criar anotações de metadados no nível do campo

Para garantir que as tabelas em conformidade contenham documentação clara no BigQuery, crie um arquivo YAML de anotação para cada tabela em src/data_modules/ticketing/ticketing/foundations/ticketing_system/annotations/. O nome do arquivo precisa corresponder exatamente ao nome da tabela de origem.

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"

Etapa 6: (opcional) definir um builder de base personalizado

Se a base de dados do PostgreSQL exigir lógica durante a compilação (como conversão automática de tipo de dados, conversão de carimbo de data/hora ou regras de limpeza de dados em todas as tabelas), você poderá definir um builder personalizado com escopo para esse módulo.

Crie 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)

Verificação do novo módulo de base

Para verificar e implantar o módulo de base de dados recém-criado:

  1. Execute o script de build e implantação do Cortex Framework: bash uv run cortex-build-and-deploy --config "config/config.yaml"
  2. Verifique se a compilação do Dataform foi concluída sem erros e se os scripts .sqlx foram gerados para customers, tickets e ticketlogitem.
  3. Siga as etapas de pós-implantação para executar as ações do pipeline do Dataform e verificar os registros em conformidade no conjunto de dados data_foundation_ticketing no BigQuery.

Para verificar se o módulo de base de dados personalizado é compilado e implantado corretamente, consulte a seção Verificação na página de extensibilidade do produto de dados.