- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 07-31-2018 07:11 AM
This post is meant to introduce industry tested patterns to coding and architecture practices. It is an effort to separate concerns, decrease maintenance and dev cycles, as well as increase scale-ability. A stepping point to what is possible with industry established patterns that have yet to be introduced to the ServiceNow community at large.
DISCLAIMER: THIS CODE WAS CREATED USING VISUAL STUDIO CODE IDE ON MY ONE HOUR TRAIN RIDE HOME FROM WORK. IT HAS NOT BEEN TESTED, NOR REVISED FOR BUGS OF ANY KIND. IT IS TO BE USED AS GUIDE... HOWEVER, THE ARCHITECTURE FOUND HERE IS VERY SIMILAR TO MY CURRENT CODING PRACTICES.
Templeting I use it to mean a practice that dictates full separation of concerns into their own items, grouping them if you will. Code meant for object Foo, should not be mixed with code for object bar, neither should technologies like html, css, and JavaScript as commonly done with ServiceNow Notification scripts. Templeting ideology introduces highly scale-able and maintainable code. Because SNow is a suite of products detailing business processes, just about every piece of code should be easily accessible to each other. Corralled objects with their own look/feel/functionality don't necessarily have a place in integrated business processes.
Code should scale upwards, sideways, shrink and be refactored with minimal effort and impact. As an example, the amount of time required for a face-lift or to port a desktop angular app to mobile specific app should be trivial. It should only require a new UI and connecting the UI to existing Business Layers. If that is not possible, then code isn't scale-able.
In this article the concept of Database Access Layer (DAL) and Business Access Layer (BAL) will be skimmed over as an example of division of concerns/templating. There will be little explanations other than comments within the code itself.
Addressed will be four artifacts:
- A Database Layer as a Script Include BaseDAL
- ideally, this file wouldn't be called directly from a BAL, rather called from other DALs. In this example, it will be called directly from BAL.
- A Business Logic Layer as a Script Include IncidentBAL
- Business Uses Cases should all go into some form of BAL. They should not be spread out in buttons, ui policies, business rules, Action Scripts, AngularJS widgets, Notification Scripts, etc.
- A Messages Data Dictionary BaseDALMessage
- this can be a loaded from sys_messages as a whole, or in parts, or be created within a script include without the use of messages/properties
- A DAL Options data dictionary IncidenOption
- Location that identifies available functionality to DALs. Outside code should not define their own behavior, rather use what's identified within the configuration of an options object.
1. BaseDAL (Database Access Layer)
This file is a wrapper to GlideRecord, eliminating the need to tightly couple GlideRecords (a database functionality) with Business Logic. This will replace writing GlideRecords directly in Business Logic, and instead be delegated to DALs.
var BaseDAL = {
developerMessage: BaseDALMessages.REQUIRED_TABLE_AND_PAYLOAD,
/**
* @description
* Crude and final basic check
*
* @param {Object} options
* var options = {
* table: 'cmdb_ci',
* encodedQuery: 'sys_class_name=cmdb_ci_computer',
* behaviors: {
* setLimit: 10000,
* orderBy: 'name'
* }
* };
*
* @param {Object} payload
* key/value pairs to be "truthied"
*/
successfullyChecked: function (options, payload) {
if (!options.hasOwnProperty('table') || !payload) {
return false;
}
return true;
},
/**
* @description
* Initialize GlideRecord with behaviors
*/
initGlideRecord: function (options) {
var record = new GlideRecord(options.table);
/**
* sets GlideRecord behaviors such as
* setWorkflow, autSysFields, etc
*/
if (options.behaviors) {
var behaviors = options.behaviors
var behavior = null;
for (behavior in behaviors) {
if (typeof record[behavior] === 'function') {
if (behaviors[behavior] != null) {
record[behavior](behaviors[behavior]);
} else {
record[behavior]();
}
}
}
}
return record;
},
/**
* @description
* Inserts any record into DataBase
*
* @param {Object} options
* key/vaue pair description for GlideRecord
* var options = {
* table: 'cmdb_ci',
* encodedQuery: 'sys_class_name=cmdb_ci_computer',
* behaviors: {
* setLimit: 10000,
* orderBy: 'name'
* }
* };
*
* @param {Object} payload
* key/value pairs to set in record
* var payload = {
* name: 'My Name',
* sys_class_name: 'cmdb_ci_computer'
* }
*
* @return {sys_id} sys_id of record inserted
*/
insert: function (options, payload) {
if (!BaseDAL.successfullyChecked(options, payload)) {
return BaseDAL.developerMessage;
}
var record = BaseDAL.initGlideRecord(options, behavior);
BaseDAL.handlePayload(record, payload);
var id = record.insert();
return id;
},
/**
* @description
* Updates a single record in the database
*
* @param {Object} options
* key/vaue pair description for GlideRecord
* var options = {
* table: 'cmdb_ci',
* encodedQuery: 'sys_class_name=cmdb_ci_computer',
* behaviors: {
* setLimit: 10000,
* orderBy: 'name'
* }
* };
*
* @param {Object} payload
* key/value pairs to set in record
* var payload = {
* name: 'My Name',
* sys_class_name: 'cmdb_ci_computer'
* }
*
* @return {sys_id} sys_id of record inserted
*/
update: function (options, payload) {
if (!BaseDAL.successfullyChecked(options, payload)) {
return BaseDAL.developerMessage;
}
var record = BaseDAL.query(options);
BaseDAL.handlePayload(record, payload);
var id = record.update();
return id;
},
/**
* @description
* Crude and final basic check
*
* @param {Object} options
* key/vaue pair description for GlideRecord
* var options = {
* table: 'cmdb_ci',
* encodedQuery: 'sys_class_name=cmdb_ci_computer',
* behaviors: {
* setLimit: 10000,
* orderBy: 'name'
* }
* };
*
* @param {Object} payload
* key/value pairs to set in record
* var payload = {
* name: 'My Name',
* sys_class_name: 'cmdb_ci_computer'
* }
*
* @return {sys_id} sys_id of record inserted
*/
updateMultiple: function (options, payload) {
if (!BaseDAL.successfullyChecked(options, payload)) {
return BaseDAL.developerMessage;
}
var record = BaseDAL.query(options);
BaseDAL.handlePayloadValues(records, payload);
record.updateMultiple();
},
/**
* @description
* queries database
*
* @param {Object} options
* key/value pair to be used to build query
* options Object format
* {
* table: 'incident',
* encodedQuery: 'active=true'
* }
* @returns {GlideRecord}
* query results
*
* @example
* var options = {
* table: 'change_request',
* sys_id: UID
* }
*
* @example
* var options = {
* table: 'cmdb_ci',
* encodedQuery: 'sys_class_name=cmdb_ci_computer',
* behaviors: {
* setLimit: 10000,
* orderBy: 'name'
* }
* };
*
* var cmdb_ci_computers = BaseDAL.query(options, behaviors);
*/
query: function (options) {
if (!options.table) {
return BaseDAL.developerMessage;
}
var record = BaseDAL.initGlideRecord(options);
if (options.sys_id) {
record.get(options.sys_id);
return record;
}
if (options.encodedQuery) {
record.addEncodedQuery(options.encodedQuery);
}
record.query();
return null;
},
/**
* @description
* sets record value using strategy record[fieldname] = value
*
* @param {GlideRecord} record
* Any GlideRecord type
*
* @param {Object} payload
* key/value pairs to set in record
*
*/
handlePayload: function (record, payload) {
for (var key in payload) {
if (record.hasOwnProperty(key)) {
record[key] = payload[key]; //set record values
}
}
},
/**
* @description
* sets record value using strategy record.setValue(fieldName, fieldValue)
*
* @param {GlideRecord} record
* Any GlideRecord type with field values to set
*
* @param {Object} payload
* key/value pairs to set in record
*
*/
handlePayloadValues: function (record, payload) {
for (var key in payload) {
record.setValue(key, payload[key]);
}
}
};
2. IncidentBAL (Business Logic)
This file will perform Business Logic, calling BaseDAL to perform CRUD operations. It uses IncidentOption for available operations. There will never be rouge code. Any code wishing to communicate with an Incident, must come through some form of IncidentBAL.
/**
* @description
* Business Layer for incident related behavior
*/
var IncidentBAL = {
/**
* @description
* Container for functionality available to insert
*/
insert: {
/**
* @description
* Insert basic incident
*
* @description {object} payload
* key/value pair fields to set in an incident
*/
basic: function (payload) {
var incidentPayload = handlePayload(payload, IncidentOption.insert.basic.payload);
var options = IncidentOptions.insert.basic.options;
var id = BaseDAL.insert(options, incidentPayload);
return id;
}
},
/**
* @description
* Container for update related functionality
*/
update: {
/**
* @description
* Updates assingment group
*
* @param {sys_id} from_sys_id
* sys_user_group.sys_id to change assignment group
*
* @param {sys_id} to_sys_id
* sys_user_group.sys_id to change assignment group to
*
*/
assignmentGroupFromTo: function (from_sys_id, to_sys_id) {
var options = IncidentOption.update.assignmentGroupFromTo.options;
var payload = IncidentOption.update.assignmentGroupFromTo.payload;
//add assignment group to encodedQuery
options.encodedQuery.replace('{0}', from);
payload = IncidentBAL.handlePayload({assignment_group: from_sys_id}, payload )
BaseDAL.updateMultiple(options, payload);
}
},
/**
* @description
* adds only those values found in both objects to a targetObject
*
* @param {Object} sourcePayload
* key/value pair to be used for source values
*
* @param {Object} targetPayload
* key/value pair to be set by from sourcePayload
*
* @returns {Object}
* targetPayload populated with values from
*
*/
handlePayload: function( sourcePayload, targetPayload ) {
for (var key in sourcePayload) {
//only add those values indicated in targetPayload
if (targetPayload.hasOwnProperty(key)) {
targetPayload[key] = sourcePayload[key]
}
}
return targtePayload;
}
}
3. IncidentOption (Data Dictionary with operations, and structure expected by BaseDAL)
The goal of this file is to become a configurable item (sys_message or sys_properties) that, instead of creating more GlideRecords, adding them to IncidentOption would trigger a corresponding function in a DAL. In an advance implementation of this approach, DALs dynamic are dynamically created with info from ItemsOption-like objects, allowing the programmer to concentrate solely on Business Logic.
/**
* @description
* Basic Options Data Dictionary for Incident
*/
var IncidentOption = {
table: 'incident',
GLIDE_MAX_LIMIT: 10000, //@TODO: grab from system default settings
GLIDE_USER_MAX_LIMIT: 50, //@TODO: grab from system default settings
/**
* @description
* container for all queries options available from Incident
*/
query: {
/**
* @description
* Option to retrieve "all" incidents
*/
all: {
table: IncidentOption.table,
behaviors: {
setLimit: IncidentOption.GLIDE_MAX_LIMIT
}
},
/**
* @description
* Option to retrieve all active incidents
*/
active: {
table: IncidentOption.table,
encodedQuery: 'active=true',
behaviors: {
setLimit: IncidentOption.GLIDE_MAX_LIMIT
}
},
/**
* @description
* Option available to retrieve all closed incidents
*/
closed: {
table: IncidentOption.table,
encodedQuery: 'active=false',
behaviors: {
setLimit: IncidentOption.GLIDE_MAX_LIMIT
}
},
/**
* @description
* Option available to retrieve opened tickets requested by a user
*/
openedRequestedBy: {
table: IncidentOption.table,
encodedQuery: 'active=true^requested_by={0}',
behaviors: {
setLimit: IncidentOption.GLIDE_USER_MAX_LIMIT
}
}
},
/**
* @description
* container for all insert options available for Incident
*/
insert: {
/**
* @description
* Options and payload available to insert a basic incident
*/
basic: {
payload: {
requested_by: null,
short_description: null,
description: null,
state: IncidentState.NEW,
},
options: {
table: IncidentOption.table
},
},
/**
* @description
* Options and payload available to insert a Major Incient incident
*/
mi: {
payload: {
requested_by: null,
short_description: null,
description: null,
major_incident: null,
state: null,
assignment_group: null
},
options: {
table: IncidentOption.table
},
}
},
/**
* @description
* container for all update options available from Incident
*/
update: {
/**
* @description
* Reuse options from insert
*/
basic: IncidentOption.insert.basic,
/**
* @description
* options and payload available to update a major incident
*/
mi: {
payload: {
assigned_to: null,
state: null
},
options: {
}
},
/**
* @description
* Options and payload available when updating a group to another
*
*/
assignmentGroupFromTo: {
payload: {
assigned_to: null
},
options: {
table: IncidentOption.table,
encodedQuery: 'assignment_group={0}',
behaviors: {
setWorkflow: false
}
}
}
}
}
4. BaseDALMessage (Message container for DAL Messages)
There should seldom be, if at all, direct calls to gs.getMessage or gs.getProperty outside of Data Dictionaries. The basic theory is that there should be extremely few places in a code base that contain their own messages (be it a hard-coded string, or "ENUM" or SN getProperty/Message. By placing all messages in a strategic location, it will always be trivial to update any amount of messages, especially so if the messages are a configuration file editable by an Admin. Every other piece of code then reads from the properties file.
var BaseDALMessage = {
REQUIRED_TABLE_AND_PAYLOAD: gs.getMessage('Table name and payload are mandatory')
}
- 5,126 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi Jibarican
without doubt this is one of the best articles in the community but this may be applicable for native programming language applications where DB connections are made/closed very carefully to reduce the overall stress on the system in case of servicenow these things are carefully handled by the platform itself so do we really need to adopt this architecture, has you or somebody tested if Gliderecortd querries are writtein in individual BRs rather than centralised script includes , how much impact it will be on the servers, I was wondering what else can be the benefits in using this architecture.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thank you for the kind words Avinash.
This code design strategy is used at my current employer. I have not done any stress testing. All I have is anecdotal stories that in the scope of proof are irrelevant. In time, I will try to provide those.
What do you mean by Handled by the platform? Do you mean by the Rhino Engine? As it is still at this level of execution time that my code works... purely during engine work, and not in charge of IO.
I presume SNow does abide by Rhino Version 1.7 R5 where scripts should execute precisely as expected. If that is the case, then it might be logical to assume that smaller, and well structure code leads to less execution times. Then a smaller stack, with lexical scope pointing to fewer objects will complete a cycle more efficiently than would multiple embedded Glides in business rules, looking about a complex lexical scope. This goes in par with SNow suggesting placing code inside Script Includes, this can't possibly exclude GlideRecords.
Back to structure... in regards to SOLID it is independent of architecture or platform as it doesnt alter platform behavior in any shape, other than time required for the Rhino Engine to complete its cycle.
When you say Architecture, I understand it to mean event driven, serverless, etc, how technologies work together to supply a business need; otherwise IO might not be fitting as the platform is still in charge of it and, I'm not creating an IO implementation, rather wrapping the GlideRecord to create a more maintainable code base. Though I do understand that badly writen code anywhere leads to more problems than server load.
Knowing that JavaScript is not altering IO leaves us to explore if the Rhino engine does behave as ServiceNow advertises it should. compilation, optimazations, transpilation, scope and execution. Execution is triggered, creating a stack from which to work, context and security identified then push and pop until the stack is finished.
This means that a stack is created by the order of execution established by the platform. Each one of my DAL Glides will be executed exactly at the time expected by the platform, regardless of location. All I've done is reduce the ticks it takes to get there.
I use this structure for everything. delivering Data Objects to the UI, web services, widgets, ui macros, etc.
Because I program for human consumption first and foremost, and dont address optimazations until the need arises, i am afforded the ability to use techniques like SOLID coupled to JavaScript industry patterns to create maintainable code.
Because of that, I know that if I ever encounter server load problems, shifting to an event driven architecture will allow me to be even faster in responding to user generated actions; such as loading a page that runs 1K after business rules creating various objects that use GlideRecords. The shift of CRUD to events would be impossible through embedded GlideRecords. So by shifting CRUD operations into the eventQueue, IO is spread about threads, drastically decreasing latency: multithreading ala SNow.
I have not have had to do this as of yet, mainly staying within the async path when asked to fix unpeformant code.
There is a plethora of industry solutions not implemented in SNow, it's as if being in the platform changed the physics of computing. I truly opine that if SNow were to have a JavaScript code evangelist in staff, current practices would cease. Their structure makes it difficult to scale and maintain. Just like SalesForce has followed the programming world into industry standard code, so will ServiceNow.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi Jibarican,
Great Article thanks for that. Inspired me to dig a bit deeper into possible architectures for ServiceNow and in general. But there is one point not pretty clear to me.
You wrote that the BaseDAL is rather called from other DALs than directly from a BAL. So, why create other DALs when there is one generalized BaseDAL which wraps the GlideRecord and which I can use for multiple BALs? Why not call directly?
Do you mean it is beeter to create multiple DALs (that are copies of a BaseDAL) for different use cases/reqirements to reduce risk in case of A broken BaseDAL? (IncidentBAL ---calls---> IncidentDAL (copy of BaseDAL) ---crud ---> DB)
If it is the other way around, why should, for instance, an IncidentDAL call the BaseDAL or a ProblemDAL call the BaseDAL rather than calling the BaseDAL from ProblemBAL or IncidentBAL? Wouldn't this make developing and bug fixing even more complicated?
This would mean that ProblemBAL ---calls---> ProblemDAL ---calls---> BaseDAL ---crud---> DB.
Regards
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi Ethan,
Happy to learn you enjoyed the post.
The approach is one of many that can be used to separate code by concerns.
The ideology states that code should be separated into layers of abstraction, each addressing one and only one concern. The more specific the function, the easier it is to find bugs, refactor, maintain, lose coupling, etc. Always choose specific over comprehensive functions. The BAL/DAL approach is part of that ideology. At a higher level, it separates Business Logic and Database Logic.
Another method to separate concerns is the Factory pattern, commonly used by a functional programmers to achieve the same (this is the pattern I also prefer).
This is what a functional approach might look.
function inserInto(tableName) {
var insert = makeInsert(tableName); //factory that wraps GlideRecord
var publicAPI = {
values: insert;
};
return publicAPI;
}
//used as
insertInto(tableName).values(values);
This is the approach you alluded to in your response when asking if by-passing the more process specific DAL (ProblemDAL, or IncidentDAL) could be done. Using a factory cleans up the code, keeps it very specific to one purpose, and over comes the tight coupling of calling the BaseDAL directly. And it's darn easy to read. Compare that to an Embedded GlideRecord and one wonders why ever directly use a GlideRecord.
In regards to the BaseDAL it is simply specialized layer, limited to CRUD that if embedded into a BAL, forces all the logic needed to execute the generic DAL to be built in each function that should only contain Business Processes.
I normally think like this.
Single line entry point. Such as in a business rule. That entry point is a utility function that is composed of other functions. Each function inside the composed utility function does a very specific job, one of which is a call to a function inside a DAL or makeDAL function.
The goal is always this:
Easy to read
All functions do one and only one thing
Entry point for processes are a single line of code pointing to a composition of functions
Categorize common functionality into a layer of abstraction
The entry point for one process is always found in exactly one place
This leads to separation of concerns, enhance readability, clear stack trails for errors, clean structure, less code surface, independent unit testing that precisely isolates error location, and an array of maintenance gains.
By creating comprehensive functions such as dictated by building requirements for a BaseDAL inside a business function,complexity increases, code grows tightly coupled, duplication is introduced by need, thereby growing increasingly brittle as future functionality is adopted.
Consider this example, a BusinessDAL function does process X, inside of which functionality to call to the BaseDAL to remove the user's membership from all his groups is built. It would be impossible to reuse that specific BaseDAL's functionality embedded inside the business process by another process, eventually forcing code duplication by need when a new process that also needs to remove memberships comes along. Were the revokeMembership separated into a SysUserGroupDAL.revokeMemberShip(), its use could be leveraged from all business process and handled from inside exactly one place in the entire code base (one and only one entry point). Embedding it in a business dal would serve the same purpose as using GlideRecord inside the BAL, except that we've added a layer of abstraction. The complexity of GlideRecord was removed yet, the same issue of tight coupling and impossible re-usability remain.
This is what a process, from entry point to completion should look like going through layers of abstraction. Paramount to the process is at-a-glance interpretation.
Entry Point, such as inside a Business Rule
LeaversBAL.revokeAllGroupMemberships(user);
LeaversBAL ScriptInclude
LeaversBAL = {
revokeAllGroupMemberships: function revokeAllGroupMemberships(user) {
ProvisionAudit.anounceProvisionDismissal(user); //this is some process that hooks into a proisions web services.
SysUserGRMemberDAL.revokeAllGroupMembership(user.sys_id);
}
}
SysUserGrMemberDAL Script Include
var SysUserGrMemberDAL = {
revokeAllGroupMemberships: function revokeAllGroupMemberships(userSysid) {
var options = LeaversDALOptions.REVOKE_ALL_GROUP_MEMBERSHIP;
var revokeOptions = makeBaseOptions(options, userSysId);
BaseDAL.deleteMultiple(revokeOptions);
}
//LeaversDALOptions Script Include
var LeaversDALOptions = {
REVOKE_ALL_GROUP_MEMBERSHIP: {
table: 'sys_user_gr_member',
encodedQuery: 'user=${sys_id}'
},
HUMAN_RESOURCES_GROUPS: {
table: 'sys_user_group',
encodedQuery: 'sys_idIN${groupIds}
}
}
The noise added by directly embedding GlideRecords and line after line of assignments, etc transformed into single-unit-of-work functions that are all now... reusable.
Hope that addresses some of your questions. 🙂
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
We user a very similar approach at my current employer.
Such as:
var OneOfOurRequestedItemsWidgetScriptInclude = {
widgetData: function widgetData () {
//this guy calls a DB Layer to extract widget data as a JSON object
var items =
RequestedItemSoftwareModel.widgetItems(
RequestedItemSoftwareModelProperty.Config.VARIABLES,
RequestedItemSoftwareModelProperty.Config.FIELDS
);
var widgetDataObject = makeCloneWithJSON(RequestedItemSoftwareModelProperty.data);
theWidgetData.row_count = items.length;
return {
data: widgetDataObject,
list: items
}
};
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
A great Thank You for this enlightenment and your effort to explain it to me!. I was misunderstanding the concept and I totally get the point of seperation of concerns also the advantage with different layers of abstraction. I could imagine, that, once established and committed for every developer, doing solution proposals becomes easified and new requirements fit perfectly to the existing architecture. Also the work or even the need for a Solution Architect becomes minimized. Definitely a concept I will keep in mind. Thanks again for your effort and your explanation!
Best Regards
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi Jibarican-
I think the messaging on this '… techniques like SOLID coupled to JavaScript industry patterns to create maintainable code.' was a bit lost.
Were you referring to SOLID Principles?
NOTE (for those unfamiliar w/ SOLID):
The SOLID principles were first authored by Robert C. Martin in his 2000 paper, Design Principles and Design Patterns. These design tenets were later expanded by Michael Feathers, who introduced the SOLID acronym. Since then, these 5 principles have upended the world of object-oriented programming, changing the way that most developers write software.
How does SOLID help us write better code? Simply put, Martin’s and Feathers’ design principles encourage us to create more maintainable, understandable, and flexible software. Consequently, as our applications grow in size, we can reduce their complexity and save ourselves a lot of headaches in the future!
The following 5 concepts make up our SOLID principles:
- Single Responsibility - a class should only have one responsibility. Furthermore, it should only have one reason to change.
- Open/Closed - classes should be open for extension, but closed for modification. In doing so, we stop ourselves from modifying existing code and causing potential new bugs in an otherwise happy application.
- Liskov Substitution - if classA is a subtype of class B, then we should be able to replace B with A without disrupting the behavior of our program.
- Interface Segregation - larger interfaces should be split into smaller ones. By doing so, we can ensure that implementing classes only need to be concerned about the methods that are of interest to them.
- Dependency Inversion - refers to the decoupling of software modules. Instead of high-level modules depending on low-level modules, both will depend on abstractions.
-Brit
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thanks Brit for the comment and SOLID write up!
Yes, this was an OO topic touching on SOLID principles. It is also the result of technical interviews where lead architects explained SOLID but, when asked to solution, the result didn't incorporate the principles.
I do struggle presenting concepts geared to folks that definitions might not be enough to readily incorporate into code. So the examples are that attempt.
p.s.
I still owe you code, been quite up to my neck in work.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Sharing this live demo I created that covers OOP with Service Oriented Architecture.
Object-Oriented Principles in ServiceNow | A Guide for Architects & Developers
A Guide for Architects and Developers Using Dependency Injection and Object-Oriented Design
Service Bus Design Pattern in ServiceNow:Reusable,Scalable Architecture for Developers & Architects
Mastering Queueing Techniques for Architects that Support Asynchronous Calls in ServiceNow
Service-Oriented Architecture in ServiceNow: Integrating Two Services Using Dependency Injection
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi @Javier Arroyo ,
I made a short video explaining this article based on my understanding. I hope the content is fine, and this helps promote your article 🙂