Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parse.com 'retweet' pattern is too verbose

So, I have been tasked with implementing a 'retweet'-like functionality in an app (iOS, Swift), using Parse.

This has been asked before here, but that's a) pretty high-level and b) I get the task at hand - I am not necessarily asking for help on the architectural decisions, though if it seems that I am obviously missing something, I'm happy to accept feedback.

My app has CAUSES which are each created by a USER. There is also a FOLLOW table with a TO and a FROM user. So to start, I simply query the CAUSES table, with the constraint that the USER who posted should match the objectId of a TO user (where the current user is the FROM user) in the FOLLOW table. More succinctly:

let getFollowedUsersQuery = PFQuery(className: Constants.kParseClassFollowers)
getFollowedUsersQuery.whereKey(Constants.kParseFieldFromUser, equalTo: PFUser.currentUser()!)

let causesQuery = PFQuery(className: Constants.kParseClassCauses)
causesQuery.whereKey(Constants.kParseFieldFromUser, matchesKey: Constants.kParseFieldToUser, inQuery: getFollowedUsersQuery)
causesQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
    if let causes = objects {
        for cause in causes {
          // populate the tableview cells, etc.
        }
    }
})

Now I have all the causes from users that i follow... that's all pretty standard.

Here's where it gets tricky.
Each CAUSE also has a Relation called SUPPORTERS. Now I need to architect a way to get all the CAUSES from people that I do not follow, but which have in their list of supporters a user that I follow.

I have yet to find an elegant solution, though I am approaching a 'brute force' one, and it is so cumbersome and verbose that the better half of my programmer's brain is screaming at me like Susan Powter...

Here's a sample:

let retweetQuery = PFQuery(className: Constants.kParseClassCauses)
retweetQuery.orderByDescending(Constants.kParseFieldCreatedAt)
retweetQuery.whereKey(Constants.kParseFieldFromUser, notEqualTo: PFUser.currentUser()!)
retweetQuery.whereKey(Constants.kParseFieldFromUser, doesNotMatchKey: Constants.kParseFieldToUser, inQuery: getFollowedUsersQuery)
retweetQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
    if let causes = objects {
        for cause in causes {
            let supporterRelations = cause.relationForKey(Constants.kParseClassSupporters)
            let supporterQuery = supporterRelations.query()
            supporterQuery.findObjectsInBackgroundWithBlock { (supporters, error) in
                if(error == nil && supporters?.count > 0) {
                    for supporter in supporters! {
                        let user:PFUser = supporter as! PFUser
                        getFollowedUsersQuery.whereKey(Constants.kParseFieldToUser, equalTo: user)
                        getFollowedUsersQuery.whereKey(Constants.kParseFieldFromUser, equalTo: PFUser.currentUser()!)
                        getFollowedUsersQuery.findObjectsInBackgroundWithBlock({ (results, error) -> Void in
                            if(error == nil && results?.count > 0) {
                                for result in results! {
                                    // do stuff
                                }
                            }
                        })
                    }
                }
            }
        }
    }
})

Now, this is pure madness, and incredibly wasteful (especially considering how Parse calculates the free tier - I feel this could really contribute heavily to my API limit if pushed to production).

Having already done two queries, I redo one entirely, then perform another query for each cause on the SUPPORTER Relations, then do another query on each user in that Relation to see if I follow them... and once I have that information, I need to loop through that User's supported causes (because of the asynchronous returning of the Parse queries, I don't feel that I can just reach back into the parent loops at all) ... which I haven't implemented yet, cause I'm about to throw in the towel - there has to be a better way!

I hope that I'm missing a strategy here...

like image 604
jesses.co.tt Avatar asked Jan 06 '16 09:01

jesses.co.tt


1 Answers

@jesses.co.tt I'm sorry if this is too little too late, I definitely take note that I am answering this months after it was asked, but I do think this is worth answering in general (and maybe could still be of value to you).

In general, I 100% agree that that triple query with Parse is a) going to be massively inefficient the way it's billed (in the old system) and b) with that in mind, seems like the wrong approach even with self-hosted Parse (which contextually is the only mechanism usable at this point since Parse has now shut down, but I think would still have been up possibly when question was asked...regardless...). There's 2 solutions I see that can fix this in a fairly clean way, assuming these changes can be made to the general schema/architecture.

1) A first solution is to re-schematize the data set and essentially "foreign key" the subset of supporters each User has in the User table itself. This way, instead of going from Cause->Supporter->User, you'd theoretically be able to do Cause->User (where from users, you'd get their supporters by default as it'd be a column there). To do this in Parse, if I remember correctly you can set a column to have an array of a certain type as it's value, and then have object links there (that show up nicely in the Parse dashboard) that are actual Supporter table objects, but maintaining this column of links to that table in your User table.

While is a bit more work on the write side, since you'll have to manually do that (by manually I mean write the code yourself to do that, it should be automated but it won't happen for free in terms of development). With this slightly more upfront write operation, you then have a 2 step read instead of 3.

2) A second solution is to use a different provider for this kind of data, if query speed is an issue. In several of my projects I use socket based approaches for this kind of data query lookup, and thinks like Pusher or Firebase (both very different levels of "all inclusiveness", Firebase being much more like a Parse but from Google this time, Pusher being a bit more basic and "DIY").

With Firebase, for example, and a socket to this data set + a schema in which the Cause table itself has a cache of what Users belong to them (and in which User has a cache of their Supporters), this 3 step look up could effectively be 1 query with 2 parameters (which is I think the ideal scenario). I believe this can be achieved with Parse as well, but would require another step of schema refactoring to the first proposed solution.

I think in the comments something similar to this is recommended (both above solutions) but in a much more un-elaborated format. I hope this helps the original question-asker or someone. This could also, theoretically, as someone recommended, use PubNub as 1 comment mentioned, but it could just as easily build this schema into a PostgreSQL DB hosted on AWS or Heroku and accomplish the exact same mechanisms as Parse without the overhead.

Also, since this is in reference to Parse which is now only offered as an Open Source self hosted solution, here's a guide to migrate to hosting Parse on one's own on AWS: link here

like image 114
BHendricks Avatar answered Nov 13 '22 20:11

BHendricks