Learning Platform
Глоссарий Troubleshooting
Урок 12.03 · 22 мин
Продвинутый
accessgovernancecontractsgroupsmesh

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

Три уровня по строгости:

AccessScopeUse case
privateТолько этот dbt-проектInternal staging, intermediate, helper-модели
protectedЭтот project + projects того же groupCross-team shared, но не “fully public API”
publicВсе projectsFormal 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 внутренние модели для подкоманд, но не для всех.

TIP

В практике 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 — это даёт две гарантии:

  1. Producer side: dbt не позволит изменить SQL так, чтобы output columns/types не соответствовали contract. Build падает.

  2. 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.

WARNING

Самая частая ошибка — 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:

  1. Start: monolith. Все модели implicit private (no access set).

  2. Step 1: Identify cross-team uses. grep -r "ref('fct_revenue')" models/ — кто использует.

  3. Step 2: Mark public + contracts. Critical cross-team models get explicit public + contract:

- name: fct_revenue
  access: public
  config:
    contract:
      enforced: true
  1. Step 3: Mark private explicitly (safety). Staging/intermediate get explicit access: private.

  2. Step 4: Split into projects. Now move models to separate dbt projects, project_dependencies.yml сохраняет access semantics.

  3. Step 5: Setup Mesh infra (manifest sharing, multi-project CI).

  4. Step 6: Continuous governance (audits, monitoring, deprecation tracking).

Проверка знанийKnowledge check
Команда обнаружила, что 50% public моделей в Mesh не имеют contract enforced. На production-incident review (модель Finance изменилась, Marketing dashboard упал) появился вопрос: "почему нет gating?". Какой план implementation?
ОтветAnswer
Этот incident — typical для Mesh без proper governance. Plan implementation: (1) **Audit current state**: - `dbt ls --resource-type model --select 'access:public'` — list public models. - `dbt ls --resource-type model --select 'access:public,config.contract.enforced:false'` — public без contract (если supported). - Альтернатива — Python script через manifest.json: ```python import json with open('target/manifest.json') as f: manifest = json.load(f) public_no_contract = [ node for node in manifest['nodes'].values() if node.get('access') == 'public' and not node.get('config', {}).get('contract', {}).get('enforced') ] print(f"Public без contract: {len(public_no_contract)}") for node in public_no_contract: print(f" - {node['name']} ({node['fqn']})") ``` (2) **Categorize public models**: - Critical (cross-team heavy use) — needed contract немедленно. - Less critical — можно gradual rollout. - Maybe should be private — некоторые модели accidentally marked public, можно reduce scope. (3) **Phased rollout contracts**: Phase 1 (week 1): Critical models (top 10 most-referenced). - Add contract to schema.yml. - Define column types, constraints from current data. - Test on dev — make sure no breaking change. - Deploy to prod. Phase 2 (week 2-4): All remaining public models. - Same process, batch by team. - Communicate with consumers — "we're adding contracts, не должно ничего сломаться". Phase 3 (ongoing): Enforce policy. - Pre-commit hook (dbt-checkpoint): "all public models must have contract enforced". - CI check: fail PR if new public model without contract. (4) **Tool setup**: ```yaml # .pre-commit-config.yaml - repo: https://github.com/dbt-checkpoint/dbt-checkpoint hooks: - id: check-model-has-contract-enforced files: 'models/marts/.*\.sql$' # И для других public-areas ``` ```yaml # .github/workflows/dbt-ci.yml - name: Check public models have contracts run: | python scripts/check_public_contracts.py # exit 1 если any public без contract ``` (5) **Communication strategy**: - Tech all-hands: explain Mesh governance gap, plan to fix. - Per-team meetings: walk через their public models, get buy-in for contracts. - Doc: "How to add contract to public model" — make easy for teams. - Slack channel #mesh-governance — updates, questions. (6) **Avoid future incidents**: - Add monitoring: "contracts violations" metric. - Quarterly review: list of public models, ownership, contract status. - Onboarding doc: "creating new public model" includes contract как required. (7) **Address current incident**: - Identify which model changed unsafely. - Add contract to it now. - Apologize to Marketing team, restore dashboard. - Use as case study for tech all-hands ("see why governance matters"). (8) **Long-term governance maturity**: - Move from contracts (column-level) to versions (full schema versioning). - API documentation generated from contracts. - Internal data product catalog. This исincident — opportunity to mature Mesh governance practice. Without it, Mesh — это just "monolith разбросанный на куски". Реалистично весь rollout — 2-3 sprint (4-6 недель). Investment, но preventing future incidents = saving engineer-hours + business trust.

Резюме

  • 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.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что такое access: protected в dbt Mesh?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5