Evolutionary Development of Complex Systems using Rapide: Transaction Processing Case Study, Fundamental Concepts

Transaction processing (TP) involves multiple application programs sharing several resources where the application programs make requests of the resources and receive results back. A exempliary TP system architecture contains a single application program that communicates with a set of resources. A "boxes and arrows" depiction of such an architecture is given in Figure 1. We will use the Rapide tools to produce evolutionary prototype models of TP.

TP System Architecture
Figure 1: A TP System Architecture.

System Architecture Services Extension

Next, we will enrich the architecture to make use of services. The first step in this process is to create the interface (or service) that will be shared by the application program and resource.

Application Program and Resource Service

The Application_Program and the Resource interfaces share a common interface called the AP_R interface. This interface denotes the kinds of interaction that can occur between these two components. In general the interaction can be described in Rapide via functions or actions. Actions are more general than functions, since functions impose additional synchronization between the caller and the callee; the caller must wait after making the function call until the callee returns before proceeding. Actions are asynchronous, and the caller is free to continue doing other activities and react to the results at his leisure. Therefore, we have chosen to represent the interaction with actions.

Select the Show Type Interfaces option of the Rapide menu. Press on the NEW button of the shwif window. Name the interface "AP_R". Add the following to the actions and services section:

action
  out  Request();
  in   Result();

Modify the behavior rules section to be:

start => animation_Iam("AP_R");;

When you are finished making the modifications, click on the OK button.

Notice that the AP_R entry is at the bottom of the Rapide Type Interfaces section of the shwif window. This is a minor problem, because we will be using the AP_R definition in our modifications of the APPLICATION_PROGRAM and RESOURCE type interfaces and a type can only be used after it is declared. Therefore, we will need to reorder the list of interface types. Thus, be aware that the following instructions are a bit unusual because raparch doesn't have a nice way of reordering the interfaces yet.

Adding a Service to the Application Program Interface

In box mode, press the <Control>-RightButton on the application program component in the canvas. Modify the actions and services section to look like:

service R : AP_R;

And modify the behavioral rules section to look like:

start =>
  animation_Iam("APPLICATION_PROGRAM")
   -> R.Request();;

R.Result() =>
  if ( $i < 4 ) then
    i := $i + 1;
    R.Request();
  end if;;

Don't close (or "OK") the APPLICATION_PROGRAM type interface window yet. Instead, first go over to the shwif window and select the APPLICATION_PROGRAM entry of the Rapide Type Interfaces section. Then click on remove. Then go back to the APPLiCATION_PROGRAM type interface window and click on OK. Notice it is saved back in the shwif window after the AP_R interface.

Adding a Service to the Resource Interface

Similarly, open up the RESOURCE type interface window. And make the following modifications to its actions and services and its behavioral rules sections:

service AP : dual AP_R;

start => animation_Iam("RESOURCE");;
AP.Request() => AP.Result();;

Don't forget to remove the resource from the shwif window before closing the resource interface window. Then click on DONE of the shwif window.

Modifications to Main to reflect Services

Finally, we will make minor modifications to the main architecture to reflect the changes we have made. Again, in up arrow mode, press <Control>-RightButton on an empty portion of the canvas. In the architectural connections section, change from:

AP.Request() => R.Request();
R.Result() => AP.Result();

to

AP.R => R.AP;

Click on OK.

Analysis of Architectures with Services

Save state with the Save As option of the File menu and generate the Rapide code with the latest modifications under the name main3.arch and main3.rpd. Then, compile and execute main3 via:

% r.cleanlib
% rpdc -M main -o main3 main3.rpd
% main3
% raptor -a main3.arch main3.log &
% pov main3.log &

Adding a Second Resource

The next upgrade we will make to our prototype is add a second resource to our architecture.

Adding Service for Second Resource to the Application Program Interface

Modify the actions and services section of the application program to look like:

service
  R1 : AP_R;
  R2 : AP_R;

And add the following declaration to the behavioral declarations section:

action generate_requests();

And modify the behavioral rules section to look like:

start =>
  animation_Iam("APPLICATION_PROGRAM")
   -> ( R1.Request() || R2.Request() );;

R1.Result() ~ R2.Result() =>
  if ( $i < 4 ) then
    i := $i + 1; 
    generate_requests();
  end if;;

generate_requests() =>
  ( R1.Request() || R2.Request() );;

Second Resource Component

Create another resource component at the bottom of the canvas for the second resource. Modify the modules properties of the resource component: 1) module name to be "RESOURCE2" and 2) module label to be "Resource2."

Open up the RESOURCE2 type interface window. And make the following modifications to its actions and services and its behavioral rules sections:

service AP : dual AP_R;

start => animation_Iam("RESOURCE2");;
AP.Request() => AP.Result();;

Create a path between the application program and resource2 components.

Modifications to Main to reflect Second Resource

Finally, we will make minor modifications to the main architecture to reflect the changes we have made. Again, in "selection tool" mode, press <Control>-RightButton on an empty portion of the canvas. In the architectural declarations section add:

R2 : RESOURCE2;

And modify the architectural connections section to be: to

