Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

virtual method for aggregate resources #13

Merged
merged 9 commits into from
Jul 9, 2015
Merged

virtual method for aggregate resources #13

merged 9 commits into from
Jul 9, 2015

Conversation

biofractal
Copy link
Contributor

No description provided.

@biofractal
Copy link
Contributor Author

reference by issue : #11

@travist
Copy link
Owner

travist commented Jun 30, 2015

Hey... sorry for the delay on this one.

I think this looks good and we will probably take it. I would like to see if you could pull in the latest 'master' which includes a lot more tests as well as adds a feature for composable middleware which allows you to call any of the methods without having to trigger a request.

If you test those and it looks good, then I will be happy to pull this request in.

Thanks!

Travis.

@biofractal
Copy link
Contributor Author

I have merged with your latest master so hopefully that is ready for you now.

composable middleware which allows you to call any of the methods without having to trigger a request

Can you explain this a little more, what is the use-case for this?

Thanks

@travist
Copy link
Owner

travist commented Jul 1, 2015

Thanks @biofractal. @zackurben you want to field this question? ^

@zackurben
Copy link
Contributor

Hello @biofractal, the use-case I ran into was, the requirement to manually invoke the handlers for an endpoint using the resourcejs reference object.

The following is a hypothetical situation that I think underlines the need for this functionality in a (hopefully) clear scenario. Imagine having 2 endpoints that were related but not both created by resourcejs:

  • Manually created: /register (standalone endpoint for POST request)
  • Resourcejs Created: /user (Includes all endpoints for CRUD operations)

By sending a request to the manually created endpoint [POST] /register we want to make a new user, but need some additional logic needs to be run before the insert operation. Since the two endpoints are separated, we can add registration specific logic to /register, and inside that requests lifecycle, make a manual call to the resourcejs endpoint [POST] /user, and let the sub-request to [POST] /user handle the response to our original request to /register.

Now, if our registered user had the ability to create other user accounts under itself (children accounts), we want to hit [POST] /user but dont need the registration specific logic anymore. If we made the registration specific logic a handler on the [POST] /user endpoint, then we would have to add some squirrelly logic to skip registration specific logic.

The addition of composable middleware, does not functionally change how resourcejs works, but instead exposes the internal reference to the handlers for manual invocation anywhere in a given application. I will say, that this functionality was possible before, but my PR greatly reduced the code required to achieve the same results in any given application.

Let me know if you have any other questions or thoughts!

@biofractal
Copy link
Contributor Author

Hi @zackurben

Well I am glad I asked for clarification because I think I have pretty much exactly this use case.

Let me explain what I am doing and hopefully you will be able to tell me if I should be using your new feature.

I have a a domain that consists of users that need to log in via two factor authentication. This process involves the creation of intermediate TTL verification objects. Therefore I have a non-resourcejs end-point that invokes a multi-step process including multiple calls to the resourcejs generated domain api:

/user/verification [POST]

  1. Get the user with the supplied credentials (GET call to resourcejs domain api)
  2. Create a verification object (POST call to resourcejs domain API)
  3. Send an SMS text message
  4. Render the verification form including the verification objectid

I did this, rather painfully, by splitting everything into two APIs

  • a domain api generated by resourcejs whose sole job was to interact with the domain objects
  • a gateway api that composes the various calls to the domain plus any 3rd party apis e.g.the SMS api (as described above)

Consumer software is not expected to interact with the domain api, although it could, instead all traffic is shunted through the gateway.

So now I am thinking your PR changes everything :-)

If I understand you correctly I can go back to having a single api. So instead of the gateway api making full-blown http calls to a distinct, subservient domain api (very chatty), I can now have a single api that composes the domain calls using the resourcejs objects?

If that sounds right to you then can you post some example code of this in action. For example (and sorry if this is obvious), how do you get at your resourcejs object instances in the non-resourcejs route?

Frankly, I am pretty excited by this change. I expended a lot of nervous energy worrying about splitting my apis up. I thought it was me just not understand REST properly so I am relieved that somebody else appears to have the same use-case.

Thanks

@biofractal
Copy link
Contributor Author

@travist
@zackurben

Just thought of a problem with the middleware approach as it applies to me.

One advantage I have found with splitting my apis up into domain and gateway is the ease of subsequently mocking out the domain. I use apiary for building up mock domain apis and this is especially useful for farming out UI work to contractors who can then safely make calls to gateway.

Instead of the gateway talking to the real domain (and so interacting with the real db), the gateway talks to the mock and so returns stock mock json. This allows all the gateway logic to be used without needing a domain api to supply the resources.

If I were to merge my gateway with my domain then I would lose this mocking ability as I would be calling directly to resourcejs via the middleware methods (I think).

