Showing posts with label recursive design. Show all posts
Showing posts with label recursive design. Show all posts

Monday, 1 June 2009

Semantic Shift

Semantic shift occurs when a data value of one data type is converted into a data value of another data type. All such conversions require a semantic shift mapping. OOA09 specifies a set of predefined semantic shift mappings which can be used within a domain or between two different domains. OOA09 also allows counterpart mappings and user-defined semantic shift mappings to be defined on a bridge. All semantic shift mappings are implicitly invoked during Action Language assignment or parameter passing. Counterpart mappings and user-defined semantic shift mappings can only be implicitly invoked within bridge mappings and domain observers since they always involve data types from different domains. Although there is no Action Language cast operator, a declare statement can be used to perform an explicit semantic shift, e.g.

declare n as Real;
declare integer_n as Integer = n;

There are many situations where a semantic shift mapping can result in an error. If a user wants to handle the error within their Action Language code then the semantic shift must be performed between a mandatory attribute or data item and a conditional attribute or data item, e.g.

declare text as String = "Hello World";
declare unsafe_text as empty or one String = text;

// The following generates a run-time error!
declare unsafe_n as Integer = text;

// The following is entirely safe
// as long as the necessary checks are performed
declare safe_n as empty or one Integer = text;
if (empty safe_n)
   // Handle syntax or out of range error...
end if;

// The following generates a run-time error!
declare unsafe_m as empty or one Integer = unsafe_text;

// The following is entirely safe
// as long as the necessary checks are performed
if (empty unsafe_text)
   // Handle missing text...
end if;
declare safe_text as String = unsafe_text;
declare safe_m as empty or one Integer = safe_text;
if (empty safe_m)
   // Handle syntax or out of range error...
end if;
In addition to type specific errors, a semantic shift between a conditional attribute or data item and a mandatory attribute or data item always generates a run-time error when the source value is empty, i.e. a NullPointerException in Java.

It is important to note that bridges do not define their own data types. All data types referenced within a bridge mapping are from either the client or server domain. All data types referenced within a domain observer are from one of the domains defined within the domain observer's enclosing project. It is also worth noting that the only data stored in a bridge is cached counterpart mappings. The Shlaer-Mellor concept of semantic shift was first discussed in [Wormhole96].

The Executable UML core types (Boolean, String, Integer, Real, Date, Timestamp and Arbitrary_ID) which are all predefined types in OOA09 are not global types. However, there are predefined semantic shift mappings between the same core types in all domains, i.e. core types from all domains can be used interchangeably.

There are predefined semantic shift mappings between all boolean types since all boolean values have the same semantics or meaning in OOA09 even if they have different aliases. There is also a predefined mapping to String which converts a boolean value to the boolean type's notation independent alias for that value, e.g. Boolean true value maps to "True" (not Shlaer-Mellor "TRUE" or Executable UML "true"). The reverse String to boolean type mapping is also predefined where aliases or the hard-coded "True" or "False" names are fuzzy matched according to standard name comparison conventions (see Naming).

There are no predefined semantic shift mappings between any enumerated types since each legal value has its own unique meaning. A user-defined semantic shift mapping (which should always use a switch statement for safety) must be defined whenever any such conversion is required. However, there is a predefined mapping to String which converts an enumerated value to the enumerated type's notation independent name for that value. The reverse String to enumerated type mapping is also predefined where legal value names are fuzzy matched according to standard name comparison conventions.

There are no predefined semantic shift mappings between symbolic types in general since each symbolic type is assumed to have unspecified but definite semantics. The only exception involves the core type String which has no syntax, semantics or length restrictions. All symbolic types have a predefined semantic shift mapping to String. The reverse String to symbolic type mapping is also predefined. However, an error will occur if a target pattern is not matched or text length is invalid.

There are no predefined semantic shift mappings between numeric types in general since each numeric type is assumed to have unspecified but definite semantics. However, there are predefined semantic shift mappings between all numeric types and several core types including String, Integer and Real since core types are assumed to have no domain specific semantics. Additionally, there are predefined semantic shift mappings between all numeric types with a Time value format and the core types Date and Timestamp. There are also predefined semantic shift mappings going in the reverse direction in both cases. For all numeric values, truncation of decimal places may occur during a semantic shift. For time values, scaling up or down of time units may also occur during a semantic shift. However, one should note that time values in OOA09 are always stored as universal time (i.e. GMT) values relative to the Java Date Epoch (1 January 1970).

