ServiceNow - Clean Architecture (a short rule set) - ServiceNow Community
Fabian Kunzke
Kilo Sage
Kilo Sage

Clean Architecture with ServiceNow

Introduction

The following set of rules is aimed to provide a maintainable, extendable and understandable architecture for any solution within service now. They are kept as short as possible to allow for a one page document. Please share your thoughts and feedback, as well as your critique.

1 Script Includes are the brain

Your brain consists of multiple areas, each with distinct functions. Same should be true for your Script includes. Differentiate them. Distinguish between data manipulation and data interaction. Your brain does do it all, but not all in one place. Make code true to yourself, learn from nature. It has been working well for ages.

2 Business Rules are the passthrough

Remember the human part? Business Rules are the spinal cord to the brain. They don’t think. They don’t do. For a matter of fact, they just don’t.

Therefore, make Script Includes the brain to your Business Rules and use the Business Rules to rapidly pass information to the brain.

3 Client scripts are for displaying data

What do they display? Data! Where do they get it from? Queries! Do they think? No! Do they do? No! But what are they there for? To display data.

That’s it.

4 Workflows are a succession of Business Rules

Follow the same principles! Workflows are “just” the spinal cord as well. They “just” don’t. Make Script Includes the brain to your Workflows.

But what about macros, widgets and pages?

Remember Client Scripts? Yeah, they are what Workflows are to Business Rules.

5 What’s with UI Actions?

Think of them as doorbells. You can hit a door bell. Does the door open cause of the doorbell? Not necessarily. Will the door open without it? Certainly not.

Use them as doorbells. Trigger actions without doing anything. Nothing to think about either.

6 Object Orientation – It’s a charm

Beauty of the cloud? You are not hosting the database. Loads of tables and relations take up too much space? None of your business. Utilize object orientation.

7 Object Orientation without Object Calisthenics

Try it. It’s like Buddhism but without the use of mediation. It works fine, but it is inefficient. And usually it prevents from achieving the cool stuff.

8 Draw a painting – visualize structures

If you cannot draw your architecture, don’t bother trying to explain it. Can’t explain your architecture? Well, overly complicated things for 500, please.

9 Read code – a lot

Things to learn from your past code and the code of other people:

  • Better ways to do it
  • How not to do it

Both important to improve. On a side note: Iterate your architecture. Rework it. The best way to improve is to learn from the past. Best way to do this is to solve issues from past projects.

10 It’s art – opinion is subjective and there is no definite best solution

Two key points here:

  1. Don’t let this prevent you from searching for the best possible solution.
  2. Even art can be objectively shit.
Comments
kostya2
Kilo Explorer

Hi Fabian,

interessting thoughts. But I cannot agree at leat on 1) 2) 4) 

What are you expiriences and references to Docs so you suggest not to "do" within Script Includes and Workflows? And not to think within Business Rules?

Cheers

Kostya
Giga Guru

Hi Fabian,

interessting thoughts. But I cannot agree at leat on 1) 2) 4) completely 

What are you expiriences and references to Docs so you suggest not to "do" within Script Includes and Workflows? And not to think within Business Rules?

Cheers

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

Basically this revolves around one main thought: if searching for something, where would you look at? If the actions performed are not strictly seperated, you will have to look into multiple points.

 

For business rules this means, that (for example) instead of calling a script include, which creates a related record, you should call a script include which returns the values (or even a glide record object). Then create the record within the business rule. Therefore the logic (getting the values) and the action (creating the record) is now seperated. If you want to change the values in the record created, look into the script include. You have troubles with the creation of the record, look into the business rule.

 

The same accounts for workflows. But now you can acutally reuse the logic you have put into the script include. From my expirences the rules 1)->5) result in a much more reusable library of script includes, not only cutting down on the amount of code but also on the maintainability. In addition workflows and business rules become a lot more readable, since the logic is contained within a function call.

 

But most importantly: The reusablity of script includes increases if database interactions (such as inserts and updates) are cut. Therefore there should be no difference in use between updating a referenced record with certain values and creating a new one, since in both cases the values (therefore the data transformation) is handeled within the script include.

 

Greetings

Fabian

Kostya
Giga Guru

Totally agree to capsulate the reusable code into Script Includes. But you cannot avoid data manupilation within Script Inculdes.

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

