"scala in goozy", alexey zlobin
DESCRIPTION
Talk in Scala in Goozy (), by Alexey @CheatEx Zlobin, at scalaby#8TRANSCRIPT
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
o 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: 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
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
o Every storage needs a DB connectiono Every service needs an entire storage system and
access to text indexeso 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 exceptions
Problem: 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
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
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