scoped-apps

安装量: 49
排名: #15106

安装

npx skills add https://github.com/groeimetai/snow-flow --skill scoped-apps

Scoped applications provide isolation and portability for custom development in ServiceNow.

Why Use Scoped Apps?

| Naming conflicts | Possible | Prevented (x_prefix)

| Portability | Difficult | Easy (Update Sets)

| Security | Open | Controlled (Cross-scope)

| Store publishing | No | Yes

| Dependencies | Implicit | Explicit

Creating a Scoped Application

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

Via MCP

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)

Table Naming

// 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 in Scoped App

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'
};

Cross-Scope Access

Calling Other Scope's 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)'
);

Accessing Other Scope's Tables

// 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();

Application Properties

Define Properties

// 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

Use Properties

// 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');

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_notifications

REST API in Scoped App

Define 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);

Calling the API

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)

Check Dependencies in Code

// Check if plugin is active
if (GlidePluginManager.isActive('com.snc.hr.core')) {
  // HR Core is available
  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

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

Common Mistakes

| 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

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

返回排行榜