{ Community }
  • Academy
  • Docs
  • Developers
  • Resources
    • Community Articles
    • Apigee on GitHub
    • Code Samples
    • Videos & eBooks
    • Accelerator Methodology
  • Support
  • Ask a Question
  • Spaces
    • Product Announcements
    • General
    • Edge/API Management
    • Developer Portal (Drupal-based)
    • Developer Portal (Integrated)
    • API Design
    • APIM on Istio
    • Extensions
    • Business of APIs
    • Academy/Certification
    • Adapter for Envoy
    • Analytics
    • Events
    • Hybrid
    • Integration (AWS, PCF, Etc.)
    • Microgateway
    • Monetization
    • Private Cloud Deployment
    • 日本語コミュニティ
    • Insights
    • IoT Apigee Link
    • BaaS/Usergrid
    • BaaS Transition/Migration
    • Apigee-127
    • New Customers
    • Topics
    • Questions
    • Articles
    • Ideas
    • Leaderboard
    • Badges
  • Log in
  • Sign up

Get answers, ideas, and support from the Apigee Community

  • Home /
  • Edge/API Management /
avatar image
29

An error handling pattern for Apigee proxies  

  • Export to PDF
ozanseymen   created · May 05, 2016 at 10:01 AM · 18.3k Views · edited · Sep 26, 2017 at 10:33 AM

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:

  • centralised error handling
  • prevents code duplication/repeat
  • simple and leaner FaultRule definitions
  • easy "catch-all" error handling

The Problem

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

The Solution

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 Errors

When 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 Code

How 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.

Possible Improvements:

  1. An AssignMessage policy that overrides and sets a new HTTP response can also be used instead of RaiseFault policy (as commented by @Dino below)
  2. If you are logging errors to an HTTP endpoint, consider using async JavaScript instead of ServiceCallout policy. ServiceCallout will do a synchronous HTTP call to the log endpoint which is not needed here. You will get better performance out of async HTTP calls to the log servers.
  3. If you can use syslog for pushing log messages to the log servers or file (private cloud), consider using MessageLogging policy within PostClientFlow.
thub.nodes.view.add-new-comment
error handling
Add comment Show 23
10 |5000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by Apigeeks only
  • Viewable by the original poster
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image jonesfloyd ♦♦ · May 06, 2016 at 03:10 PM 0
Link

Ozan, this is awesome. I've linked to it from the Fault Handling topic in the docs. Thanks for putting this together!

avatar image ozanseymen ♦♦ jonesfloyd ♦♦   · May 10, 2016 at 09:52 AM 0
Link

the honour is all mine. Thanks @Floyd Jones.

avatar image maivizhi · May 23, 2016 at 06:28 AM 0
Link

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?

avatar image ozanseymen ♦♦ maivizhi   · May 23, 2016 at 12:38 PM 0
Link

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!

avatar image maivizhi ozanseymen ♦♦ · May 23, 2016 at 12:47 PM 0
Link

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

  • Which approach will reduce the processing time - going with multiple assign Message or Single JS to handle multiple error case?

Can you please suggest the best approach to reduce the processing time?

Thanks

Maivizhi A

Show more comments
avatar image Dino ♦♦   · Jun 20, 2016 at 05:00 PM 1
Link

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?

avatar image ozanseymen ♦♦ Dino ♦♦   · Jun 22, 2016 at 08:13 AM 0
Link

"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:

  1. You are handling the error caught during the flow execution and raising a new fault from scratch targeted to the client at that point. In this case it makes sense to use RaiseFault.
  2. You are modifying the fault that is already raised by your policies or target connection. In this case, it makes sense to use AssignMessage.

In practice it doesn't really matter which policy you choose for this particular case.

avatar image GargiTalukdar   · Aug 19, 2016 at 10:04 AM 0
Link

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.

avatar image ozanseymen ♦♦ GargiTalukdar   · Aug 19, 2016 at 11:10 AM 0
Link

Hi @GargiTalukdar

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:

  1. Error response format is always consistent.
  2. Easy maintenance, e.g. "CORS headers returned from error responses" feature can be implemented by modifying this file rather than 10.

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.

avatar image davidmehi ♦ · Mar 14, 2017 at 08:19 PM 1
Link

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

avatar image Omid Tahouri ♦ davidmehi ♦   · May 16, 2017 at 05:45 PM 0
Link

+1 you beat me to it!

avatar image Kevin Shomper · Sep 21, 2017 at 10:40 PM 0
Link

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.

avatar image Jorge Middleton · Jan 29, 2018 at 02:49 AM 1
Link

@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...

avatar image Mike Dunker @Google ♦♦   · Oct 10, 2018 at 09:25 AM 1
Link

Can't believe I didn't see this before now, @ozanseymen. Great article -- I'm linking to this in the API Platform Learning Guide.

avatar image Steve Scheider · Jun 11, 2020 at 03:54 PM 1
Link

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

Article

Contributors

avatar image

Follow this article

53 People are following this .

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Navigation

An error handling pattern for Apigee proxies
  • An Improved Pattern for Fault Handling

Related Articles

oAuth returned fault name - invalid_client

  • Products
    • Edge - APIs
    • Insights - Big Data
    • Plans
  • Developers
    • Overview
    • Documentation
  • Resources
    • Overview
    • Blog
    • Apigee Institute
    • Academy
    • Documentation
  • Company
    • Overview
    • Press
    • Customers
    • Partners
    • Team
    • Events
    • Careers
    • Contact Us
  • Support
    • Support Overview
    • Documentation
    • Status
    • Edge Support Portal
    • Privacy Policy
    • Terms & Conditions
© 2021 Apigee Corp. All rights reserved. - Apigee Community Terms of Use - Powered by AnswerHub
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Create an article
  • Post an idea
  • Spaces
  • Product Announcements
  • General
  • Edge/API Management
  • Developer Portal (Drupal-based)
  • Developer Portal (Integrated)
  • API Design
  • APIM on Istio
  • Extensions
  • Business of APIs
  • Academy/Certification
  • Adapter for Envoy
  • Analytics
  • Events
  • Hybrid
  • Integration (AWS, PCF, Etc.)
  • Microgateway
  • Monetization
  • Private Cloud Deployment
  • 日本語コミュニティ
  • Insights
  • IoT Apigee Link
  • BaaS/Usergrid
  • BaaS Transition/Migration
  • Apigee-127
  • New Customers
  • Explore
  • Topics
  • Questions
  • Articles
  • Ideas
  • Badges