5 _ [LAB - Hotelaria] Construção da Camada Bronze Append-Only com Databricks Asset Bundles
- Michel Souza Santana
- há 5 dias
- 7 min de leitura

Após concluir a ingestão Source → Landing, avançamos para a construção da camada Bronze. O objetivo desta etapa foi transformar os arquivos Parquet armazenados em Unity Catalog Volumes em tabelas Delta gerenciadas no Unity Catalog, mantendo a Bronze como uma camada append-only, auditável e reprocessável.
A arquitetura até este ponto ficou assim:
Neon PostgreSQL
↓
Foreign Catalog: neon_hotelaria.public
↓
Job Source → Landing
↓
Unity Catalog Volume
/Volumes/hotelaria_dev/landing/neon_postgresql/<tabela>/
↓
Job Landing → Bronze
↓
hotelaria_dev.bronze.<tabela>
1. Objetivo da Bronze
A camada Bronze representa os dados brutos estruturados, já dentro do Lakehouse, mas ainda sem aplicação de regras de negócio, deduplicação final ou modelagem analítica.
A decisão arquitetural foi:
Bronze = append-only
Ou seja:
não usar merge
não atualizar registros existentes
não deletar registros
não deduplicar como regra principal
apenas adicionar novas cargas
Essa decisão preserva histórico, rastreabilidade e capacidade de auditoria. Se uma mesma linha vier novamente da Landing por causa do lookback da marca d’água, ela será gravada novamente na Bronze com novos metadados de ingestão.
A deduplicação e a aplicação de regra de negócio ficam para a Silver.
2. Por que a Landing fica em Volume e a Bronze em tabela Delta?
A Landing foi criada em Unity Catalog Volume porque ela armazena arquivos físicos de extração, como Parquet, organizados por tabela, data e batch. Volumes são objetos do Unity Catalog voltados para governança de arquivos e dados não tabulares ou semitabulares. Já tabelas governam dados tabulares. (Documentação Databricks)
Exemplo de Landing:
/Volumes/hotelaria_dev/landing/neon_postgresql/hoteis/ingestion_date=2026-05-09/batch_timestamp=20260509143000/
A Bronze foi criada como tabela Delta porque a partir dessa camada queremos governança tabular, consultas SQL, evolução de schema, versionamento Delta e integração com as próximas camadas do Lakehouse.
Exemplo de Bronze:
hotelaria_dev.bronze.hoteis
hotelaria_dev.bronze.hospedes
hotelaria_dev.bronze.reservas
3. Papel dos contratos na Bronze
Antes de criar a Bronze, criamos contratos YAML por tabela:
contracts/neon_hotelaria/
├── consumos.yml
├── faturas.yml
├── hoteis.yml
├── hospedes.yml
├── quartos.yml
├── reservas.yml
└── reservas_ota.yml
Cada contrato define:
nome da tabela
origem
caminho da Landing
destino da Bronze
modo de carga
coluna de watermark
chave primária
schema esperado
regras mínimas de qualidade
Exemplo conceitual:
table_name: hoteis
landing:
path: /Volumes/hotelaria_dev/landing/neon_postgresql/hoteis
file_format: parquet
retention_days: 15
bronze:
full_name: hotelaria_dev.bronze.hoteis
load_mode: append_only
incremental:
enabled: true
watermark_column: update_date
lookback_minutes: 1440
primary_key:
- hotel_id
Com isso, o pipeline da Bronze não precisa depender de regras espalhadas no código. Ele lê os contratos e entende de onde carregar e para onde gravar cada tabela.
4. Controle de arquivos processados
Para evitar reprocessar os mesmos arquivos da Landing, usamos uma tabela de controle:
hotelaria_dev.control.processed_files
Ela registra:
source_system
source_table
source_file_path
source_file_name
file_modification_time
file_size
ingestion_id
processed_at
target_table
status
created_at
A lógica é simples:
1. Listar arquivos Parquet da Landing
2. Consultar quais arquivos já foram processados
3. Carregar apenas arquivos novos
4. Gravar na Bronze em modo append
5. Registrar os arquivos carregados em processed_files
Assim, se o job da Bronze rodar novamente sem novos arquivos, ele não duplica dados.
5. Script comum para leitura dos contratos
Criamos o arquivo:
src/common/contracts.py
Objetivo inicial:
ler contratos YAML
carregar todos os contratos
buscar contrato por tabela
A primeira versão usava file para descobrir o caminho do projeto. Porém, quando executado no Databricks como notebook_task, essa variável não existe.
6. Erro encontrado: file is not defined
Ao executar o job landing_to_bronze, ocorreu o erro:
NameError: name '__file__' is not defined
Esse erro aconteceu porque arquivos .py executados como notebooks no Databricks não possuem a variável file, comum em scripts Python tradicionais.
O trecho problemático era:
current_dir = os.path.dirname(os.path.abspath(__file__))
A correção foi usar o caminho do notebook no contexto do Databricks:
dbutils.notebook.entry_point
.getDbutils()
.notebook()
.getContext()
.notebookPath()
.get()
Com isso, conseguimos identificar o caminho publicado pelo Asset Bundle dentro do workspace e localizar a pasta contracts.
7. Script Bronze corrigido
Criamos o script:
src/bronze/load_landing_to_bronze.py
Responsabilidades:
resolver caminho do projeto no Databricks
ler contratos YAML
listar arquivos Parquet da Landing
identificar arquivos ainda não processados
validar schema mínimo
adicionar metadados técnicos
gravar tabela Delta na Bronze em append
registrar arquivos processados
Principais metadados adicionados à Bronze:
_metadata_row_hash
_metadata_bronze_ingestion_id
_metadata_bronze_loaded_at
_metadata_source_system
_metadata_source_table
_metadata_primary_key
_metadata_watermark_column
_metadata_load_mode
O metadatarow_hash é calculado com base nas colunas de origem previstas no contrato. Ele será útil nas próximas camadas para identificar alterações, deduplicações e versionamento lógico.
8. Criação do Job Landing → Bronze
Criamos o arquivo:
resources/jobs/job_landing_to_bronze.yml
Conteúdo do job:
resources:
jobs:
landing_to_bronze:
name: "landing_to_bronze [${bundle.target}]"
description: "Carga append-only da Landing em Volumes para Bronze Delta usando contratos YAML"
tasks:
- task_key: landing_to_bronze
notebook_task:
notebook_path: ../../src/bronze/load_landing_to_bronze.py
base_parameters:
target_catalog: ${var.target_catalog}
source_system: ${var.source_system}
table_name: all
O caminho do notebook segue a mesma regra usada no job da Landing:
resources/jobs/job_landing_to_bronze.yml
↓
../../src/bronze/load_landing_to_bronze.py
Esse caminho é necessário porque o arquivo .yml está dentro de resources/jobs, enquanto o notebook está dentro de src/bronze.
9. Validação e deploy com Databricks Asset Bundles
Depois de criar o job da Bronze, validamos o projeto localmente:
pytest tests/contracts/test_contracts.py
Resultado esperado:
5 passed
Depois validamos o Bundle:
databricks bundle validate --target dev --profile hotelaria-free
Em seguida, fizemos deploy:
databricks bundle deploy --target dev --profile hotelaria-free
E executamos o job:
databricks bundle run landing_to_bronze --target dev --profile hotelaria-free
A Databricks recomenda Bundles como abordagem para CI/CD porque eles permitem versionar recursos, jobs e pipelines junto com o código do projeto, além de possibilitar validação e deploy por automação externa como GitHub Actions. (Documentação Databricks)
Ajustes na esteira CI/CD com GitHub Actions
Durante esta etapa, também ajustamos a esteira CI/CD do projeto no GitHub Actions.
O objetivo era validar automaticamente:
contratos YAML
testes com pytest
Databricks Bundle validate
10. Criação dos GitHub Secrets
No GitHub, os secrets foram criados em:
Repository
→ Settings
→ Secrets and variables
→ Actions
→ Repository secrets
Criamos:
DATABRICKS_HOST
DATABRICKS_TOKEN
O DATABRICKS_HOST recebeu:
O DATABRICKS_TOKEN recebeu um token gerado no Databricks, não no GitHub.
GitHub Actions usa secrets para armazenar informações sensíveis, como tokens, e eles só ficam disponíveis para o workflow quando são referenciados explicitamente. (GitHub Docs)
11. Correção importante: token GitHub x token Databricks
Durante o processo houve uma confusão natural entre tokens do GitHub e tokens do Databricks.
Tokens como:
ghp_...
github_pat_...
são tokens do GitHub.
Eles servem para acessar GitHub, repositórios, packages, APIs do GitHub etc.
Para o workflow executar comandos como:
databricks current-user me
databricks bundle validate --target dev
é necessário um token do Databricks.
A autenticação por PAT no Databricks usa variáveis como DATABRICKS_HOST e DATABRICKS_TOKEN. (Documentação Databricks)
12. Erro de autenticação no GitHub Actions
O primeiro erro foi:
cannot configure default credentials
Depois, ao configurar os secrets, o erro mudou para:
Credential was not sent or was of an unsupported type for this API
HTTP Status: 401 Unauthorized
Endpoint: GET /api/2.0/preview/scim/v2/Me
Isso indicava que:
o host estava chegando ao workflow
o token estava chegando ao workflow
mas o token não tinha permissão/scope suficiente
13. Correção: API scope no token Databricks
Ao gerar o token no Databricks, a tela solicitou API scope(s).
A correção foi gerar um novo token Databricks com escopo adequado, como:
all-apis
ou, quando aplicável, escopos suficientes para permitir chamadas de workspace, jobs, Unity Catalog e identificação do usuário.
Depois disso, o workflow conseguiu autenticar e executar o databricks bundle validate.
14. Workflow de validação
O workflow de validação ficou em:
.github/workflows/validate.yml
Com a ideia:
name: validate
on:
pull_request:
branches:
- main
- master
push:
branches:
- main
- master
jobs:
validate:
runs-on: ubuntu-latest
env:
DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}
DATABRICKS_AUTH_TYPE: pat
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Python dependencies
run: |
pip install pyyaml pytest
- name: Install Databricks CLI
run: |
curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/v0.299.1/install.sh | sudo sh
databricks version
- name: Validate Databricks authentication
run: |
databricks current-user me
- name: Validate contracts
run: |
pytest tests/contracts/test_contracts.py
- name: Validate Databricks Bundle
run: |
databricks bundle validate --target dev
Esse workflow garante que alterações no projeto passem por validação antes de seguirem adiante.
15. Observação sobre Node.js 20 no GitHub Actions
Durante a execução, o GitHub Actions exibiu um aviso sobre depreciação do Node.js 20 em actions como:
actions/checkout@v4
actions/setup-python@v5
Esse aviso não era o erro principal da esteira. Ainda assim, adicionamos:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
para antecipar a execução com Node.js 24 quando suportado pelo runner.
Validações após a Bronze
Após a execução bem-sucedida do job landing_to_bronze, validamos as tabelas no Databricks SQL Editor.
16. Listar tabelas Bronze
show tables in hotelaria_dev.bronze;
Resultado esperado:
consumos
faturas
hoteis
hospedes
quartos
reservas
reservas_ota
17. Contar registros por tabela
select 'hoteis' as tabela, count(*) as total from hotelaria_dev.bronze.hoteis
union all
select 'hospedes', count(*) from hotelaria_dev.bronze.hospedes
union all
select 'quartos', count(*) from hotelaria_dev.bronze.quartos
union all
select 'reservas', count(*) from hotelaria_dev.bronze.reservas
union all
select 'consumos', count(*) from hotelaria_dev.bronze.consumos
union all
select 'faturas', count(*) from hotelaria_dev.bronze.faturas
union all
select 'reservas_ota', count(*) from hotelaria_dev.bronze.reservas_ota
order by tabela;
18. Validar arquivos processados
select
source_table,
count(*) as total_arquivos_processados,
max(processed_at) as ultima_execucao
from hotelaria_dev.control.processed_files
group by source_table
order by source_table;
Essa consulta confirma se os arquivos da Landing foram registrados como processados.
19. Validar metadados da Bronze
select
_metadata_source_system,
_metadata_source_table,
_metadata_load_mode,
count(*) as total
from hotelaria_dev.bronze.hoteis
group by
_metadata_source_system,
_metadata_source_table,
_metadata_load_mode;
Resultado esperado:
_metadata_source_system = neon_postgresql
_metadata_source_table = hoteis
_metadata_load_mode = append_only
Resultado alcançado
Ao final desta etapa, temos:
Landing funcionando em Unity Catalog Volume
Bronze criada como Delta append-only
Contratos YAML sendo usados como referência da carga
Controle de arquivos processados funcionando
Metadados técnicos aplicados na Bronze
Job landing_to_bronze criado via Asset Bundle
CI com GitHub Actions validando contratos e Bundle
Token Databricks corrigido com API scope adequado
Deploy local e validação CI funcionando
Estrutura atual do projeto
Laboratorio-ETL-end-to-end-with-assets-budles-and-DLT/
├── .github
│ └── workflows
│ └── validate.yml
├── contracts
│ └── neon_hotelaria
│ ├── consumos.yml
│ ├── faturas.yml
│ ├── hoteis.yml
│ ├── hospedes.yml
│ ├── quartos.yml
│ ├── reservas.yml
│ └── reservas_ota.yml
├── resources
│ ├── jobs
│ │ ├── job_landing_to_bronze.yml
│ │ └── job_source_to_landing.yml
│ └── pipelines
├── src
│ ├── bronze
│ │ └── load_landing_to_bronze.py
│ ├── common
│ │ └── contracts.py
│ └── landing
│ └── ingest_neon_to_landing.py
├── sql
│ ├── control
│ │ └── 01_seed_ingestion_watermark.sql
│ └── setup
│ └── 00_setup_catalog_schemas_volumes.sql
├── tests
│ └── contracts
│ └── test_contracts.py
├── databricks.yml
└── README.md
Decisão arquitetural desta etapa
A decisão final da Bronze foi:
Landing:
arquivos Parquet em Unity Catalog Volume
histórico físico para reprocessamento
Bronze:
tabelas Delta no Unity Catalog
append-only
metadados técnicos
sem merge, update ou delete
Controle:
ingestion_watermark para Source → Landing
processed_files para Landing → Bronze
Contratos:
um YAML por tabela
usados como referência técnica do pipeline
CI/CD:
GitHub Actions validando contratos e Bundle
autenticação Databricks via DATABRICKS_HOST e DATABRICKS_TOKEN
Com isso, encerramos a etapa de construção da Bronze com uma base organizada, testável, versionada e preparada para o próximo passo: o job orquestrador Source → Landing → Bronze.

