Automate error handling and observarability with hooks

栏目: IT技术 · 发布时间: 3年前

内容简介:By standardise common error handling, observability logic or other patterns as reusable modules, it ensures consistency of observability standards across micro-services, codebases or teams.Hooks can automate standardised log, metrics and tracing in a struc

hooks

Purpose

Consistency as Toolings (CasT)

what to observability and control patterns, like linters to code styles

By standardise common error handling, observability logic or other patterns as reusable modules, it ensures consistency of observability standards across micro-services, codebases or teams.

/* handler.js */
import logger, metrics from '@opbi/toolchain';
import { eventLogger, eventTimer } from '@opbi/hooks';

const withObserver = chain(eventLogger, eventTimer);
const getSubscription = withObserver(userProfileApi.getSubscription);
const cancelSubscription = withObserver(subscriptionApi.cancel)

const handleUserCancelSubscription = async ({ userId }, meta, context) => {
  const { subscriptionId } = await getSubscription( { userId }, meta, context );
  await cancelSubscription({ subscriptionId }, meta, context);
};

export default withObserver(handleUserCancelSubscription);

/* router.js */
import handleUserCancelSubscription from './handler.js';

await handleUserCancelSubscription({ userId }, meta, { logger, metrics });

Hooks can automate standardised log, metrics and tracing in a structure reflecting the call stacks. This greatly improves observability coverage and makes monitor and debugging a breeze with good precision locating problematic function.

[info] event: handleUserCancelSubscription
[info] event: handleUserCancelSubscription.getSubscription
[error] event: handleUserCancelSubscription.cancelSubscription, type: TimeoutError

Readability, Reusability, Testability (RRT)

Turn scattered repeatitive control mechanism or observability code from interwined blocks to more readable, reusable, testable ones.

By abstract out common control mechanism and observability code into well-tested, composable hooks, it can effectively half the verboseness of your code. This helps to achieve codebase that is self-explanatory of its business logic and technical behaviour. Additionally, conditionally turning certain mechanism off makes testing the code very handy.

Let's measure the effect in LOC (Line of Code) and LOI (Level of Indent) by an example of cancelling user subscription on server-side with some minimal error handling of retry and restore. The simplification effect will be magnified with increasing complexity of the control mechanism.

Using @opbi/hooks Hooks: LOC = 16, LOI = 2
// import userProfileApi from './api/user-profile';
// import subscriptionApi from './api/subscription';
// import restoreSubscription from './restore-subscription'

import { errorRetry, errorHandler, chain } from '@opbi/hooks';

const retryOnTimeoutError = errorRetry({
  condition: e => e.type === 'TimeoutError'
});

const restoreOnServerError = errorHandler({
  condition: e => e.code > 500,
  handler: (e, p, m, c) => restoreSubscription(p, m, c),
});

const cancelSubscription = async ({ userId }, meta, context) => {
  const { subscriptionId } = await chain(
    retryOnTimeoutError
  )(userProfileApi.getSubscription)( { userId }, meta, context );

  await chain(
    errorRetry(), restoreOnServerError,
  )(subscriptionApi.cancel)({ subscriptionId }, meta, context);
};

// export default cancelSubscription;
Vanilla JavaScript: LOC = 32, LOI = 4
// import userProfileApi from './api/user-profile';
// import subscriptionApi from './api/subscription';
// import restoreSubscription from './restore-subscription'

const cancelSubscription = async ({ userId }, meta, context) => {
  let subscriptionId;

  try {
    const result = await userProfileApi.getSubscription({ userId }, meta, context);
    subscriptionId = result.subscriptionId;
  } catch (e) {
    if(e.type === 'TimeoutError'){
      const result = await userProfileApi.getSubscription({ userId }, meta, context);
      subscriptionId = result.subscriptionId;
    }
    throw e;
  }

  try {
    try {
      await subscriptionApi.cancel({ subscriptionId }, meta, context);
    } catch (e) {
      if(e.code > 500) {
        await restoreSubscription({ subscriptionId }, meta, context);
      }
      throw e;
    }
  } catch (e) {
    try {
      return await subscriptionApi.cancel({ subscriptionId }, meta, context);
    } catch (e) {
      if(e.code > 500) {
        await restoreSubscription({ subscriptionId }, meta, context);
      }
      throw e;
    }
  }
};

