Scala at egraphs

Post on 15-Feb-2016

36 views 0 download

description

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. - PowerPoint PPT Presentation

Transcript of Scala at egraphs

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

Scala 2.8Play 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:

1. Not because we’re code ninjas. I mean, lol.

2. Little Scala habits with big wins.

3. Type-safety wherever possible. Like in forms.

A simple example from our codebase

def authenticate(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 dev turned Scala dev might write:

val maybeAccount: 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.

NoSuchElementExceptions are 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 map or match.

Simple but seriously effective for null-safety.

And Type-Safety

def authenticate(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.requireCelebrityAndProductUrlSlugs has parameter of type:

(Celebrity, Product) => Any

Can you spot where the bug is?

def postStorefrontFinalize(celebrityUrlSlug: String, productUrlSlug: String) = postController() {

val redirectOrPurchaseData = { 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

def requireCelebrityAndProductUrlSlugs(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…

def postSubscribeMailingList(email: String) = postController() { validateIsEmail(email) if (validationErrors.isEmpty) { redirectWithValidationErrors(…) } else { SubscribeEmail(email).save() new Redirect(GetConfirmation.url(email)) }}

Forms: Complexity Grows

def postAccount(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 def stringsToValidate = { paramsMap(this.name) } def value: Option[ValueType] def error: Option[FormError] }

protected def paramsMap: Iterable[String] protected def formAssumingValid: ValidFormType def errorsOrValidatedForm: 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: CloudBees vs Heroku vs 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 SeattleBusiness Development in Malibu, CA

www.twitter.com/egraphswww.facebook.com/egraphs

Will Chanwill@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.