1

We have an application where users can sign in only with Facebook. On successful Facebook login an another http rest call is made to store the name, email and some other information from Facebook login provider. We would end up with data consistency issues if The second RESTful service fail to store the information from fb to the the Mongodb

  1. User can only signup/ sign in via Facebook
  2. On every successful signup Facebook sends back the some information like the users name, email and uid
  3. We then store this information on our database immediately

So if the 3rd step fails, we loose the information about the user who signed up via Facebook.

loginWithFacebook() {
      const provider = new firebase.auth.FacebookAuthProvider();
      provider.addScope('user_birthday');
      provider.addScope('user_friends');
      provider.addScope('user_gender');
      return new Promise<any>((resolve, reject) => {
        this.afAuth.auth
        .signInWithPopup(provider) // a call made to sign up via fb
        .then(res => {
          if (res) {
            resolve(res);
            if (res.additionalUserInfo.isNewUser) { // creatin profile only if he is a new user
              this.createProfile(res); // a call to store the response in the db
            }
            this.setTokenSession(res.credential.accessToken);
          }
        }, err => {
          console.log(err);
          reject(err);
        })
      })
    }

Is there a neat architectural way of orchestrating this?

Karty
  • 1,329
  • 6
  • 21
  • 32
  • Please provide more details about what is in these requests, and how the data relates between the two. Ideally, you simply remove the need to do this. But, if you must, there are some ways around the problem, each with varying tradeoffs. Also, are both requests happening at the same time, or are they sequential? – Brad Nov 11 '18 at 19:25
  • I have updated my questions. – Karty Nov 11 '18 at 19:35
  • See https://stackoverflow.com/questions/47740838/couple-firebase-firestore-and-firebase-storage-calls-together-into-a-batch/47740956#47740956 – Frank van Puffelen Nov 11 '18 at 19:36
  • 1
    I think your edit makes my answer rather unnecessary. This is not a distributed system. If the REST call to store the data fails (because of a transient issue), just tell that to the user and ask them to sign up again. – nicholaswmin Nov 11 '18 at 19:36
  • They can sign in only once. The fb will not let sign in again. – Karty Nov 11 '18 at 19:41
  • 1
    @Karty You need to add actual details about what you're sending/receiving for all of these API calls. The three steps you just listed out all require only one API call. I don't see what the problem is here. Also, I suspect your Facebook authentication is bungled a bit... while the API is a real hassle to deal with, it is possible to check your app's authentication status. – Brad Nov 11 '18 at 19:46
  • @Brad added the code in my question – Karty Nov 11 '18 at 20:01
  • 1
    @Karty You should be returning a promise from `createProfile()`, and then returning that promise here so that the outer promise all rolls up together. Additionally though, I don't see a massive risk here. Where is it that you're immediately calling another API request after `createProfile()`? Why not return the created profile with that request so whatever needs it can use it right away? Also, your server-side script shouldn't be returning until it is reasonably certain important data is committed and replicated in the DB (not always possible). – Brad Nov 11 '18 at 20:04
  • The user is a new user only when he signs in for the first time. signInWithPopup is the first call to fb to sign up / login the createProfile method will store the response in the db. Create profile is the last call after successfuly signed up with fb. – Karty Nov 11 '18 at 20:16

1 Answers1

3

Buckle up, because this is not a trivial problem.

If this is a true microservice architecture, where each service has it's own dedicated database, then you obviously can't efficiently perform a transaction. You might be able to do a 2-phase commit but that has problems of it's own, especially with long-lived DB locks.

A scalable approach to your problem is the Distributed Saga Pattern:

Implement each business transaction that spans multiple services as a saga. A saga is a sequence of local transactions. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails because it violates a business rule then the saga executes a series of compensating transactions that undo the changes that were made by the preceding local transactions.

I think the above covers it pretty succinctly so I'm going to intentionally veer off on a tangent:

Distributed systems are hard to manage. Make sure you aren't violating YAGNI with your system architecture. If possible, ask yourself whether you really need a distributed architecture at this point.

Ensuring data consistency in a distributed system is a relatively complex task that in most cases requires a dedicated coordinator service, extra code to manage it all and all the complexity that comes with all this extra machinery.

nicholaswmin
  • 21,686
  • 15
  • 91
  • 167