PDL Front End API Consumer Best Practice

Xinjiang Shao

10/19/2022

Agenda

  • Design Pattern: Interceptor Pattern
  • Features Usage
    • Single Session Support
    • Request Retries
    • Invalid Response Filtering
    • Order Alerts, Cutoff Alerts
  • What's the current best practice?
    • Error Handling Example

Interceptor Pattern

Key aspects of the pattern are that the change is transparent and used automatically. In essence, the rest of the system does not have to know something has been added or changed and can keep working as before.

https://en.wikipedia.org/wiki/Interceptor_pattern

What went wrong before?

- Interceptor Chain for Requests and Responses

- PR Single Session Support

  • SESSION_EXPIRED
    SESSION_LOCKED LOGIN_REQUIRED

 

 

https://bitbucket.peapod.com/projects/PEAP/repos/peapod-client/pull-requests/4156/overview

Multiple Interceptors

Given you add multiple response interceptors and when the response was fulfilled

  • then each interceptor is executed
  • then they are executed in the order they were added
  • then only the last interceptor's result is returned
  • then every interceptor receives the result of its predecessor
  • and when the fulfillment-interceptor throws
    • then the following fulfillment-interceptor is not called
    • then the following rejection-interceptor is called
    • once caught, another following fulfill-interceptor is called again (just like in a promise chain).

Read the interceptor tests for seeing all this in code.

Response Schema

Error Schema

// instanceof Error
{
    config: {},
    request: {}
    response: {
      data:{}, 
      headers: {}, 
      status: 400, 
      statusText: "" 
    },
    message: ""
}
{
  // `data` is the response that was provided by the server
  data: {},

  // `status` is the HTTP status code from the server response
  status: 200,

  // `statusText` is the HTTP status message from the server response
  // As of HTTP/2 status text is blank or unsupported.
  // (HTTP/2 RFC: https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.4)
  statusText: 'OK',

  // `headers` the HTTP headers that the server responded with
  // All header names are lower cased and can be accessed using the bracket notation.
  // Example: `response.headers['content-type']`
  headers: {},

  // `config` is the config that was provided to `axios` for the request
  config: {},

  // `request` is the request that generated this response
  // It is the last ClientRequest instance in node.js (in redirects)
  // and an XMLHttpRequest instance in the browser
  request: {}
}

Example for PDL

// OrderAdjustmentsAPI
import ApiService from '@/api'

put(payload) {
    return ApiService.put(`/api/v5.0/user/${payload.userId}/order/${payload.basketId}/adjustments/${payload.field}`, payload.params)
}

// CartPaymentInformation Vuex Store
try {
    let promoCodeEffect
	promoCodeEffect = await OrderAdjustmentsAPI.put(payload)
    // The fallback approach
    if (promoCodeEffect?.response) {
        promoCodeEffect = promoCodeEffect.response
    }
    
    if (promoCodeEffect.status !== 200 || promoCodeEffect.data.response.code !== 'PROMO_CODE_UPDATED') {
      const responseCode = safeAccess(() => promoCodeEffect.data.response.code, 'PROMO_CODE_UNKNOWN_ERROR')
      throw new Error(responseCode)
    }
} catch(e) {
	// throw site alert on unknown error
      if (e.message === 'PROMO_CODE_UNKNOWN_ERROR') {
        setCheckoutAlert(commit, { msg: 'There was an error updating the promo code.' })
      }
}

What's ApiService.put()?

// api/index.js
import axios from 'axios'

const http = axios.create()


export default {
  getInstance() {
    return http
  },
  // current method
  async put(url, data, config) {
    try {
      const response = await http.put(url, data, config)
      return response
    } catch (error) {
      return error
    }
  },
  // alternative option for PUT
  async corePut(url, data, config) {
    const response = await http.put(url, data, config)
    return response
  },
}

Recommended Approach

// OrderAdjustmentsAPI
import ApiService from '@/api'

put(payload) {
    return ApiService.corePut(`/api/v5.0/user/${payload.userId}/order/${payload.basketId}/adjustments/${payload.field}`, payload.params)
}

// CartPaymentInformation Vuex Store
try {
    const result = await OrderAdjustmentsAPI.put(payload)
    if (result.status >= 200 && result.status < 400) {
       // Mostly likely to reach here
    }
    if (result.status !== 200) {
       // It is unlikely to reach here if we use corePut without swollaw errors
    }
} catch(error) {
    // throw site alert on unknown error
    if (error.response.status >= 400 && error.response.status <= 500) {
      // Set current error code with e.g. error.response.data.response.code = 'PROMO_CODE_INVALID_BUS_RULE'
    }
}

Related Docs

  • https://confluence-aholddelhaize.atlassian.net/wiki/spaces/PWC/pages/113153212551/URI+Template+Path+for+axios
  • https://confluence-aholddelhaize.atlassian.net/wiki/spaces/PWC/pages/147889697682/Error+Handling+for+Web
  • https://confluence-aholddelhaize.atlassian.net/wiki/spaces/PWC/pages/56319123571/Global+Interceptors+for+API+Requests+on+Front-end

PDL Front End API Consumer Best Practice

By Xinjiang Shao

PDL Front End API Consumer Best Practice

  • 274