foursquare com scala lift

20
foursquare.com & scala/lift Harry Heymann 1/11/2010

Upload: daniel-fernandez

Post on 26-Jun-2015

83 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Foursquare Com Scala Lift

foursquare.com & scala/lift

Harry Heymann 1/11/2010

Page 2: Foursquare Com Scala Lift

Background

foursquare.com is a "mobile social network"Users "check in" at locations to inform their friends of where they are, to participate in a game, and use the sytem to gather information on what's going on around themYou guys checked in here right? :-)

Page 3: Foursquare Com Scala Lift

Background

foursquare.com originally developed in PHP (running on Apache, using MySQL as a datastore)Written by someone who wasn't an engineer. Poor code quality.A rewrite was clearly required to move forward, but the choice of language and toolkit was wide open anything was possibleI chose to use Scala & The Lift Web Framework

Page 4: Foursquare Com Scala Lift

Why Scala/Lift

I had a strong background in Java so the switch to Scala was easyI like type and compiled code, so something like Python/Django or Ruby/Rails was never seriously consideredJava/Wicket would have been an obvious choice at this point, but experimenting with it for a little bit left me cold. I found it to be not as typesafe as one would want (too many things were just Objects) and the lack of first level functions clearly hindered the expressiveness of the toolkit

Page 5: Foursquare Com Scala Lift

Why Scala/Lift

Scala felt natural. I'd been programming in Java using a very functional/immutable data structures way for a while and Scala just made that easier and more naturalI was very very impressed with the ease of writing AJAX using Lift. Just inline the function to run when a control is clicked in the client and the framework takes care of the rest. No HTTP to worry aboutI wanted to do something a little fun/different. It's the sort of decision that can be made at a startup without the institutional pressures of a larger company.

Page 6: Foursquare Com Scala Lift

The Rewrite

Started (by my self) on day one of the job.Sessions shared between Lift/PHP code using the database for sessionid -> userid mapping. Never really worked 100% perfectly. Scala code serving live pages in production by day 3.90% complete rewrite took 90 days.Last 10% (which required changes on the iPhone client, thus drastically slowing things down) took an additional 60 days. Completed TODAY with release of version 1.5 of client.

Page 7: Foursquare Com Scala Lift

The Rewrite

Lift doesn't play very well with MySQL. We switched to PostgreSQL, and had a disastrous downtime filled weekend fixing various bugs (mostly on the PHP side of things) that cropped up because of differences. If you're starting a Lift project go with PostgreSQL from day 1

Page 8: Foursquare Com Scala Lift

A quick AJAX example

"todo_checkbox" -> ajaxCheckbox( userStatus.equals(Full(TipUserStatus.todo)), (b: Boolean) => { val currentUser = User.currentUser.open_! if (b) markTodo(currentUser) else clearStatus(currentUser) val className = if (b) "tip_checked" else "tip_todo_unchecked" SetElemById("tip_todo"+id, Str(className), "className")}, ("id", "tip_todo_checkbox"+id)))

Page 9: Foursquare Com Scala Lift

lazy loading in 6 lines

<lift:Util.lazyLoad> <lift:Settings.twitter> <!-- markup for twitter settings) </lift:Settings.twitter></lift:Util.lazyLoad>

Page 10: Foursquare Com Scala Lift

lazy loading in 6 lines

def lazyLoad(xhtml: NodeSeq) = { val id = randomString(6) val (name, exp) = ajaxInvoke(() => { SetHtml(id, xhtml) })

<div id={id}> Loading...<img src="/img/ajax_spinner.gif" height="32" width="32" alt="wait"/> {Script(OnLoad(exp.cmd))} </div> }

Page 11: Foursquare Com Scala Lift

A quick look at our API Implementation

