"scala in goozy", alexey zlobin

30
Scala in Goozy Alexey Zlobin, e- Legion [email protected] om @CheatEx

Upload: vasil-remeniuk

Post on 24-May-2015

496 views

Category:

Technology


0 download

DESCRIPTION

Talk in Scala in Goozy (), by Alexey @CheatEx Zlobin, at scalaby#8

TRANSCRIPT

Page 1: "Scala in Goozy", Alexey Zlobin

Scala in Goozy

Alexey Zlobin, e-Legion

[email protected]@CheatEx

Page 2: "Scala in Goozy", Alexey Zlobin

Index

1.Goozy overview2.Scala's place3.Lift4.Cake pattern in scale5.Scalaz and other fancy stuff6.Summary

Page 3: "Scala in Goozy", Alexey Zlobin

What is Goozy?

A social network built around the concept of sticky note

• A note could be left anywhere in the Web.

• It is associated with particular page element.

Central UI concept: user's related feed with all new notes and comments

Page 4: "Scala in Goozy", Alexey Zlobin

Top-level architecture

Page 5: "Scala in Goozy", Alexey Zlobin

Scala's place

API server

Main functions:• Storage access• Background tasks (feed writing)• Email sending• Text indexing

Page 6: "Scala in Goozy", Alexey Zlobin

Why scala?

• Fast• Сoncise• Expressive• Advanced OO• Some functional stuff

o Simple concurrency• All Java legacy available

Page 7: "Scala in Goozy", Alexey Zlobin

The team

• 2 persons• Strong Java background• Fancy about technologies• Love to try new things

Page 8: "Scala in Goozy", Alexey Zlobin

Lift

Utilized features:• REST• JSON serialisation

That's it...

Page 9: "Scala in Goozy", Alexey Zlobin

Lift: issues

Localisation performance• Hand-made localisation on standard resource bundles

gave 4 times throughput improvement.

Very memory-consuming JSON serialisation.• Not so efficient PrettyPrinter is used• Functional-styled string escaping

Poor code style• Extremely long map-match-if hierarchies• Mutable, difficult to debug LiftRules design

Page 10: "Scala in Goozy", Alexey Zlobin

Goozy API logical structure

Application is composed from three layers.

Each layer consists from several similar components. Like UserController, UserService, etc.

Page 11: "Scala in Goozy", Alexey Zlobin

Conceptual problems

• Components of each level depend from each other• Components most likely have several dependencies

from the previous level•  A lot of common stuff inside a level

o Every storage needs a DB connectiono Every service needs an entire storage system and

access to text indexeso Etc...

Page 12: "Scala in Goozy", Alexey Zlobin

The solution: cake pattern

• Each logically closed piece of functionality is represented as component

• Dependencies are expressed as self-types

• Common features expressed as mix-ins

• Common combinations of functionality are expressed as mix-ins of several components

Page 13: "Scala in Goozy", Alexey Zlobin

Cake pattern: consequences

+ All top-level architecture is expressed in one less than 100 LOC file

+ Compile-time dependency checks

+ The biggest file is around 1000 LOC

- Long dependency lists• Poor design?

- Implicit dependency on mix-in order (linearisation strikes back)• Prefer def and lazy

- A bit unclear how to deal with several dependencies of the same type but different runtime implementation

Page 14: "Scala in Goozy", Alexey Zlobin

scalaz.ValidationOne sweet morning I sent a link to the colleague...

On the next morning we had a new dependency and totally refactored request parameters analysis.

Now everything is validated.

Page 15: "Scala in Goozy", Alexey Zlobin

Initial solution: domain exceptions

class GoozzyException(    val message: String) extendsException(message)

case class UserNotFound(    userId: String) extendsGoozzyException(    "user " + userId + " not found")

Page 16: "Scala in Goozy", Alexey Zlobin

Error handling: own error typeabstract class Error(    val httpCode: Int,    val code: String)

abstract class PermissionError(    code: String) extendsValidationError(403, code)

