I wanted to document a pattern that I've been using since Apigee introduced the concept of "DefaultFaultRule" (too long ago - apologies I have been lazy). I have been explaining this to many Apigeeks so it is used in many Apigee projects but I don't believe it is properly documented anywhere.
The reasons why I use this pattern are:
In conventional error handling implementation, we tend to put all handler logic within FaultRule element - this has the advantage of creating a self contained error handling logic for a specific error condition.
<FaultRule name="Expired Access Token"> <Condition>(fault.name = "access_token_expired")</Condition> <Step> <Name>ServiceCallout.LogError</Name> </Step> <Step> <Name>RaiseFault.ExpiredAccessToken</Name> </Step> </FaultRule>
Above FaultRule handles access token expiry error scenario by logging the error and returning a clean error response specific to access token expiry scenario. Let's assume the RaiseFault policy looks like this:
<RaiseFault name="RaiseFault.ExpiredAccessToken"> <FaultResponse> <Set> <Headers> <Header name="Content-Type">application/json</Header> </Headers> <Payload contentType="application/json" variablePrefix="@" variableSuffix="#">{ "code": "400", "message": "Access token has expired", "info": "https://developers.myapi.com" }</Payload> <StatusCode>400</StatusCode> <ReasonPhrase>Bad Request</ReasonPhrase> </Set> </FaultResponse> <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables> </RaiseFault>
However as the number of FaultRules increase, we will need to duplicate these policies and make minor modifications in them to handle different type of errors, e.g. invalid access token error.
<FaultRule name="Expired Access Token"> <Condition>(fault.name = "access_token_expired")</Condition> <Step> <Name>ServiceCallout.LogError</Name> </Step> <Step> <Name>RaiseFault.ExpiredAccessToken</Name> </Step> </FaultRule> <FaultRule name="Invalid Access Token"> <Condition>(fault.name = "invalid_access_token")</Condition> <Step> <Name>ServiceCallout.LogError</Name> </Step> <Step> <Name>RaiseFault.InvalidAccessToken</Name> </Step> </FaultRule>
ServiceCallout.LogError policy has been referenced one more time for "invalid access token" scenario so we can log that error type. I also need to copy/paste RaiseFault policy and change the message to say "Access token is invalid".
If in the future I want to change the structure of the error responses, I will need to change all RaiseFault policies one by one. I can get rid of these unnecessary duplications by reusing both policies for all error scenarios. I can do that by templating both policies to use variables to fill in actual data. I also want to refer those policies once in proxy definition rather than referencing them in every FaultRule.
This is where DefaultFaultRule come in. Its official definition is "A default fault rule acts an exception handler for any error that is not explicitly handled by another fault rule". I would also translate this as "A default fault rule acts as a common FaultRule which is executed if no other FaultRule has executed RaiseFault policy". Read more about this here: http://docs.apigee.com/api-services/content/fault-handling#creatingfaultrules-definingthecustomerrormessagereturnedfromafaultrule
So here is the refactored error handling logic using DefaultFaultRule construct:
<FaultRules> <FaultRule name="Expired Access Token"> <Condition>(fault.name = "access_token_expired)</Condition> <Step> <Name>AssignMessage.SetExpiredAccessTokenErrorVariables</Name> </Step> </FaultRule> <FaultRule name="Invalid Access Token"> <Condition>(fault.name = "invalid_access_token")</Condition> <Step> <Name>AssignMessage.SetInvalidAccessTokenErrorVariables</Name> </Step> </FaultRule> </FaultRules> <DefaultFaultRule name="all"> <AlwaysEnforce>true</AlwaysEnforce> <Step> <Condition>(flow.myapi.error.code = null)</Condition> <Name>AssignMessage.SetInternalServerErrorVariables</Name> </Step> <Step> <Name>ServiceCallout.LogError</Name> </Step> <Step> <Name>RaiseFault.Json</Name> </Step> </DefaultFaultRule>
In above refactored snippet, FaultRule elements are very lean - they are only responsible for setting data relevant to that particular error scenario. Those variables will then get used by the common policies under DefaultFaultRule.
Here is an example of AssignMessage.SetExpiredAccessTokenErrorVariables policy:
<AssignMessage name="AssignMessage.SetExpiredAccessTokenErrorVariables"> <AssignVariable> <Name>flow.myapi.error.code</Name> <Value>400</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.message</Name> <Value>access token has expired</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.info</Name> <Value>https://developers.myapi.com</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.status</Name> <Value>400</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.reason</Name> <Value>Bad Request</Value> </AssignVariable> </AssignMessage>
So these variables can then be used by the actual RaiseFault policies that is common to all error type:
<RaiseFault name="RaiseFault.Json"> <FaultResponse> <Set> <Headers> <Header name="Content-Type">application/json</Header> </Headers> <Payload contentType="application/json" variablePrefix="@" variableSuffix="#">{ "code": "@flow.myapi.error.code#", "message": "@flow.myapi.error.message#", "info": "@flow.myapi.error.info#" }</Payload> <StatusCode>{flow.myapi.error.status}</StatusCode> <ReasonPhrase>{flow.myapi.error.reason}</ReasonPhrase> </Set> </FaultResponse> <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables> </RaiseFault>
RaiseFault.Json is responsible for defining the structure of the json error response while the data comes from the individual FaultRule.
Catching Unhandled ErrorsWhen an error is thrown from any Apigee policy or custom code that is not handled by any of the FaultRule conditions, Apigee will start executing the policies under DefaultFaultRule. In this situation we would like to return a stock 500 response to the consuming apps instead of the default Apigee response so that we are consistent in our error responses for all cases.
This is handled by the following Step definition in DefaultFaultRules:
<Step> <Condition>(flow.myapi.error.code = null)</Condition> <Name>AssignMessage.SetInternalServerErrorVariables</Name> </Step>
Which basically says "if one of the variables that should have set for this error is null, assume this error is not handled by any FaultRules and set the variables to some stock 500 response".
Here is a sample implementation of AssignMessage.SetInternalServerErrorVariables policy:
<AssignMessage name="AssignMessage.SetUnhandledErrorVariables"> <AssignVariable> <Name>flow.myapi.error.code</Name> <Value>500</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.message</Name> <Value>internal server error</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.info</Name> <Value>https://developers.myapi.com</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.status</Name> <Value>500</Value> </AssignVariable> <AssignVariable> <Name>flow.myapi.error.reason</Name> <Value>Internal Server Error</Value> </AssignVariable> </AssignMessage>
Don't forget that if multiple fault rules have a condition that evaluates to true, then the last of those fault rules executes.
Errors in Custom CodeHow can we utilise DefaultFaultRule logic when we are handling errors in custom code, e.g. JS? All we need to do is to set variables that we need and force Apigee to halt flow processing and go straight to FaultRules:
if (...) { context.setVariable('flow.myapi.error.message', 'xyz parameter should be boo'); ... set other error variables similar to AssignMessage policies throw new Error(); //halt current execution flow }
When this error is thrown in JS, Apigee will start executing FaultRules. None of the conditions will match which is a good thing as we have already set all variables we need. DefaultFaultRule will be executed to perform logic necessary.
If you find it inconsistent to define the error message in your custom code rather than in AssignMessage policies, you can set variables indicating type of the error and other parameters in your custom code and allow another policy in FaultRules to define the format of the error message. Don't forget that the main point here is to let all errors flow through to FaultRules and a single policy within DefaultFaultRules to package everything in an HTTP response.
Ozan, this is awesome. I've linked to it from the Fault Handling topic in the docs. Thanks for putting this together!
the honour is all mine. Thanks @Floyd Jones.
Awasome document @oseymen@apigee.com!
Well organised error Handling! B/w can we have a single JS to handle all errors(instead of multiple assign message policies)? Which is the Best/effective approach?
Example:
<FaultRules> <FaultRule name="Expired Access Token"> <Condition>(fault.name = "access_token_expired)</Condition> <Step> <Name>AssignMessage.SetExpiredAccessTokenErrorVariables</Name> </Step> </FaultRule> <FaultRule name="Invalid Access Token"> <Condition>(fault.name = "invalid_access_token")</Condition> <Step> <Name>AssignMessage.SetInvalidAccessTokenErrorVariables</Name> </Step> </FaultRule> </FaultRules> <DefaultFaultRule name="all"> <AlwaysEnforce>true</AlwaysEnforce> <Step> <Name>ServiceCallout.LogError</Name> </Step> <Step> <Name>RaiseFault.Json</Name> </Step> </DefaultFaultRule>
instead can we go with
<DefaultFaultRule name="fault-rule"> <Step> <Name>JS.setError</Name> </Step> <Step> <Name>AM.assignError</Name> </Step> <AlwaysEnforce>false</AlwaysEnforce> </DefaultFaultRule>
where Js.setError will be
var faultName = context.getVariable ("fault.name"); if("access_token_expired".equalsIgnoreCase(faultName)) { <set Error> } else if("invalid_access_token".equalsIgnoreCase(faultNamm)) { <set Error> } else { <set UnHandledError> }
and AM.assignError will be my template to raise error.
Can you please suggest the best way of handling it?
Sure @maivizhi - technically that would also work but you might end up with a big JS code there. So I guess it comes down to personal preference. But I really wanted to highlight the importance of DefaultFaultRule element to create a single point where we handle errors and you got that right!
Thanks @oseymen@apigee.com.Yeah i got the use of DefaultFaultRule.I would prefer to go with the approach which will improve the performance like
Can you please suggest the best approach to reduce the processing time?
Thanks
Maivizhi A
Is raising a fault necessary in the DefaultFaultRule ?
I think the flow is already in Fault, therefore there is no need to use RaiseFault. One could use AssignMessage and set the payload, correct?
"Need" is a strong word there. You can achieve the same result with AssignMessage or custom code.
I can think of two ways of looking at this problem:
In practice it doesn't really matter which policy you choose for this particular case.
Thanks @oseymen@apigee.com for sharing a well organized way to handle errors. I have one query regarding handling custom errors. If we have to handle a lot custom errors then will using the JS policy instead of RaiseFault policy reduce the performance? Which is better.. having a JS policy will lot of if..else block or having specific JS policy to handle each custom error (the number of JS policy will grow with the number of custom errors) or having a RaiseFault policy?
Using a Raise Fault policy, I will not be able to set those variables but can set the error message and skip the execution of the raise fault policy in Default Fault block.
I support the idea of a single RaiseFault policy in the proxy. This is the only place where you define the structure of the error response going back to client. This ensures two things:
This RaiseFault policy will contain variables to set message, info, response code, etc. Now for these, AssignMessage policy (one per error type), or a single JS policy with lots of if/else statements will do fine. I don't envisage too much performance hit with using JS in this scenario but I can see that file becoming a maintenance bottleneck very quickly if you have too many error conditions.
What I generally do in my projects is to catch each individual error type in FaultRules with a specific Condition and put a 4 line AssignMessage policy for each error type to set the variables to be then used by the RaiseFault policy. I get a lot of AssignMessage policies with this approach but it causes no harm to me and makes maintenance straightforward.
Hope this helps.
Thanks for posting this Ozan
I incorporated this technique into a "proxy template" that can be used as a starting point with other best practices build in.
https://github.com/davidmehi/edge-proxy-template
I also created an example where the error handling logic is separated out into a shared flow. This way, the same common error handling logic can be used by multiple proxies with minimal effort. The example is here
https://github.com/davidmehi/edge-shared-errorhandling-flow-example
I've written an article that seeks to build on Ozan's pattern. While there are elements of this approach I like, and I fully agree with Ozan's summary "main point", I recommend some modifications to the approach to fit better with Apigee's native fault handling, which will reduce code duplication and improve maintenance.
@Ozan Seymen, few years ago I used the same approach on Axway API Gateway and I cannot see any better way to handle errors in a centralized way as you describe here.
Now I'm working on Apigee and this post is exactly want I need.
Thanks for that...
Can't believe I didn't see this before now, @ozanseymen. Great article -- I'm linking to this in the API Platform Learning Guide.
Item 2 - if you remove the Response element from the ServiceCallout, it will be a fire-and-forget call. See: https://community.apigee.com/questions/53829/service-callout-policy-for-logging.html