feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.03k stars 748 forks source link

Microservices - current state #939

Open danny-waite opened 6 years ago

danny-waite commented 6 years ago

I love feathers having used it for a number of projects to date. I find it's real sweet spot to be applications that have a single backend service or for creating http based microservices. However when considering it's use in a more advanced microservices design that I'd feel happy running in production, I find various roadblocks:

I realise a lot of these topics have been covered in various issues and posts however I was looking for more of a 'current state of the world' view and recommendations moving forward. I guess I'm looking to stay as much on the happy/proven path without having to bake my own functionality where possible.

I'm more than happy to contribute if some of the above capabilities are lacking currently.

daffl commented 6 years ago

I know @claustres has been doing the work around feathers-distributed and had some great ideas around distributing so maybe he can chime in here, too.

From my perspective on a module basis, feathers-sync has no open issues and about two thousand downloads a month so I'm assuming that it works for the people using it (it does for me). Unfortunately assuming is all you can do in open source because a small number of issues either means it works great for everybody or nobody is using it πŸ˜‰

From an architectural perspective I found that instead of forcing to split everything up right at the beginning you can actually get pretty far with the monolith. With feathers-sync it is fairly straightforward to scale that monolith onto multiple instances to get scalability and fault tolerance. At the moment I would even go as far as claiming that this covers 95% of all application use cases.

When we do split up our APIs it usually happens more naturally on a per-responsibility basis. They then just communicate with each other through Feathers clients using all the infrastructure that is already in place.

My question for you (and others that are asking about this) would be:

subodhpareek18 commented 6 years ago

A monolith might be a good way to start, and the conventional feathers service definitely is service oriented and would make it super easy to pluck it out and put it on a different server later on.

But there are a lot of benefits to being able to split up feathers services into separate servers, which might become apparent with scale. Disclaimer: I have not worked at large scale so the points below are just my thoughts, and I have been thinking a lot about it, because we have to transition from current feathers monoliths to a distributed system soon.

As I said before feathers already let's you write service oriented code. But if something like a more polished version of feathers-distributed was baked in, it would've been magical and in my opinion make feathers the near perfect framework in Node.js it definitely deserves to be. I am already in love with it, and appreciate all the hard work put into it.

As for now, I have two feathers based monoliths and I have written a sort of a REST only client in one for the other. Which is ofcourse in no way ideal but lets me get on with it for now.

// client

import feathers from '@feathersjs/client';
import errors from '@feathersjs/errors';
import ax from 'axios';

export default class FeathersClient {
  constructor(opts) {
    const { url, token } = opts;

    const client = feathers();
    const restClient = feathers.rest(url);
    const axios = ax.create({ headers: { 'X-Access-Token': token } });
    client.configure(restClient.axios(axios));

    this.client = client;
  }

  async create(rawData) {
    const { service, method, args: { id, data, params } = {} } = rawData;
    const proxy = this.client.service(service);

    const methodMap = {
      find: () => proxy.find(params),
      get: () => proxy.get(id, params),
      create: () => proxy.create(data, params),
      update: () => proxy.update(id, data, params),
      patch: () => proxy.patch(id, data, params),
      remove: () => proxy.remove(id, params)
    };

    try {
      const res = await methodMap[method]();
      return res;
    } catch (error) {
      throw new (errors[error.name] || errors.BadRequest)(error.message);
    }
  }
}
// usage

this.use('/server1', new FeathersClient(server1Config));
/**
.
.
.
*/
export default class ServiceA {
  setup(app) {
    this.otherServer = app.service('/server1')
  }

  async find() {
    const res = await this.otherServer.create({
      service: 'someServiceOnServer1',
      method: 'patch',
      args: { id, data, params }
    })
  }
}

The ideal behaviour however would have been, one could simply do this.

export default class ServiceA {
  setup(app) {
    // service discovery
    this.someServiceOnServer1 = app.service('/someServiceOnServer1')
  }