I guess our opinions here differ. I am certain that it is not only possible, but also the best way to go about it. If you can provide me with an example or any argument why it is not possible to avoid data manipulation within script includes, please provide me with it.

 

Thanks for your thoughts.

 

Greetings

Fabian

dpacheco
Giga Expert

...and Rule 11, which is:

 

There are always exceptions to Rules 1-11.

 

Thanks for the article.

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

 

No. I have not yet found a reason for exceptions, nor do i intend to add rule 11 as stated. If you have expirenced exceptions, please provide me with them. Up till now i have only noticed my own lazyness when sticking to that ruleset.

 

Greetings

Fabian

Uncle Rob
Kilo Patron

#TodayILearned ... new community's language filter is going to be way more awesome!

Fabian Kunzke
Kilo Sage
Kilo Sage

I am intrigued by that #outofnowhere comment. What's up with that language filter?

Uncle Rob
Kilo Patron

What I mean is the old community was FAR more restrictive on words you can say.
But sometimes you just have to say it. 😉
Otherwise, how will shit change?

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

It has been a week now and i would like to thank for the feedback i have gotten so far. I also would like to address a few questions, i have been asked since.

 

1) Do you think this is possible to stick to these rules without exceptions?

So far i have not encountered any exceptions to this rule. I have been sticking to it while developing several applications, including interfaces, webservices and workflow based contentmanagement. The main exception i have been fighting against is my own lazyness. At times sticking to the rules is (honestly) seemingly pointless. So far these applications contained loads of reuseable code as well as a easy to read structure.

 

2) Do you at all times stick to these rules?

As stated in the first question, it is hard at some times. Imagine just implementing a small concept to create timeslots for work logged within a ticket. It would be easy to just put it into a business rule. In this case though, a script include is required, returning a glide record object which is then inserted via the business rule. It is a few minutes extra work, but i have found it to be well worth the time.

 

3) Why aren't those best practices?

Because they are not best practices. See, best practices work on their own. For example there is a best practice to not use abbrevations. This is a stand alone best practice. It works for code no matter what the context is. Now look at the above ruleset. Pick one point and take it as a stand alone best practice. For point 8-10 this might work. Even 6 and 7. But the most important ones 1->5 don't quite cut it, if taken seperate. Also, sticking to each rule, but one will not work (imo). Therefore this would be one large best practice containing 10 steps. Which is exactly why it's a ruleset.

 

4) Aren't they contradictory to the docs?

Yes, indeed they are. This was brought up in a discussion with a coworker stating, that rule 1 does not match the best practices mentioned within the docs. Service Now uses script includes for data transformation. Alot. I do not like this. I could go on and on why i dislike it, but it would probably take up more than the ruleset itself. So i might put that into an article itself.

 

 

I will keep this up to date depending on the feedback and questions. So far thanks for the responses.


Greetings

Fabian

John Caruso
Kilo Guru

I'd like to hear more about the "Why?" behind your Rule 1 and 2.

 

Also, where does import set / transform fit within your rules?

 

Thanks for your thought-provoking contribution.

 

It makes me think of Command Query Separation / CQRS approaches - and just the general practice of separation of concerns.

 

 

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

 

Thanks for the feedback. I have worked on a broader blog for each of the points, but i am unable to finish them at the moment since it is pretty busy around here.

 

To give you an example for both of your querstions:

 

I do not like the implementation of transform maps and import sets choosen by service now. The idea behind it is good, but it is too restricive, yet overcomplicated (imo).

I have had the requirement to copy records/transfer records from one table to another. This is exactly what a transform map would do. Since i dislike the oob one (and quite honestly do not know an easy way to manipulate it in a way allowing for internal table transformation) i just wrote an application myself. It is similar in the way, that it contains a table to map from one source table field to one target table field. Thats it.

The logic of the whole transform mapping lies within a roughly 230 line script include. It basically takes the current record and the target table as inputs and returns a record of the target table. This record then needs to be .update(); -ed.

 

Now why rule 1&2:

The script include does not care if you want to update or insert a target record. It just returns the record. Whats done with it, is handeled outside of the script include. Therefore you can use the same bit of code no matter the action beeing performed. You want to transform it in a way to have a history? Don't worry, just alway .insert(); Do you just want to debug and don't do anything with the returned record? Fine, just don't .update()/.insert();

 

