question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature Proposal: Middleware API for event handlers

See original GitHub issue

Hi 😄

First, I love the probot framework, thanks to the whole team for developing such an awesome framework and making it opensource!

Feature Proposal

Recently I build a probot app for my team for posting pull request notifications to our team’s slack channel. In the process, I wanted to see if I could have middleware for github event handlers which allows defining event/context related transformations at an app level scale and not per individual event handler. Less code duplication and less errors. I’m proposing a middleware implementation for probot event handlers. The middleware API I’m proposing works similar to the redux middleware API.

Middleware API

The following shows the event handler middleware api.

next : the next middleware in the chain. The last next is going to be the original event handler. E.g) app.on(event, handler)

context: the context for the current github event being processed

next => context => {
  // do something with `context` or `context.payload`.
  // forward context to the next middleware in the chain
  return next(context);
};

// async middleware
next => async context => {
  // fetch some data from another api.
  const data = await fetchData();
  // forward context to the next middleware in the chain
  return next({
    ...context,
    payload: {
      ...context.payload,
      ...data
    }
  });
};

I’ll start with some middleware examples, followed by how I implemented it for my probot application.

To begin, assume I have a probot application subscribed to pull_request events from github.

Middleware 1 - Metadata

A middleware can transform the context.payload object and include parsed metadata. Similar to probot-metadata. However, compared to probot-metadata, reduces the code duplication involved in having each event handler parse the metadata. It’s all defined in a single place now.

const parseMetadataFromBody = require('../utils/parse-metadata-from-body');

const parseMetadata = next => (context) => {
  if (!context.payload) {
    return next(context);
  }

  const { payload } = context;

  if (!payload.pull_request) {
    return next(context);
  }

  const {
    pull_request: { body },
  } = payload;

  const { metadata, bodyWithoutMetadata } = parseMetadataFromBody(body);

  return next({
    ...context,
    payload: {
      ...context.payload,
      pull_request: {
        ...context.payload.pull_request,
        body: bodyWithoutMetadata,
      },
      myApp: {
        metadata,
      },
    },
  });
};

module.exports = parseMetadata;

Middleware 2 - Error Reporting

Another middleware implemented was one which can capture any errors and log them. No more need to capture errors at the event handler level. Any error thrown by an event handler will be captured by this middleware. This middleware also is asynchronous!

const slack = require('../notification-clients/slack');

const errorReporting = next => async (context) => {
  try {
    // capture errors thrown by any middleware down the chain
    // and event handlers
    const result = await next(context);
    return result;
  } catch (error) {
    await slack.dumpLogs([`${error.message}\n${error.stack}`]);
    return 'Failed to handle event';
  }
};
module.exports = errorReporting;

Middleware 3 - Appending Additional Pull Request Information

The following middleware will detect pull_request events and automatically append additional information returned by the github api for that pull request and also the files included in the pull request. Now any event handler will have all this information immediately!

const path = require('path');
const addPullRequestInfo = next => async (context) => {
  const { payload } = context;
  if (payload.pull_request && payload.pull_request.number) {
    const {
     pull_request: { number },
      repository: {
        name,
        owner: { login },
      },
    } = payload;

    const { data: pullRequestInfo } = await context.github.pullRequests.get({
      owner: login,
      repo: name,
      number,
    });

   // for example purposes
    const { data: files } = await context.github.pullRequests.getFiles({
      owner: login,
      repo: name,
      number,
      per_page: 100,
      page: 1,
    });

    const { fileTypes, fileTypesPretty } = getFileTypes(files);

    return next({
      ...context,
      payload: {
        ...context.payload,
        pull_request: {
          ...pullRequestInfo,
          files,
        },
      },
    });
  }

  return next(context);
};

module.exports = addPullRequestInfo;

The possibilities are endless. Applying these transformations and data retrieval at the application scale removes a lot of cruft away from individual event handlers.

Implementation Of The Middleware API

Currently, here’s how I implemented the middleware api in my probot application. It takes the app object and patches app.on to add the middleware functionality.

const compose = require('./utils/compose');

const applyEventMiddleware = app => (middlewares) => {
  const originalAppOn = app.on.bind(app);
  app.on = (event, handler) => {
    // build chain. a->b->c
    const handlerWithMiddlewares = compose(...middlewares)(handler);
    originalAppOn(event, handlerWithMiddlewares);
  };
};

module.exports = applyEventMiddleware;

Composition of the middleware chain

// ./utils/compose
const compose = (...functions) => {
  if (functions.length === 0) {
    return arg => arg;
  }

  if (functions.length === 1) {
    return functions[0];
  }

  return functions.reduce((a, b) => (...args) => a(b(...args)));
};

module.exports = compose;

If we could have the middleware api integrated into Probot, it would definitely clean this patching up.

Hence, I’m proposing a middleware API for event handlers. This reduces code duplication and errors by defining necessary transforms and data retrieval operations at the application level and not the event handler level.

If this proposal is approved, I would be happy to help contribute for it. Thank you! 😄

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:3
  • Comments:7 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
gr2mcommented, Feb 5, 2019

In Probot, you’ll probably do something like this:

app.hook.error('webhook', async (error, event) => {
  await slack.dumpLogs([`${error.message}\n${error.stack}`]);
  return 'Failed to handle event';
})
0reactions
stale[bot]commented, Apr 7, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Object-relational event middleware for web applications
Web based applications are increasingly being used for highly reactive systems where clients expect to be notified of broadcast style informa-.
Read more >
ROS2 Middleware Change Proposal - Next Generation ROS
This allows the custom middleware event handlers to notify the executor immediately via a callback pointer. The provided event type and ...
Read more >
Event Handler API - Verastream Host Integrator - Micro Focus
Event handling is a feature in Verastream Host Integrator that extends the capabilities of models by allowing you to define specific events that...
Read more >
DADO: Enhancing Middleware to Support Crosscutting ...
features induce complex implementations which cross-cut differ- ent languages, OSs, and hardware platforms, while still needing to share data and events.
Read more >
Applying a Pattern Language to Develop Extensible ORB ...
cient, event-driven, and concurrent communication software, including ORB middleware. To focus our discussion, this paper presents a case study.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found