There are predefined semantic shift mappings between all arbitrary ID types and several core types including String and Integer. There are also predefined semantic shift mappings going in the reverse direction. These are in addition to the predefined semantic shift mappings between Arbitrary_ID core types in all domains. However, great caution should be used when passing arbitrary ID values around since arbitrary ID attributes may have their values reallocated automatically whenever object instances are created or deleted or whenever related object instances are assigned new arbitrary ID values.

There are no predefined semantic shift mappings between object instance types. However, a counterpart mapping can be defined between an object instance in one domain and an object instance in another domain. A counterpart mapping operation is invoked whenever a specific object instance in the client domain is shifted for the first time. The operation is expected to return the counterpart object instance in the target domain. The operation may return nothing if the counterpart mapping is conditional. However, the operation will not be invoked again in the future. Once a counterpart mapping has been established between two object instances then the link remains in a bridge cache until either of the object instances is deleted. This is not a flexible mapping for dynamic counterpart relationships. It is equivalent to joining two classes in different packages using multiple inheritance. It creates a permanent link which is only broken when one part is deleted. Although counterpart mappings are one-way, a counterpart mapping and it's reverse mapping share the same bridge cache. Once a link has been formed in either direction, a semantic shift can be performed in both directions as long as counterpart mappings were defined in both directions. Dynamic counterpart relationships can still be created using user-defined semantic shift mappings between object instance types. However, such mappings are always re-invoked when a semantic shift is requested, i.e. counterpart mappings will always be much faster.

There are no general purpose predefined semantic shift mappings involving event instance, return coordinate or transfer vector types. However, an event instance type for a solicit event parameter of a request wormhole has a predefined mapping to a transfer vector type for an event data item or input parameter of a control reception point (see Asynchronous Communications between Domains). In the opposite direction, a transfer vector type for a self parameter of an asynchronous return wormhole has a predefined mapping to an event instance type for a self parameter of an asynchronous return mapping.

There are predefined semantic shift mappings between all data types and all external types (in another domain) since converting an arbitrary data value into an external value only involves wrapping the original data value as an external value. No information is lost in the process. However, the reverse mapping is not so straightforward. If an external type has only been mapped from a single data type (across all bridges) then the reverse mapping is entirely safe. Otherwise, the reverse mapping is only safe if the wrapped data value has exactly the same type as the target type. An error is generated if the actual type does not match the target type.

User-defined semantic shift mappings can only be defined in a bridge between client and server data types. An analyst should not need to define a mapping between two data types within the same domain. User-defined semantic shift mappings between boolean types should never be defined since boolean value semantics are fixed across all domains. User-defined semantic shift mappings involving core types should also never be defined since core value semantics are also fixed across all domains. A user-defined semantic shift mapping between a pair of object instance types should not be defined if a counterpart mapping for that pair is already defined. User-defined semantic shift mappings should also not involve return coordinate or transfer vector types. Finally, user-defined semantic shift mappings must never involve external types since the predefined semantic shift mappings involving external types always take precedence. Given these restrictions and rules, user-defined semantic shift mappings should never overlap any of the predefined semantic shift mappings.

Semantic shift mappings are summarized in the following table:

