Developing Ensemble Productions
Programming in Ensemble
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

This chapter discusses common programming tasks and topics in Ensemble. It includes the following sections:

Introduction
When you create business host classes or adapter classes, you typically implement callback methods, define additional methods as needed, and add or remove settings.
Within a business host or adapter, your methods typically do some or all of the following tasks:
Key Principles
It is important to understand the programming practices that are best suited within Ensemble productions. Ensemble business hosts execute in separate processes, which means that you should make sure that:
Similar considerations apply to business rules and data transformations.
Also, you must often handle error codes received from Ensemble methods. The Ensemble development framework is designed to allow your custom code to be simple and linear as possible with regard to error codes. For example:
Given these precautions built into the Ensemble framework, InterSystems recommends that for normal circumstances your custom code should simply check the error code of each call, and if it is an error value, quit with that value. The following is an example of this coding style. Note the absence of traps or Do {} While 0 blocks:
Class Test.FTP.FileSaveOperation Extends Ens.BusinessOperation
{
 Parameter ADAPTER = "EnsLib.File.OutboundAdapter";

 Method OnMessage(pRequest As Test.FTP.TransferRequest,
                  Output pResponse As Ens.Response) As %Status
 {
 Set pResponse=$$$NULLOREF
 Set tFilename=..Adapter.CreateTimestamp(pRequest.Filename,"%f_%Q")
 ; file with timestamp should not already exist
 $$$ASSERT('..Adapter.Exists(tFilename))
 Set tSC=..Adapter.PutStream(tFilename,pRequest.StreamIn) Quit:$$$ISERR(tSC) tSC
 Quit $$$OK
 }
}  
More complicated scenarios are sometimes useful, such as for instance executing a number of SQL statements in a business operation using the SQL adapter and then calling a rollback before returning if any of them fail. However, the coding style in the previous example is the best practice in simple circumstances.
Later chapters in this book provide additional principles for specific kinds of business hosts.
Passing Values by Reference or as Output
If you are not familiar with passing values by reference or output, this section is intended to orient you to this practice.
Many Ensemble methods return at least two values: a status (an instance of %Status) and a response message or other returned value. Typically the response message is returned by reference or as output. If a value is returned by reference or as output, that means:
The following examples demonstrate these points.
Typical Callback Method
The following shows the signature of a typical callback method:
method OnRequest(request As %Library.Persistent, Output response As %Library.Persistent) as %Status
The keyword Output indicates that the second argument is meant to be returned as output. In your implementation of this method, you would need to do the following tasks, in order to satisfy the method signature:
  1. Set a variable named response equal to an appropriate value. This variable must have a value when the method completes execution.
  2. End with the Quit command, followed by the name of a variable that refers to an instance of %Status.
For example:
Method OnRequest(request As %Library.Persistent, Output response As %Library.Persistent) as %Status
{
   //other stuff
   set response=myObject
   set pSC=..MyMethod() ; returns a status code
   quit pSC
}
This example discusses a value returned as output, but the details are the same for a value passed by reference.
Typical Helper Method
The following shows the signature of a typical inherited helper method:
method SendRequestSync(pTargetDispatchName As %String, 
                       pRequest As %Library.Persistent, 
                       ByRef pResponse As %Library.Persistent, 
                       pTimeout As %Numeric = -1, 
                       pDescription As %String = "") as %Status
The keyword ByRef indicates that the third argument is meant to be returned by reference. To invoke this method, you would use the following:
 set sc=##class(pkg.class).SendRequestSync(target,request,.response,timeout,description).
Notice the period before the third argument.
This example discusses a value passed by reference, but the details are the same for a value returned as output.
Adding and Removing Settings
To provide new settings for a production, a business host, or an adapter, modify its class definition as follows:
  1. Add a property for each configuration setting you wish to define.
  2. Add a class parameter called SETTINGS to the class.
  3. Set the value of SETTINGS to be a comma-delimited list of the names of the properties you have just defined. For example:
    Property foo As %String;
    
    Property bar As %String;
    
    Parameter SETTINGS = "foo,bar";
    See the following section for additional details for SETTINGS.
The foo and bar settings now automatically appear in the configuration display on the [Ensemble] > [Production Configuration] page whenever an item of that class is selected for configuration.
To remove an inherited configuration setting, list the property in the SETTINGS class parameter, preceded by a hyphen (-). For example:
Parameter SETTINGS = "-foo";
Specifying Categories and Controls for Settings
By default, a setting is displayed in the Additional Settings category on the Details tab when you configure a production. By default, the setting provides one of the following input mechanisms, depending on the property on which the setting is based:
In all cases, the InitialExpression of the property (if specified) controls the initial state of the input field.
You can override both the location and control type. To do so, include the setting name in SETTINGS as follows:
Parameter SETTINGS = "somesetting:category:control";
Or:
Parameter SETTINGS = "somesetting:category";
Parameter SETTINGS = "somesetting::control";
Where category and control indicate the category and control to use, respectively. The following subsections provide details.
You can include multiple settings, as follows:
Parameter SETTINGS = "setting1:category:control1,setting2:control2:editor,setting3:category:control3";
For example (with disallowed line breaks included):
Parameter SETTINGS = "HTTPServer:Basic,HTTPPort:Basic,SSLConfig:Connection:sslConfigSelector,
ProxyServer:Connection,ProxyPort:Connection,ProxyHTTPS:Connection,
URL:Basic,Credentials:Basic:credentialsSelector,UseCookies,ResponseTimeout:Connection" 
Category for a Setting
category is one of the following case-sensitive, literal values:
Or use your own category name.
Control for a Setting
control specifies the name of a specific Zen control to use when viewing and modifying the setting in the [Ensemble] > [Production Configuration] page. The control can be any valid Zen component, so you can build your own components. Ensemble provides controls to handle commonly needed data; see Examples for Settings Controls.”
To supply extra values to your component in order to display a suitable set of options, append a set of name-value pairs as follows:
myControl?Prop1=Value1&Prop2=Value2&Prop3=Value3
Where:
Passing Values to the context Property of a Control
Ensemble uses the general-purpose selector component, which you can also use; to do so, specify control as selector and then append property/value pairs.
The selector component allows almost any list of data or options to be displayed to the user and which allows the user to type data if needed. To configure the component in this way, use the following syntax to set the context property of the control:
context={ContextClass/ContextMethod?Arg1=Value1&Arg2=Value2&Arg3=Value3}
Where:
Note that the selector component also has a property named multiSelect. By default, the user can select only one item. To enable the user to select multiple items, include a property/value pair as follows: multiSelect=1.
Examples for Settings Controls
The following list gives examples for control, organized by the kind of data that they enable the user to select.
These examples use Ensemble classes. Many of these examples use the Ens.ContextSearch class, which provides a large number of useful methods. If this list does not cover your scenario, see the class documentation for Ens.ContextSearch to determine whether any of the existing methods supply the data you need. If that class does not cover your scenario, you can create your own subclass of Ens.ContextSearch.
business hosts in the same production
selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}
Or, if the user should choose only one business host:
selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}
business partners
partnerSelector
business rules
ruleSelector
character sets
selector?context={Ens.ContextSearch/CharacterSets}
credential sets
credentialsSelector
framing
selector?context={Ens.ContextSearch/getDisplayList?host=@currHostId&prop=Framing}
local interfaces
selector?context={Ens.ContextSearch/TCPLocalInterfaces}
schema categories appropriate for a specific business host
selector?context={Ens.ContextSearch/SchemaCategories?host=classname}
Where classname is the class name of the business host. For example:
selector?context={Ens.ContextSearch/SearchTableClasses?host=EnsLib.MsgRouter.RoutingEngineST}
search table classes appropriate for a specific business host
selector?context={Ens.ContextSearch/SearchTableClasses?host=classname}
Where classname is the class name of the business host. For example:
selector?context={Ens.ContextSearch/SearchTableClasses?host=EnsLib.EDI.ASTM.Service.Standard}
SSL configurations
sslConfigSelector
Specifying Default Values for Settings
As you define business host classes (and possibly adapter classes), you should consider how to control the default values for any settings of those items. Ensemble can take the default value for a setting from one of three sources:
Some settings are dependent on the environment, such as TCP/IP addresses or file paths; typically you configure these settings to have their source outside the production, while others, such as ReplyCodeActions are design decisions and most likely you develop your application to retrieve these from the production definition.
You can develop your production to have configuration settings come from different sources. The primary purpose is to make it easier to move productions from one Ensemble instance to another, such as from test to live.
Once you define a production, you can change the source of both production and business host settings on the Production Configuration page of the Management Portal. See Configuring Ensemble Productions for details.
The use of default settings allows you to define production and business host settings outside the production definition where you can preserve them during a production upgrade. To facilitate updating productions or moving productions from one system to another, you can omit settings and take their values from a structure that is installed on the system. When a setting is missing, Ensemble retrieves the default setting from outside the production definition if one exists.
See the descriptions for the following methods in the Ens.Director class entry of the Class Reference for programming details:
Accessing Properties and Methods from a Business Host
When you define a method in a business host class, you might need to access properties or methods of that class or of the associated adapter. This section briefly describes how to do these things.
Within an instance method in a business host, you can use the following syntaxes:
Accessing Production Settings
You might need to access a setting of the production. To do so, use the macro $$$ConfigProdSetting. For example, $$$ConfigProdSetting("mySetting") retrieves the value of the production setting called mySetting. InterSystems suggests you wrap this macro in a $GET call for safety; for example:
 set myvalue=$GET($$$ConfigProdSetting("mySetting"))
