"Scala in Goozy", Alexey Zlobin
-
Upload
vasil-remeniuk -
Category
Technology
-
view
1.642 -
download
1
description
Transcript of "Scala in Goozy", Alexey Zlobin
Index
1. Goozy overview2. Scala's place3. Lift4. Cake pattern in scale5. Scalaz and other fancy stuff6. Summary
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
Top-level architecture
Scala's place
API server
Main functions:● Storage access● Background tasks (feed writing)● Email sending● Text indexing
Why scala?
● Fast● Сoncise● Expressive● Advanced OO● Some functional stuff
○ Simple concurrency● All Java legacy
available
The team
● 2 persons● Strong Java background● Fancy about technologies● Love to try new things
Lift
Utilized features:● REST● JSON serialisation
That's it...
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
Goozy API logical structure
Application is composed from three layers.
Each layer consists from several similar components. Like UserController, UserService, etc.
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...
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
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
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.
Initial solution: domain exceptions
class GoozzyException( val message: String) extendsException(message)
case class UserNotFound( userId: String) extendsGoozzyException( "user " + userId + " not found")
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")
Validations and exceptionsProblem: exceptions are here
Solution: catch'em and convert!
Validations and exceptions
1. Define the "converter"2. Write some utilities to make them more accessible
One more error for whatever happen
case class ExceptionalError( @transient exception: Throwable) extendsInternalError
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) }
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 }
Useful utils: specific
Just a trivial code which parses strings, extracts values from mongo objects, etc.
See my blog for details...
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}
Conversion: the problem
But some other code was...
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*/)}
Improved solution
1. Don't play haskell2. Play java3. ...when it is easier
Error handling: big picture
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
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
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