Source TypeTarget TypeSemantic Shift Mapping
Boolean TypeBoolean Typepredefined
Boolean TypeStringpredefined
StringBoolean Typepredefined
(error if boolean value not fuzzy matched)
Enumerated TypeStringpredefined
StringEnumerated Typepredefined
(error if legal value not fuzzy matched)
Symbolic TypeStringpredefined
StringSymbolic Typepredefined
(error if pattern not matched or length invalid)
Numeric TypeStringpredefined
Numeric TypeIntegerpredefined
(truncate decimal places)
Numeric TypeRealpredefined
Numeric Type
(with Time value format)
Datepredefined
(scale up or down to match time units, truncate decimal places)
Numeric Type
(with Time value format)
Timestamppredefined
(scale up to match time units, truncate decimal places)
StringNumeric Typepredefined
(error if not real or out of range, truncate to maximum decimal places)
IntegerNumeric Typepredefined
(error if out of range)
RealNumeric Typepredefined
(error if out of range, truncate to maximum decimal places)
DateNumeric Type
(with Time value format)
predefined
(error if out of range, scale up or down to match time units)
TimestampNumeric Type
(with Time value format)
predefined
(error if out of range, scale down to match time units)
Arbitrary ID TypeStringpredefined
Arbitrary ID TypeIntegerpredefined
StringArbitrary ID Typepredefined
(error if not integer or out of range)
IntegerArbitrary ID Typepredefined
(error if out of range)
Arbitrary_IDArbitrary_IDpredefined
Object Instance TypeObject Instance Typecounterpart mapping
(error if no counterpart and mapping not conditional)
Event Instance Type
(for solicit event parameter of request wormhole)
Transfer Vector Type
(for event data item or input parameter of control reception point)
predefined
Transfer Vector Type
(for self parameter of asynchronous return wormhole)
Event Instance Type
(for self parameter of asynchronous return mapping)
predefined
Data TypeExternal Typepredefined
External TypeData Typepredefined
(error if actual type not target type)
Data TypeData Typeuser-defined
(error if no return value and mapping not conditional)

Sunday, 24 May 2009

Asynchronous Communications between Domains

This technical note should be read after Synchronous Communications between Domains since I have tried to avoid repeating myself too much here. Most asynchronous communications between domains involve:

  • request wormholes (domain-crossing events or bridging processes) in client domains,
  • control reception points (external events or synchronous services) in server domains,
  • and request mappings in bridges.
However, asynchronous communications may also involve:
  • solicit event parameters on request wormholes,
  • asynchronous return wormholes in server domains,
  • asynchronous return mappings in bridges,
  • and transfer vector types.

Request wormholes are associated with a terminator in a client domain and include domain-crossing events and bridging processes. A domain-crossing event may have any number of carried data items. A bridging process may have any number of user-defined input or output parameters. However, output parameters are not relevant here when dealing with asynchronous communications. Bridging processes are normally used for synchronous communication. However, they may also be used for asynchronous communication as long as a synchronous return mapping exists.

Control reception points are associated with a terminator in a server domain and include external events and synchronous services. External events indicate internal events (state model, domain-crossing or polymorphic events) that can arrive from outside a given domain via a specific terminator. Events may have any number of carried data items. Synchronous services may have any number of user-defined input or output parameters. However, output parameters are not relevant here when dealing with asynchronous communications. Unlike bridging processes, synchronous services may be used in a purely asynchronous manner since the results may not be required by a particular client domain. Dummy return coordinates are passed to synchronous services when they are invoked asynchronously allowing the server domain to execute a dummy synchronous return unaware that it has been invoked asynchronously.

A request mapping in a client-server bridge is implemented using an Action Language statement block. It maps a request wormhole to a control reception point. If the request wormhole is a domain-crossing event then the carried data items are passed as input parameters into the request mapping. Otherwise, the input parameters of the bridging process are passed into the request mapping. Request mappings have no output parameters. The code in a request mapping must either generate the associated external event or invoke the associated synchronous service. It may also decide to do neither without consequence. Output parameters should not be accessed when invoking a synchronous service asynchronously. Any attempt to do so is a static coding error. There may be any number of request mappings for a given request wormhole.

External events may be both unsolicited and solicited. Request mappings to external events define unsolicited events which either represent a new thread of control within the server domain or extend the existing thread of control associated with the client domain's request wormhole. Solicited events within a client domain on the other hand extend an existing thread of control associated with a previously invoked request wormhole to a server domain. Solicited events are defined using solicit event parameters on request wormholes. A request wormhole (whether used synchronously or asynchronously) may have zero to many solicit event parameters. A solicit event parameter holds a complete event instance created in a client domain which is then passed to a server domain as a transfer vector. The transfer vector can then be used to invoke an asynchronous return wormhole in the server domain which as a consequence generates a solicited event in the client domain.

