domain-separation について
domain-separationは、ServiceNowでマルチテナントを構成するための開発ガイドです。ドメイン階層、ユーザーの所属、ドメイン対応クエリ、ドメイン間アクセスをES5の例とMCPツールの手順で説明します。
主な機能
- 01ドメイン階層の設計
- 02ドメイン対応クエリの設定
- 03ユーザーのドメイン所属管理
- 04可視性の設定とドメイン切り替え
- 05MCPツールのワークフロー
活用シーン
- 01テナント向けドメイン構造の設計
- 02ユーザーのドメインへの割り当て
- 03ドメイン内でのレコード可視性の確認
- 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",
})
ベストプラクティス
- 階層の計画 - 実装前にドメイン構造を設計します
- ドメインの最小化 - 必要な分離のみを作成します
- ユーザーアクセス - 必要最小限のドメインを割り当てます
- テスト - ドメインピッカーを使ってテストします
- グローバルデータ - 共有データは TOP ドメインに保持します
- パフォーマンス - ドメインクエリにはオーバーヘッドが発生します
- ドキュメント - ドメインの目的を文書化します
- ES5 のみ - 最新の JavaScript 構文は使用しません
コメント
Markdown 対応