Guru: Flexible Interfaces
November 1, 2021 Ted Holt
The details are murky, it’s been eons ago. Probably the mid-1990’s. I was working on an AS/400 that ran a mixture of System/36 and native applications. I needed to call a program that had been written in the latest version of RPG from both S/36 RPG II and native RPG III (a.k.a. RPG/400) programs. I hope I’m remembering this correctly. It’s been so long.
The problem I ran into was rooted in a numeric parameter. S/36 programs passed numeric parameters in zoned decimal format, whereas native RPG and CL programs used packed decimal. The called program defined the parameter as packed decimal. I could have changed the called program and the native callers to use data structures to define the parameter as zoned decimal, but the idea didn’t thrill me. I ended up changing the called program to accept the parameter in either packed decimal or zoned decimal format.
Such was my introduction to flexible interfaces, and it was only an introduction. Through the years, I’ve had to provide interface alternatives from time to time. Fortunately, it is not difficult.
The First Decision
When faced with the need for more than one interface into a routine (program or subprocedure), the first choice you must make is whether or not to use a wrapper. A wrapper is a routine that stands between a caller and another, incompatible routine.
I’ll stick to the same example to illustrate. Here’s part of program CUS004R, which accepts a seven-digit customer account number as packed decimal.
dcl-pi *n; inCustomer packed(7) const; end-pi; // Use inCustomer to do something.
I need to call this program from an RPG II program, which cannot pass packed-decimal parameters.
Here are the salient pieces of wrapper program CUS005R, which bridges the gap between the two.
**free dcl-pi *n; inCustomer zoned(7) const; end-pi; dcl-pr CUS004R extpgm; inCustomer packed(7) const; end-pr CUS004R; dcl-s Customer packed(7); Customer = inCustomer; CUS004R (Customer); return;
CUS005 receives the customer number in zoned decimal format, reformats the value into packed decimal, and calls CUS004R.
In this the case, the parameter is passed as input only. That is, CUS004R does not change it. If there were input-output (updateable) or output-only parameters, wrapper program CUS005R would have reformat them from packed to zoned decimal before returning to caller CUS006R.
Finally, here’s caller program CUS006R, which passes zoned-decimal parameters. (I don’t have access to a system with the S/36 environment at present, so I use RPG III instead.)
I DS I 1 70CUSTNO * C CALL 'CUS005R' C PARM CUSTNO
Voilà! CUS006R calls CUS004R indirectly using wrapper CUS005R.
Using a wrapper is a perfectly good programming technique. If you look through the alphabetic list of APIs in the API Finder, you’ll often see two names — a short one in all caps and a longer one in mixed case — for an API. An example is QSYRTVFI/QsyRetrieveFunctionInformation. I understand that in many (most? all?) cases, the short name is a program wrapper over a procedure of the longer name. In other words, you can access the same routine as a program call or a subprocedure call. If you use the Display Program (DSPPGM) command to view information about QSYRTVFI, you’ll find procedure QsyRetrieveFunctionInformation inside service program QSYFNUSG.
But you may prefer not to use a wrapper. Instead, you can make CUS004R receive a customer account number in either packed or decimal format.
dcl-ds CustomerParm qualified based(P1); Packed packed(7) pos(1); Zoned zoned (7) pos(1); end-ds Customerparm; dcl-pi *n; inCustomer char(8); end-pi; dcl-s SelectedCustomer packed(7); dcl-s IsValid ind inz(*on); P1 = %addr(inCustomer); monitor; SelectedCustomer = CustomerParm.Packed; on-error; IsValid = *off; endmon; if not IsValid; monitor; SelectedCustomer = CustomerParm.Zoned; on-error; IsValid = *off; endmon; endif; if not IsValid; // handle the error endif;
And that’s what I did those many years ago. IBM had not yet given us the MONITOR op code, so I used something, probably the TESTB opcode, to figure out which type of data I had. The principle is the same.
If you decide not to use a wrapper, you come to . . .
The Second Decision
Will the parameter format be implicit or explicit? Implicit means that the called routine itself determines how the parameter is formatted. Explicit means that the caller must tell the called routine what the parameter looks like. Both methods are valid, and I don’t consider one any better than the other.
In the last example, the parameter format was implicit. For an example of an explicit definition, I once again turn to IBM APIs. The Retrieve Object Description API (QUSROJBD) can return data in four formats. The caller uses the third parameter to tell the API which format to use — OBJD0100, OBJD0200, OBJD0300, or OBJD0400. If new data formats are needed someday, IBM will have no trouble adding them.
That’s not the only explicit parameter definition in that API. The sixth parameter, error code, supports various formats, and the caller loads the bytes-provided subfield of a data structure to specify how to handle an exception.
Do You Really Need It?
To write a wrapper or not, to explicitly or implicitly describe the data, these are not life-or-death questions. Whatever you decide, the result will probably work fine. Either way you get less duplicated source code, and your routine can adapt more easily to organizational changes.
However, don’t forget the YAGNI principle. Perhaps you’re writing a new Web API. Should you support every data format you can think of — XML, JSON, CSV, pipe-delimited, ad infinitum ad nauseum? I don’t think so. I would suggest supporting only the one(s) you know you’ll need and wait for the users of your API to request others. Who knows? A few years from now, you might have to add support for data formats that have not been invented yet.
According to Bernard Ighner, Everything Must Change. As much as we might like to freeze the status quo at a pleasant equilibrium, the world doesn’t work that way. This applies to information systems as much as anything, and like life in general, the skill we need is not to predict the future but to design our applications to be open to change. Allowing for flexibility in the passing of parameters can be part of that design.