Skip to content

Commit e09ec29

Browse files
authored
Extension Point Example (#1960)
Extension Point Example Thank you for your contribution.
1 parent ceaf255 commit e09ec29

7 files changed

+394
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var CustomNotificationHandlerInterface = Class.create();
2+
CustomNotificationHandlerInterface.prototype = {
3+
/**
4+
* Initialize the notification handler
5+
* @param {GlideRecord} record - The record triggering the notification
6+
* @param {Object} config - Configuration object
7+
*/
8+
initialize: function(record, config) {
9+
this.record = record;
10+
this.config = config || {};
11+
},
12+
13+
/**
14+
* Process the notification
15+
* @returns {Object} Result object with status and message
16+
*/
17+
process: function() {
18+
throw new Error('process() must be implemented by extension');
19+
},
20+
21+
/**
22+
* Validate if notification should be sent
23+
* @returns {Boolean} True if notification should be sent
24+
*/
25+
shouldNotify: function() {
26+
throw new Error('shouldNotify() must be implemented by extension');
27+
},
28+
29+
/**
30+
* handles if the implementation needs to run
31+
* @returns {Boolean} True if implementation will run
32+
*/
33+
handles: function(notificationSystem) {
34+
return notificationSystem == "DEFAULT";
35+
},
36+
37+
type: 'CustomNotificationHandlerInterface'
38+
};
391 KB
Loading
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
var CustomNotificationHandlerInterface = Class.create();
2+
CustomNotificationHandlerInterface.prototype = {
3+
/**
4+
* Initialize the notification handler
5+
* @param {GlideRecord} record - The record triggering the notification
6+
* @param {Object} config - Configuration object
7+
*/
8+
initialize: function(record, config) {
9+
this.record = record;
10+
this.config = config || {};
11+
},
12+
13+
/**
14+
* Process email notification
15+
* @returns {Object} Result with status and details
16+
*/
17+
process: function() {
18+
try {
19+
if (!this.shouldNotify()) {
20+
return {
21+
success: false,
22+
message: 'Notification criteria not met'
23+
};
24+
}
25+
26+
var email = new GlideEmailOutbound();
27+
email.setSubject(this._buildSubject());
28+
email.setBody(this._buildBody());
29+
email.setRecipients(this.emailConfig.recipients || '');
30+
email.send();
31+
32+
return {
33+
success: true,
34+
message: 'Email notification sent successfully'
35+
};
36+
} catch (e) {
37+
gs.error('EmailNotificationHandler error: ' + e.message);
38+
return {
39+
success: false,
40+
message: 'Error sending notification: ' + e.message
41+
};
42+
}
43+
},
44+
45+
/**
46+
* Validate notification criteria
47+
* @returns {Boolean}
48+
*/
49+
shouldNotify: function() {
50+
if (!this.record || !this.record.isValidRecord()) {
51+
return false;
52+
}
53+
54+
// Check if priority is high enough
55+
var priority = this.record.getValue('priority');
56+
var minPriority = this.config.minPriority || 3;
57+
58+
return parseInt(priority) <= parseInt(minPriority);
59+
},
60+
61+
/**
62+
* Build email subject
63+
* @returns {String}
64+
* @private
65+
*/
66+
_buildSubject: function() {
67+
var template = this.emailConfig.subjectTemplate || 'Notification: ${number}';
68+
return GlideStringUtil.substitute(template, {
69+
number: this.record.getDisplayValue('number'),
70+
short_description: this.record.getDisplayValue('short_description')
71+
});
72+
},
73+
74+
/**
75+
* Build email body
76+
* @returns {String}
77+
* @private
78+
*/
79+
_buildBody: function() {
80+
var body = 'Record Details:\n';
81+
body += 'Number: ' + this.record.getDisplayValue('number') + '\n';
82+
body += 'Short Description: ' + this.record.getDisplayValue('short_description') + '\n';
83+
body += 'Priority: ' + this.record.getDisplayValue('priority') + '\n';
84+
body += 'State: ' + this.record.getDisplayValue('state') + '\n';
85+
return body;
86+
},
87+
88+
/**
89+
* handles if the implementation needs to run
90+
* @returns {Boolean} True if implementation will run
91+
*/
92+
handles: function(notificationSystem) {
93+
return notificationSystem == "Email";
94+
},
95+
96+
type: 'CustomNotificationHandlerInterface'
97+
};
442 KB
Loading
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var eps = new GlideScriptedExtensionPoint().getExtensions("x_snc_etension_p_0.CustomNotificationHandlerInterface");
2+
for (var i = 0; i < eps.length; i++) {
3+
// checking which extension point works
4+
gs.info("Does the extension point applies: " + eps[i].handles("Email"));
5+
if (eps[i].handles("Email")) {
6+
//invoking the process function
7+
eps[i].process();
8+
}
9+
}
190 KB
Loading
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Extension Points
2+
3+
## Overview
4+
5+
Extension Points in ServiceNow provide a powerful mechanism for creating extensible, plugin-based architectures. They allow you to define interfaces that can be implemented by multiple extensions, enabling dynamic behavior selection at runtime without modifying core code.
6+
7+
## What are Extension Points?
8+
9+
Extension Points follow the interface-implementation pattern where:
10+
- **Extension Point (Interface)**: Defines a contract with methods that implementations must provide
11+
- **Extensions (Implementations)**: Concrete classes that implement the interface methods
12+
- **Consumer**: Code that discovers and invokes extensions dynamically using `GlideScriptedExtensionPoint`
13+
14+
This pattern is ideal for:
15+
- Creating pluggable notification systems
16+
- Building extensible validation frameworks
17+
- Implementing strategy patterns
18+
- Supporting multi-tenant customizations
19+
- Enabling third-party integrations
20+
21+
## Files in this Directory
22+
23+
### 1. CustomNotificationHandlerInterfaceExtension.js
24+
Defines the base interface (Extension Point) for notification handlers.
25+
26+
**Key Methods:**
27+
- `initialize(record, config)`: Constructor for setting up the handler
28+
- `process()`: Must be implemented - processes the notification
29+
- `shouldNotify()`: Must be implemented - validates if notification should be sent
30+
- `handles(notificationSystem)`: Determines if this implementation applies to a specific system
31+
32+
### 2. CustomNotificationHandlerInterfaceImplementation.js
33+
Concrete implementation of the notification handler interface for email notifications.
34+
35+
**Features:**
36+
- Email notification processing using `GlideEmailOutbound`
37+
- Priority-based notification criteria
38+
- Template-based subject and body generation
39+
- Error handling and logging
40+
- Configurable recipients and templates
41+
42+
### 3. ExtensionPointCallingExample.js
43+
Demonstrates how to discover and invoke extension point implementations dynamically.
44+
45+
**Pattern:**
46+
```javascript
47+
var eps = new GlideScriptedExtensionPoint().getExtensions("scope.InterfaceName");
48+
for (var i = 0; i < eps.length; i++) {
49+
if (eps[i].handles("criteria")) {
50+
eps[i].process();
51+
}
52+
}
53+
```
54+
55+
## How to Use Extension Points
56+
57+
### Step 1: Create the Extension Point (Interface)
58+
59+
Create a Script Include with the interface definition:
60+
61+
```javascript
62+
var CustomNotificationHandlerInterface = Class.create();
63+
CustomNotificationHandlerInterface.prototype = {
64+
initialize: function(record, config) {
65+
this.record = record;
66+
this.config = config || {};
67+
},
68+
69+
process: function() {
70+
throw new Error('process() must be implemented by extension');
71+
},
72+
73+
shouldNotify: function() {
74+
throw new Error('shouldNotify() must be implemented by extension');
75+
},
76+
77+
handles: function(notificationSystem) {
78+
return notificationSystem == "DEFAULT";
79+
},
80+
81+
type: 'CustomNotificationHandlerInterface'
82+
};
83+
```
84+
85+
**Important Configuration:**
86+
- Set **API Name** to match the type (e.g., `CustomNotificationHandlerInterface`)
87+
- Check **Client callable** if needed from client-side
88+
- Set appropriate **Application** scope
89+
90+
### Step 2: Create Extension Point Record
91+
92+
1. Navigate to **System Definition > Extension Points**
93+
2. Create a new Extension Point record:
94+
- **Name**: Descriptive name (e.g., "Custom Notification Handler Interface")
95+
- **API name**: Full scoped name (e.g., `x_snc_extension_p_0.CustomNotificationHandlerInterface`)
96+
- **Example implementation**: Reference your interface Script Include
97+
98+
### Step 3: Create Implementations (Extensions)
99+
100+
Create Script Includes that extend the interface:
101+
102+
```javascript
103+
var EmailNotificationHandler = Class.create();
104+
EmailNotificationHandler.prototype = Object.extendsObject(CustomNotificationHandlerInterface, {
105+
106+
process: function() {
107+
// Implementation-specific logic
108+
var email = new GlideEmailOutbound();
109+
email.setSubject(this._buildSubject());
110+
email.setBody(this._buildBody());
111+
email.send();
112+
113+
return { success: true, message: 'Email sent' };
114+
},
115+
116+
shouldNotify: function() {
117+
// Custom validation logic
118+
return this.record.getValue('priority') <= 3;
119+
},
120+
121+
handles: function(notificationSystem) {
122+
return notificationSystem == "Email";
123+
},
124+
125+
type: 'EmailNotificationHandler'
126+
});
127+
```
128+
129+
### Step 4: Register Extensions
130+
131+
1. Navigate to **System Definition > Extensions**
132+
2. Create Extension records for each implementation:
133+
- **Extension point**: Select your Extension Point
134+
- **Name**: Implementation name
135+
- **Implementation**: Reference your implementation Script Include
136+
137+
### Step 5: Invoke Extensions Dynamically
138+
139+
Use `GlideScriptedExtensionPoint` to discover and execute extensions:
140+
141+
```javascript
142+
// Get all extensions for the interface
143+
var eps = new GlideScriptedExtensionPoint().getExtensions("x_snc_extension_p_0.CustomNotificationHandlerInterface");
144+
145+
// Iterate and invoke matching extensions
146+
for (var i = 0; i < eps.length; i++) {
147+
gs.info("Checking extension: " + eps[i].type);
148+
149+
if (eps[i].handles("Email")) {
150+
var result = eps[i].process();
151+
gs.info("Result: " + JSON.stringify(result));
152+
}
153+
}
154+
```
155+
156+
## Best Practices
157+
158+
### 1. Interface Design
159+
- Define clear contracts with well-documented methods
160+
- Use JSDoc comments for all interface methods
161+
- Throw errors for unimplemented required methods
162+
- Provide sensible defaults in the base interface
163+
164+
### 2. Implementation Guidelines
165+
- Always implement all required interface methods
166+
- Use the `handles()` method to control when implementation applies
167+
- Include comprehensive error handling
168+
- Return consistent result objects
169+
- Log important operations for debugging
170+
171+
### 3. Naming Conventions
172+
- Use descriptive names for Extension Points
173+
- Follow ServiceNow naming standards
174+
- Include scope prefix in API names
175+
- Use consistent naming across interface and implementations
176+
177+
### 4. Performance Considerations
178+
- Cache extension instances when possible
179+
- Avoid heavy processing in `handles()` method
180+
- Use early returns to skip unnecessary processing
181+
- Consider lazy initialization for expensive resources
182+
183+
### 5. Testing
184+
- Test each implementation independently
185+
- Verify `handles()` logic with various inputs
186+
- Test error handling and edge cases
187+
- Validate behavior when no extensions match
188+
189+
## Common Use Cases
190+
191+
### 1. Multi-Channel Notifications
192+
Create extensions for Email, SMS, Slack, Teams, etc., all implementing a common notification interface.
193+
194+
### 2. Validation Frameworks
195+
Build extensible validation systems where different validators can be plugged in based on record type or business rules.
196+
197+
### 3. Data Transformation
198+
Implement multiple transformation strategies that can be selected dynamically based on data source or format.
199+
200+
### 4. Integration Adapters
201+
Create adapters for different external systems, all conforming to a common integration interface.
202+
203+
### 5. Approval Workflows
204+
Build flexible approval mechanisms where different approval strategies can be applied based on conditions.
205+
206+
## Troubleshooting
207+
208+
### Extensions Not Found
209+
- Verify Extension Point API name matches exactly
210+
- Check that Extension records are active
211+
- Ensure Script Includes are in the correct application scope
212+
- Verify no typos in scope prefix
213+
214+
### Methods Not Executing
215+
- Confirm `handles()` method returns true for your criteria
216+
- Check for errors in implementation code
217+
- Verify Script Include is set to correct type
218+
- Review logs for error messages
219+
220+
### Performance Issues
221+
- Reduce number of extensions if possible
222+
- Optimize `handles()` method logic
223+
- Consider caching extension instances
224+
- Profile code to identify bottlenecks
225+
226+
## Additional Resources
227+
228+
- [ServiceNow Extension Points Documentation](https://docs.servicenow.com)
229+
- [GlideScriptedExtensionPoint API Reference](https://developer.servicenow.com/dev.do#!/reference/api/latest/server/no-namespace/c_GlideScriptedExtensionPointScopedAPI)
230+
- [Script Includes Best Practices](https://developer.servicenow.com/dev.do#!/learn/learning-plans/tokyo/new_to_servicenow/app_store_learnv2_scripting_tokyo_script_includes)
231+
232+
## Example Scenarios
233+
234+
### Scenario 1: Priority-Based Email Notifications
235+
Use the provided implementation to send email notifications only for high-priority incidents (priority 1-3).
236+
237+
### Scenario 2: Multi-System Notification Router
238+
Create multiple implementations (Email, SMS, Slack) and route notifications based on user preferences or incident severity.
239+
240+
### Scenario 3: Custom Business Logic
241+
Extend the interface to add custom validation rules, approval logic, or data transformation specific to your organization.
242+
243+
## Contributing
244+
245+
When adding new Extension Point examples:
246+
1. Include both interface and at least one implementation
247+
2. Provide clear documentation in code comments
248+
3. Add usage examples
249+
4. Include screenshots of configuration if helpful
250+
5. Follow the repository's code quality standards

0 commit comments

Comments
 (0)