feathersjs / feathers

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

Microservices: Recommended way to refactor an app by splitting services? #332

Open lius opened 8 years ago

lius commented 8 years ago

I'm trying to find a practical/recommend way to extract services, separating them into their own instances, following microservices-style. In this matter, there is an interesting statement on the landing page of feathers:

Service Oriented: Feathers Gives you the structure to build service oriented apps from day one. When you eventually need to split up your app into microservices it's an easy transition and Feathers your apps can scale painlessly.

But, I still could not find information about how to deal with this use case in feathers.

In practical terms, I'm focusing initially on two basic topics:

Thanks in advance!

daffl commented 8 years ago

You are right, there isn't too much documentation out there yet and we created https://github.com/feathersjs/feathers/issues/157 a little while ago to keep track of that. Here are some of my thoughts on the two topics you mentioned:

Let's assume we initially have a server with two services, maybe something like

app.use('/users', memory())
  .use('/todos', memory());

The users service is getting more traffic than the one server can handle so we want to move it to another system by creating another app for it:

// server1
app.use('/users', memory());
// server2
app.use('/todos', memory());

For the /todos service to now communicate with the remote service we can use Feathers as the client to connect to it (Websockets are fast and bidirectional so why not use them for server-to-server communication?):

// server2
const client = require('feathers/client')
const socketClient = require('feathers-socketio/client');
const io = require('socket.io-client');

const socket = io('http://other-server.com');
const otherApp = client().configure(socketClient(socket));

app.use('/todos', memory())
 .use('/users', otherApp.service('users'));

This would basically transparently pass the user service to the remote service through a websocket connection. Anything using the original /users endpoint won't have to change anything.

