Upload
william-chan
View
438
Download
0
Embed Size (px)
Citation preview
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 >130 MLB players
Digital, authenticated, shareable
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 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]}
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
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.
Find Us
www.egraphs.com
Headquarters in SeattleBusiness Development in Malibu, CA
www.twitter.com/egraphswww.facebook.com/egraphs
Will [email protected]
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.