JetNet/Oracle Tuxedo Guide


Chapter 5. Defining Services in JetNet and Oracle Tuxedo Applications

In JetNet and Oracle Tuxedo applications, services are subroutines that do the work required for an application to access a resource manager, usually a database. They are invoked by service requests made by clients or other services.

To expedite responses to service requests, the application can run multiple instantiations of a server. Service requests are routed to servers in the way that provides the fastest response.

This chapter covers:


Services

A service can consist of three parts:

To create a service, you can:

The latter method is convenient if you are creating services that do not access a database. You might also choose to create the service components manually if you converted an application using the clnt2svr utility and special handling is required, for example, if a client screen implements partial commands via the transaction manager. For more information on clnt2svr, refer to page A-2.

Service Routine

Service routines are responsible for optionally receiving data from the client, performing some task, and optionally returning data to the client. A service routine can perform any task, such as building a query for accessing a database.

Service routines are built for you when you use the screen wizard to create the server portion of your application—that is, the service component. These services perform the database transactions required by your application. For more information on creating services with the screen wizard, refer to page 4-19.

You can also write service routines. These can be written in the same way you write any other Panther application code. JPL is most convenient, but you can code a service as a C or Java function if that suits your application needs.

The JPL service code can reside in a library that is accessible to the server, or it can be implemented as screen-level JPL (in the JPL Procedures property) on a service component (described in the Service Component section). The routine cannot be attached at widget-level since the routine must be recognized outside the scope of the widget. At runtime, the service code procedure is sought first at screen-level (on the service component), then in public modules, and finally in open libraries on the server.

For information on how to write a service routine, refer to page 5-9.

Service Component

A service component is a graphical service. It's a Panther screen that should look, for the most part, like the client screen it is servicing. However, service components reside on the server (in a server library such as server.lib) and so are not visible to the user at runtime. The service component should contain the same widgets as the client screen so that it can handle the data that flows between the client screen and the service.

In brief, the client makes a service request and passes information to the service. The service accesses its corresponding service component which receives the information and performs any runtime processing, including accessing the database via the transaction manager or the database interface. In this way, the service component can carry out a variety of database-related services, such as finding, inserting, deleting and updating records.

The easiest way to create service components is by using the screen wizard. You can create them at the same time you create the client screen. When service components are created in this way, the service routines are automatically provided, so you don't have to write the routines at all. In addition, service components can hold the routines for more than one service, and multiple services can use the same service routine or the same service component.

Service components can be created and edited in the screen editor, just like any Panther screen. Refer to page 12-1 in the Application Development Guide for further information on creating service components.

JIF Service Definition

When a service call is made, or a server is told to advertise services, the application server obtains information about the services by consulting the JIF, which at runtime resides on the application server in common.lib.

A JIF service definition requires the following information:

You can create and modify the JIF in the JIF editor. For information on using the JIF editor, refer to Chapter 24, "JIF Editor," in Using the Editors.

Optional Service Attributes

You can optionally specify several other service attributes in the JIF:

For further information on these service features, refer to page 24-5 in the Using the Editors.


Creating Graphical Services

Services can be represented as a screen, called a service component, that resides on the server. You can create client screens and associated service components with the screen wizard or you can build service components by dragging database objects from a repository to screens using the screen editor.

Creating Services with the Screen Wizard

Using the screen wizard makes service creation practically effortless. To facilitate client and server development, the wizard lets you build the client screen and service component at the same time.

The JPL code that implements the transactions via service calls and the transaction manager are provided for you, and made public via the service component's JPL Procedures property. Unless specialized tasks are necessary for your application, you do not need to write any service code at all.

Database transaction services are named by the screen wizard and implemented via the Service properties on the master table view of the client screen: Delete Service, Insert Service, Select Service, and Update Service.

While client screens and service components in the screen wizard are created as pairs, that is, one service component for each client screen, you can also develop service components that can service multiple client screens. This can be done by creating a service component for each table view that client screens might use. In this way, a service component can service multiple client screens, and in turn, client screens might make service requests to more than one service component. If a client screen must access multiple service components, Service properties must be defined for all table views on the screen. In other words, the master table view on the client screen will call services specific to the master section of the screen, and the detail and subdetail sections will call services specific to their own table view. Therefore, the Service properties for each table view on the screen must be identified.

In general, it is best to create both client screen and corresponding service component at the same time. Save client screens in the client library, and save service components in the server library. Even if you don't plan on using the resulting client screen for your application interface, you can use it to test its associated service component; consider saving such client screens in a test library.

Once services are created in the screen wizard, you must define them in the JIF. When you invoke the JIF editor:

Once services are added to the JIF, all servers that are currently running are made aware of changes to the JIF. The new services are immediately available for the application to advertise. Refer to page 5-14 for more information.

For more information on using the screen wizard, refer to Chapter 4, "Screen Wizard," in Using the Editors.

Building Services with the Screen Editor

To create a service component using the screen editor, you must include and identify all the components necessary to implement the service. In addition to building the screen, you must code the service routines and set the appropriate property values on the client screens that will use the service component.

For screens that use the transaction manager, both client screen and service component must contain the same database information—the same table views, the same columns, and so on. To create a service component from scratch with the screen editor:

  1. Copy the database-related widgets from the repository or from the client screens that will use this service component.
  2. For the master table view on client screens, set the appropriate Service properties for handling the database transaction (if the screen is using the transaction manager).
  3. Write the service code that will be used by the service component and make it available to the service component, by either:
  4. Define the services in the JIF on the application server.
  5. Save the service component in the server library.

Modifying Service Components

For the most part, service components must have the same contents and property values as the client screens that use them. Therefore, changes you make on a client screen must also be made to its corresponding service component.

To modify a service component, open it in the screen editor and make the appropriate edits. Some typical modifications might include adding a transaction manager hook function as screen-level JPL or defining the Use In Where property of a widget.


Initiating a Service

To initiate a service, a client screen must have a way to make a service call. A service call can be implemented as JPL code (using the service_call command) attached to a widget on the client screen, or by naming the service in the Service properties of a client screen's master table view. Services identified as Service properties are handled by the JetNet transaction model jetrb1 on screens that use the transaction manager.

For more information on the JetNet transaction model, refer to page 35-12.


Using Service Aliases to Test Services

Once the service has been added to the JIF and the service component and service code are available on the application server, you can test its behavior. To allow several developers to work on the same application, each developer has the ability to test their own version of a service using service aliases.

With service aliases, a developer's name can be appended to the service name. The original service name is available for all other users of the application; the developer is able to change functionality without breaking the application for other users.

To use service aliases:

The JIF automatically assigns a unique name to each service when the service is added to the JIF. This unique service name, in combination with the developer's name, provide the service alias.

To discontinue service aliases:


Writing Service Routines

A service routine is responsible for:

Table 5-1 lists the basic functions that a service routine should perform and includes the JPL, library function, and SQL that can be used to implement them.

Table 5-1 Service functions and corresponding Panther commands and SQL

Function Code Source

Receive arguments from client agent

receive

JPL command

sm_receive

Panther library function

Fetch data from database

call sm_tm_command ("VIEW") or ("SELECT")

JPL command to Panther library function for the transaction manager

dbms QUERY SELECT ...

JPL command to database interface

Force screen validation

sm_s_val

Panther library function

Update the database

call sm_tm_command ("SAVE")

JPL command to Panther library function for the transaction manager

dbms RUN UPDATE ...

dbms RUN DELETE ...

JPL command to database inter face

Return results to client agent

service_return

JPL command

The following procedure is an example of a JPL service routine that receives the account id and amount from the client and uses that information, by way of the transaction manager, to perform a bank account withdrawal.

proc withdraw()
vars message

// service WITHDRAWAL
receive arguments ({account_id, amount})
call sm_tm_command("SELECT")
if (account_id->num_occurrences <= 0)
{
service_return failure ({message = "Invalid account."})
}
// check if the amount to be withdrawn is more
// than the withdrawal limit
if (amount > max_withdrawal)
{
message = \
"Withdrawal limit is " ## max_withdrawal
service_return failure ({message})
}

account_balance = account_balance - amount
if (account_balance < 0)
{
message = \
"Account overdraft attempt on account " ## account_id
log message
service_return failure ({message})
}

call sm_tm_command("SAVE")
service_return ({message = @NULL, balance = account_balance})
}

The following service routine uses data it receives from the client to apply business logic. Storing business logic on the application server can facilitate application maintenance since you only need to update the service routine in order to effect new business practices. In this example, the service routine uses the customer's id number and the gross amount of an order to determine at what amount a percentage of discount should be applied.

// Use gross_amt to determine level of discount for
// a customer. Apply the discount and return net_cost

proc discount()
receive arguments ({customer_id, gross_amt})
call sm_tm_command("VIEW")
if (customer_id->num_occurrences <= 0)
{
service_return failure ({message = "Invalid customer id"})
}
if (gross_cost > 1000)
{
net_cost = gross_cost * (1 - discount)
}
else if (gross_amt > 100)
{
net_cost = gross_amt * (1 - discount / 2)
}
else
net_cost = gross_amt

service_return success ({net_cost})

Storing and Invoking JPL Service Code

You can write JPL service routines directly in the screen editor. The service and your application's requirements, for development and production, will determine where it makes the most sense to store and invoke service code:

Service Code and Service Components

A service routine that resides directly on the service component is immediately available when the client makes the service request. Service components are opened or made available either when their associated service is advertised by the server, or when the service is requested.

Panther's built-in development and production pre_service handlers make the necessary service component available, and therefore, the routine is made available.

The built-in post_service handlers close or deselect the service component after the service completes.

Changes you make to the service code are immediately available to your application without having to restart servers or recompile.

JIF-Invoked Services

If you have service routines that are not associated with a service component, there are two ways you can make these routines available:

For development purposes, store a JPL service routine in a module—one service routine per module—and do not include the proc statement. When the service is requested, the JIF is consulted to determine the name of the service's routine—in this case, the procedure and module name would be the same. At runtime, if a procedure by the given name is not found, the service seeks a module having the specified name, and executes its unnamed procedure (refer to page 19-2 in the Application Development Guide for information on the unnamed procedure in a JPL module).

In this way, you can easily access, update, and retest code without affecting the server.

Once you are ready to deploy your application, edit the JPL module and add a proc statement to identify the procedure (same as the module name). You can then public these JPL service routines when the server is initialized.

Public Services

If the service routine does not reside directly with a service component, the JPL procedures can be made public either from the service component's entry function, or on server initialization.

When JPL is made public, it is available to the entire application until the server is brought down, or until the module is unloaded. For development purposes, making modules public on server initialization is the least flexible, because the server offering the services must be brought down and reinitialized in order to retest service code that has been modified.

During the development cycle, consider unloading public modules from the service component's exit function. In this way you can update the code, if necessary, and retest it without affecting the server that advertises the service.


Service Groups

Once you determine what services are required by your application, you can create service groups. You can group services for your application if you have just one application server or multiple servers. Service groups are useful because:

You define service groups in the JIF. Refer to page 24-12 in the Using the Editors for information on using the JIF editor.

Criteria for Grouping Services

To determine what services should be grouped, consider establishing a logical coherence within the application's tasks, such as:

In general, it is easier and more efficient for servers to advertise groups of services. For instance, if a server's initialization routine specifies that all services should be advertised, all is interpreted as all services defined in the JIF (refer to page 3-23 for JetNet or to page 8-17 for Oracle Tuxedo).

If your application has multiple (unique) servers, each server should have at least one service group that contains all of the services that the server handles. Additional servers might be necessary if some services use an XA-compliant database interface, while others do not, or if your application accesses more than one database.

Adding Services to Existing Service Groups

During development, you can easily add or update services associated with a service group. When a service is added or updated in the JIF and it is saved to the application's common library, all running servers are notified to reread the JIF. Service groups that are advertised at server initialization are readvertised, and the new or updated service is immediately available to your application.

For details on how to define service groups in the JIF, refer to page 24-12 in the Using the Editors. For details on advertising a service group when starting your servers, refer to page 3-23.


Service Messages and Data Types

In JetNet and Oracle Tuxedo applications, clients and servers exchange data through messages, and the data type of those messages must be specified. For example, a service call can have 0 to 2 messages, depending on how the service is defined, for request data supplied to the server, and reply data expected by the client when the service returns.

Messages can be composed of simple strings; or they can contain combinations of numeric and string data.

In JetNet applications, messages are in JAMFLEX data buffers.

In Oracle Tuxedo applications, messages can be data buffers using the JAMFLEX, FML, or FML32 data types, or messages can be strings using the STRING data type.

For example, when a client calls a bank deposit service, it supplies a message to the service that is composed of several data fields, such as the bank account number and deposit amount. The client also expects the service to return with a reply message whose data includes the bank balance. So, the service call specifies two messages, one for input data and another for reply data, where each message can itself contain multiple data fields:

service_call "DEPOSIT"( \
{account_id, amt}, \
{errMsg, acctBal} )

In this example, the service request message consists of the account number and deposit amount; on return, the service can supply the client with an error message in case the service fails, and the updated account balance. Each message contains two data fields.

The previous example contains two JAMFLEX messages. A service call can also specify messages of different types. For example the following call to service OPEN_ACCT specifies a STRING message type for input data and a JAMFLEX buffer for reply data:

service_call "OPEN_ACCT"("Fred Jones", {acct_num, start_bal})

The following sections discuss service message types.

Buffer Data Types

Service messages can be defined as buffers that can contain multiple components of data of different types. In JetNet applications, messages are in JAMFLEX data buffers, which can contain string, integer, and floating point data. In Oracle Tuxedo applications, messages can be data buffers using the JAMFLEX, FML, or FML32 data types.

All buffer types are represented in JPL as a comma-delimited list of fields in this format:

{[field [, field] ] }

The buffer can contain 0 or more fields, where each buffer field has this format:

fieldname [= prolfx-expr] 

fieldname is the name of a field in the buffer, and prolfx-expr can be a Panther variable or constant. If you omit prolfx-expr, Panther assumes that fieldname has the same name as a Panther variable or widget and maps its data accordingly.

Note: If the buffer type is FML or FML32, fieldname must correspond to the name of a field defined in the FML file. Refer to page 5-17 for more information.

For example, a message can be specified as follows if the Panther variables and the corresponding buffer field names have the same names:

{EMP_ID, EMP_NAME, EMP_ADDR}

In the next example, the names of the Panther variables and the corresponding buffer field names are different, so explicit mapping is required:

{EMP_ID=id, EMP_NAME=name, EMP_ADDR=addr}

Default Mapping

Reply buffer data can be automatically mapped to Panther variables of the same name through ellipses. For example, this service_call command specifies to map all buffer fields to client variables that have the same name:

service_call "get_emp_age" ({emp_id},{...})

In the next example, the buffer field emp_age is mapped to Panther variable age. All other buffer fields are mapped to same-named client variables:

service_call "get_emp_age" ({emp_id}, {emp_age=age, ...})

NULL Arguments

You can specify the data in a message buffer to be NULL through the keyword @tpi_null. For example, this service_return command returns a NULL buffer to the service call:

service_return failure ({@tpi_null})

Arrays

Buffers can reference array occurrences. If a message field specifies the name of an array, Panther references each occurrence in that array. You can also specify ranges of occurrences. In the following example, the first 5 occurrences of emp_id are sent to the service:

service_call "get_emp_args" ({emp_id[1..5]}, {...})

FML and FML32 Buffers

A Oracle Tuxedo application can support FML buffers for messages, both FML and the enhanced FML32. For complete information on FML buffers, refer to your Oracle Tuxedo documentation.

You define FML fields in the FML file as one of the following data types: char, string, short, long, float, and double. Fields are listed by name, field number, and type. A fourth Flag field is unused. For example:

#Name    		Number			Type  			Flag
#---- ------ ---- ----
CUSTNUM 1 string -
CUSTID 2 long -
CUSTADDR 3 string -
CUSTCITY 4 string -
CUSTSTATE 5 string -
CUSTZIP 6 long -

Type conversion from the data type specified by the user to the FML field type is performed as the data allows. In the following example, if amount is of type float, and if amount = 3, then 3 is converted to a floating point number:

{amount = 3}

Converting from JAMFLEX to FML

You can convert JAMFLEX buffers to FML buffers later in the development cycle:

STRING Data Types

A Oracle Tuxedo application can also support STRING message types. A STRING message can send or receive only a single string. A STRING input message can be any string expression, either a variable or string constant; a STRING reply message must be a variable.

For example, the following JPL procedure calls a service that expects STRING messages for input and output:.

proc lastname
vars last_name
service_call "get_last_name" ("Jim", last_name)
msg emsg "Last name is ", last_name

In this example, service get_last_name receives the string Jim as an input message. When the service completes, the return data is put in the output message and mapped to Panther variable last_name.

In the next example, the input message to the same service refers to a Panther variable:

service_call "get_last_name" (first_name[i], last_name)

In this case, the first name passed to the service is the value in the ith occurrence in array first_name.

Setting Service Message Types

The data types for service messages are set in the JIF as part of service or queue definitions. For each service or queue definition, you can specify the data type for its request and reply messages. You specify a data type for both messages, only one, or for none.

When the request broker processes one of these messages, it checks the JIF for its data type and uses this information in order to pack or unpack the message data. The format of a message must conform with its JIF definition; otherwise, an error occurs.

service_call messages can contain zero to two comma-delimited messages. Other commands such as dequeue and service_forward allow only zero or one message for either input or output.

Several JPL commands send messages that are independent of service definitions:

These commands set their message's data type. For example, this broadcast command sends a message that informs the specified user how much time has elapsed since she logged in and how much time remains until the login lapses:

broadcast USER userName TYPE JAMFLEX \ 
({sinceLogin, timeLeft})