airbnb / hypernova

A service for server-side rendering your JavaScript views
MIT License
5.82k stars 210 forks source link

Alternative Server Architecture #29

Closed awolden closed 5 years ago

awolden commented 8 years ago

I've been looking into this project and I am really excited about it. I am curious if there has been any talk about having the flexibility to use a server environment that is not express. Namely the use-case I find most interesting would be the ability to run hypernova as a lambda, which would require a more stateless implementation.

Thanks!

goatslacker commented 8 years ago

@awolden I'm interested in hearing thoughts about this. I haven't experimented much with AWS lambda so I'm not sure what would be involved.

Wouldn't it just be using the renderBatch logic? If so, we can just pull that out of this package or provide a way so you can link to it.

awolden commented 8 years ago

@goatslacker Funny you should mention renderBatch, I built a PoC where I directly called renderBatch. The only real issue I ran into was that the renderBatch promise doesn't actually return the results and it is highly coupled to express objects. If the renderBatch was a little more decoupled from express, it would be easy to seamlessly integrate it into a lambda.

Quick PoC:

"use strict";

const renderBatch = require('hypernova-lambda/lib/utils/renderBatch');
const requireDir = require('require-dir');
const components = requireDir('../components');

const defaultConfig = {
    bodyParser: {
        limit: 1024 * 1000,
    },
    devMode: false,
    endpoint: '/batch',
    files: [],
    logger: {},
    plugins: [],
    port: 8080,
};

module.exports = function makeRequest(e, context) {
    /*---------
    Mock things that look like express
    ----------*/
    const requestObject = {
        body: e.payload
    }
    const responseObject = new MockResponse();

    // set-up hypernova config
    const config = Object.assign({}, defaultConfig, {
      getComponent(name) {
        return components[name];
      }
    });

    // initialize renderer
    const renderer = renderBatch(config, () => false);

    // call renderer with mock objects
    renderer(requestObject, responseObject);

    // wait for renderer to complete and return the results
    responseObject.waitForCompletion().then((results) => {
        context.succeed(results);
    })
    .catch(err => context.failure(err));

}

class MockResponse {
    waitForCompletion() {
        return new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject = reject;
        });
    }
    status(statusCode) {
        this.statusCode = statusCode;
        return this;
    }
    end() {
        if(!this.returnValue || this.returnValue.error){
          return this.reject(this.returnValue.error || 'Unknown error');
        }
        return this.resolve(this.returnValue);
    }
    json() {
        this.returnValue = arguments[0];
        return this;
    }
}
goatslacker commented 8 years ago

If whatever server you're using has a concept of a "request" and "response" then this should be as easy as:

import BatchManager from './BatchManager';
import { processBatch } from './lifecycle';

export default (req, res) => {
  const jobs = req.body;
  const config = {};
  const manager = new BatchManager(req, res, jobs, config);
  return processBatch(jobs, config.plugins, manager).then(() => manager).catch(() => manager);
};

The promise can return the BatchManager instance and then you'll be able to pull the statusCode from there or call getResults() yourself to get all the results.

The jobs being pulled from req.body can be configured of course, same with config.

awolden commented 8 years ago

@goatslacker, sorry for the delayed response on this, I haven't looked into this much further than the PoC I have above. Node AWS Lambda's do not have a concept of req or res, but it should be easy enough to mock objects that look like req/res. I like your suggested solution using processBatch. I will give it a try!

Thanks for the help.

-Alex

marconi1992 commented 5 years ago

@awolden there's a npm package to implement it into a lambda https://www.npmjs.com/package/hypernova-lambda

marconi1992 commented 5 years ago

@ljharb looks like this question was answered. I even shared an existing package using the approach discussed aboved.