Model access: public/private/protected
В Mesh access — это формальный механизм управления тем, какие модели может использовать кто. Три уровня — public, private, protected — и их правильное применение определяет, насколько Mesh реально решает governance проблемы.
Этот урок — про конкретную практику access management: как настроить, debug-ить, и evolve со временем.
Model contracts: концепт enforced контракта (dbt II)Три уровня access
# schema.yml
models:
- name: stg_invoices
access: private # default, доступна только в этом проекте
- name: int_revenue_adjusted
access: protected # доступна в этом проекте + проектах того же group
- name: fct_revenue
access: public # доступна везде, любой проект может ref
Три уровня по строгости:
| Access | Scope | Use case |
|---|---|---|
private | Только этот dbt-проект | Internal staging, intermediate, helper-модели |
protected | Этот project + projects того же group | Cross-team shared, но не “fully public API” |
public | Все projects | Formal API между teams |
private: default и naturally enforced
private — default. Если access не указан — модель private.
- name: stg_orders
# access не указан, считается access: private
В этом case любая попытка cross-project ref:
-- В другом проекте:
select * from {{ ref('finance', 'stg_orders') }} -- ERROR
Падает с compilation error:
Model 'stg_orders' is private. Refs cannot extend across projects.
Это безопасный default — internal модели не “случайно” exposed.
public: full API
- name: fct_revenue
access: public
description: "Daily revenue by product"
config:
contract:
enforced: true
columns:
- name: date
data_type: date
constraints:
- type: not_null
- name: revenue_usd
data_type: decimal(18,2)
public означает:
- Любой другой проект может ref эту модель.
- Это formal API — изменения должны быть communicated и versioned.
Best practice: public всегда с contract. Без contract public model — это лишь “честное слово” Finance не ломать схему. С contract — dbt enforce это автоматически.
protected: middle ground через groups
protected — модель доступна внутри group. Что такое group?
# finance_dbt/dbt_project.yml
groups:
- name: finance_team
owner:
email: [email protected]
# finance_dbt/models/schema.yml
models:
- name: int_revenue_adjusted
access: protected
group: finance_team
Теперь int_revenue_adjusted доступна:
- В
finance_dbt(own project). - В любых других dbt-проектах, которые declare group
finance_team.
Use case — sub-projects одной team:
finance_team/
finance_core/ ← dbt-проект 1
models/
int_revenue_adjusted (protected, group=finance_team)
finance_reports/ ← dbt-проект 2
models/
fct_quarterly_report -> ref('finance_core', 'int_revenue_adjusted') # OK, same group
marketing_dbt/ ← другой team
models/
fct_marketing -> ref('finance_core', 'int_revenue_adjusted') # ERROR, not in finance_team group
protected нужен, чтобы expose внутренние модели для подкоманд, но не для всех.
В практике protected используется реже, чем public/private. На малых-средних организациях достаточно бинарного public/private. protected появляется при сложной структуре с sub-teams.
Cascade access — ref private model from same project
Внутри одного проекта access никак не ограничивает. Все модели могут ref-нуть друг друга независимо от access:
-- finance_dbt/models/fct_revenue.sql
select * from {{ ref('stg_invoices') }} -- stg_invoices private, OK внутри проекта
Это expected — access fact только для cross-project ref.
Контракт enforcement cross-project
Когда модель public с contract: enforced: true — это даёт две гарантии:
-
Producer side: dbt не позволит изменить SQL так, чтобы output columns/types не соответствовали contract. Build падает.
-
Consumer side: cross-project ref проверяет, что contract существует. Если нет — warning.
- name: fct_revenue
access: public
config:
contract:
enforced: true
columns:
- name: date
data_type: date
constraints:
- type: not_null
- name: revenue_usd
data_type: decimal(18,2)
constraints:
- type: not_null
Сценарий: Finance переименовывает column revenue_usd на revenue_amount. dbt build падает:
Compilation Error in model fct_revenue:
Constraint validation failed: Column 'revenue_usd' specified in contract but not in SQL output.
SQL returns: ['date', 'revenue_amount', ...]
Finance не может сделать breaking change без explicitly bumping version. Это и есть API contract enforcement.
Versioned public models
Когда public model меняется breaking — нужно сделать версию:
- name: fct_revenue
access: public
latest_version: 2
versions:
- v: 1
deprecation_date: '2026-12-31'
defined_in: fct_revenue_v1
- v: 2
defined_in: fct_revenue_v2
config:
contract:
enforced: true
columns:
# схема для v2
Два физических файла:
models/fct_revenue_v1.sql— старая версия, оставлена для backward compat.models/fct_revenue_v2.sql— новая.
Consumers могут pin to v1 или v2:
-- Marketing использует v1
select * from {{ ref('finance', 'fct_revenue', v=1) }}
-- Product использует v2
select * from {{ ref('finance', 'fct_revenue', v=2) }}
После deprecation_date v1 может быть удалена. Marketing должна успеть migrate.
Детально про versions — урок 04 этого модуля.
Groups как ownership boundary
Groups в dbt-core имеют ещё одно use case — ownership labeling:
# dbt_project.yml
groups:
- name: finance_team
owner:
email: [email protected]
slack: '#finance-data'
- name: marketing_team
owner:
email: [email protected]
slack: '#marketing-data'
В моделях:
- name: fct_revenue
group: finance_team
access: public
- name: stg_invoices
group: finance_team
# access default private
Преимущества:
- Test failure routing: dbt-checkpoint / observe tools могут routing failure к slack channel owner.
- Doc generation: dbt docs автоматически groups models по owner.
- Cost attribution: queries tagged with group -> cost dashboards per team.
Practical setup: 3-проект Mesh
Real-world example:
# finance_dbt/dbt_project.yml
config-version: 2
name: 'finance_dbt'
groups:
- name: finance_team
owner:
email: [email protected]
models:
finance_dbt:
+group: finance_team
staging:
+access: private # все staging private
intermediate:
+access: protected # intermediate доступна finance_team subprojects
marts:
+access: public # marts — formal API
# finance_dbt/models/marts/schema.yml
models:
- name: fct_revenue
description: "Daily revenue. **Public API** — see [contract docs]."
config:
contract:
enforced: true
columns:
- name: date
data_type: date
tests:
- not_null
- name: revenue_usd
data_type: decimal(18,2)
tests:
- not_null
Получили: 100 staging — private. 50 intermediate — protected. 20 marts — public с contracts. Это structured exposure: only relevant data has formal API.
Audit: какие модели public
На большом проекте важно знать, какие модели exposed как API. Простой query через dbt ls:
dbt ls --resource-type model --select 'access:public' --output json | jq
Выдаёт список public models. Полезно для:
- Audit — не expose accidentally.
- Documentation — список API endpoints.
- Communication — список breaking-change-risky models.
Failure modes
1. Model access not specified, accidentally private
Compilation Error: Model 'int_user_aggregations' is private. Cross-project ref не работает.
Finance не set access, default private. Marketing fails.
Fix: Finance добавляет access: public (если действительно намерены) или Marketing должна use derived model.
2. Contract change без version bump
Finance: меняет column type revenue_usd: decimal(10,2) -> decimal(18,2).
dbt:
Compilation Error: Contract validation failed.
Existing constraint says decimal(10,2), SQL returns decimal(18,2).
Fix: либо revert SQL, либо обновить contract (но это breaking change для downstream — нужна version 2).
3. Cross-team breakage без contract
Finance: public model без contract. Меняет колонку (rename). Marketing build падает runtime.
Fix: добавить contract на public model. Going forward changes auto-blocked.
4. Protected leak across teams
Finance protected model, group=finance_team. Marketing создаёт project, declares group=finance_team (ошибочно). Cross-project ref проходит — но это violation of intent.
Fix: groups должны быть team-scoped и not lightly declared. Convention: group declaration only при actual team membership.
5. Deprecation миграция
Finance deprecates fct_revenue_v1 (deprecation_date passes). Marketing ещё ref-ит v1. dbt warning не блокирует, build OK, но в production runtime — table удалена админом, runtime error.
Fix: monitoring deprecation usage. Tools as dbt-checkpoint + custom scripts to alert.
Самая частая ошибка — Finance забывает access: public на новой модели, которая должна быть API. Marketing тогда не может cross-project ref её. Convention в team: PR review checklist включает “is this a new public model? Then add access: public + contract”.
Tools для governance
В open source ecosystem:
- dbt-osmosis — auto-generates schema.yml с column-level metadata.
- dbt-checkpoint — pre-commit hooks, например, “all models must have access set”.
- dbt-meta-testing — assert что public models have descriptions, contracts.
- dbt-loom — manifest sharing с auto-detection of access violations.
Пример pre-commit hook через dbt-checkpoint:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/dbt-checkpoint/dbt-checkpoint
hooks:
- id: check-model-has-description
files: models/marts/.*\.sql$
- id: check-model-has-contract-enforced
files: models/marts/.*\.sql$
Это блокирует merge PRs, где новая mart-модель без description / contract.
Access patterns by layer
Convention для structuring access по слоям:
models/
staging/ -> access: private (default)
intermediate/ -> access: protected (with group)
marts/ -> access: public (with contract)
exports/ -> access: public (with contract, for downstream consumers)
Это даёт:
- Staging стабильны внутри проекта.
- Intermediate доступны для team’s sub-projects.
- Marts — formal API.
- Exports — special models для downstream ML/Reverse ETL.
В dbt_project.yml через +access:
models:
my_project:
staging:
+access: private
intermediate:
+access: protected
+group: my_team
marts:
+access: public
+contract: { enforced: true }
Все модели в staging/ автоматически private. Override per-model в schema.yml если нужно exception.
Mesh evolution: from monolith to Mesh
Migration step-by-step:
-
Start: monolith. Все модели implicit private (no access set).
-
Step 1: Identify cross-team uses.
grep -r "ref('fct_revenue')" models/— кто использует. -
Step 2: Mark public + contracts. Critical cross-team models get explicit public + contract:
- name: fct_revenue
access: public
config:
contract:
enforced: true
-
Step 3: Mark private explicitly (safety). Staging/intermediate get explicit
access: private. -
Step 4: Split into projects. Now move models to separate dbt projects,
project_dependencies.ymlсохраняет access semantics. -
Step 5: Setup Mesh infra (manifest sharing, multi-project CI).
-
Step 6: Continuous governance (audits, monitoring, deprecation tracking).
Резюме
access— три уровня:private(default),protected(within group),public(everywhere).publicдолжна идти сcontract: enforced— без этого нет formal API.- Groups — для
protectedмодели и ownership labeling. - Internal model access не ограничен — внутри проекта access не применяется.
- Versioned models — для breaking changes на public моделях.
- Audit через
dbt ls --select 'access:public'или manifest parsing. - Tools: dbt-checkpoint, dbt-osmosis, custom scripts для governance enforcement.
- Migration monolith -> Mesh через explicit marking access (public/private), затем splitting на projects.
- Failure modes: missing access, contract violations, protected leak, deprecation miss.