[Wormhole96] proposed that transfer vectors should encapsulate partial event instances with all event data items determined from asynchronous return wormhole input parameters. OOA09 doesn't allow partial event instances at all. It does allow an event instance to be created from another event instance allowing new event data item values to be supplied. It also allows event data items to be read from an event instance in the same way as attributes can be read from an object instance. This allows full or partial overriding of event data items on event instances. However, this always involves creating a new event instance since event instances are immutable in OOA09.

Solicited event parameters have a multiplicity and conditionality providing some indication as to how many solicited events are expected by the client domain. If more than one solicited event is generated when no more than one was expected then an error should be reported but the solicited event is still generated. If no solicited events are generated but at least one was expected and all transfer vectors go out of scope then an error should be reported. A solicited event is not automatically generated here. This behaviour differs from what happens when all return coordinates go out of scope and a synchronous return hasn't been invoked. This is because a missing asynchronous return is a protocol error which should be handled in the appropriate state models while a missing synchronous return is a permanently blocked concurrent thread.

An asynchronous return wormhole is an abstract process associated with a terminator in a server domain which may be invoked in any composed operation (action or synchronous service). It may have any number of user-defined input parameters. Asynchronous return wormholes have no output parameters. A self parameter with a transfer vector type is also automatically defined. However, explicit asynchronous return wormholes are not always required in OOA09 since transfer vectors may arrive in a domain in a complete state allowing a solicited event to be immediately generated without involving an asynchronous return mapping.

An asynchronous return wormhole is normally invoked on a transfer vector value using an invoke return statement, e.g.

transfer_vector.return(message:"Hello World");
However, an implicit asynchronous return wormhole involving a complete transfer vector (one not associated with an asynchronous return wormhole) is always invoked using an ordinary generate statement, e.g.
generate transfer_vector;
The generate statement is used in this case since an asynchronous return mapping is never involved, i.e. the transfer vector's event instance is immediately generated (or delayed if a delay clause is used).

An asynchronous return mapping in a client-server bridge is implemented using an Action Language statement block. It maps an asynchronous return wormhole to a solicited event parameter. It is invoked whenever an asynchronous return is performed using a partial transfer vector. No asynchronous return mappings are required (or possible) for complete transfer vectors. The input parameters associated with a asynchronous return mapping match the input parameters of the asynchronous return wormhole. Asynchronous return mappings have no output parameters. A self parameter with an event instance type is also automatically defined. The self parameter contains the event instance wrapped by the asynchronous return wormhole's transfer vector. It can be used immediately to generate a solicited event. However, some adjustment will normally be required using the input parameters. Any adjustment will involve creating a new event instance. The code in an asynchronous return mapping will normally generate the associated external event. However, it is not required to do so. If there are no asynchronous return mappings for an explicit asynchronous return wormhole then the partial transfer vector is treated like a complete transfer vector, i.e. a solicited event is automatically generated from the transfer vector's wrapped event instance. However, this should be reported as an error. This behaviour differs from what happens when no synchronous return mapping is specified. If you don't want an asynchronous return then you shouldn't pass a transfer vector into a control reception point.

A predefined partial transfer vector type is defined for each asynchronous return wormhole allowing partial transfer vectors to be safely passed around within a domain since the parameters associated with a partial transfer vector can be statically determined within Action Language code. A single predefined complete transfer vector type is also defined allowing solicited events to be generated without the need for an explicit asynchronous return wormhole or an asynchronous return mapping. The predefined complete transfer vector type is generic. The predefined partial transfer vector types are partially generic in that they can be used across multiple control reception points.

Monday, 18 May 2009

Synchronous Communications between Domains

All bridges between domains allow communications in both directions. Even though the domains being bridging are labelled as either client or server. Those roles may be reversed for a given communication. Explicit communications between domains are known as wormholes in Shlaer-Mellor OOA/RD. Wormholes were introduced in OOA96 and later defined in [Wormhole96] as an alternative to domain-crossing events and bridging processes. However, OOA09 integrates these concepts together to gain benefits from both approaches. All synchronous communications between domains involve:

  • bridging processes (request wormholes) in client domains,
  • synchronous services in server domains,
  • request mappings in bridges,
  • synchronous return wormholes in server domains,
  • synchronous return mappings in bridges,
  • and return coordinate types.