case class Banned(    userId: String,    groupId: String) extendsPermissionError("BANNED")

Page 17: "Scala in Goozy", Alexey Zlobin

Validations and exceptions

Problem: exceptions are here

Solution: catch'em and convert!

Page 18: "Scala in Goozy", Alexey Zlobin

Validations and exceptions

1.Define the "converter"2.Write some utilities to make them more accessible

Page 19: "Scala in Goozy", Alexey Zlobin

One more error for whatever happen

case class ExceptionalError(    @transient exception: Throwable) extendsInternalError

Page 20: "Scala in Goozy", Alexey Zlobin

The converter

Function is a perfect abstraction!

val dispatchException: Function[    Throwable,    Error] = {    case UserNotFound(name) =>        MissedEntity(name, "user")    ...    //don't remove it!!!    case t => ExceptionalError(t)  }

Page 21: "Scala in Goozy", Alexey Zlobin

Useful utils: generic

type GzVal[+OUT] = Validation[Error, OUT]

def safeVal[T]: Catch[GzVal[T]] =    handling(classOf[Throwable]) by { e =>        dispatchException(e).fail    }

def safe[T](block: => T): GzVal[T] =    safeVal( block.success )

def editData(up: Edit):    GzVal[Data] = safeVal {        //all the dangerous stuff here    }

Page 22: "Scala in Goozy", Alexey Zlobin

Useful utils: specific

Just a trivial code which parses strings, extracts values from mongo objects, etc.

See my blog for details...

Page 23: "Scala in Goozy", Alexey Zlobin

Validation: the good thing

Some code was pretty:

for {    data <- getData(recordId)    userData <- getUserData(userId)    permission <-        checkPermission(data, userData)    newData <- updateData(data)} yield {    //all dirty hacks there}

Page 24: "Scala in Goozy", Alexey Zlobin

Conversion: the problem

But some other code was...

Page 25: "Scala in Goozy", Alexey Zlobin

Not so prettydef readFields(rec: DBObject, ...): GzVal[Fields] = {  val deletedBy =    for (userId <- get[ObjectId](note, "deleted_by");           user <- getUserData(userId).toSuccess(MissedEntity(...)))    yield user  for {    id <- get[String](rec, "_id")    content <- get[String](rec, "content")    updated <- asValidOption(get[DateTime](rec, "upd"))    //twelve more    user <- getUserById(userId, currentUserId map      (new ObjectId(_)))        .toSuccess(          MissedEntity(userId.toString, "user"):            ValidationError)  } yield DataRecord(/*you won't see it*/)}

Page 26: "Scala in Goozy", Alexey Zlobin

Improved solution

1.Don't play haskell2.Play java3....when it is easier

Page 27: "Scala in Goozy", Alexey Zlobin

Error handling: big picture

Page 28: "Scala in Goozy", Alexey Zlobin

Validation: pros and cons+ Comprehensible error aggregation and reporting

+ The only imaginable way to deal with 20+ request parameters with 2-3 constraints on each

+ Unified approach to request validation and runtime error handling

+ Type-level check for correct error handling

- Monads and Applicatives cause massive brain damage

- Complicated error reports from the compiler

- You can't just ignore possibility of runtime exception

Page 29: "Scala in Goozy", Alexey Zlobin

Lessons

1. Always prefer simple tools2. Options and Eithers (Validations): they really work

o It is possible to live with both exceptions and eitherso Performance consequences are not clearo Some times I had to use things far behind my

understanding (sequence, traverse)– Server-side testing is difficult

o Testing approach should be established before any code is written

Page 30: "Scala in Goozy", Alexey Zlobin

References

1. http://www.assembla.com/spaces/liftweb/wiki/REST_Web_Services - REST support in Lift 

2. http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html - complete cake pattern intro

3. https://gist.github.com/970717 - easy Validation example4.  

http://www.scala-lang.org/api/current/scala/util/control/Exception$.html - built-in DSL for exception handling