scoped-apps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseScoped Application Development for ServiceNow
ServiceNow限定范围应用开发
Scoped applications provide isolation and portability for custom development in ServiceNow.
限定范围应用(Scoped applications)为ServiceNow中的自定义开发提供隔离性和可移植性。
Why Use Scoped Apps?
为何使用限定范围应用?
| Feature | Global Scope | Scoped App |
|---|---|---|
| Naming conflicts | Possible | Prevented (x_prefix) |
| Portability | Difficult | Easy (Update Sets) |
| Security | Open | Controlled (Cross-scope) |
| Store publishing | No | Yes |
| Dependencies | Implicit | Explicit |
| 特性 | 全局作用域 | 限定范围应用 |
|---|---|---|
| 命名冲突 | 可能存在 | 已避免(x_前缀) |
| 可移植性 | 困难 | 容易(更新集) |
| 安全性 | 开放 | 受控(跨作用域) |
| 商店发布 | 不支持 | 支持 |
| 依赖关系 | 隐式 | 显式 |
Creating a Scoped Application
创建限定范围应用
Via Studio (Recommended)
通过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 access1. 导航至:系统应用 > Studio
2. 点击:创建应用
3. 输入:
- 名称:"My Custom App"
- 作用域:"x_mycom_myapp"(自动生成)
- 版本:1.0.0
4. 配置:
- 运行时访问:勾选需要跨作用域访问的表Via MCP
通过MCP
javascript
snow_create_application({
name: "My Custom Application",
scope: "x_mycom_custom",
version: "1.0.0",
description: "Custom application for..."
});javascript
snow_create_application({
name: "My Custom Application",
scope: "x_mycom_custom",
version: "1.0.0",
description: "Custom application for..."
});Scope Naming Convention
作用域命名规范
x_[vendor]_[app]
Examples:
- x_acme_hr (ACME Corp HR App)
- x_mycom_inventory (My Company Inventory)
- x_snc_global (ServiceNow Global)x_[供应商]_[应用]
示例:
- x_acme_hr (ACME Corp HR应用)
- x_mycom_inventory (我司库存管理应用)
- x_snc_global (ServiceNow全局)Table Naming
表命名规则
javascript
// 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();javascript
// 限定范围的表会自动添加前缀
// Studio中的表名:"task_tracker"
// 实际表名:"x_mycom_myapp_task_tracker"
// 创建记录
var gr = new GlideRecord('x_mycom_myapp_task_tracker');
gr.initialize();
gr.setValue('name', 'My Task');
gr.insert();Script Include in Scoped App
限定范围应用中的脚本包含
javascript
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'
};javascript
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();
},
// 标记为可从其他作用域访问
// 要求:"可访问范围:所有应用作用域"
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'
};Cross-Scope Access
跨作用域访问
Calling Other Scope's Script Include
调用其他作用域的脚本包含
javascript
// 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)'
);javascript
// 当前作用域:x_mycom_otherapp
// 调用:x_mycom_myapp.TaskManager
// 选项1:直接调用(如果允许访问)
var tm = new x_mycom_myapp.TaskManager();
var task = tm.getTask(sysId);
// 选项2:使用GlideScopedEvaluator
var evaluator = new GlideScopedEvaluator();
evaluator.putVariable('sysId', sysId);
var result = evaluator.evaluateScript(
'x_mycom_myapp',
'new TaskManager().getTask(sysId)'
);Accessing Other Scope's Tables
访问其他作用域的表
javascript
// 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();javascript
// 检查是否允许跨作用域访问
var gr = new GlideRecord('x_other_app_table');
if (!gr.isValid()) {
gs.error('No access to x_other_app_table');
return;
}
// 如果允许,正常查询
gr.addQuery('active', true);
gr.query();Application Properties
应用属性
Define Properties
定义属性
javascript
// 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_myappjavascript
// 在 应用 > 属性 中
// 名称:x_mycom_myapp.default_priority
// 值:3
// 类型:字符串
// 在 应用 > 模块 中
// 创建指向以下地址的「属性」模块:
// /sys_properties_list.do?sysparm_query=name=x_mycom_myappUse Properties
使用属性
javascript
// 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');javascript
// 获取属性值
var defaultPriority = gs.getProperty('x_mycom_myapp.default_priority', '3');
// 设置属性值(需要管理员权限)
gs.setProperty('x_mycom_myapp.default_priority', '2');Application Files Structure
应用文件结构
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_notificationsx_mycom_myapp/
├── 表
│ ├── x_mycom_myapp_task
│ └── x_mycom_myapp_config
├── 脚本包含
│ ├── TaskManager
│ └── ConfigUtils
├── 业务规则
│ └── 验证任务
├── UI页面
│ └── task_dashboard
├── REST API
│ └── 任务API
├── 计划任务
│ └── 每日清理
└── 应用属性
├── default_priority
└── enable_notificationsREST API in Scoped App
限定范围应用中的REST API
Define Scripted REST API
定义脚本化REST API
javascript
// 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);javascript
// 资源:/api/x_mycom_myapp/tasks
// HTTP方法: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);Calling the API
调用API
bash
curl -X GET \
"https://instance.service-now.com/api/x_mycom_myapp/tasks" \
-H "Authorization: Bearer token"bash
curl -X GET \
"https://instance.service-now.com/api/x_mycom_myapp/tasks" \
-H "Authorization: Bearer token"Application Dependencies
应用依赖
Declare Dependencies
声明依赖
Application > Dependencies
Add:
- sn_hr_core (HR Core)
- sn_cmdb (CMDB)应用 > 依赖关系
添加:
- sn_hr_core (HR核心)
- sn_cmdb (配置管理数据库)Check Dependencies in Code
在代码中检查依赖
javascript
// Check if plugin is active
if (GlidePluginManager.isActive('com.snc.hr.core')) {
// HR Core is available
var hrCase = new sn_hr_core.hr_case();
}javascript
// 检查插件是否激活
if (GlidePluginManager.isActive('com.snc.hr.core')) {
// HR核心可用
var hrCase = new sn_hr_core.hr_case();
}Publishing to Store
发布至商店
Checklist Before Publishing
发布前检查清单
□ 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□ 所有表都配置了合适的ACL
□ 没有硬编码的sys_id
□ 没有硬编码的实例URL
□ 已声明所有依赖
□ 属性都有默认值
□ 文档完整
□ 测试用例通过
□ 未修改全局作用域
□ 更新集已在干净实例上测试Version Management
版本管理
Major.Minor.Patch
1.0.0 - Initial release
1.1.0 - New feature added
1.1.1 - Bug fix
2.0.0 - Breaking change主版本.次版本.补丁版本
1.0.0 - 初始版本
1.1.0 - 新增功能
1.1.1 - 修复Bug
2.0.0 - 破坏性变更Common Mistakes
常见错误
| Mistake | Problem | Solution |
|---|---|---|
| Global modifications | Won't deploy cleanly | Keep changes in scope |
| Hard-coded sys_ids | Fails on other instances | Use properties or lookups |
| Missing ACLs | Security vulnerabilities | Create ACLs for all tables |
| No error handling | Silent failures | Add try/catch, logging |
| Accessing global tables directly | Upgrade conflicts | Use references, not copies |
| 错误 | 问题 | 解决方案 |
|---|---|---|
| 修改全局作用域 | 无法干净部署 | 将变更限定在自身作用域内 |
| 硬编码sys_id | 在其他实例上运行失败 | 使用属性或查找方式 |
| 缺少ACL | 安全漏洞 | 为所有表创建ACL |
| 无错误处理 | 静默失败 | 添加try/catch和日志 |
| 直接访问全局表 | 升级时冲突 | 使用引用而非复制 |
Best Practices
最佳实践
- Single Responsibility - One app per business function
- Explicit Dependencies - Declare all requirements
- Property-Driven - Configurable without code changes
- Defensive Coding - Check access before operations
- Documentation - Include README, release notes
- Testing - Automated tests for critical functions
- Versioning - Semantic versioning for updates
- 单一职责 - 一个应用对应一个业务功能
- 显式依赖 - 声明所有依赖项
- 属性驱动 - 无需修改代码即可配置
- 防御式编码 - 操作前检查访问权限
- 文档完善 - 包含README和发布说明
- 测试充分 - 为关键功能编写自动化测试
- 版本规范 - 使用语义化版本管理更新