Reusability aside. The main advantage is readability. See, when i look at a business rule and it says "incident.update()" i know this thing updates (or potentially inserts) an record (which by the naming i can assume is an incident). Whereas "Transformer.transform(current, 'incident')" leaves a lot of guesswork. Even worse in cases where it might not be ovious that records are created jsut based on the script includes naming.

 

So to sum it up: Where do is see transforms and data imports? They are hard to judge. But it is really easy to adjust them, once done within the boundaries of the ruleset. It will take time to get use to it (and by it i mean the way of implementing applications). But when trouble arrives it is a lot easier to resolve issues.

Rule 1&2 aswell as any other rule aim to solve one issue: Readablility! My personal goal is to write code that does not need to be commented (which i have not yet archieved). Every other advantage like reuseability just follows naturally.

 

I hope i could have answered some questions.

 

Greetings
Fabian

John Caruso
Kilo Guru

@fabiankunzke , (not sure how to tag someone correctly - and there is apparently no preview before posting - so I'll try this... nope doesn't work - let me know how I can tag you like you did me)

 

Fabian,

 

Definitely agree that for some cases import set / transform can be more complicated than simple scripting of the transform. 

 

I also see the value of letting the Script Include have the concern of doing the work of transforming from a source record to a target record, without doing an insert or update, thus making the transformation logic reusable in multiple contexts. 

 

If then your Business Rules has the concern of actually doing the insert or update for the transformed record, what has the concern for processing the entire batch of records (i.e., what would be the import set using OOB techniques)?

 

I can see how for your table to table transform you could create a insert/update BR on the source table to immediately trigger the transform (relying on SI) and insert/update the target table.

 

But what about a more batch-oriented process like importing from some external data source (for example a flat file or JDBC or REST) where you get a whole batch of records perhaps once a day that needs to be transformed to a target table?

 

Or a scenario where some type of rollup needs to be done and a simple BR on source to target would not work - for example, your source table represents a User phone number feed in the format "user_id, phone, phone_type" (where phone_type could be WORK, HOME, CELL, etc), and these multiple phone_type records need to rolled up into a single User record with corresponding fields (i.e., work_phone, cell_phone, etc.)?

 

Just curious how you would implement a solution for these scenarios and still follow your rules?

 

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

 

The easiest way to tag somone is by hitting "Comment" below the answer/comment. I actually do not know if your tag worked, since i am getting notified regardless (since this is an article by me).

 

Actually what you are talking/suggesting is exactly how i have set up a "framework" (it's not really a framework, but i can't find a better naming) to connect generic ReadMultiple SOAP APIs for data imports (just for the record: i do not prefer to use pulls for data-imports). Essentially the framework allows to create a table based on the XML-Response from a SOAP-ReadMultiple call. On this table a business rule is present to trigger the transform and insert the new record (or update the existing one).

To get the Data into the table there is a script include translating the XML into a JSON array with {Name: Value}, which is then handed over to the transformer, which in return will return an array of records. Therefore, the scheduled data import will call the ReadMultiple SOAP, then call the script include translating it into a JSON array, followed by calling the transformer (actually a subpart of the transformer, but that would require too much technical details), and finally: inserting/updating the records in the import table (which is basically comparable with the importSet logic). Advantage of this is, that ALL information handed over by the API called is now stored in a table. Therefore, the import action and the transformation is seperated.

Now to answer your question regarding the Import of data requiring multiple related entries:

I would (personally) follow rule 4 here. This allows to trigger multiple transforms within a workflow creating all needed records step by step. Depending on how complex this gets this will impact performance though. But it allows to have great maintainability. (NOTE: Again, each step in the workflow would be comparable to the single business rule noted above. The logic would still be calling the Script Include)

 

To give some perspective on how good this actually works: With this implementation i was able to connect 4 ReadMultiple APIs with their mapping done within the span of less than 2 days. Furthermore, additional mappings or different transformations could be done within a few minutes.

This example is not used to say "hey look, i am a magician." Instead it shows, that by following the ruleset expandable and maintainable structures can be implemented. I am not saying there is no other way and i am not saying that by "just following the rules" it will magically turn shit into gold. What i am saying is it works better than anything else i have worked with (which includes the SNOW best practices).

 

Greetings

Fabian

John Caruso
Kilo Guru

Thanks again for sharing. This type of broad architectural design discussion really interests me. Please let me know my understanding of your proposed solution is correct: 

  1. The import "framework" makes the outbound SOAP call, receives the XML response, then inserts it into Table A.
  2. Table A has an insert/update BR, which begins the transform process for that XML payload (which could ultimately include multiple target records). The transform process relies on SIs to do the heavy lifting, returning some sort of object(s) back to the BR which could then do additional inserts/updates as needed.
  3. Those additional insert/updates could trigger subsequent workflows that handle things like data rollups.

So, what is eluding me is how is your "framework" allowed to insert that initial XML payload into Table A? If it were me implementing this solution, I would use one or more Script Includes to make the outbound web service call and insert the response. But that would violate Rule #1: Script Includes don't insert.

Fabian Kunzke
Kilo Sage
Kilo Sage

Good Morning,

 

Your assumption in general is correct. It is in fact how it works. The main point i would like to make is that this can be run asynchron. This means you would not be stuck with one huge job running actions for a year straight, but rather a lot of smaller actions which are allowed to be run in parallel.

 

And you are bringing up a great point, which is the to get the initial payload into the system.

 

At the moment this is the where i am actually violating one of my rules, since i am using a schedule script to do this (which is technically a script include). This scheduled job follows the rules of a business rules, meaning it calls for script includes and handels the actions. So thanks for bringing this up, i should rework this.

 

My solution would be to schedule a workflow for the import, which is proably even better, Because (compared to the scheduled job) it would allow for some sort of progress indication and a better vizualisation of what is actually happening. The workflow would be started by a scheduler (or even by a record created for a new interface run) and then import the data within said workflow. This would allow to use the same workflow for any kind of import (SOAP that is) since it can be parametrized pretty easyly. Also it could be possible to build a more generic importer with a bit more complex logic to accomodate for diffferent response-types of SOAPs (e.g. if the response it self is a XML document).

 

Even further the "in between" table could be cut. I do not prefer that, since it takes away the transparency it archives, but if runtime/database size is an issue it would be a possibility (although as i said i would not approve that).

 

I am relly glad you brought this up, since it so far flew under my radar. I actually like the idea of importing the data with a workflow instead of "just" a scheduled job.

 

Greetings

Fabian

John Caruso
Kilo Guru

So a workflow should have the concern of managing the import and transform process.

Then I assume it is okay (per the rules) to use a Run Script activity to do data access (CRUD) operations, but not business logic (which is handled by Script Includes).

Correct?

If I'm following correctly then the answer to my original question of "Why?" (for 1,2,4) is this effectively creates what more traditional (i.e., non-ServiceNow) application development would call a layered architecture - something that has numerous benefits that I'll not detail here.

Business Logic layer

  • Script Includes
  • Workflows - at least the high level process

