Scala at egraphs

20
Scala at egraphs.com September 2012

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

Page 1: Scala  at  egraphs

Scala at egraphs.com

September 2012

Page 2: Scala  at  egraphs

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

Page 3: Scala  at  egraphs

Like This

www.egraphs.com/66

Page 4: Scala  at  egraphs

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

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

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

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

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

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

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

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

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

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

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

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

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

Page 17: Scala  at  egraphs

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

Page 18: Scala  at  egraphs

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.

Page 19: Scala  at  egraphs

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

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.