Tata AIG General Insurance Company - Insurer Innovation Award 2024
"Scala in Goozy", Alexey Zlobin
1. Scala in Goozy
Alexey Zlobin, e-Legion
Alexey.Zlobin@gmail.com
@CheatEx
2. Index
1. Goozy overview
2. Scala's place
3. Lift
4. Cake pattern in scale
5. Scalaz and other fancy stuff
6. Summary
3. 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
9. 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
10. Goozy API logical structure
Application is composed
from three layers.
Each layer consists from
several similar
components. Like
UserController,
UserService, etc.
11. 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...
12. 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
13. Cake pattern: consequences
+ All top-level architecture - Long dependency lists
is expressed in one less ● Poor design?
than 100 LOC file
- Implicit dependency on mix-
+ Compile-time in order (linearisation strikes
dependency checks back)
● Prefer def and lazy
+ The biggest file is
around 1000 LOC - A bit unclear how to deal
with several dependencies of
the same type but different
runtime implementation
14. scalaz.Validation
One 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.
15. Initial solution: domain exceptions
class GoozzyException(
val message: String) extends
Exception(message)
case class UserNotFound(
userId: String) extends
GoozzyException(
"user " + userId + " not found")
16. Error handling: own error type
abstract class Error(
val httpCode: Int,
val code: String)
abstract class PermissionError(
code: String) extends
ValidationError(403, code)
case class Banned(
userId: String,
groupId: String) extends
PermissionError("BANNED")
19. One more error for whatever happen
case class ExceptionalError(
@transient exception: Throwable) extends
InternalError
20. 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)
}
21. 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
}
22. Useful utils: specific
Just a trivial code which parses strings, extracts
values from mongo objects, etc.
See my blog for details...
23. 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
}
25. Not so pretty
def 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*/)
}
28. Validation: pros and cons
+ Comprehensible error - Monads and Applicatives
aggregation and reporting cause massive brain
damage
+ The only imaginable way
to deal with 20+ request - Complicated error reports
parameters with 2-3 from the compiler
constraints on each
- You can't just ignore
+ Unified approach to possibility of runtime
request validation and exception
runtime error handling
+ Type-level check for
correct error handling
29. Lessons
1. Always prefer simple tools
2. 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
30. 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 example
4. http://www.scala-lang.
org/api/current/scala/util/control/Exception$.html - built-in
DSL for exception handling