For details about $GET, see the Caché ObjectScript Reference.
Also see Using Ens.Director to Access Settings in the chapter “Advanced Topics.”
Choosing How to Send Messages
In business operations and business processes, your methods typically invoke inherited methods to send messages to other business hosts within the production. This section discusses the options.
Synchronous and Asynchronous Sending
When you define business service, business process, and business operation classes, you specify how to send a request message from that business host. There are two primary options:
The choice of how to send a message is not recorded in the message itself and is not part of the definition of the message. Instead, this is determined by the business host class that sends the message.
Deferred Sending
In addition to the straightforward alternatives of synchronous (wait) and asynchronous (do not wait), it is possible to send messages outside Ensemble using a mechanism called deferred response.
Suppose a business process wishes to invoke an action outside Ensemble. It sends a request to a business operation, which performs the invocation and returns the response. The business process is the intended recipient of any response; the business operation is simply the means by which the request goes out and the response comes in. The business operation will relay a response back if the business process made the request synchronously, or if it made the request asynchronously with asynchronous response requested. The following diagram summarizes this mechanism.
Now suppose the business operation that receives a request from a business process has been written to use the deferred response feature. The original sender is unaware of the fact that the response is going to be deferred by the business operation. Deferring the response is a design decision made by the developer of the business operation. If the business operation does in fact defer the response, when the original sender receives the response at the end of the deferral period, it is unaware that the response was ever deferred.
A business operation defers a response by calling its DeferResponse() method to generate a token that represents the original sender and the original request. The business operation must also find a way to communicate this token to the external entity, which is then responsible for including this token in any later responses to Ensemble. For example, if the external destination is email, a business operation can include the token string in the subject line of the outgoing email. The entity receiving this email can extract this token from the request subject line and use it in the response subject line. In the following diagram, the item “t” represents this token.
Between the time when the business operation defers the request, and when the response is finally received by the original sender, the request message has a status of Deferred. After the original sender receives the corresponding response, the request message status changes from Deferred to Completed.
An incoming event in response to the request can be picked up and returned to the original sender by any business host in the production. Exactly where the event arrives in Ensemble depends on the design of the production; typically, it is the task of a business service to receive incoming events from outside Ensemble. The business host that receives the incoming event must also receive the deferred response token with the event. The business host then calls its SendDeferredResponse() method to create the appropriate response message from the incoming event data and direct this response to the original sender. The original sender receives the response without any knowledge of how it was returned. The following figure shows a request and its deferred response.
Generating Event Log Entries
The Event Log is a table that records events that have occurred in the production running in a given namespace. The Management Portal provides a page that displays this log, which is intended primarily for system administrators, but which is also useful during development. (For details on this page, see Monitoring Ensemble Productions.)
The primary purpose of the Event Log is to provide diagnostic information that would be useful to a system administrator in case of a problem while the production is running.
Ensemble automatically generates Event Log entries, and you can add your own entries. Any given event is one of the following types: Assert, Info, Warning, Error, and Status. (The Event Log can also include alert messages and trace items, discussed in the next sections.)
To generate Event Log entries:
  1. Identify the events to log.
    Not all types of error or activity should necessarily generate Event Log entries. You must choose the occurrences to note, the type to use, and the information to record. For example, Event Log entries should appear in case of an external, physical problem, such as a bad network connection.
    The Event Log should not register program errors; these should be resolved before the production is released.
  2. Modify the applicable parts of the production (typically business host classes) to generate Event Log entries in ObjectScript or in Basic, as described in the following subsections.
