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
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 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:
ServiceMessageActionEnum (in the OpenEdge.CommonInfrastructure.Common.ServiceMessage namespace)
Message actions (types)
There are 3 message actions (or types) defined in the OERI, in the OpenEdge.Core.System.ServiceMessageActionEnum enumeration.
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.
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.
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.
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.