Problem List API - Calling Conventions

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()
 ;
 ...
 S %=$$NEW^GMPLAPI2(.RETURN,GMPDFN,GMPROV,.GMPFLD)
 ...
 Q
 

So, what do you think about this approach? Any comments will be greatly appreciated.

Catalin

 

like0

Comments

Problem List API - Calling Conventions

Sam Habiel's picture

Part of the issue for me is that the RPC broker doesn't provide a
standardized way to provide application level errors to the client;
nor does the clients seem to have a convention on how to
report/interpret application level errors.

The RPC broker does have a way to send an error message for runtime
errors. Could we exploit that? It's actually flexible enough.

See this code from XWBRW:
SNDERR ;send error information
;XWBSEC is the security packet, XWBERROR is application packet
N X
S $X=0 ;Start with zero
S X=$E($G(XWBSEC),1,255)
D WRITE($C($L(X))_X)
S X=$E($G(XWBERROR),1,255)
D WRITE($C($L(X))_X)
S XWBERROR="",XWBSEC="" ;clears parameters
Q
;

On Tue, Jun 26, 2012 at 2:05 AM, catalinb <catalin.branea@infoworld.ro> wrote:
> 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()
>  ;
>  ...
>  S %=$$NEW^GMPLAPI2(.RETURN,GMPDFN,GMPROV,.GMPFLD)
>  ...
>  Q
>
>
> So, what do you think about this approach? Any comments will be greatly
> appreciated.
>
> Catalin
>
>
>
> --
> Full post: http://www.osehra.org/blog/problem-list-api-calling-conventions
> Manage my subscriptions: http://www.osehra.org/og_mailinglist/subscriptions
> Stop emails for this post:
> http://www.osehra.org/og_mailinglist/unsubscribe/814

like0