AP.R1 => R.AP;
AP.R2 => R2.AP;

Analysis of Architectures with Second Resource

Save state with the Save As option of the File menu and generate the Rapide code with the latest modifications under the name main4.arch and main4.rpd. Then, compile and execute main4 via:

% r.cleanlib
% rpdc -M main -o main4 main4.rpd
% main4
% raptor -a main4.arch main4.log &
% pov main4.log &

System Architecture Service Set Extension

We will extend the the system to use sets (or more precisely lists) of services.

AP_R Service Modification

The AP_R service must be modified to work around an implementation deficiency in the current Rapide compiler's ability to match certain kinds of patterns. The "bug" occurs when the compiler attempts to match patterns of the form: (?Idx : Integer) Service_Set(?Idx).Action_Name(). The work-around is to use a parameter of the event to record the index. Thus, instead of writing the previous pattern we will write:

(?Idx : Integer)
Service_Set(?Idx).Action_Name(?Idx)

Therefore, we will modify the actions of the AP_R service interface to include a parameter that will record the appropriate service set index value.

action
  out  Request(index : Integer);
  in   Result(index : Integer);

Application Program Component

Use the shwif window to modify the type interface for the application program.

Actions and Services

The application program will now contain a set of services for connecting to the a set of resources.

service Rs(1..2) : AP_R;

Behavioral Rules

start =>
  animation_Iam("APPLICATION_PROGRAM")
   -> generate_requests();;

[ !i in 1..2 rel ~ ] Rs(!i).Result(!i) =>
  if ( $i < 4 ) then
    i := $i + 1; 
    generate_requests();
  end if;;

generate_requests() =>
  [ !i in 1..2 rel || ] Rs(!i).Request(!i);;

Resources Component

Modify the Resource component of the main architecture to be named "RESOURCES" and labelled "Resources." Then modify the type interface name from "RESOURCE" to "RESOURCES", and the actions and services section to be:

service AP(1..2) : dual AP_R;

Modify the behavior section to be:

start =>
  animation_Iam("RESOURCES");;

(?i : Integer) AP(?i).Request(?i) =>
  AP(?i).Result(?i);;

Main Architecture

AP : APPLICATION_PROGRAM;
Rs : RESOURCES;
AP.Rs => Rs.AP;

Analysis

Save state with the Save As option of the File menu and generate the Rapide code with the latest modifications under the name main5.arch and main5.rpd. Then, compile and execute main4 via:

% r.cleanlib
% rpdc -M main -o main5 main5.rpd
% main5
% raptor -a main5.arch main5.log &
% pov main5.log &

Resources Component (Sub)Architecture

Now we will give the resources component an (sub)architecture for its implementation.

Modify the modules properties of the resources component: 1) module name to be "SOME_RESOURCES" and 2) module label to be "Some\nResources."

In "Selection tool" mode, double click on the Some Resources component to bring up subarchitecture. Draw two components, one named "RESOURCE" and labelled "Resource" and the other named "RESOURCE2" and labelled "Resource2." Add paths from the components to the top of the canvas. Modify the architecture description for Some Resources: Return Type Interface is RESOURCES Declarations in architecture include:

action animation_Iam(name: string);
R  : RESOURCE;
R2 : RESOURCE2;

and architectural connections include:

  AP(1) => R.AP;
  AP(2) => R2.AP;
initial
  animation_Iam("SOME_RESOURCES"); 

Modify the RESOURCE (and RESOURCE2) type interfaces found in the shwif window. Modify the behavior rules to be:

(?i : Integer) AP.Request(?i) =>
  AP.Result(?i);;

Modify the architecture description of "MAIN" to include: Rs : RESOURCES is SOME_RESOURCES();

Save state with the Save As option of the File menu and generate the Rapide code with the latest modifications under the name main6.arch and main6.rpd. Then, compile and execute main4 via:

% r.cleanlib
% rpdc -M main -o main6 main6.rpd
% main6
% raptor -a main6.arch main6.log &
% pov main6.log &

Variable Number of Resources

All of the previous examples assumed the system had only one or two resources. This section will prototype a family of systems (or style) where the number of resources varies. In particular, the number of resources will be defined by a command line parameter when the associated executable is run.

Modification to Application Program

Since the number of resources will vary, the number of AP_R "services" the application will use must be appropriately modified. We modify the APPLICATION_PROGRAM from being an interface type to an interface type constructor containing a formal parameter that declares the number of resource services. Use the shwif to modify the APPLICATION_PROGRAM interface from:

APPLICATION_PROGRAM

service Rs(1..2) : AP_R;

[!i in 1..2 rel ~] Rs(!i).Result(!i)
=>

[!i in 1..2 rel || ] Rs(!i).Request(!i);;

to

APPLICATION_PROGRAM(n : Integer)

service Rs(1..n) : AP_R;

[!i in 1..n rel ~] Rs(!i).Result(!i)
=>

[!i in 1..n rel || ] Rs(!i).Request(!i);;

Modification to RESOURCES Interface

Similarly, modify the RESOURCES interface from:

RESOURCES

