Scala at egraphs sept 2012

20
Scala at egraphs.com September 2012

Transcript of Scala at egraphs sept 2012

Page 1: Scala at egraphs sept 2012

Scala at egraphs.com

September 2012

Page 2: Scala at egraphs sept 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 >130 MLB players

Digital, authenticated, shareable

Page 3: Scala at egraphs sept 2012

Like This

www.egraphs.com/66

Page 4: Scala at egraphs sept 2012

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)

Page 5: Scala at egraphs sept 2012

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.

Page 6: Scala at egraphs sept 2012

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.

Page 7: Scala at egraphs sept 2012

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) } }}

Page 8: Scala at egraphs sept 2012

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 …}

Page 9: Scala at egraphs sept 2012

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.

Page 10: Scala at egraphs sept 2012

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) } }}

Page 11: Scala at egraphs sept 2012

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

Page 12: Scala at egraphs sept 2012

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) }}

Page 13: Scala at egraphs sept 2012

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.

Page 14: Scala at egraphs sept 2012

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)) }}

Page 15: Scala at egraphs sept 2012

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.

Page 16: Scala at egraphs sept 2012

Forms: Very Complex, But Type-Safe

Most complex form we’ve written so far is our purchase flow.20+ form inputs…. Our homegrown solution is (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]}

Page 17: Scala at egraphs sept 2012

Code aside, amazing time to build a business

It is an amazing time to launch a business… there is SO much available to coders to speed the building of complete software.

Amazing community support from Scala, Play, Squeryl, AWS communities, etc.

We host our application servers with CloudBees, which manages AWS EC2 instances for us. They handle deployment, SSL, and scaling.

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 good, but everyone overpromises.

Play2 and direct control of servers are forcing decision: CloudBees vs Heroku vs hire Tech Ops team

Page 18: Scala at egraphs sept 2012

We’re Hiring

Rolling out MLB for all baseball fans within months of launch. Other sports, music, and other verticals of celebrity are

forthcoming.

Change how stars and fans connect via products with global potential.

Teammates who used to market booze, win MLB World Series, play sports professionally, manage sports teams, as well as

work at other software startups / big companies.

Engineering team of 4 server devs and 1 iOS dev.Engineering process with no formal manager. A team of equals.

Page 19: Scala at egraphs sept 2012

Find Us

www.egraphs.com

Headquarters in SeattleBusiness Development in Malibu, CA

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

Will [email protected]

Page 20: Scala at egraphs sept 2012

Scala Philosophy

Scala has a reputation for being academic and overly-complicated

and full of ways to shoot yourself in the foot. Here is:

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.