scoped-apps について
scoped-appsは、ServiceNowで分離性と移植性を備えたアプリを開発するためのガイドです。アプリ作成、スコープ間アクセス、REST API、依存関係、リリース手順を扱い、MCPによる作成例も掲載しています。
主な機能
- 01スコープ付きアプリの作成
- 02スコープ間アクセスの管理
- 03プロパティと依存関係の設定
- 04Scripted REST APIの構築
- 05リリースとバージョン管理の準備
活用シーン
- 01業務機能ごとの独立アプリの作成
- 02アプリスコープ間のアクセス制御
- 03依存関係とプロパティの明示的な設定
- 04Store公開に向けた準備
MCP 対応
snow_create_applicationを使ってMCP経由でServiceNowアプリを作成する例を示しています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill scoped-appsインストール後、Claude Code で/scoped-appsを入力して読み込まれたか確認します。
詳細
ServiceNow 向けスコープ付きアプリケーション開発
スコープ付きアプリケーションは、ServiceNow のカスタム開発に分離性と移植性をもたらします。
スコープ付きアプリを使用する理由
| 機能 | グローバルスコープ | スコープ付きアプリ |
|---|---|---|
| 名前の競合 | 発生する可能性あり | 防止される(x_prefix) |
| 移植性 | 困難 | 簡単(Update Sets) |
| セキュリティ | 開放 | 制御される(スコープ間) |
| ストアへの公開 | いいえ | はい |
| 依存関係 | 暗黙的 | 明示的 |
スコープ付きアプリケーションの作成
Studio から作成(推奨)
1. Navigate: System Applications > Studio
2. Click: Create Application
3. Enter:
- Name: "My Custom App"
- Scope: "x_mycom_myapp" (auto-generated)
- Version: 1.0.0
4. Configure:
- Runtime access: Check tables needing cross-scope access
MCP 経由で作成
snow_create_application({
name: "My Custom Application",
scope: "x_mycom_custom",
version: "1.0.0",
description: "Custom application for...",
})
スコープの命名規則
x_[vendor]_[app]
Examples:
- x_acme_hr (ACME Corp HR App)
- x_mycom_inventory (My Company Inventory)
- x_snc_global (ServiceNow Global)
テーブルの命名
// Scoped tables are automatically prefixed
// Table name in Studio: "task_tracker"
// Actual table name: "x_mycom_myapp_task_tracker"
// Creating records
var gr = new GlideRecord("x_mycom_myapp_task_tracker")
gr.initialize()
gr.setValue("name", "My Task")
gr.insert()
スコープ付きアプリ内の Script Include
var TaskManager = Class.create()
TaskManager.prototype = {
initialize: function () {
this.tableName = "x_mycom_myapp_task_tracker"
},
createTask: function (name, description) {
var gr = new GlideRecord(this.tableName)
gr.initialize()
gr.setValue("name", name)
gr.setValue("description", description)
return gr.insert()
},
// Mark as accessible from other scopes
// Requires: "Accessible from: All application scopes"
getTask: function (sysId) {
var gr = new GlideRecord(this.tableName)
if (gr.get(sysId)) {
return {
name: gr.getValue("name"),
description: gr.getValue("description"),
}
}
return null
},
type: "TaskManager",
}
スコープ間アクセス
別のスコープの Script Include の呼び出し
// From scope: x_mycom_otherapp
// Calling: x_mycom_myapp.TaskManager
// Option 1: Direct call (if accessible)
var tm = new x_mycom_myapp.TaskManager()
var task = tm.getTask(sysId)
// Option 2: GlideScopedEvaluator
var evaluator = new GlideScopedEvaluator()
evaluator.putVariable("sysId", sysId)
var result = evaluator.evaluateScript("x_mycom_myapp", "new TaskManager().getTask(sysId)")
別のスコープのテーブルへのアクセス
// Check if cross-scope access is allowed
var gr = new GlideRecord("x_other_app_table")
if (!gr.isValid()) {
gs.error("No access to x_other_app_table")
return
}
// If accessible, query normally
gr.addQuery("active", true)
gr.query()
アプリケーションプロパティ
プロパティの定義
// In Application > Properties
// Name: x_mycom_myapp.default_priority
// Value: 3
// Type: string
// In Application > Modules
// Create "Properties" module pointing to:
// /sys_properties_list.do?sysparm_query=name=x_mycom_myapp
プロパティの使用
// Get property value
var defaultPriority = gs.getProperty("x_mycom_myapp.default_priority", "3")
// Set property value (requires admin)
gs.setProperty("x_mycom_myapp.default_priority", "2")
アプリケーションファイルの構成
x_mycom_myapp/
├── Tables
│ ├── x_mycom_myapp_task
│ └── x_mycom_myapp_config
├── Script Includes
│ ├── TaskManager
│ └── ConfigUtils
├── Business Rules
│ └── Validate Task
├── UI Pages
│ └── task_dashboard
├── REST API
│ └── Task API
├── Scheduled Jobs
│ └── Daily Cleanup
└── Application Properties
├── default_priority
└── enable_notifications
スコープ付きアプリ内の REST API
Scripted REST API の定義
// Resource: /api/x_mycom_myapp/tasks
// HTTP Method: GET
;(function process(request, response) {
var tasks = []
var gr = new GlideRecord("x_mycom_myapp_task_tracker")
gr.addQuery("active", true)
gr.query()
while (gr.next()) {
tasks.push({
sys_id: gr.getUniqueValue(),
name: gr.getValue("name"),
status: gr.getValue("status"),
})
}
response.setBody({
result: tasks,
count: tasks.length,
})
})(request, response)
API の呼び出し
curl -X GET \
"https://instance.service-now.com/api/x_mycom_myapp/tasks" \
-H "Authorization: Bearer token"
アプリケーションの依存関係
依存関係の宣言
Application > Dependencies
Add:
- sn_hr_core (HR Core)
- sn_cmdb (CMDB)
コード内での依存関係の確認
// Check if plugin is active
if (GlidePluginManager.isActive("com.snc.hr.core")) {
// HR Core is available
var hrCase = new sn_hr_core.hr_case()
}
ストアへの公開
公開前のチェックリスト
□ All tables have proper ACLs
□ No hard-coded sys_ids
□ No hard-coded instance URLs
□ All dependencies declared
□ Properties have default values
□ Documentation complete
□ Test cases pass
□ No global scope modifications
□ Update Set tested on clean instance
バージョン管理
Major.Minor.Patch
1.0.0 - Initial release
1.1.0 - New feature added
1.1.1 - Bug fix
2.0.0 - Breaking change
よくある間違い
| 間違い | 問題 | 解決策 |
|---|---|---|
| グローバルスコープの変更 | 正しくデプロイできない | 変更をスコープ内に収める |
| sys_id のハードコード | 他のインスタンスで失敗する | プロパティまたは検索を使用する |
| ACL の不足 | セキュリティ上の脆弱性 | すべてのテーブルに ACL を作成する |
| エラー処理がない | エラーが通知されずに失敗する | try/catch とログ記録を追加する |
| グローバルテーブルへの直接アクセス | アップグレード時の競合 | コピーではなく参照を使用する |
ベストプラクティス
- 単一責任 - ビジネス機能ごとにアプリを 1 つ作成します
- 依存関係の明示 - 必要な依存関係をすべて宣言します
- プロパティ駆動 - コードを変更せずに設定できるようにします
- 防御的なコーディング - 操作の前にアクセス権を確認します
- ドキュメント - README とリリースノートを含めます
- テスト - 重要な機能に自動テストを用意します
- バージョン管理 - 更新にはセマンティックバージョニングを使用します
コメント
Markdown 対応