As for authentication, there is many different options. In the above scenario the easiest way would be for server1 to just whitelist the other servers IP address since server2 is still the only point of communication for the clients. Eventually server2 could just become a gateway that handles user authentication and then just passes service calls through to other servers (which don't have to worry about authentication other than checking the origin IP address).

lius commented 8 years ago

Thanks for the response! Do you plan to cover this topic on official docs (maybe a sub-section of Guides)? Let me know if I can contribute in this regard.

daffl commented 8 years ago

Definitely. And we could definitely use some help. Maybe lets gather first what we'd like to see in a guide and then make some demo applications?

ekryski commented 8 years ago

💯 @daffl is bang on. This is something that I am planning on tackling officially over the next month or so as I have committed to giving a presentation on it.

lius commented 8 years ago

@daffl The approach you mentioned using feathers-client on a backend microservice allows bidirectional communication via streaming data, since socket.io is used. Feathers already has semantic/API for this scenario? I'm thinking on a situation where a microservice publish a event for other interested microservices, not a browser client.

daffl commented 8 years ago

Yes but why couldn't the other interested services also use websockets? We are thinking of adding other providers (e.g. for different messaging services) but for now websockets seem to be fast enough and it isn't written anywhere that they can only be used in the browser.

justingreenberg commented 8 years ago

@daffl @ekryski as feathers applications become distributed, we would also need to address issues such as at-least-once delivery (acks), idempotency, transactions, backpressure (queues), etc. have you considered something like "feathers-service-worker?".. ie. external processes that consume events from durable amqp (ie rabbitmq) queue or redis (using ie kue)?

lius commented 8 years ago

@daffl I think I did not understand well before your answer. As long as the interested services uses web sockets to subscribe to the producer service, using regular events API to publish new events on producer it's enough to implement this in feathers, right?

I agree with @justingreenberg, another issue to be addressed is replaying requests when the responsible microservices goes offline (crash, updates, etc). Technically, this would be address by using message queues too.

imns commented 8 years ago

Any progress on this? I'd really love to see some documentation / a working example. Especially in regards to how authentication is handled.

ekryski commented 8 years ago

@imns we are working on this. I started working on example and splitting an app up and we realized there are some limitations with how auth is set up. So we're currently refactoring auth to better support this. Should be about a week for that to land.

imns commented 8 years ago

Oh man, I could use this so bad right now :D

Just out of curiosity are you reworking auth to work with multiple front-ends as well? I think a lot of larger apps now-a-days use a microservice on the backend, but also break their front-end apps up.

marshallswain commented 8 years ago

If you have a few extra bucks, I think this book could be considered recommended reading: https://www.amazon.com/Building-Microservices-Sam-Newman/dp/1491950358/ref=sr_1_1?ie=UTF8&qid=1469071253&sr=8-1&keywords=Microservices

juanpujol commented 8 years ago

Guys, any updates on this? Thank you.

marshallswain commented 8 years ago

@juanpujol the latest information will always be in this ticket. Thanks for checking, though.

idibidiart commented 7 years ago

(marcfawzi here)

I've not read the entire thread but I assume that we'll be able to use a database per service (in the Micreoservices architecture having this separation is crucial to maintaining the abstraction. Else, if all services share the same database what prevents folks from defining relations across service models (a violation of the microservices abstraction) instead of composing services using the uniform service API?

daffl commented 7 years ago

Nothing really prevents that, I'd say it's on you not to do that. It will only really be a problem if you define your models and relationships at the ORM level. I don't think that getting relationships at the service level by calling out to another services violates the microservice abstraction.

idibidiart commented 7 years ago

Yeah exactly what I meant. I tend to think that segregation of services where each service has its own db is a sure way to force composition via the service interface rather than at the ORM level.

But Feathers is more general than a Microservices framework, so all good here. Thanks David! This keeps looking better and better the more I dig into it! Great work all around!

zpetterd commented 7 years ago

Just thought I would mention this here are it solves some of the problems that come up with using Feathers in a micro services environment. It is only the first implementation but if you have anything to contribute feel free to here https://github.com/zapur1/feathers-rabbitmq/issues/1

idibidiart commented 7 years ago

@zapur1 that looks promising... I've posted a comment to get the discussion started

ekryski commented 7 years ago

You can use also use https://github.com/feathersjs/feathers-sync with rabbitMQ, Redis, or MongoDB as the message brokers in between services to keep them all in sync.

zpetterd commented 7 years ago

@ekryski feathers-sync solves a bit of a different problem, what if you only want a event to be received once within the realm of a named service(not a Feathers service but a microservices style service)? If you had multiple instances of a single service receiving events from a single API app each one will receive the event likely duplicating the actions that are taken when that event is received across multiple instances of the exact same code.

zpetterd commented 7 years ago

@ekryski I just had a look at the repo again and zapur1/feathers-rabbitmq solves the exact problem you mention in "Caveats"

InnerPeace080 commented 7 years ago

if i connect a service 1 to service 2 by socketClient , so if my service 2 have multi instance , how can i implement load balance :(

dottodot commented 7 years ago

If I create a separate serverside app that connects via socketClient what's the best way to authenticate so that any of the services are available regardless of any restrictions that have been set up.

claustres commented 6 years ago

I add my 2 cents on this with https://github.com/kalisio/feathers-distributed, it aims at deploying N feathers apps holding different services talking together, so that you can develop each one independently. It is different from https://github.com/feathersjs/feathers-sync which aims at deploying N feathers apps holding the same services as far as I understand. All of this raises a set of questions like:

aecorredor commented 6 years ago

@daffl on the example that you gave involving server 1 and server 2, how would you secure the /users service in server 2? If we had that service defined in server 2 we could use hooks in the specific service hook file, but since we're doing app.use('/users', otherApp.service('users'));, how would we make sure that calls to that service from server 2 would only be done if the user authenticated first?

EDIT: Nvm, I think I have an idea: we could do something like const usersService = app.service('users') and then usersService.hooks(hooks) where hooks has the auth hooks required to secure the endpoint right?

daffl commented 6 years ago

I wrote more about how distributed authentication could be done in https://stackoverflow.com/questions/41076627/evaluating-featherjs-authentication-needs/41095638#41095638:

There are several ways of splitting up services each with their own advantages and drawbacks. One generally important thing for Feathers is that there are no sessions, just JSON web tokens. JWTs are stateless and can be read by any server that shares the same secret so there does not have to be a central session store. The two main options I can think of are:

  1. Have a main application that handles authorization and managing all connected clients but instead of having services that talk to the database they connect to separate simple individual API servers in the internal network. This is the easier setup and the advantage is that the internal API servers can be super simple and don't need authentication at all (since the main application is allowed to do everything and will make queries according to the authenticated users restrictions). The disadvantage is that the main application is still the bottleneck (but with a decreased load since it basically acts as a proxy to internal APIs).

my50m

  1. Every client connects to every API server they need using a JWT. The JWT is created by a separate authentication (or user) API. This is the more scalable solution since the only bottleneck is retrieving the most up-to-date user information from a common users service (which might not even always be necessary). The disadvantage is that it is more complex to manage on the client side and authentication (at least for JWT) will have to be configured on every server. Due to the statelessness of JWT however, there does not need to be any shared sessions.

lw1bg