Business Rules Scripting
Business Rules Scripting is a feature of Data Abstract that allows you to use EcmaScript (also known as JavaScript) code snippets to specify rules and business logic for your multi-tier data access, right inside your schema.
Traditionally, to add business logic (such as to enforce data access restrictions and data consistency requirements) to your applications, you had to implement these so-called “business rules” as part of your custom server’s program logic, in the language and platform used to implement your server (be it one of the .NET languages or Delphi). This had several downsides. For one, the business rules were hardcoded inside the server executable, so updating or changing them meant compiling, testing and deploying a new version of the server executable. For another, it meant that the business rules were only applied on the server – to perform pre-check of rules compliance on the client, the rules needed to be manually duplicated in client code as well (potentially re-implemented in different languages or for different platforms), and to be kept in sync between client and server. This also meant frequently updating the client applications, if business rules changed.
With Data Abstract’s new EcmaScript based scripting, business rules can be written once, put inside the schema, and will automatically be enforced on the server and client. Changing the business rules means merely updating the schema on the server for the updated versions of the scripts to be applied.
The Case for Client and Server Side Rule Checking
There are good reasons for wanting to check business rules both on the server and on the client.
First of all, it is important to realize that, for any real security to be in place, rules must be enforced on the server. No matter what types of checks or input validation logic you have in place in your client application, the server should always treat your client as “untrusted”. Hackers or malicious users might hijack and adjust your client application, or might even implement their own client application that conforms to your server’s API to try and execute requests that they are not authorized to.
Imagine a scenario where different users of your system have access to different sets of data. Regular users might, for example, have access to partial employee records, while only top management has access to salary information. If this restriction was merely enforced by filtering the display on the client, a user could create a separate client application (or hack his existing client app), and make it display more data than should be available – using his regular and limited system login. Only by actually applying security checks and filtering data on the server, can you assure that a client will never be exposed to data it should not have access to. The same goes for enforcing integrity and restrictions on data manipulation, as well.
Rule #1: Do not trust the client application.
Why, then, would you even want to run business logic on the client at all? The main reason is convenience. If business logic is only enforced on the server, that means the client application (and the user) will only be informed of violating that logic when its changes are being sent to the server. The user might make changes to several records, unaware his modifications are invalid, and it is not until he applies his changes (which, when working offline, may not be until a point in the future) that he is confronted with a range of error messages. Even when your application is running “online”, it requires a roundtrip to the server and a failed attempt to apply his changes to inform the user of the problem. This is not good user experience.
The same goes for data requests: If a user does not have access to a specific set or subset of data, it’s better to not let him query for it – for example by graying out the relevant options – than to let him ask, and receive an error from the server.
Ideally, you want to present your user with warnings or validation messages as soon as possible, even while he is editing his data. This is what client-side business rules bring to the table.
Rule #2: Client-side validation is for user convenience only.
Data Abstract Business Rules Scripting allows you to define a shared set (or subset) of rules in a single place, that can be used both on the server to actually enforce the rules, and on the client to provide the convenience of verifying the rules during data entry.
Types of Business Rules Scripts
There are two different places, or scopes, where business rules scripts
can be applied. The first is globally on a per-schema level, for events
that apply to the entire schema. The second is on individual objects
(such as data tables and commands) in the schema, for events that apply
to specific commands. For example, the global beforeCommit()
event will
get fired whenever a transaction involving data in any table in the
schema is committed. In contrast, the per-table beforeDelete()
event
will fire whenever a row in a specific data table is being deleted.
Some events can run on both the client and the server (such as the
aforementioned beforeDelete()
), while others may run on the server only
(such as the also before-mentioned beforeCommit()
) or on the client only
(such as onNewRow()
). This is owed to the fact that some events only
make sense on the server (the client does not know about transactions,
delta processing, error handling, and various other concepts) or on the
client (in the client application, onNewRow()
may be used to
initialize/pre-fill a new row with default values before the user begins
editing; this concept does not apply to the server, which will only see
the finished row).
Finally, some events are specific to Relativity server, and beyond the scope of this article.
The Business Rules Scripting Events topic lists all available events in detail, and also specifies whether they apply to Client, Server, Client & Server or Relativity, and whether they are schema-wide or per object.
Adding Business Rules Scripts to your Schema
Business Rules Scripts can be added to your schemas in two ways, either by setting them in code or – more conveniently – by specifying them in your schemas right inside Schema Modeler. For this article, we’ll use Schema Modeler.
The quickest way to access the Business Rules Scripts in Schema Modeler
is to select a table (or command) from the left-hand source tree, click on the disclosure triangle beside the table name and then click on the
Scripts
item which will open the Business Rules Script editor,
shown below.
As you can see, the editor provides two tabs, to edit Server
and Client
& Server
scripts, respectively.
Any event code placed in the Server tab will be kept private to the
server, and will only be executed there. This includes server-specific
events, but also any events that would be available on the client, as
well. For example, even though beforeDelete()
is supported on both the client and server, if you provide a handler on the Server tab, this handler will only execute on the server.
Any event code placed on the Client & Server tab will – as the name
implies – run both on the client and server. If you implement
beforeDelete()
on this tab, it will run on both sides.
Note: Events not available on a particular side will be ignored, if present. For example, if you implement a server-only event on the Client & Server tab, it will of course not run client side; if you implement a client-only event, it will be ignored on the server. In theory, you could implement all your events on the Client & Server tab, and it would work fine. The main reason to move server-specific events to the Server tab is to keep them private from clients, as only the Client & Server script will be sent out and available to clients.
For events running on both client and server, the global isServer
API
can be used to distinguish between the two, if slightly different code
paths are needed for client vs. server. For example, you might provide a
single event handler, but have more advanced and complicated checks run
on the server, by embracing them in a “if (isServer) { … }
” clause.
Implementing Event Handlers
Events are handled by providing an event handler for them, in form of a JavaScript function with a specific name and signature matching the event you want to handle. Once again, the Business Rules Scripting Events topic provides a complete overview and documentation of the available event handler types.
The easiest way to do this is to use the Add Event dropdown button in Schema Modeler to select the appropriate event from the list and have Schema Modeler automatically insert a complete method body with the proper name and signature into your script.
Inside the function body, you have full access to the JavaScript language and the standard JavaScript APIs to write the logic for your event handler. Please refer to generally available JavaScript literature and specifications for details (see reference at the bottom of this article).
In addition to the standard JavaScript APIs, Data Abstract makes a range of additional object types and global APIs available. This is comparable to how, for example, web browsers would expand the JavaScript API to provide access to the web page’s document object model.
The Global Object topic provides a complete list of all global APIs, while Business Rules Scripting API provides documentation for all object types available through the global API, as well as those exposed via event handler parameters.
The most commonly used global variables are session
, which gives you
access to the current
Session object
to access information about the logged-in user (if any) and lda
, which
provides a
LocalDataAdapter
object that can be used to run additional local queries on the server.
The aforementioned isServer
variable can be used on both client and
server to distinguish between client and server, respectively. Also
available on both client and server, the schema
variable exposes a
Schema object
that gives access to information from the current schema, such as
available data tables and their fields, etc.
Additional objects are available through the event handler’s parameters.
For example, the beforeProcessDelta()
event handler receives a
Delta object that
can be used to inspect, validate and adjust the actual changes received
from the server.
As you can see, many of these objects made available through the scripting API reflect already familiar classes from the platform-specific .NET, Objective-C and Delphi APIs, such as the Local Data Adapters, Schemas or Deltas. These objects expose much the same APIs (or a subset thereof) as their big siblings, but are tailored for use from JavaScript. They allow you to implement business logic in much the same ways as you would traditionally have implemented them within your .NET or Delphi server code.
Enabling Scripting in Your Application
To enable business rules scripts to run in a Data Abstract application, both client and server side, a Scripting Provider is needed
For Data Abstract for .NET, simply
drop a component on your data service (server side) or next to your
Remote Data Adapter. Both the service and the RDA have a
ScriptProvider
property that you can connect to the dropped component.
Scripting support for Xcode and Delphi will come soon (see below for details).
Three Examples
To round off this article, let’s take a look at a few examples of business rules scripts.
First, a client-side onNewRow()
may be used to pre-fill a new row with
some default values before it is presented to the user for filling.
Here, we populate the “id” field with a new GUID, and set the “name” to
a default value:
function onNewRow(row)
{
row["id"] = newGuid();
row["name"] = "New Customer"
}
Next, a beforeProcessDeltaChange()
handler validates if the current user
has the right to delete rows, before allowing a specific change. The
handler checks the session for a specific value (ostensibly set by some
other part of the application), and refuses any “DELETE
” change, if the
“CAN_DELETE
” value is not present:
function beforeProcessDeltaChange(delta, change, wasRefreshed, canRemove)
{
if (change.isDelete && session["CAN_DELETE"] != "YES")
fail("You are not allowed to delete rows for this table.")
return canRemove;
}
Lastly, we use the Local Data Adapter to make some additional changes to our database after a change has been processed successfully. More specifically, we store date, user ID and the row ID in a “History” table:
function afterProcessDeltaChange(delta, change, wasRefreshed)
{
lda.insert("History", { Date = new Date(), RowID = change["ID"], UserID = session.["UserID"] } );
}
Current Limitations of Scripting Support
Business Rules Scripting via EcmaScript is currently supported for:
- Relativity Server, .NET Custom Servers and Delphi Custom Servers
- .NET, Cocoa and Delphi clients
On .NET, scripting is built on RemObjects Software's Open Source Script for .NET JavaScript engine. on Delphi, it uses the Open Source SpiderMonkey engine.
We are working on expanding that support to Data Abstract for Cocoa, using the native JavaScriptCore framework for OS X.
Note we are evaluating options to support client scripting on the iOS platform with Data Abstract for Cocoa, but the App Store guidelines put restrictions on executing downloaded script code in iOS apps.
At this stage, we have no plans to support EcmaScript scripting in Data Abstract for Delphi for non-Windows platforms (via Free Pascal or future cross-platform support in Delphi), due to lack of available options for scripting engines. But this is an area we will be keeping under review.