Important:
If you need to notify users actively about certain conditions or events, use alerts, which are discussed in the next section and in Defining Alert Processors,” later in this book.
Generating Event Log Entries in ObjectScript
Within business host classes or other code used by a production, you can generate Event Log entries in ObjectScript. To do so, use any of the following macros. These macros are defined in the Ensemble.inc include file, which is automatically included in Ensemble system classes:
Macro Details
$$$LOGINFO(message) Writes an entry of type Info. Here and later in this table, message is a string literal or an ObjectScript expression that evaluates to a string.
$$$LOGERROR(message) Writes an entry of type Error.
$$$LOGWARNING(message) Writes an entry of type Warning.
$$$LOGSTATUS(status_code) Writes an entry of type Error or Info, depending on the value of the given status_code, which must be an instance of %Status.
$$$ASSERT(condition) Writes an entry of type Assert, if the argument is false. condition is a ObjectScript expression that evaluates to true or false.
$$$LOGASSERT(condition) Writes an entry of type Assert, for any value of the argument. condition is a ObjectScript expression that evaluates to true or false.
The following shows an example with an expression that combines static text with the values of class properties:
 $$$LOGERROR("Awaiting connect on port "_..Port_" with timeout "_..CallInterval)
The following example uses an ObjectScript function:
 $$$LOGINFO("Got data chunk, size="_$length(data)_"/"_tChunkSize)
Generating Event Log Entries in Basic
To generate Event Log entries in Caché Basic, use the following syntax:
"Ens.Util.Log".LogMethodName(Me.%ClassName(1),CurrentMethod,arg)
Where LogMethodName is one of the method names in the following table, arg is the argument for that method, and CurrentMethod is the name of the current method.
Method Details
LogInfo Writes an entry of type Info. The argument is a string literal or an ObjectScript expression that evaluates to a string.
LogError Writes an entry of type Error. The argument is a string literal or an ObjectScript expression that evaluates to a string.
LogWarning Writes an entry of type Warning. The argument is a string literal or an ObjectScript expression that evaluates to a string.
LogStatus Writes an entry of type Error or Info, depending on the value of argument.
LogAssert Writes an entry of type Assert, if the argument is false. The argument is a ObjectScript expression that evaluates to true or false.
For example:
"Ens.Util.Log".LogInfo(Me.%ClassName(1),MyMethod,"log this")
Generating Alerts
An alert sends notifications to applicable users while an Ensemble production is running, in the event that an alert event occurs. The intention is to alert a system administrator or service technician to the presence of a problem. Alerts may be delivered via email, text pager, or another mechanism. All alerts also write messages to the Ensemble Event Log, with the type Alert.
The Ensemble alert mechanism works as follows:
In a business host class (other than a BPL process class), do the following to generate an alert:
  1. Create an instance of Ens.AlertRequest.
  2. Set the AlertText property of this instance. Specify it as a string that provides enough information so that the technician has a good idea of how to address the problem.
  3. Invoke the SendAlert() method of the business host class. This method runs asynchronously and thus does not delay the normal activities of the business host.
For example, in ObjectScript:
 set alert=##class(Ens.AlertRequest).%New()
 set msg="Suspended HL7 Message "_pRequest.%Id()
   _" because ACK.MSA.Acknowledgment Code = '"_tAcknowledgmentCode_"'"
 set alert.AlertText=msg
 do ..SendAlert(alert)
Note:
For information on generating alerts in BPL, see Developing BPL Processes.
Adding Trace Elements
Tracing is a tool for use primarily during development. You add trace elements so that you can see the behavior of various elements in a production, for the purpose of debugging or diagnosis. To add trace elements to a production, you identify the areas in your code (typically business host classes) where you would like to see runtime information. In those areas, you add lines of code that (potentially) write trace messages. Note that these are messages only in a general sense; trace messages are simply strings and are unrelated to Ens.Message and its subclasses.
In most cases, you can define two kinds of trace elements: user elements and system elements. In most cases, it is more appropriate to define user trace elements.
Note:
For information on writing trace elements in BPL, DTL, or business rules, see Developing BPL Processes, Developing DTL Transformations, and Developing Business Rules.
Writing Trace Messages in ObjectScript
To write trace messages in ObjectScript, use the following lines of code:
For example:
$$$TRACE("received application for "_request.CustomerName)
Writing Trace Messages in Basic
To write trace messages in Caché Basic, use the following lines of code:
Writing Trace Messages in BPL or DTL
To write user trace messages in a BPL business process or in a DTL data transformation, use the <trace> element. See the Ensemble Business Process Language Reference or the Ensemble Data Transformation Language Reference.