As we move to service programs, OPM RPG programs get left out in the cold, but creative parameter use can keep them in the mix until you get a chance to convert them.
Encapsulating business logic is all about parameters. In the first article in this series, I covered optional parameters. In the second article, I introduced service programs into the mix by using a service program as an adapter to an existing RPG program with a complex parameter list. Service programs are the underlying building block for most advanced architectures on the IBM i. They allow you to group together related logic and then expose it to the outside world in many ways.
The parameter list of RPG is powerful in its own right thanks to its bidirectional parameters but is also fairly unique. The service program makes it easy to write procedures that are recognizable by the rest of the world, be it Java or SQL or ODBC. In fact, everybody can use a service program procedure—except our good old OPM RPG programs. And this article is going to show you how to let your old RPG programs join in the fun.
Acknowledging Reality
Converting an OPM RPG program to ILE isn't that hard, and adding a program call is an excellent opportunity to convert those programs. But the reality is that there are circumstances in which you may not want to convert your program. You may have programs that are working fine and have been working fine for ages, and there's just no need to add the complication of switching to ILE, even if it is just a straight-line conversion. All you want to do is add a call to a new program—say, a logger or a security check—without converting the program. The problem, though, is that the called program is a service program. In that case, it might be best to just bite the bullet and convert the old OPM program to ILE. However, in the rare circumstance where that's not an option, you can use a slightly different version of the Adapter pattern that we discussed in the second article. I think in this case seeing the code is probably the fastest way to the result, to let's go right to it.
d GetVolts pr 10u 0 extproc('GetVolts')
d iAmps 10u 0 const
d iOhms 10u 0 const
d GetAmps pr 10u 0 extproc('GetAmps')
d iVolts 10u 0 const
d iOhms 10u 0 const
d GetOhms pr 10u 0 extproc('GetOhms')
d iAmps 10u 0 const
d iVolts 10u 0 const
d OhmsLaw pi
d iVolts 10p 0
d iAmps 10p 0
d iOhms 10p 0
/free
select;
when iVolts = 0;
iVolts = GetVolts( iAmps: iOhms);
when iAmps = 0;
iAmps = GetAmps( iVolts: iOhms);
when iOhms = 0;
iOhms = GetOhms( iAmps: iVolts);
endsl;
*inlr = *on;
/end-free
This is the adapter program; it sits between the OPM program and the service program. The service program is OHMLAWSVC, and it implements Ohm's Law. If you're unfamiliar with Ohm's Law, it's pretty simple: given any two of volts, amps, or ohms (voltage, current, or resistance), you can calculate the third one. The calculations are very simple, and this program is almost certainly overkill, but I'm just using it to illustrate the technique.
The service program exposes the three procedures shown above: GetVolts, GetAmps, and GetOhms, each of which calculates one of the three values, taking the other two values as parameters. RPG III can't call any of these procedures directly, so we need the program OHMSLAW as an adapter. You call the program with three parameters, two of them having values while the third is zero. OHMSLAW checks the three input parameters, and if one is zero, it uses the other two to calculate it, calling the appropriate service program procedure.
This makes it very easy to call the service program from OPM RPG programs. You create a simple PLIST for the program, and then whenever you need to call it, you just fill the parameters and call the program. Let's take a case in which we want to calculate the resistance given the current and voltage. The calling code might look something like this:
C PLOHMS PLIST
C PARM PVOLTS 100
C PARM PAMPS 100
C PARM POHMS 100
(...)
C Z-ADDXVOLTS PVOLTS
C Z-ADDXAMPS PAMPS
C Z-ADD0 POHMS
C CALL 'OHMSLAW' PLOHMS
C Z-ADDPOHMS XOHMS
I move in the values (say, for the screen) for volts and amps. I set POHMS to zero to identify it as the value to be calculated and then call the program. And with that, the service program is available to any program, OPM or ILE, that needs it.
One additional point to observe is that while the service program uses the more modern variable type of unsigned integers (the 10u0 definition), RPG III programs are much more comfortable with packed decimal values. That could be another sticking point between the programs, so the adapter program not only switches calling conventions, but also maps the packed decimal parameters of the OPM world to the unsigned integers of ILE.
A More Realistic Example
The above example is fairly trivial, especially since the calculations are so basic. XOHMS is simply XVOLTS divided by XAMPS; you don't need to call an external program to do that calculation. But that doesn't negate the concept: you can use an adapter program to translate between the OPM world and the ILE world.
Let me give you another example. Some shops have adopted a setter/getter architecture for their database access. Take a simple program that needs to get address information for a label. In the setter/getter world, you call a procedure to get (or update) each individual field in a database record. The ILE code for this might look like this:
wAddress1 = GetAddressLine1(ordnum);
wAddress2 = GetAddressLine2(ordnum);
wAddress3 = GetAddressLine3(ordnum);
wCity = GetCity(ordnum);
wState = GetState(ordnum);
wZip = GetPostalCode(ordnum);
It's not a real problem for ILE programs, but it really complicates the code when you're calling from an OPM program. Even using the adapter technique I show above is cumbersome. Since the procedures don't share any parameters, you'd have to set up an adapter program for every procedure and a parameter list for every adapter. But adapters can be very flexible. Here's a better version:
d GetAddrs pi
d iOrdNum like(OHORDNUM)
d iAddr1 like(OHADRLN1)
d iAddr2 like(OHADRLN2)
d iAddr3 like(OHADRLN3)
d iCity like(OHCITY)
d iState like(OHSTATE)
d iZip like(OHPSTCDE)
/free
iAddr1 = GetAddressLine1(iOrdnum);
iAddr2 = GetAddressLine2(iOrdnum);
iAddr3 = GetAddressLine3(iOrdnum);
iCity = GetCity(iOrdnum);
iState = GetState(iOrdnum);
iZip = GetPostalCode(iOrdnum);
*inlr = *on;
/end-free
As you can see, the adapter calls multiple service program procedures to gather all the related information. This doesn't have to be limited to a single service program, either. The adapter can call multiple service programs. This is a general way to get around the inherent procedure limitation of a single return parameter. Other options exist, including returning a data structure, but each technique has its own issues. Regardless of the issues, though, I hope it's clear that you can use adapters to provide more flexibility in your parameters, which in turn can make your use of service programs more productive.
More importantly, even your old OPM RPG programs don't have to be left out of your architectural upgrades, so you don't have to worry about designing your procedures to a lower common standard in order to accommodate the old code. Even if you can't convert those old programs today, you can write your new code using the most modern of standards and then make them available to the legacy code through the magic of adapters. Parameters are fun!
LATEST COMMENTS
MC Press Online