  async find() {
    const res = await this.someServiceOnServer1.patch(id, data, params)
  }
}
danny-waite commented 6 years ago

wow, I was just about to write a response @daffl's excellent response however I really cannot say it any better than @subodhpareek18

As @subodhpareek18 said, neither large monoliths or distributed microservices are easy but breaking things down into services means:

I just feel that given the current capabilities of feathers and its great developer experience, if we could extend that to a well designed microservices capability (with gateway functionality) I really think it would become the absolute champion framework.

claustres commented 6 years ago

Micro-service is a recurrent raised issue, I think an article in the blog or a dedicated section in the doc would be great to avoid this popping up again an again (I can help).

Like @daffl from my perspective on a module basis, feathers-distributed has no open bug issues and about fifty downloads a month so I'm assuming that it works for the people using it. Unfortunately we did not yet test it on our production infrastructure so we warn that for us it is still in beta, however if someone told us it has been successfully used in production we can change the README πŸ˜…

I must confess I have some trouble with the microservices trend. Splitting things up with a well-defined boundary and interface is something probably as old as programming is and known as modularity. IMHO microservices are more a deployment issue than anything else: deploying all modules in the same process space (a.k.a. monolith) or in different process space (a.k.a. microservices).

Note: different processes can be on the same physical host (e.g. when deploying services as containers in a cluster).

Of course this changes how you code a little bit because you need to use some inter-process communication layer and if you want to support auto-scaling (i.e. scale on-the-fly) something like service discovery. You also have to manage load balancing. This makes a best solution almost impossible to be found because you can reuse a lot of different tools depending on your needs on these subjects.

I agree you can go pretty far with the monolith and feathers-sync. I would say it is something between the true monolith and the true microservices, i.e. you scale your entire app not its underlying modules/services according to their workload. It does not handle auto-scaling as well and this is why we launched feathers-distributed. The goal is to make the communication layer, service discovery and load balancing transparent from the programmer perspective based on the simple cote module. You can create modules/services as usual and simply choose to deploy them in microservices easily if required.

Note: IMHO it is the way to go in order to keep the feathers core as minimalist and flexible as possible. Feathers is a framework to build apps/services, deploying as microservices should be the responsibility of others frameworks or third-party modules.

You could of course split up your API "by hand" on a per-responsibility basis as @daffl said and just communicate with each other through Feathers clients using all the infrastructure that is already in place. However this will require more manual work, will create a tight coupling with your underlying infrastructure and will not allow auto-scaling unless you create some discovery mechanism.

Last but not least I would like to answer some of your ideas:

Yes for really large apps but for tens of services load-balancing and keeping some "dead" services in memory will not consume anything relevent

The fact your services are distributed does not make your app fault-tolerant. If you are awaiting the answer of a service that fails, no matter where it is located, if you do not handle the error your app will fail too. Some tools allow to make things like the communication layer or DB fault-tolerant to hardware failures but this does not cover your business logic.

Using a gateway you can also completely hide your underlying service infrastructure using VPC or things like than, which is probably far more better. Usually your business logic is on the server side, the clients will not orchestrate service calls so you don't need to make all servers public. Managing secrets like API keys is really hard with auto-scaling.

That's true but at some point you need integration and end-to-end testing, which is far more important than unit testing. As previously said you can also create different modules that are integrated into a monolith app so that different people can work on modules or the app, i.e. a monolith app does not mean a single repo IMHO.

I hope this comment helped the debate and don't forget that you can help building a great feathers-distributed πŸ‘

musicformellons commented 5 years ago

Moleculerjs has nice microservice features and seems to gain traction. There is an adapter to feathers services: https://github.com/zygos/moleculer-adapter-feathers#readme

dekelev commented 4 years ago

I've just published a new library to distribute FeathersJS apps over the network with inter-service communication using HTTP protocol.

The feathers-http-distributed module was built as preparation for migrating all our FeathersJS services to Kubernetes, though it can be used for a broad range of use cases besides Kubernetes.

Enjoy!