Data Access layer

  • Business Rules
  • Workflows
    • Run Script activities (that only do data access and delegate business logic to Script Includes)
    • Custom Activities (")
    • Core Activities (creating Tasks, etc.)
Fabian Kunzke
Kilo Sage
Kilo Sage

Good Morning,

I am sorry for coming back to this this late. Indeed i think you are correct. Until now i was not aware of the term "layered architecture" (so thanks for that) but i see where it is coming from. Without diving too deep into the concept of this it seems to encapsule what points 1-> 4 are suppose to push forward.

Even though the concepts seem similar i would rather assosiate it with the "seperation of concerns". This goes hand in hand with the "layered architecture". Esentially the search for issues or expansions is simplified by seperating/dedicating actions into different "layers" (i will just stick to this term).

Thanks for your input. I have thought about writing a blog with more detail and integrating input into it, but i have not yet found the time to do this properly.

Greetings

Fabian

Javier Arroyo
Kilo Guru

Neat approach. I look at business rules as mediators, hence there should be zero work done in them except directing traffic. Everything about the architecture of business rules screams "I mediate". Which is why I wouldn't solution BRs with your approach. Instead I bootstrap them by loading a singleton to be used by all BRs. This approache doesnt require redundant IIF to provide proper scope, or prevent collisions.

My business rules then look like this

ChaneRequestBefore.massageSomeData().

Because the lexical context is fully aware of current, previous, etc, there is no need to implicitly pass them into the bootstrapped script include. Inside the script include there can be a module to either transfer control to the DAL (Incident.insertNewMI(payLoad))or execute business logic.

A script include is then no more than business logic.

Because I view BRa mediators, I use templeting to divide program logic between BALs and DALs. This, ofcourse, allows porting to Emacs6 rather trivial.

Passed from the BAL script include to the DAL script include aren't GlideRecords, rather POJSO. It is the duty of the DAL to set the values passed in the payload tothe correct object.

What this introduces is never ever having to write another GlideRecord, as well the ability to fully decouple all layers, something impossible to do when using Rule 1.

For every table there will be a few script includes. A database access layer to carryout crud operations, a business layer, an event script include, etc. What this means is that the DAL can be fully configurable, allowing the developer to write configuration that suddenly create any object, and a DAL operation to go with it.

 

In regards to Rule 1, outside of service now this would be considered mixing concern, and tightly coupling concerns. The BR, by virtue of being a mediator, should by no means perform crud operations. It makes refactoring code a pain in the rear, leave that in the DAL whereit belongs. Basically, think Java Hibernate.

With this approach code would read.

BR ChangeRequestAfterBAL.closeMyTasks().

SI var ChanceRequestAfterBAL= {

closeMyTasks: function() { //do some logic ChangeTaskBAL.closeChildren( current.sys_id); } 

}

ChangeTaskBAL would then call ChangeTaskDAL to load the task, and perform business requirements

 

. Every piece of code is then decoupled, reusable, and damn trivial to maintain.

Javier Arroyo
Kilo Guru
I'm not sure what is meant by Client Scripts are for displaying data but, Snow ui behavior should mimic standard practices of the web community. Load a library, and data dictionary to go with the precise functionality needed to make whatever state a record is in... work. This means that client scripts call function from the loaded lib, while using the data dictionary to function. The client script, aside an onload of not using libs, should be calls to a singleton (api) This includes UI Policies, and actions. These items should do no more than execute functionality. By doing so, a configuration item then controls toggling, compulsory, label changes etc. This avoids dev cycles for trivial behavior such as hiding this when that, etc. So while I dont understand what rule 3 means, as long as there is decoupling, along with decreased dev cycles, hence maintenance, I'm thumbs up. A client script should be no more than ChangeStateCliente[g_form.getValue("state")].reactToChangeStartDate(); The idea is to decrease page weight by reducing artifacts required to make the current state work,while providing a centralized place to hold business logic, and data structure.
Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

you are exactly right. I think some detail is lost by my extensive shortening of the rule (and the selfrestriction of keeping it one page long). I like the way you have explained it.

Thanks,

Fabian

Fabian Kunzke
Kilo Sage
Kilo Sage

Hello,

I have not yet come around to fully understand all of the words used by you (and i do not mean this in a disregarding way, i just don't understand erverything to the fullest). This is also what has kept me from responding for so long, ecentially not being 100% on everything you have written. Thanks for the input thought.

So here is my (quite entry level) understanding on your explanation:

Basically you have extended the BR functionality by a mediator functionality, reducing the interactive load between capsules (mainly BAL-> DAL) from full on GlideRecords to basic javascript objects. Your DAL then is focused on updating changes from those POJOs to the GR logic. And this is the point where i got lost.

To my understanding this would require your BRs to call a BAL capsule which then calls a DAL capsule (with call i mean hand over information)? The only reason i see for doing this would be the argument of the DAL logic getting quite complex. However i don't see that within the rest of your argument. And this is the part where it gets confusing to me.

Assuming the logic of DAL capsules is kept simple, why would one need a dedicated DAL instance.

Which is why i am assuming that my assumption is wrong. But then it confuses me even more. Why have the BAL call the DAL to load the task (or any DB-object)? Why not start of with the BR, let it call the BAL function. The have the BAL function return to the BR which then calls the DAL in a second step?

For me, here is the thing: If what i understood is correct (and again i currently assume that i missunderstood) you are using the BRs to call BAL function capsules which in THEMSELVES call DAL function capsules. This (at least from my understanding) does not at all decuple the line of logic. Instead of a mediator the BR just kicks of a bunch of interactions.

Let me try to rework your example:

BR closeMyTasks

var tasks = taskUtil.getAllChildTasks(current.sys_id);

for(var taskIndex in tasks)
{
  var task = tasks[taskIndex];
  task.status = 'closed'; //-> don't quote me on this, could be "10" or something else
  task.update();
}

This would be the code of the business rule. This will change 2 things:
1) Since the DAL logic is super simple (and (imo) should always be simple) it stays within the BR.
2) Instead of POJOs GlideRecord objects are used

Here is what i am taking away from this:
If (and only if) everything i understood is correct, you are investing a lot of effort to maneuver around the use of GlideRecord objects as they are meant to be used. I just do not see the point of this, as i am under the impression, that GlideRecords are meant to be used. And i would love to hear your point on this as i would like to understand where you are coming from. To me it seems as if there is much more to your understanding of this than i am currently capable of seeing.

Looking forwards for your response.

Kind regards

Fabian

Javier Arroyo
Kilo Guru

No worries, mate. I can't understand myself half the time.

And, I just lots 3 hours worth of response, code samples and all in both functional and OO style So, this time around I won't have code.

I haven’t done anything to the business rules other than making them entry point processes. A single line of code for each BR points to the functionality in a script include. The bootstrapped Singleton is a Script Include from which the single line BRs that follow will use (this is really unnecessary yet I use it depending on what is going to happen down stream)

Limiting the complexity of BRs is done with DRY and SOLID or Functional techniques in mind. Break everything down into isolated unites of work with similar functionality and bundled them into Script Includes. One doesn’t necessarily need to separate into BAL or DAL, however, if they aren’t used, some sort of Factory should be used to have single entry points for everyone to leverage.

I still ue GlideRecords but only though a wrapper. Pojos are used but not in replacement of GlideRecords. I still return a GlideRecord to business logic functionality as you have, with the difference that I don’t any code bleed outside Script Includes.

 

Your example reworked in a BR would be

ClosedTaskAuditor.doSomethingWithMyClosedChildren(current.sys_id);

 

Changed was the ambiguous taskUtil because I don’t know what is bundled inside a util, is it functions that don’t belong anywhere, is it entry point helpers, is it the God object?

Looping at the entry point, as done in the example, already increases comprehension times, One must interpret what it says, rather than understanding it. Where it reads “something else”, what if there where another 10 simple lines of code? The call site is not only cluttered, it’s impossible to decipher what happens when a ticket is in the closed sate. It will have to be analyzed just to know what something else meant. It’s also become impossible to scale it. A sound functionality that does something hot to closed tickets has been limited and placed in a BR. I would want to use .

To keep a code base as small as possible, logic inside BR is not the way. It, instead, leads to code duplication through isolation.

The reason not to have more than a Single Line of Code in a business rule is because a

1. BR is a mediator. It shouldn’t do any work, all it does is direct processes to their handler functions.

2. if a another process outside the BR execution context wants to leverage the code, itmust know to duplicate those two calls.

3. It’s logic can’t be reused.

4. It leads to code duplication and scalability by need.

It’s more scalable to bundle the entry point of the functionality, whether in a BAL with a DAL, or utility function (in functional terms not OO terms), than it is to separate the two at the entry point.

 

In a nutshell,

it's easier to read

TaskStageHandler.closeChildren(current);

than it is to read a simple

var tasks = taskUtil.getAllChildTasks(current.sys_id);

for(var taskIndex in tasks)
{
  var task = tasks[taskIndex];
  task.status = 'closed'; //-> don't quote me on this, could be "10" or something else
  task.update();
}

 

Fabian Kunzke
Kilo Sage
Kilo Sage

Good morning,

That certainly helped a lot. I was not that far of then. Thanks for the imput, sorry for your loss of time. Your approach actually makes a lot of sense. I have nothing to add to it, as i am convinced it is one of the best, if not the best, code structure i have come along. Although that means to rework or scrap the ruleset, i am nothing short but amazed by the idea behind your solution to enhanced code. I don't yet know how to encorporate something like this into the ruleset, but (and if that is okay for you) i will try. Thanks for sharing.

To answer some of your questions:
You are right, your example of code is easier to read. And yes, going back to more complex projects of my past i actually have code duplicated through out multiple business rules. This could be improved by your approach ("could" in this case means it will be improved when restructuring the code).
The taskUtil class was just some random script include name that came to my mind. Your naming convention seems alot more sound and i have to improve on that part as i catch myself more and more introducing "god classes" (i like that name).

Overall, thanks again for the insights. Alot of things can be improved and i am greatful for you sharing your knowledge.

Have a great day.

Fabian

edit: 

So i took the time to read trough most of your articles. They are more than just useful. Aside the fact that i am currently fangirling a bit, i am truly amazed by your approach to coding. And this is where i would be so glad to have the community ability to hand out badges. You are a wizard (Harry!).

John Caruso
Kilo Guru

Regarding ruleset, I'd certainly second a (ServiceNow-specific) rule like:
"All script outside of script includes should be limited to single line calls to functions within script includes."

Honestly, I wish wherever a script field existed (outside script includes), instead of entering script, it was flow designer-like experience where action-like functions/services (aka components) are selected from an in context component library and in context data configures the components via data pills.

Essentially, all script would be encapsulated within components grouped into libraries/modules (like actions/steps are grouped into spokes).

Wish list idea aside...

In general, across all platforms and languages, the three rules that I think drive the best design/architectures are:
"Maximize cohesion"
"Minimize coupling"
"Use descriptive and consistent names"

To me, grouping like functionality into script includes helps to maximize cohesion, which helps to increase maintainability and discoverability (in much the same way a reusable component library would).

Naming your functions well and grouping into namespaces / classes is hugely important.

This seems easy, but in reality there is an art to it. And not all coders are artists. 

 

Javier Arroyo
Kilo Guru
Thank you Fabian for the kind comments!! Feel free to use what you like and, discard what doesn't help you. If you ever want to talk code,, hit me up. I'd be ore than happy... I'm glad to hear you enjoyed my other posts. I think they are pretty cool, too. 🙂
Javier Arroyo
Kilo Guru
Love your thoughts, John. When the 'overrides' field appeared in objects, I swore SNow was going to beging injecting functionality into components. No more code this here, that over there, duplicate it here because no one knows a functionality hidden in BR X for table Y exists. No more 100 line code sitting outside Script includes. All that would be there was a visual aid to where the magic happens.
Fabian Kunzke
Kilo Sage
Kilo Sage

Hello everyone,

I have finally found the time to rework the first chapters. You can find the "old" version below. 

Why did i do that?

Thanks to a lot of the feedback i have gotten the idea of a more radical view formed for how business rules, workflows and script includes interact. Instead of limiting the power of a script include i have noticed, that reducing the range of motion for workflows and business rules makes a lot more sense. Additionally script includes must not be restricted but well distinguished. Therefore, the first rule is changed so that script includes can focus on data interactions (deletes, updates etc.) BUT must be separate from script includes of data manipulation. This follow the same principle of separation of concern by effectively introducing two types (or more) of script includes.

At this point i want to thank John Caruso and especially Javier Arroyo for an open discussion which lead to this improvement. Also thanks to Robert Fedoruk not only for the soap, but also the insights into new ideas.

I highly recommend going through the comments of this article for deeper insights into these changes.

tl:dr: Script includes must do database interactions BUT be seperated by the action type (e.g. you must not have a single script include transforming data AND inserting it).

Thanks again for all the open discussions. You can find the original first four chapters below:

 

1 Script Includes are for Data Manipulation

Don’t save. Don’t update. Don’t insert. Don’t delete.

In fact: Script Includes don’t. They provide.

2 Business Rules are for Data Transformation

Do save. Do update. Do insert. Do delete.

In fact: Business Rules do, but won’t think. Therefore, make Script Includes the brain of Business Rules.

3 Client scripts are for displaying data

What do they display? Data! Where do they get it from? Queries! Do they think? No! Do they do? No! But what are they there for? To display data.

That’s it.

4 Workflows are a succession of Business Rules

Follow the same principles! They do, but won’t think. Make Script Includes the brain of your Workflows.

But what about macros, widgets and pages?

 

Remember Client Scripts? Yeah, they are what Workflows are to Business Rules.

 

Greetings

Fabian

Uncle Rob
Kilo Patron

Sweet!  Update to one of my favorite threads!

Fabian Kunzke
Kilo Sage
Kilo Sage
Hello, A year later and here we are. Ruleset is changed accordingly as it indeed makes more sense to do data manipulation within Script includes. Regards Fabian
CKDees
Mega Contributor

10-2. - Or even LITERALLY these days, LOL!

Fabian Kunzke
Kilo Sage
Kilo Sage

I did not read this comment till now. Thanks for the laugh 😄

Fabian Kunzke
Kilo Sage
Kilo Sage

Update: I am amazed that "sh*t" is now no longer allowed in comments or articles.

Version history
Last update:
‎02-28-2018 01:41 AM
Updated by: