200 likes | 301 Views
Scala at egraphs.com. September 2012. Who We Are. Started in Oct 2011, launched in July 2012 An egraph is a digital autograph + audio greeting from your favorite star Currently have >100 MLB players Digital, authenticated, shareable. Like This. www.egraphs.com/66. Tech Stack.
E N D
Scala at egraphs.com September 2012
Who We Are Started in Oct 2011, launched in July 2012 An egraph is a digital autograph + audio greeting from your favorite star Currently have >100 MLB players Digital, authenticated, shareable
Like This www.egraphs.com/66
Tech Stack Scala2.8 Play Framework 1.2.4 App servers managed by CloudBees Postgres cluster managed by EnterpriseDB (AWS, Redis, iOS, Crashlytics, et al)
Want to hear something interesting? From June 1 through mid-July launch, WE STOPPED WRITING TESTS. I am not proud. Amazingly, things didn’t fall over when we opened to the public and customers started using the site. Almost zero NPEs. Few logic bugs.
Scala FTW We were able to achieve this: • Not because we’re code ninjas. I mean, lol. • Little Scala habits with big wins. • Type-safety wherever possible. Like in forms.
A simple example from our codebase defauthenticate(email: String, passwordAttempt: String) : Either[AccountAuthenticationError, Account] = { findByEmail(email) match { case None => Left(new AccountNotFoundError) case Some(account) => account.password match { case None => Left(new AccountPasswordNotSetError) case Some(password) if password.is(passwordAttempt) => Right(account) case _ => Left(new AccountCredentialsError) } } }
My Old Habits A Java dev would have to write: Account account = findByEmail(email); if (account != null) { … handle my business … } A Java devturned Scaladev might write: valmaybeAccount: Option[Account] = findByEmail(email) if (maybeAccount.isDefined) { val account = maybeAccount.get … handle my business … }
Null-Safety Both approaches are error-prone because humans are error-prone. NoSuchElementExceptionsare thrown when you try to None.get. Why not sidestep this whole class of errors altogether? Months into the Egraphs project when we were no longer total n00bs, we did a global-search for Option.get and rewrote them instead to mapor match. Simple but seriously effective for null-safety.
And Type-Safety defauthenticate(email: String, passwordAttempt: String) : Either[AccountAuthenticationError, Account] = { findByEmail(email) match { case None => Left(new AccountNotFoundError) case Some(account) => account.password match { case None => Left(new AccountPasswordNotSetError) case Some(password) if password.is(passwordAttempt) => Right(account) case _ => Left(new AccountCredentialsError) } } }
First Logic Bug Found Since Launch Punch line first: Big surprise that it happens in the few lines of code where types are erased and we lose type-safety. The thing to know about the next slide… celebFilters.requireCelebrityAndProductUrlSlugshas parameter of type: (Celebrity, Product) => Any
Can you spot where the bug is? defpostStorefrontFinalize(celebrityUrlSlug: String, productUrlSlug: String) = postController() { valredirectOrPurchaseData = { celebFilters.requireCelebrityAndProductUrlSlugs { (celeb, product) => val forms = purchaseFormFactory.formsForStorefront(celeb.id) for (formData <- forms.allPurchaseFormsOrRedirect(celeb, product).right) yield { (celeb, product, formData) } } } redirectOrPurchaseData match { case Right((celeb: Celebrity, product: Product, formData: PurchaseForms)) => EgraphPurchaseHandler(celeb, product, formData).execute() case Left(result: play.mvc.Http.Response) => result case whoops => throw new RuntimeException(”This is not a valid purchase request: " + whoops) } }
Warts and All defrequireCelebrityAndProductUrlSlugs( continue: (Celebrity, Product) => Any ) = { requireCelebrityUrlSlug { celebrity => requireCelebrityProductUrl(celebrity) { product => continue(celebrity, product) } } } // We intend to rewrite this with parameterized types and Either // … not with an Any return type.
Forms: Starting Simple Forms explode in complexity with increasing number of inputs. Probably each form input needs to be validated. Often these validations require knowledge of the model objects. This is as simple as it gets… defpostSubscribeMailingList(email: String) = postController() { validateIsEmail(email) if (validationErrors.isEmpty) { redirectWithValidationErrors(…) } else { SubscribeEmail(email).save() new Redirect(GetConfirmation.url(email)) } }
Forms: Complexity Grows defpostAccount(email: String, pw: String, confirmPw: String) = postController() { validateIsEmail(email) validatePasswordIsValid(pw) validateIsSame(pw, confirmPw) validateIsTrue(accountStore.findByEmail(email).isEmpty) if (validationErrors.isEmpty) { redirectWithValidationErrors(…) } else { val account = Account(email).withPassword(pw).save() new Redirect(GetAccount.url(account)) } } // Already, complexity is increasing faster because inputs are interrelated // and require logic from domain models.
Forms: Very Complex, But Type-Safe Most complex form we’ve written so far is our purchase flow. 20+ form inputs…. Our homegrown solution was (abridged): trait Form[+ValidFormType] { protected abstract class FormField[ValueType]{ def name: String defstringsToValidate = { paramsMap(this.name) } def value: Option[ValueType] def error: Option[FormError] } protected defparamsMap: Iterable[String] protected defformAssumingValid: ValidFormType deferrorsOrValidatedForm: Either[Iterable[FormError], ValidFormType] }
Other Topics I Can Talk About Amazing time to launch a business… there is SO much available to a developer to speed the building of complete software. Amazing community support from Scala, Play, and other communities. We host our application servers with CloudBees, which manages AWS EC2 instances for us. They handle deployment, SSL, and autoscaling. Ecosystem provides monitoring and logging cheaply. EnterpriseDB provides us a remote DBA team based in India 24/7. [Cost <75% in-house DBA, which we have not had time to hire anyway.] But cloud services are a brave new world. Things are mostly great, but everyone does overpromise a bit. Play2 and control forcing question: CloudBeesvsHerokuvs hire Tech Ops team
We’re Hiring Rolling out MLB for all baseball fans within months of launch. Other sports, music, and other verticals of celebrity forthcoming. Change how stars and fans connect via products with global potential. Team that used to market booze, win MLB World Series, play sports professionally, manage sports teams, and work at software startups / big companies. Engineering team of 4 server devs and 1 iOS dev. Engineering process with no formal manager. A team of equals.
Find Us www.egraphs.com Headquarters in Seattle Business Development in Malibu, CA www.twitter.com/egraphs www.facebook.com/egraphs Will Chan will@egraphs.com
Scala Philosophy A balanced attitude for Scala programmers Prefer vals, immutable objects and methods without side effects. Reach for them first. Use vars, mutable objects, and methods with side effects when you have a specific need and justification for them.