service AP(1..2) : dual AP_R;
to
RESOURCES(n : Integer)

service AP(1..n) : dual AP_R;

Modification to SOME_RESOURCES Interface

Unfortunately, a bug in the raparch tool requires that to make modifications to the Some Resources component you must first delete and then recreate it!

Must modify name to SOME_RESOURCES(n : Integer) in several places... Then modify the architecture description to return the type interface: RESOURCES(n) and to be:

action animation_Iam(name: string);
Rs : array[Integer] of RESOURCE
       is (1..n, default is new(RESOURCE));
  for i : Integer in 1..n generate
    AP(i) => Rs[i].AP;
  end generate;
initial
  animation_Iam("SOME_RESOURCES");

Note: Experiment by using different connections here. In particular, look at the poset produced using "=>" and "||>". Details of connections may be found in the Architecture LRM, but we will give a brief overview of Rapide connections:

A connection consists of an optional sequence of outermost placeholder declarations, a trigger to the left of an operator, an operator (to, => or ||>) and a body to the right of the operator. The scope of the outermost placeholder declarations extends to the end of the rule.

A connection rule with the to operator is called a basic connection.

A connection rule with the => operator is called a pipe connection.

A connection rule with the ||> operator is called an agent connection.

A connection rule between service names is called a service connection.

In general, connections execute by searching for matches to its trigger. Upon finding a match and associated binding of outermost placeholders, the body of the connection will be generated. The relationships among the events of the body, the triggering events, and other events generated by the connection are dependent upon whether the connection is basic, pipe or agent.

Basic Connections

The generated event is causally and temporally equivalent to the triggering event.

Pipe Connections

The generated event(s) are all causally dependent upon all triggering events, and upon all events generated by previous executions of the connection.

Agent Connections

The generated event(s) are all causally dependent upon all triggering events, but they are (otherwise) causally unrelated to the events generated by previous executions of the connection.

Service Connections

A (basic|pipe|agent) service connection between dual services, say S and R, results in a set of (basic|pipe|agent) connections, one for each pair of constituents with the same name in the two services.

Duality is the Rapide way of denoting the gender of the services. To connect two components together through services, the trigger and body of the rule must be service names and the service named in the body must be of the dual type of the type of the service named in the trigger.

A connection between services is usually bi-directional in the sense that constituents from each service will be action names in a trigger of a connection defined by the service connection.

Modification to Main Architecture

n  : Integer is String_to_Integer( Arguments[1] );
AP : APPLICATION_PROGRAM(n);
Rs : RESOURCES(n) is SOME_RESOURCES(n);

Note: Experiment using different connections here also. In particular, look at the poset produced using "=>" and "||>".

Further Modification to Resource and Some Resources Components

To associate a particular Raptor Resource component with a specific component in the Rapide code, a little extra code needs to be written. As you may have already guessed, the RESOURCE component must generate an appropriate "animation_Iam" event with the same string that the architecture picture used to name the component. This means the RESOURCE interface must be given an appropriate "index" at run-time to specify which index it has in the RESOURCES's array of RESOURCES.

Modification to RESOURCE Interface

We chose to define an "in" action that provides the "index". Modify the actions and services section to include:

action in Init(i : Integer);
Also, modify the behavior to inclued:

(?i : Integer) Init(?i) =>
  animation_Iam("RESOURCE" & Image(Integer, ?i));;

Modification to RESOURCES Interface

Add the following to the connections:

animation_Iam =>
  for i : Integer in 1..n generate
    Rs[i].Init(i)
  end generate;

Run-time Interaction with Application Program

  action
   Continue(),
   Parallel_Requests(),
   Single_Request(i : Integer);
  bool : var Boolean; 
  str : var String; 
  /*
After a Start or Continue event, decide to quit or generate either a single resource request or requests to all resources in parallel.
  */
  Start or Continue =>
   IO.Cput("Which resource to use?  [-1] quit, [0] all in parallel");
   str := IO.Cget();
   bool := Is_Image(Integer, $str);
   while ( not $bool ) do
    IO.Cput($str & " is not an integer, enter an integer.");
    str := IO.Cget();
    bool := Is_Image(Integer, $str);
   end do;
   if ( Value(Integer, $str) = 0 ) then
    Parallel_Requests();
   elsif ( Value(Integer, $str) > 0 ) then
    Single_Request( Value(Integer, $str) );
   end if;;

  /*
After making the decision generate a single resource request.
  */
  (?i : Integer) Single_Request(?i) ||> Rsrcs(?i).Request();;

  /* 
After making the decision generate a request to each (1 to NumRsrcs) of the resources in parallel.
  */
  Parallel_Requests ||>
   [!i in 1..NumRsrcs rel || ] Rsrcs(!i).Request();;

  /* 
If the AP made a single request and received a result back or if the AP made parallel requests and received NumRsrcs results back, then continue.
  */
  ((?i : Integer) Single_Request(?i) ~ Rsrcs(?i).Results) or (1)
   (Parallel_Requests
    ~ [!i in 1..NumRsrcs rel ~] Rsrcs(!i).Results)
  ||>
   Continue();;