Introduction

Data requests (fetch, save, etc) and the responses to these requests (some data, errors etc) are central to applications. The OERA separates concerns, so that the requestor (ie UI) is part of the Presentation Layer, while the responder is in the Business Components layer. These layers are typically on either side of an AppServer boundary, and the OERA provides for service adapter and service interface pairs which are provide the API for calling across session boundaries. Anyone who wants data from a service, needs to get it via a Service Adapter and its associated Service Interface. The data requests and responses are called Service Messages in the OERI, and can be specialised by type of request, and by the type of data store (dataset etc). These messages are managed by a Service Message Manager.

The Service Message and associated functionality is part of the OERA Common Infrastructure. This is because

  1. Requests span OERA layers, and only the Common Infrastructure is accessible by all layers in the OERA
  2. Data - or service - requests are not made solely by the UI in the Presentation Layer. Services can themselves make requests to other services. The Enterprise Services layer may also make service requests.

In a layered architecture like the OERA where each layer only talks to the next layer one will often find that service requests need to be passed through many layers. With a traditional ABL approach this is typically accomplished by passing the same parameters through several APIs. This approach will prevent operational responsiveness since a small change to the request may require adding new parameters to public APIs in all layers. This can in some cases be resolved by an overload that calls the old API, but this is not always practical.

One way to limit the problem is to define the main request APIs as final and defining events or hooks for any additional behavior. This way one can add alternative overloads that calls the same hooks and events without losing behavior. This will still require changes to public APIs and to the code that calls the APIs .

One way to minimize this problem is to pass the message as an object (parameter object). This way the parameter values are stored as properties in the class allowing the object through the layers instead of the actual parameters; and will support new information by adding a property to the paramter object. This means that only the consumer - to read the property - and the producer - to write  the property - need to change, while all the layer APIs remain unchanged.

The fact that the objects are passed by reference means that this approach is more efficient than traditional parameters that passes scalar data by value. The parameter class will scale better the more layers and more data that is passed. The overhead of creating the parameter instance on each message will offset this benefit, but this can in many cases be avoided by reusing message instances. This approach is not without issues as the parameter class cannot be passed over a session making it necessary to translate or transform the object to a form than can be passed with parameters over a session.

Service Message

Service messages implement the OpenEdge.CommonInfrastructure.Common.ServiceMessage.IServiceMessage interface. There are also IServiceRequest and IServiceResponse interfaces in the same package; these interfaces are empty, and they exist only as a means to identify the type of message to the compiler and programmer.

These interfaces provide the following contract:

Member NameTypeComment
MessageId Character Unique message identifier. Allows for completely decoupled request and response messages to be tied back together. A GUID by default.
ActionType

ServiceMessageActionEnum (in the OpenEdge.CommonInfrastructure.Common.ServiceMessage namespace)

The type of action to be performed on the service associated with this request.
Service Character Identifies the service message target. Used to find the ServiceAdapter and Business component (entity, task, workflow). This property is a character (as opposed to a Progress.Lang.Class instance) since it (a) needs to be passed across a session boundary, and (b) may be passed to a non-ABL session. In the OERI

Message actions (types)

There are 3 message actions (or types) defined in the OERI, in the OpenEdge.Core.System.ServiceMessageActionEnum enumeration.

Message ActionDescription
FetchData Retrieve data from the service's data source, via the Business Component layer
SaveData Commit data to the service's data source, via the Business Component layer
FetchSchema Retrieve the metaschema the service uses from the Business Component layer. An example would be the schema of the ProDataSet used in a Business Entity for use in a Model.
ACK Service layer Acknowledgement of request. Used for async requests.
PerformWorkStep Performs a Business Workflow
PerformWorkFlow Performs a Business Workstep
UserLogin Performs a UserLogin action (used by the Security Manager)
UserLogout Performs a UserLogout action (used by the Security Manager)
ValidateSession Validates the passed user credentials (used by the Security Manager)

Class diagram

The class diagram below shows the specialisation of service messages by action and data store. Note that the generic IServiceRequest and IServiceResponse interfaces don't appear on the diagram.

Decoupled request and response

The ABL makes it straightforward to manage and retrieve data from another session. But the fact that it is easy to implement does not necessarily mean that it is easy to change. It is for example quite common to implement request APIs that return data directly. For instance,

FetchData(input cRequest, output table-handle hResponse).

does not really cause any problem by itself, but if you implement callers and overrides that depend on the bundling of request and response you might find it hard to decouple later if for example the requests needed to be bundled with other data requests.

The OERI separates request and response by defining separate request and response classes as well as separate APIs. Consumers of the data (such as Models) have a dedicated GetData method to deal with all responses, which allows the requests to be issued in many different ways.

The ultimate benefit of this separation is that it should be easy to extend it to use asynchronous requests.

Data request flows in the Presentation Layer outlined.

ServiceMessageManager

The ServiceMessageManager provides an interface through which to create and execute messages, with botha  synchronous and asynchronous API. It implements the OpenEdge.CommonInfrastructure.Common.IServiceMessageManager interface. . The ServiceMessageManager is controlled (managed) by the ServiceManager.

IServiceProvider

All requests are made through objects that implement the OpenEdge.CommonInfrastructure.Common.ServiceMessage.IServiceProvider interface: this allows the ServiceMessageManager to simply call the ServiceManager's GetServiceProvider() API and execute the request on that object. Note that the ServiceMessageManager has no idea of where the service is implemented - that is the responsibility of the implementing object.

Broadly speaking, in OERA terms there are 2 kinds of IServiceProviders: service adapters and service interfaces. The former are usually on a client and are used to call across session boundaries, such as an AppServer. Service interfaces are implemented in the business logic (or other) layers and execute the requests against the Business Components (entities, workflows, what have you). Note that there is no requirement that a service adapter be implemented on the client and the service interface on the server. The IServiceProvider implementation for a service in the Business Components layer could be another Service Adapter to an external service, such as a WebService. Note furthermore that the ServiceMessageManager doesn't care about any of this - all it wants is a service provider to pass the request into.

How then, does the application decide what the concrete or implementation of the service provider is? AutoEdge|TheFactory uses the InjectABLdependency injection container to store this information, and the relevant service provider will be returned depending on what bindings (or mappings) have been established.

The request/response flow is shown graphically in the attached document.

OERA_Service_Messages.pdf