Feathers: Allow RPC style method in feathers service

Created on 29 Dec 2016  ·  12Comments  ·  Source: feathersjs/feathers

Cannot define custom method name in Feathers service

I know feathersjs after using seneca and actionhero. Feathersjs does have several good points: real time api, hook, database adapter (sequelize, knex, objection). However I cannot define my own method name that is not within predefined service methods [find, get, create, update, patch, remove, setup].

In feathers.slack, eddyystop suggested using method name as first parameter
rpcService.create({ action: 'the RPC action', param1: ... , ... })
but it looks like an anti-pattern way.

I create a math service that has add, minus, multiply, divide methods. I have to route to one endpoint and use switch to check first parameter. The logic becomes bulky and hard to test.

Some examples that using limited verbs of REST may not fit:

  • Send email action that does not store mail message in database. Method 'create' does not describe accurately 'send' action
  • Place an order in e-commerce web site. Behind scene, there are many records will be inserted in one transaction: order_item, order_header, voucher_tracking, .... It is hard to relate this action to one entity.

The doctrine of REST is resource and limited HTTP verbs.
The doctrine of RPC is action, command.

Suggestion

It will be great if feathers support both REST and RPC. Give developer freedom to choose which option is most suitable per service.

  • Allow appending method : service.method(params) . method should be VERB --> RPC style
  • Allow appending entity: service.entity(params). entity should be NOUN --> REST style

Most helpful comment

Feathers is built around the REST architectural constraints and there are many good reasons for it (some mentioned in what @marshallswain already linked). So far I have not come across a case where any "RPC" style action couldn't also be modelled through the Feathers service and hooks pattern.

The benefits (like security, predictability, sending well defined real-time events) so far have heavily outweighed the change in thinking required when conceptualizing your application logic.

For your examples:

  • Send email action that does not store mail message in database. Method 'create' does not describe accurately 'send' action

It doesn't say anywhere that resources have to be database records. It can be any kind of resource (like the current weather for a city or creating an email which will send it). Sending emails is usually done with either a separate email service:

app.use('/email', {
  create(data) {
    return sendEmail(data);
  }
})

Or in a hook.

  • Place an order in e-commerce web site. Behind scene, there are many records will be inserted in one transaction: order_item, order_header, voucher_tracking, .... It is hard to relate this action to one entity.

This is what Feathers hooks are for. So when creating a new order you also have a well defined hook chain:

app.service('orders').before({
  create: [
    validateData(),
    checkStock(),
    checkVoucher()
  ]
}).after({
  create: [
    chargePayment(), // hook that calls `app.service('payment').create()`
    sendEmail(), // hook that calls `app.service('email').create()`
    updateStock() // Update product stock here
  ]
})

It might feel a little weird at first but I encourage you to try and fit it into those patterns. If you are looking for an RPC style library then Feathers is not the right choice.

All 12 comments

You may have already ready this, but if not, some of this is covered here: https://docs.feathersjs.com/clients/readme.html#caveats

Feathers is built around the REST architectural constraints and there are many good reasons for it (some mentioned in what @marshallswain already linked). So far I have not come across a case where any "RPC" style action couldn't also be modelled through the Feathers service and hooks pattern.

The benefits (like security, predictability, sending well defined real-time events) so far have heavily outweighed the change in thinking required when conceptualizing your application logic.

For your examples:

  • Send email action that does not store mail message in database. Method 'create' does not describe accurately 'send' action

It doesn't say anywhere that resources have to be database records. It can be any kind of resource (like the current weather for a city or creating an email which will send it). Sending emails is usually done with either a separate email service:

app.use('/email', {
  create(data) {
    return sendEmail(data);
  }
})

Or in a hook.

  • Place an order in e-commerce web site. Behind scene, there are many records will be inserted in one transaction: order_item, order_header, voucher_tracking, .... It is hard to relate this action to one entity.

This is what Feathers hooks are for. So when creating a new order you also have a well defined hook chain:

app.service('orders').before({
  create: [
    validateData(),
    checkStock(),
    checkVoucher()
  ]
}).after({
  create: [
    chargePayment(), // hook that calls `app.service('payment').create()`
    sendEmail(), // hook that calls `app.service('email').create()`
    updateStock() // Update product stock here
  ]
})

It might feel a little weird at first but I encourage you to try and fit it into those patterns. If you are looking for an RPC style library then Feathers is not the right choice.

Agree with thread starter. This could be a design flaw of FeatherJS.
While email and orders services example can be modeled using 'create' method, there are also plenty business logic instances in my line of work where we cannot simply use rest methods.

I will not continue a high level discussion of this. If you have a specific example we're happy to show how to model more complex use cases around those patterns. Feathers is a framework built around and enforcing the REST architecture pattern. If you want to provide an actually RESTful API (and get real-time functionality for free) there is no way around those constraints.

If you don't, as I already said, you will have to look for a different framework.

I found GraphQL + PostgraphQL is more expressive. Already switched !
Featherjs is a good REST framework but it limits itself to few REST verbs, not practical in real use.

I'm not sure I understand how a pattern that is used by virtually every tech company in the world and has worked for the entirety of the internet for the past 25 years is not practical in real use?

GraphQL works well well for data querying but being able to define arbitrary mutations (instead of a well defined set of verbs that also determine how/if your data is being changed) has its own set of challenges (and is one of the reasons why nobody is using e.g. SOAP anymore).

Hi, I do feel FeathersJS is extremely well design, except this part.
(Infact, i did something similar as FeatherJS REST Crud & Query Service in Java in my work, and we used it extensively. Not a fan of GraphQL)

However, REST service is not the only service an application uses. Or rather rest service shouldnt be just called 'service'.
In certain business logic cases , we need service methods that for eg 'doSomething', instead of create/update/patch/delete. (Of cos, its possible to create a custom service api route, but if we wish to use the websocket, we can't as it has already been controlled by framework isnt it? unless we modify featherjs websocket handling code itself)

Anyways, I do hope u guys can take this as feedback to further improve the already cool framework. Cheers.

I still think it is often just an adjustment in the mental model of approaching the problem. Most "custom" methods/functionality can be represented as a chain of hooks or a custom service even if it might not seem that way initially.

For global "tasks" I don't think there is anything wrong with creating a tasks service that allows to kick them of. It could be used like

app.service('tasks').create({
  action: 'doSomething',
  arguments: []
});

Those can be used through websockets and also report their status back e.g. through a custom event.

yes. certainly, that is a workaround. not that neat though. still a workaround. :/

I've created a plugin, which lets you add custom methods to services: https://www.npmjs.com/package/feathers-custom-methods

@arve0 Still using create on the client, but I like it so far. I am thinking of adding

class BaseService {
  constructor (options) {
    this.options = options || {};
  }

  async create (data, params) {
    if (Array.isArray(data)) {
      return Promise.all(data.map(current => this.create(current, params)));
    }
    return data;
  }

}

module.exports = BaseService;

Base service, so that any service created doesnt need to include create command.

const BaseService = require('../base/base.service');

/* eslint-disable no-unused-vars */
class Service extends BaseService{


  async doSomething (){
    console.log('custom');
     return {
      text: `custom response`
    };

  }
}

module.exports = function (options) {
  return new Service(options);
};

module.exports.Service = Service;

Also, it would be nice to include this code into generator. :)

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue with a link to this issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

codeus-de picture codeus-de  ·  4Comments

perminder-klair picture perminder-klair  ·  3Comments

RickEyre picture RickEyre  ·  4Comments

rstegg picture rstegg  ·  3Comments

davigmacode picture davigmacode  ·  3Comments