mcpField
D
domain-separation

domain-separation

github.com/serac-labs開発7756 回インストール9 週間前に更新
リポジトリ

domain-separation について

domain-separationは、ServiceNowでマルチテナントを構成するための開発ガイドです。ドメイン階層、ユーザーの所属、ドメイン対応クエリ、ドメイン間アクセスをES5の例とMCPツールの手順で説明します。

主な機能

  1. 01ドメイン階層の設計
  2. 02ドメイン対応クエリの設定
  3. 03ユーザーのドメイン所属管理
  4. 04可視性の設定とドメイン切り替え
  5. 05MCPツールのワークフロー

活用シーン

  1. 01テナント向けドメイン構造の設計
  2. 02ユーザーのドメインへの割り当て
  3. 03ドメイン内でのレコード可視性の確認
  4. 04MCP経由でのドメイン設定の検索

MCP 対応

ドメイン分離に利用できるMCPツールとして、snow_query_table、snow_execute_script、snow_artifact_manageを挙げています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serac-labs/serac --skill domain-separation

インストール後、Claude Code で/domain-separationを入力して読み込まれたか確認します。

詳細

ServiceNow のドメイン分離

ドメイン分離を使用すると、ドメイン間でデータとプロセスを分割し、マルチテナンシーを実現できます。

ドメインアーキテクチャ

TOP (Global)
    ├── Domain A (Customer 1)
    │   ├── Sub-domain A1
    │   └── Sub-domain A2
    └── Domain B (Customer 2)
        └── Sub-domain B1

主なテーブル

テーブル目的
domainドメイン定義
sys_user_has_domainユーザーのドメイン所属
domain_pathドメイン階層のパス
sys_db_objectテーブルのドメイン設定

ドメイン設定(ES5)

ドメインを作成する
// Create domain (ES5 ONLY!)
var domain = new GlideRecord("domain")
domain.initialize()

domain.setValue("name", "Acme Corp")
domain.setValue("description", "Domain for Acme Corporation")

// Parent domain (empty for top-level)
domain.setValue("parent", parentDomainSysId)

// Domain visibility
domain.setValue("active", true)

domain.insert()
ドメイン対応クエリ
// Query respecting domain separation (ES5 ONLY!)
function getDomainAwareRecords(tableName, query) {
  var gr = new GlideRecord(tableName)

  // Domain separation is automatic when enabled
  // Records are filtered to user's visible domains

  if (query) {
    gr.addEncodedQuery(query)
  }
  gr.query()

  var records = []
  while (gr.next()) {
    records.push({
      sys_id: gr.getUniqueValue(),
      sys_domain: gr.getValue("sys_domain"),
      sys_domain_path: gr.getValue("sys_domain_path"),
    })
  }

  return records
}
ドメイン間アクセス
// Access records across domains (requires elevated privileges) (ES5 ONLY!)
function getCrossdomainRecords(tableName) {
  var gr = new GlideRecord(tableName)

  // Disable domain separation for this query
  gr.setQueryReferences(false)

  // Query all domains
  gr.queryNoDomain()

  var records = []
  while (gr.next()) {
    records.push({
      sys_id: gr.getUniqueValue(),
      domain: gr.sys_domain.getDisplayValue(),
    })
  }

  return records
}

ユーザーのドメイン所属(ES5)

ユーザーをドメインに割り当てる
// Add user to domain (ES5 ONLY!)
function addUserToDomain(userSysId, domainSysId, isPrimary) {
  // Check if already assigned
  var existing = new GlideRecord("sys_user_has_domain")
  existing.addQuery("user", userSysId)
  existing.addQuery("domain", domainSysId)
  existing.query()

  if (existing.next()) {
    return existing.getUniqueValue()
  }

  // Create assignment
  var assignment = new GlideRecord("sys_user_has_domain")
  assignment.initialize()
  assignment.setValue("user", userSysId)
  assignment.setValue("domain", domainSysId)
  assignment.setValue("primary", isPrimary)
  return assignment.insert()
}
ユーザーのドメインを取得する
// Get domains accessible to user (ES5 ONLY!)
function getUserDomains(userSysId) {
  var domains = []

  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", userSysId)
  membership.query()

  while (membership.next()) {
    var domain = membership.domain.getRefRecord()
    domains.push({
      sys_id: domain.getUniqueValue(),
      name: domain.getValue("name"),
      is_primary: membership.getValue("primary") === "true",
    })
  }

  return domains
}

ドメイン分離テーブル(ES5)

テーブルのドメイン分離を設定する
// Enable domain separation on table (ES5 ONLY!)
// Note: This is typically done via UI, shown for reference

var tableConfig = new GlideRecord("sys_db_object")
if (tableConfig.get("name", "u_custom_table")) {
  // Enable domain separation
  tableConfig.setValue("domain_separated", true)

  // Domain separation type
  // 'simple' = records belong to one domain
  // 'containment' = records visible to parent domains
  tableConfig.setValue("domain_id_type", "simple")

  tableConfig.update()
}
特定のドメインにレコードを作成する
// Create record in specific domain (ES5 ONLY!)
function createInDomain(tableName, data, domainSysId) {
  var gr = new GlideRecord(tableName)
  gr.initialize()

  // Set field values
  for (var field in data) {
    if (data.hasOwnProperty(field)) {
      gr.setValue(field, data[field])
    }
  }

  // Set domain
  gr.setValue("sys_domain", domainSysId)

  return gr.insert()
}

ドメインピッカー(ES5)

ピッカーで使用可能なドメインを取得する
// Get domains for domain picker widget (ES5 ONLY!)
function getDomainsForPicker() {
  var domains = []
  var userId = gs.getUserID()

  // Get user's accessible domains
  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", userId)
  membership.query()

  while (membership.next()) {
    var domain = membership.domain.getRefRecord()
    if (domain.getValue("active") === "true") {
      domains.push({
        sys_id: domain.getUniqueValue(),
        name: domain.getValue("name"),
        is_primary: membership.getValue("primary") === "true",
        is_current: domain.getUniqueValue() === gs.getSession().getCurrentDomainID(),
      })
    }
  }

  // Sort: primary first, then alphabetically
  domains.sort(function (a, b) {
    if (a.is_primary && !b.is_primary) return -1
    if (!a.is_primary && b.is_primary) return 1
    return a.name.localeCompare(b.name)
  })

  return domains
}
現在のドメインを切り替える
// Switch user's current domain (ES5 ONLY!)
function switchDomain(domainSysId) {
  var session = gs.getSession()

  // Verify user has access
  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", gs.getUserID())
  membership.addQuery("domain", domainSysId)
  membership.query()

  if (!membership.next()) {
    gs.addErrorMessage("You do not have access to this domain")
    return false
  }

  // Switch domain
  session.setDomainID(domainSysId)
  gs.addInfoMessage("Switched to domain: " + membership.domain.getDisplayValue())

  return true
}

ドメインの可視性ルール(ES5)

ドメインの可視性を確認する
// Check if record is visible in current domain (ES5 ONLY!)
function isRecordVisibleInDomain(tableName, recordSysId) {
  var gr = new GlideRecord(tableName)
  gr.addQuery("sys_id", recordSysId)
  gr.query()

  // If record is found, it's visible in current domain context
  return gr.hasNext()
}
ドメインパスを取得する
// Get full domain hierarchy path (ES5 ONLY!)
function getDomainPath(domainSysId) {
  var path = []

  var domain = new GlideRecord("domain")
  if (!domain.get(domainSysId)) {
    return path
  }

  // Build path from current to root
  while (domain.isValidRecord()) {
    path.unshift({
      sys_id: domain.getUniqueValue(),
      name: domain.getValue("name"),
    })

    if (!domain.parent) break
    domain = domain.parent.getRefRecord()
  }

  return path
}

MSP/マネージドサービスのパターン(ES5)

新しいテナントをオンボーディングする
// Create new tenant domain with initial setup (ES5 ONLY!)
function onboardTenant(tenantData) {
  // Create domain
  var domain = new GlideRecord("domain")
  domain.initialize()
  domain.setValue("name", tenantData.name)
  domain.setValue("parent", tenantData.parentDomain || "")
  var domainSysId = domain.insert()

  // Create tenant admin user
  var adminUser = new GlideRecord("sys_user")
  adminUser.initialize()
  adminUser.setValue("user_name", tenantData.adminEmail)
  adminUser.setValue("email", tenantData.adminEmail)
  adminUser.setValue("first_name", tenantData.adminFirstName)
  adminUser.setValue("last_name", tenantData.adminLastName)
  var adminSysId = adminUser.insert()

  // Assign user to domain
  addUserToDomain(adminSysId, domainSysId, true)

  // Assign tenant admin role
  var role = new GlideRecord("sys_user_has_role")
  role.initialize()
  role.setValue("user", adminSysId)
  role.setValue("role", getTenantAdminRoleSysId())
  role.insert()

  return {
    domain_sys_id: domainSysId,
    admin_sys_id: adminSysId,
  }
}

MCP ツール統合

使用可能なツール
ツール目的
snow_query_tableドメイン対応データをクエリする
snow_execute_scriptドメインスクリプトをテストする
snow_artifact_manageドメイン設定を検索する(action='find')
ワークフローの例
// 1. Query domains
await snow_query_table({
  table: "domain",
  query: "active=true",
  fields: "name,parent,sys_id",
})

// 2. Get user domain memberships
await snow_query_table({
  table: "sys_user_has_domain",
  query: "user=user_sys_id",
  fields: "domain,primary",
})

// 3. Check domain-separated tables
await snow_query_table({
  table: "sys_db_object",
  query: "domain_separated=true",
  fields: "name,label,domain_id_type",
})

ベストプラクティス

  1. 階層の計画 - 実装前にドメイン構造を設計します
  2. ドメインの最小化 - 必要な分離のみを作成します
  3. ユーザーアクセス - 必要最小限のドメインを割り当てます
  4. テスト - ドメインピッカーを使ってテストします
  5. グローバルデータ - 共有データは TOP ドメインに保持します
  6. パフォーマンス - ドメインクエリにはオーバーヘッドが発生します
  7. ドキュメント - ドメインの目的を文書化します
  8. ES5 のみ - 最新の JavaScript 構文は使用しません

コメント