"Scala in Goozy", Alexey Zlobin

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

description

Talk in Scala in Goozy (http://goozy.com/), by Alexey @CheatEx Zlobin, at scalaby#8

Transcript of "Scala in Goozy", Alexey Zlobin

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

○ 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: issuesLocalisation 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

○ Every storage needs a DB connection○ Every service needs an entire storage system and

access to text indexes○ 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 exceptionsProblem: 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

○ It is possible to live with both exceptions and eithers○ Performance consequences are not clear○ Some times I had to use things far behind my

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

○ 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