Early in the development process we decided we need a consistent calling convention for our API tags. We also followed a set of guiding principles: no assumed variables (except for DUZ, DT, U), validate the input parameters, find a way to report business logic errors to the UI layer.
So we explored the following options:
- The RPC broker way: TAG(RESULT,PARAM1,PARAM2,…)
This is pretty nice. The return value is always at the beginning of the parameters list so it is easy to identify it. It is a bit awkward to check if the call succeeded:D TAG(.RESULT,…) I ‘RESULT D REPORT^ERROR
Compared to:I ‘$$TAG(.RESULT,…) D REPORT^ERROR
There is no obvious way to report errors back when RESULT is of type SINGLE VALUE so some tags rely on some kind of hack (INACT^ORQQPL2 returns an array). Also, sometimes the return value is inconsistent with the documentation (see ADDSAVE^ORQQPL1 where instead of IFN it returns 1).
- The DBS FileMan way: TAG(PARAM1,PARAM2,…,MSG_ROOT)
It relies on an assumed variable (DIERR) to check if the call failed. If it did, MSG_ROOT arrays will contain a lot of info (errors, messages, help).
- The Microsoft’s COM way: HRESULT foo(**pRESULT,param1, param2,…)
HRESULT=0 means success. It is an integer structured as groups of bits meaning: error source (e.g. GMPL, kernel, etc.),severity, error number. The problem with that is the reversed meaning of OK (0=OK,1=fail). The second problem is that it's a bit of work to translate the error number into some meaningful message, so you have to get through additional layers to retrieve that info.
All assumptions made were based on two main requirements for this API: to make the codebase more modular and to make the existing functionality accessible via modern programming techniques. This means that the API tags would be called from the existing M code as well as from the RPC Broker. So in order to get the best of both worlds we decided to follow the RPC Broker path but to return a 0/1 value to indicate failure/success:TAG(.RESULT,PARAM1,PARAM2,…)
TAG would be an extrinsic function returning 0=fail/1=success. If it failed, RETURN(i) will be initialized with the error messages, otherwise, RETURN will contain the normal return values.
Every error would be in the form of: ErrorID^Message. I would expect from a future GUI client to map the errors to exceptions, and the application could do error processing based on the ErrorID. Also they could lookup the supplied ErrorID in a resource file to aid in internationalization.
With this approach you could call the API from M code in a more natural way, like this:I ‘$$NEW^GMPLAPI2(.GMPIFN,GMPDFN,GMPROV,.GMPFLD) W GMPIFN(0) Q ; WRITES ERROR AND BAILS OUT
And for the RPC Broker we can write a simple wrapper like this:ADDSAVE(RETURN,GMPDFN,GMPROV,GMPVAMC,ADDARRAY) ; SAVE NEW RECORD
; RETURN - Problem IFN if success, 0 otherwise
; ADDARRAY - array used for indirect sets of GMPFLDS()
So, what do you think about this approach? Any comments will be greatly appreciated.