A bridging process is an abstract process associated with a terminator in a client domain which may be invoked in any composed operation (action or synchronous service). It may have any number of user-defined input and output parameters. A thread of control passes from one domain to another when a bridging process is invoked. A synchronous return must be invoked by a server domain to continue the original thread of control. Whether processing can still occur within the client domain while waiting for a synchronous return depends on how many concurrent threads have been allocated to the client domain.

A synchronous service (as defined in [SyncServ96]) is a composed operation implemented using an Action Language statement block or a process model. It is associated with a terminator in a server domain. It may have any number of user-defined input and output parameters. A special Return Coordinate parameter is also defined automatically. This parameter is required to invoke a synchronous return wormhole. Return coordinates can be passed on within the server domain as event data items or ordinary parameters and stored as attributes. However, they should not be stored for long since the client domain is waiting for a result. They can also be passed to other domains as external values. However, they can't be used to invoke synchronous returns in other domains.

A request mapping in a client-server bridge is implemented using an Action Language statement block. It maps a request wormhole (domain-crossing event or bridging process) to a control reception point (external event or synchronous service). A synchronous communication always requires a bridging process and a synchronous service. If a domain-crossing event is mapped to a synchronous service then any synchronous returns will be ignored. If a bridging process is mapped to an external event then no synchronous return is possible since new return coordinates are not passed to external events. However, it is still possible to carry an existing return coordinate via an event. The input parameters associated with a request mapping match the input parameters associated with the bridging process. Request mappings have no output parameters. The code in a request mapping must invoke the synchronous service converting bridging process input parameters to synchronous service input parameters.

There may be any number of request mappings for a given bridging process. However, only one synchronous return can be used to satisfy a given invocation. OOA09 allows multiple request mappings, all of which may invoke a synchronous return. Only the first response will be used to satisfy the request. Any additional responses will be ignored. Whether it is good design practice to allow multiple responses here is open to debate. If all return coordinates associated with the synchronous services invoked in response to a bridging request go out of scope then the bridging process invocation can terminate. If all output parameters associated with the bridging process are conditional or have default values then this is a recoverable error that should be flagged but is otherwise acceptable. If there are any mandatory output parameters without default values then this is a fatal error with serious consequences since the original thread of control can't continue. All such fatal errors can be eliminated from deployed systems by ensuring that all bridging process output parameters are either conditional or have a default value. A software architecture may place time limits or other restrictions on bridging processes as long as a default synchronous return is possible, e.g. a software architecture may break a deadlock by invoking default synchronous returns on deadlocked synchronous communications.

A synchronous return wormhole is an abstract process associated with a synchronous service (and as a consequence a terminator) in a server domain which may be invoked in any composed operation (action or synchronous service). The input parameters are automatically determined from the output parameters of the associated synchronous service. Synchronous return wormholes have no output parameters. A self parameter with a return coordinate type is also automatically defined. However, explicit synchronous return wormholes are not actually required in OOA09 since synchronous return mappings and return coordinate types are associated directly with synchronous services rather than synchronous return wormholes. The only benefit of an explicit synchronous return wormhole is that it can be labelled and shown separately on process models.

A synchronous return wormhole is normally invoked on a return coordinate value using an invoke return statement, e.g.

return_coordinate.return(message:"Hello World");
However, within a synchronous service statement block, a synchronous return wormhole can also be invoked using an ordinary non-empty return statement, e.g.
return message:"Hello World";
An empty return statement can't be used for this purpose even if there are no output parameters.

A synchronous return mapping in a client-server bridge is implemented using an Action Language statement block. It maps a synchronous service to a bridging process. It is invoked whenever an explicit or implicit synchronous return occurs. The input parameters associated with a synchronous return mapping match the output parameters of the synchronous service while the output parameters match the output parameters of the bridging process. The code in a synchronous return mapping must convert the synchronous service output values to bridging process output values. If there are no synchronous return mappings for a synchronous service then any synchronous returns will simply be ignored.

A predefined return coordinate type is defined for each synchronous service allowing return coordinates to be safely passed around within a domain since the parameters associated with a return coordinate can be statically determined within Action Language code. There is no generic return coordinate type in OOA09 as a consequence. However, that does not mean that return coordinates can't be passed to a service domain as external values since any return coordinates must be passed back to their server domains (requiring type verification) before they can be used.