![4 _ [LAB - Hotelaria] Ajuste de Fundação: Contratos, CI/CD e Padronização antes da Bronze](https://static.wixstatic.com/media/430b63_f69a815994ad4c4188695cc393807f09~mv2.png/v1/fill/w_980,h_1225,al_c,q_90,usm_0.66_1.00_0.01,enc_avif,quality_auto/430b63_f69a815994ad4c4188695cc393807f09~mv2.png)
![3 _ [LAB - Hotelaria] Ingestão Source → Landing com Databricks CLI, Asset Bundles e Unity Catalog Volumes](https://static.wixstatic.com/media/430b63_f631572693b04e16ab6e55b86a4a93db~mv2.png/v1/fill/w_980,h_653,al_c,q_90,usm_0.66_1.00_0.01,enc_avif,quality_auto/430b63_f631572693b04e16ab6e55b86a4a93db~mv2.png)
![2 _ [LAB - Hotelaria] Integração do Neon PostgreSQL com Databricks Free Edition](https://static.wixstatic.com/media/430b63_0f94c120084448168f97c7ded696ce8a~mv2.png/v1/fill/w_980,h_653,al_c,q_90,usm_0.66_1.00_0.01,enc_avif,quality_auto/430b63_0f94c120084448168f97c7ded696ce8a~mv2.png)
Comentários