// export default cancelSubscription;

How to Use

Install

yarn add @opbi/hooks

Standard Function

Standardisation of function signature is powerful that it creates predictable value flows throughout the functions and hooks chain, making functions more friendly to meta-programming. Moreover, it is also now a best-practice to use object destruct assign for key named parameters.

Via exploration and the development of hooks, we set a function signature standard to define the order of different kinds of variables as expected and we call it action function :

/**
 * The standard function signature.
 * @param  {object} param   - parameters input to the function
 * @param  {object} meta    - metadata tagged for function observability(logger, metrics), e.g. requestId
 * @param  {object} context - contextual callable instances or unrecorded metadata, e.g. logger, req
 */
function (param, meta, context) {}

Config the Hooks

All the hooks in @opbi/hooks are configurable with possible default settings.

In theexample, errorRetry() is using its default settings, while restoreOnServerError is configured errorHandler . Descriptive names of hook configurations help to make the behaviour very self-explanatory. Patterns composed of configured hooks can certainly be reused.

const restoreOnServerError = errorHandler({
  condition: e => e.code > 500,
  handler: (e, p, m, c) => restoreSubscription(p, m, c),
});

Chain the Hooks

"The order of the hooks in the chain matters."
Automate error handling and observarability with hooks

Under the hood, the hooks are implemented in the decorators pattern. The pre-hooks, action function, after-hooks/error-hooks are invoked in a pattern as illustrated above. In theexample, as errorRetry(), restoreOnServerError are all error hooks, restoreOnServerError will be invoked first before errorRetry is invoked.

Ecosystem

Currently available hooks:

Hooks are named in a convention to reveal where and how it works [hook point][what it is/does] , e.g. errorCounter, eventLogger . Hook points are named before, after, error and event (multiple points).

Extension

You can easily create more standardised hooks with addHooks helper. Open source them aligning with the above standards via pull requests or individual packages are highly encouraged.

Decorators

Hooks here are essentially configurable decorators, while different in the way of usage. We found the name 'hooks' better describe the motion that they are attached to functions not modifying their original data process flow (keep it pure). Decorators are coupled with class methods, while hooks help to decouple definition and control, attaching to any function on demand.

//decorators
class SubscriptionAPI:
  //...
  @errorRetry()
  cancel: () => {}
//hooks
  chain(
    errorRetry()
  )(subscriptionApi.cancel)

Adaptors

To make plugging in @opbi/hooks hooks to existing systems easier, adaptors are introduced to bridge different function signature standards.

const handler = chain(
  adaptorExpress(),
  errorRetry()
)(subscriptionApi.cancel)

handler(req, res, next);

Refactor

To help adopting the hooks by testing them out with minimal refactor on non-standard signature functions, there's an unreleased adaptor to bridge the function signatures. It is not recommended to use this for anything but trying the hooks out, especially observability hooks are not utilised this way.

Reducers

Integration with Redux is TBC.

Pipe Operator

We are excited to see how pipe operator will be rolled out and hooks can be elegantly plugged in.

const cancelSubscription = ({ userId }, meta, context)
  |> chain(timeoutErrorRetry)(userProfileApi.getSubscription)
  |> chain(restoreOnServerError, timeoutErrorRetry)(subscriptionApi.cancel);

Inspiration

License

MIT


以上所述就是小编给大家介绍的《Automate error handling and observarability with hooks》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

后谷歌时代:大数据的衰落及区块链经济的崛起

后谷歌时代:大数据的衰落及区块链经济的崛起

乔治·吉尔德 / 现代出版社 / 2018-9-5 / 68

以大数据和机器智能为基础的谷歌时代(信息互联网时代)是一个令人敬畏的时代。但它即将终结。 《后谷歌时代》一书的作者乔治•吉尔德是一位颇具远见卓识的智者。他在技术和文化领域具有无与伦比的视野和见地。他向读者描述了谷歌所面临信任与安全危机,并勇敢地预测了即将到来的后谷歌时代。 谷歌用其惊人的“搜索和排序”能力吸引了整个世界。功能强大的搜索引擎,看似免费小应用,诸如视频、地图、电子邮箱等,让......一起来看看 《后谷歌时代:大数据的衰落及区块链经济的崛起》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具