def dispatch: LiftRules.DispatchPF = { case req@Req(List("api", "v1", "addtip"), _, PostRequest) => () => wrap(req, requireAuth(addTip)) case req@Req(List("api", "v1", "addvenue"), _, PostRequest) => () => wrap(req, requireAuth(addVenue)) case req@Req(List("api", "v1", "cities"), _, GetRequest) => () => wrap(req, cities) // ... }

Page 12: Foursquare Com Scala Lift

A quick look at our API Implementation

def wrap(req: Req, f: (Req, Box[User]) => Elem) = { def getResponse() = { MasterAuthenticator.authenticate(req) match { case Full(authResult) => (f(req, Full(authResult.user)), 200) case Empty => (f(req, Empty), 200) } } req.path.suffix match { case "" | "xml" => { val (xml, code) = getResponse XmlCodeResponse(Utility.trim(xml), code) } case "json" => { val (xml, code) = getResponse JsonCodeResponse(xmlToJson(xml), code) } case _ => XmlCodeResponse(<error>Invalid Suffix</error>, 501) }}

Page 13: Foursquare Com Scala Lift

A quick look at our API Implementation

def xmlToJson(xml: Elem): JsExp = { Xml.toJson(xml) map { case JField("id", JString(s)) => JField("id", JInt(s.toInt)) case JField("geolat", JString(s)) => JField("geolat", JDouble(s.toDouble)) case JField("geolong", JString(s)) => JField("geolong", JDouble(s.toDouble)) }

JsRaw(Printer.compact(render(json3)))}

Page 14: Foursquare Com Scala Lift

A quick look at our API Implementation

def cities(req: Req, currentUser: Box[User]) = { val geolat = getAttr(req, "geolat", asDouble) val geolong = getAttr(req, "geolong", asDouble) val cities = (geolat, geolong) match { case (Full(lat), Full(long)) => City.activeCities(currentUser, Geolocation.sortOn(lat, long)) case _ => City.activeCities(currentUser, City.alphaSort) } <cities>{cities.flatMap(GenXml(_))}</cities> }}

Page 15: Foursquare Com Scala Lift

A quick look at our API Implementation

def apply(city: City): Elem = { <city> <id>{city.id.is}</id> <name>{city.displayName.is}</name> <shortname>{city.shortName.is}</shortname> <timezone>{city.tz.is}</timezone> <geolat>{city.geolat}</geolat> <geolong>{city.geolong}</geolong> </city> }

Page 16: Foursquare Com Scala Lift

Upsides

Virtually every page on the site is better, more interactive, and prettier than it was before.Writing AJAX style code actually easier in Lift that writing traditional forms (which isn't to say that handling traditional forms is hard). All web programming should work like this.New code is smaller (fewer lines), easier to understand, has fewer bugs, and more features. "Four stars to @foursquare - 1st site in a while I have taken a good look at that didn't have a single security issue (that I could find)" - @rasmus

Page 17: Foursquare Com Scala Lift

Downsides (with apologies to dpp)

Lift has stateful servers. Ultimately this will lead to challenges in production, though it hasn't been too bad so far.Mapper (the Lift ORM layer) is still fairly immature, and comes with a fair number of quirksThe Lift SiteMap is heavier weight than we need. A simpler version for sites that don't need Menu functionality would be helpful.As we scale up, we're likely to hit strange/unexpected issues that no one has seen before. A more commonly used environment might come with more easily accessible global knowledge. But, the Lift community has been VERY helpful thusfar. Small can still be helpful.

Page 18: Foursquare Com Scala Lift

Some stats

~14,000 lines of scala. Includes the website, the mobile website, & the Rest API (including an OAuth server implementation).~6,000 lines of markup

A pretty small amount of code for a fully functional web startup.

Page 19: Foursquare Com Scala Lift

The Future

We still using a single PostgreSQL instance as our only datastore, this model will no get us too much further.Mapper <-> JSON functionality has been implemented in Lift and so now we need to decide where/how to store that JSON (Memcached, Casandra, Goat Rodeo?).We'll be forging some new ground in the scala world here, hopefully it will work out well. Ultimately though the CS concepts here are independent of the language being used.

Page 20: Foursquare Com Scala Lift

That's It!

Questions? We've got time.

PS: We're hiring. E-mail me: [email protected]