Perhaps, therefore, it would it be a good idea to add this mocking capability to resourcejs? If a mock url was provided via the options then a call to the api would route into the mock api rather than running through the resourcejs method and so through to the db.

If you think this idea has legs then I will open a new issue (always assuming I have actually understood your middleware stuff in the first place).

@zackurben
Copy link
Contributor

Hey @biofractal,

as it stands, I believe my addition of composable middleware will work for you, however I'm not sure it will be the most elegant solution, when compared to your proposed virtual solution.

With the ability to manually invoke an endpoint, you can do the following with the user/register scenario I described previously:

// other boilerplate code..

/**
 * Create our resourcejs endpoints for /user
 *
 * [POST] /user
 * [GET] /user
 * [GET] /user/:userId
 * [PUT] /user/:userId
 * [DELETE] /user/:userId
 */
var resourcejs = Resource(app, '', 'user', ResourceModel).rest();

app.post('/register', function(req, res, next) {
  // Do some user registration specific logic here!

  // Manually invoke resourcejs endpoint for POST /user!
  resourcejs['/user/:userId'].post.call(this, req, res, next);
});

With the previous code, any request to [POST] /register will execute its specific logic, and internally invoke the [POST] /user endpoint to get a response.

That is a pretty simple example of what you can do with access to the resourcejs middleware. Additionally for your mocking scenario, you can either set res.skipResource = true; and chain an external endpoint to an internal endpoint, where data wont be manipulated or persisted, or just outright mock an endpoint; for example:

// other boilerplate code..

/**
 * Create our resourcejs endpoints for /user
 *
 * [POST] /domain/user
 * [GET] /domain/user
 * [GET] /domain/user/:userId
 * [PUT] /domain/user/:userId
 * [DELETE] /domain/user/:userId
 */
var resourcejs = Resource(app, '/domain', 'user', ResourceModel).rest({
  beforePost: function(req, res, next) {
    // Some before db storage logic!

    if (req.skipResource) {
      // if data persistence was cancelled by /mock2/user, 
      // return what would have been persisted to the db.
      res.status(201).send(someObject);
    } else {
       // Allow resourcejs to continue normal execution.
       next();
    }
  }
});

// An example of mocking with the middleware..
// Any request will return a dummy response, where any logic could be added.
app.post('/mock1/user', function(req, res, next) {
  res.status(201).send({_id: null, username: req.body.username});
});

// An additional way to mock with req.skipResource
app.post('/mock2/user', function(req, res, next) {
  // this will allow the request to perform any logic upto the point of db manipulation.
  req.skipResource = true;

  resourcejs['/domain/user/:userId'].post.call(this, req, res, next);
});

// Link the public /gateway/authenticate endpoint to the internal /domain/user endpoint.
app.post('/gateway/authenticate', function(req, res, next) {
  // Do some user authentication specific logic here!

  // Manually invoke resourcejs endpoint for GET /domain/user/:userId!
  // Here the response is dependent on the result from the manual request
  // with the given request data.
  resourcejs['/domain/user/:userId'].get.call(this, req, res, next);
});

Hopefully this has helped you find a solution for your problem, because I think what you want it already in Resource.js! Let me know if you have any additional questions or thoughts!

@biofractal
Copy link
Contributor Author

Hi @zackurben

Thanks for the quick and comprehensive reply.

I think you are correct, there does not need to be any new functionality added to resourcejs. I am currently working on a mocking system that uses the before and after handlers - it appears to show some promise. Once I have restored mocking I will definitely be trying out your middleware feature to see how flies. I will keep you posted.

Do we have a better place for this type of ad hoc resourcejs discussion?

@zackurben
Copy link
Contributor

@biofractal no problem, let me know if any more issues arise.

We can take this to email, but I rather keep this discussion public for anyone else who runs into similar issues.. Let me know what you think.

@biofractal
Copy link
Contributor Author

I was wondering if maybe we should start a new issue for the open discussions of ideas, or does the GitHub repo provide some other mechanism for general topics? My pedant-gland is throbbing because we are way off-topic for this pull-request :-)

I think resourcejs has some potential as a package. You and I are using it and hitting on the real-life issues. It would be good to share those experiences and maybe come up with some ideas.

@zackurben
Copy link
Contributor

@biofractal haha understood.

If you have a specific/general topic, you can make an issue and tag travis and myself for discussion, but other than that I cant think of a good way to publicly converse topics easily.

Let me know what you think about that.

@biofractal
Copy link
Contributor Author

@travist

Can you accept this PR please? Last PR related note I made was to mention that I had brought my fork back in line with your upstream-master so I reckon we are good to go.

@travist
Copy link
Owner

travist commented Jul 9, 2015

I am good with it...

travist added a commit that referenced this pull request Jul 9, 2015
virtual method for aggregate resources
@travist travist merged commit 40b64bc into travist:master Jul 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants