SlideShare a Scribd company logo
1 / 68
Introducing the type-safe API for ZIO
DynamoDB
2 / 68
- Avinder Bahra
https://github.com/googley42
3 / 68
Talk Outline
quick recap on using the AWS Java SDK
where we got up to in last years presentation - what was missing?
quick tour of the low level API
introduction to the new Type-safe API
show examples using the new API
summary and wrap up
4 / 68
Using AWS Java SDK recap
5 / 68
AWS SDK example
final case class Student(
email: String, // partition key
enrollmentDate: Option[Instant],
payment: Payment
)
sealed trait Payment
object Payment {
final case object DebitCard extends Payment
final case object CreditCard extends Payment
final case object PayPal extends Payment
}
creates a Student DynamoDB table
serialises above model to the DynamoDB taking into account
optional fields
sum types
integrates with AWS Java SDK and does Java interop
does batched writes and reads
...what does the code look like?...
6 / 68
Lots of bolierplate code!
page 1 ... ... page 4!!!
240 lines of code!
7 / 68
AWS SDK example
Sumary
- lots of tedious boilerplate code
- not type safe
- prone to run time errors
8 / 68
ZIO DynamoDB solution
9 / 68
ZIO DynamoDB solution
val liveDynamoDBExecutorLayer = ...
val errorOrListOfStudent = (for {
avi = Student("avi@gmail.com", "maths", ...)
adam = Student("adam@gmail.com", "english", ...)
_ <- (put("student", avi) zip put("student", adam)).execute
listOfErrorOrStudent <- forEach(List(avi, adam)) { st =>
get[Student]("student",
PrimaryKey("email" -> st.email, "subject" -> st.subject)
)}.execute
} yield EitherUtil.collectAll(listOfErrorOrStudent))
.provideLayer(liveDynamoDBExecutorLayer)
10 / 68
Tour of Low Level API
AttributeValue's
ProjectionExpressions's
ConditionExpressions's
UpdateExpressions's
11 / 68
AttributeValue
An AttributeValue is DynamoDB type and value pair that is stored in the
database
sealed trait AttributeValue
final case class Binary(value: Iterable[Byte]) extends AttributeValue
final case class BinarySet(value: Iterable[Iterable[Byte]]) extends AttributeValue
final case class Bool(value: Boolean) extends AttributeValue
// ... etc etc
final case class String(value: ScalaString) extends AttributeValue
final case class Number(value: BigDecimal) extends AttributeValue
12 / 68
Item (AttrMap)
A DynamoDB Item is record - a map of field name to AttributeValue. In the
low level API it is modelled as an AttrMap
final case class AttrMap(map: Map[String, AttributeValue])
type Item = AttrMap
type PrimaryKey = AttrMap
val aviItem1 = AttrMap(Map("email" -> AttributeValue.String("email"),
"age" -> AttributeValue.Number(BigDecimal(21)))
// using an apply method that takes a type class
val aviItem2 = AttrMap("email" -> "avi@gmail.com", "age" -> 21)
val bool = aviItem1 == aviItem2 // true
13 / 68
DynamoDBQuery trait
sealed trait DynamoDBQuery[+A] {
def execute: ZIO[Has[DynamoDBExecutor], Exception, A] = ???
def zip[B](that: DynamoDBQuery[B]): DynamoDBQuery[(A, B)] = ???
...
def forEach[A, B](values: Iterable[A])(body: A => DynamoDBQuery[B])
: DynamoDBQuery[List[B]]
}
trait DynamoDBExecutor {
def execute[A](query: DynamoDBQuery[A]): ZIO[Any, Throwable, A]
}
final case class GetItem(...) extends DynamoDBQuery[Option[Item]]
// etc etc
14 / 68
ProjectionExpression
In DynamoDB a projection expression is a way of accessing the attribute
email
address.line1
tutors[1].surname
In the API it is represented as a sealed trait
sealed trait ProjectionExpression {...}
final case class Root extends ProjectionExpression
final case class MapElement(...) extends ProjectionExpression
final case class ListElement(...) extends ProjectionExpression
15 / 68
$ function (type unsafe)
manually creating ProjectionExpression's using the constructors is painful
ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42
16 / 68
$ function (type unsafe)
manually creating ProjectionExpression's using the constructors is painful
ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42
so we introduced the $ function
$("foo.bar.baz") === 42 && $("students[0].firstname") === "Avi"
17 / 68
$ function (type unsafe)
manually creating ProjectionExpression's using the constructors is painful
ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42
so we introduced the $ function
$("foo.bar.baz") === 42 && $("students[0].firstname") === "Avi"
which is really convenient but totally type unsafe
18 / 68
Condition and Filter Expressions
In DynamoDB a condition expression is a way specifying a condition that must
hold for a PutItem, UpdateItem, or DeleteItem
A very similar concept is a filter expression that applies to a DynamoDB Query
In ZIO DynamoDB both Condition and Filter expressions concepts are modelled
by:
sealed trait ConditionExpression{...}
19 / 68
Condition and Filter Expressions Syntax
20 / 68
Update Expressions
update-expression ::=
[ SET action [, action] ... ]
[ REMOVE action [, action] ...]
[ ADD action [, action] ... ]
[ DELETE action [, action] ...]
set-action ::=
path = value
value ::=
operand
| operand '+' operand
| operand '-' operand
operand ::= path | function
function
list_append (list1, list2)
if_not_exists (path, value)
remove-action ::= path
add-action ::= path value
delete-action ::= path value
21 / 68
Example of Low Level API
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
22 / 68
Example of Low Level API
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
private val program = for {
_ <- (putItem("student", avi) zip putItem("student", adam)).execute
} yield ()
23 / 68
Example of Low Level API
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
private val program = for {
_ <- (putItem("student", avi) zip putItem("student", adam)).execute
item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute
} yield ()
24 / 68
Example of Low Level API
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
private val program = for {
_ <- (putItem("student", avi) zip putItem("student", adam)).execute
item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute
_ <- updateItem("student", PrimaryKey("email" -> "avi@a.com")){
$("payment").set("Paypal") + $("age").add(1)
}.execute
} yield ()
25 / 68
Example of Low Level API
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
private val program = for {
_ <- (putItem("student", avi) zip putItem("student", adam)).execute
item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute
_ <- updateItem("student", PrimaryKey("email" -> "avi@a.com")) {
$("payment").set("Paypal") + $("age").add(1)
}
_ <- deleteItem("student", PrimaryKey("email" -> "avi@a.com")).where(
$("payment") === "Paypal" && $("age") > 21
).execute
} yield ()
26 / 68
Overview of Low Level APIs - Example
val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42)
val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21)
private val program = for {
_ <- (putItem("student", avi) zip putItem("student", adam)).execute
item <- getItem("student", PrimaryKey("email" -> "a@b.com")).execute
_ <- updateItem("student", PrimaryKey("email" -> "a@b.com")){
$("payment").set("Paypal") + $("age").add(1)
}
_ <- deleteItem("student", PrimaryKey("email" -> "a@b.com")).where(
$("payment") === "Paypal" && $("age") > 21
).execute
student: Student = tediousCustomDeSerialisationCode(item)
} yield student
27 / 68
Type Safe Serialisation API using ZIO Schema
ZIO Schema
"Compositional, type-safe schema definitions, which enable auto-
derivation of codecs and migrations."
28 / 68
Type Safe Serialisation API using ZIO Schema
final case class Student(email: String, subject: String, age: Int)
object Student {
implicit lazy val schema: Schema[Student] = DeriveSchema.gen[Student]
}
val avi = Student("avi@gmail.com", "maths", 21)
val adam = Student("adam@gmail.com", "english", 21)
29 / 68
Type Safe Serialisation API using ZIO Schema
object Student {
implicit lazy val schema: Schema[Student] = DeriveSchema.gen[Student]
}
val avi = Student("avi@gmail.com", "maths", 21)
val adam = Student("adam@gmail.com", "english", 21)
private val program = (for {
_ <- (put("student", avi) zip put("student", adam)).execute
student <- get("student", primaryKey("avi@gmail.com", "maths"))
.execute.right
} yield student == avi /* true */ )
type classes to derive codecs for serialisation to DynamoDB for put and
get
note that we are serialising case class directly
this is a huge increase in usability!
30 / 68
Codec customisations using annotations
annotations for cutomising
tagged union vs discrimated union
enum storage
names
case class
case class fields
discriminator field
enum values
31 / 68
Where we left off in the last talk
val avi: Student = get("student", primaryKey)
put[Student]("student", Student("avi@gmail.com", ...),...)
.where( $("addr.line1").contains("street") && $("addr.line2").beginsWith("f") )
updateItem("student", primaryKey) {
$("firstname").set("Avinder") +
$("groups").appendList(Chunk("group1"))
}.where( $("email") === "avi@gmail.com" )
deleteItem("student", primaryKey)
.where( $("age") > "18" && $("email") === "avi@gmail.com") )
querySomeItem("tableName1", limit = 10, $("email"), $("payment"), $("age"))
.sortOrder(ascending = false)
.whereKey(PartitionKey("email") === "avi@gmail.com")
.filter( $("age") > 21 && $("subject").beginsWith("Math") )
Can you spot the run time error problem in the above code?
32 / 68
33 / 68
Where we left off in the last talk
34 / 68
Introducing the new type safe API
35 / 68
Optics
Provide a way of navigating your immutable data structure that reduces
boilerplate
Prism - access product data
Lens - access sum type data
Traversal - access a collection
Drilling down into a field eg Student.email is a Scala language feature that is
not a value
Optics libraries take these language features and turn them into values that
can be composed
Typically libraries use use functions in the encoding (executable encoding)
36 / 68
Reified Optics
sealed trait Payment
object Payment {
final case object DebitCard extends Payment
// ...
implicit val schema = DeriveSchema.gen[Payment]
}
final case class Student(
email: String, // partition key
enrollDate: Instant,
payment: Payment
)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, enrollDate, payment) = ProjectionExpression.accessors[Student]
}
37 / 68
Reified Optics
Reified optics are not enough on their own - we needed extend this type safety
to the rest of the API
so we added phantom type parameters to our lower level representations:
sealed trait ProjectionExpression[-From,
+To]
sealed trait ConditionExpression[-From]
38 / 68
New type safe API
the new type safe API uses reified optics to constrain expressions to the
type we are working with
39 / 68
New type safe API - Update Expressions
val student = Student(...)
object Student {
val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student]
}
The update expressions here are typesafe.
40 / 68
New type safe API - Condition Expressions
val student = Student(...)
object Student {
val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student]
}
41 / 68
New type safe API - delete condition expressions
val student = Student(...)
object Student {
val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student]
}
Elephant injection attack will not work!
42 / 68
New Type-Safe API examples
43 / 68
Example 1 - Optimistic locking
44 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
45 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
46 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = ???
47 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) =
for {
before <- get[Student]("student", primaryKey).execute.absolve
} yield ()
48 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) =
for {
before <- get[Student]("student", primaryKey).execute.absolve
_ <- update[Student]("student", primaryKey) { ... }.execute
} yield ()
49 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) =
for {
before <- get[Student]("student", primaryKey).execute.absolve
_ <- update[Student]("student", primaryKey) {
f(before) + version.add(1)
} ...
} yield ()
50 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) =
for {
before <- get[Student]("student", primaryKey).execute.absolve
_ <- update[Student]("student", primaryKey) {
f(before) + version.add(1)
}.where(Student.version === before.version).execute.retry(policy)
} yield ()
51 / 68
Optimistic locking example
final case class Student(email: String, payment: Payment, age: Int,
version: Long = 0)
object Student {
implicit val schema = DeriveSchema.gen[Student]
val (email, payment, age, version) = ProjectionExpression.accessors[Student]
}
def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) =
for {
before <- get[Student]("student", primaryKey).execute.absolve
_ <- update[Student]("student", primaryKey) {
f(before) + version.add(1)
}.where(Student.version === before.version).execute.retry(policy)
} yield ()
for {
_ <- optimisticUpdate(primaryKey("avi@gmail.com", "maths")) { student =>
if (student.age > 42)
Student.payment.set(Payment.DebitCard)
else
Student.payment.set(Payment.CreditCard)
}.execute
} yield ()
52 / 68
Example 2 - Migration Pipeline Example
53 / 68
Migration Pipeline Example
final case class Student(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment
)
final case class StudentCourses(
email: String,
courses: Set[String]
)
final case class Student2(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment,
courses: Set[String]
)
54 / 68
Migration Pipeline Example
final case class Student(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment
)
final case class StudentCourses(
email: String,
courses: Set[String]
)
final case class Student2(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment,
courses: Set[String]
)
55 / 68
Migration Pipeline Example
final case class Student(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment
)
final case class StudentCourses(
email: String,
courses: Set[String]
)
final case class Student2(
email: String, // partition key
age: Int,
enrollmentDate: Instant,
payment: Payment,
courses: Set[String]
)
56 / 68
Migration Pipeline Example
private val program = for {
students <- scanAll[Student](tableName = "s")
.parallel(16)
.filter(Student.age > 21 && Student.enrollmentDate > aDate)
.execute
studentsAndCourses =
batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email))
_ <- batchWriteFromStream(studentsAndCourses) {
case (s, sc) => put(tableName = "Students2",
Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses)
)
}.runDrain
} yield ()
57 / 68
Migration Pipeline Example
private val program = for {
students <- scanAll[Student](tableName = "s")
.parallel(16)
.filter(Student.age > 21 && Student.enrollmentDate > aDate)
.execute
studentsAndCourses =
batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email))
_ <- batchWriteFromStream(studentsAndCourses) {
case (s, sc) => put(tableName = "Students2",
Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses)
)
}.runDrain
} yield ()
58 / 68
Migration Pipeline Example
private val program = for {
students <- scanAll[Student](tableName = "s")
.parallel(16)
.filter(Student.age > 21 && Student.enrollmentDate > aDate)
.execute
studentsAndCourses: ZStream[..., (Student, StudentCourses)] =
batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email))
_ <- batchWriteFromStream(studentsAndCourses) {
case (s, sc) => put(tableName = "Students2",
Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses)
)
}.runDrain
} yield ()
59 / 68
Migration Pipeline Example
private val program = for {
students <- scanAll[Student](tableName = "s")
.parallel(16)
.filter(Student.age > 21 && Student.enrollmentDate > aDate)
.execute
studentsAndCourses: ZStream[..., (Student, StudentCourses)] =
batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email))
_ <- batchWriteFromStream(studentsAndCourses) {
case (s, sc) => put(tableName = "Students2",
Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses)
)
}.runDrain
} yield ()
60 / 68
Summary
61 / 68
Summary - Condition and Filter Expression
62 / 68
Summary - Type Safety coverage
63 / 68
Summary - Update Expressions
update-expression ::=
[ SET action [, action] ... ]
[ REMOVE action [, action] ...]
[ ADD action [, action] ... ]
[ DELETE action [, action] ...]
set-action ::=
path = value
value ::=
operand
| operand '+' operand
| operand '-' operand
operand ::= path | function
function
list_append (list1, list2)
if_not_exists (path, value)
remove-action ::= path
add-action ::= path value
delete-action ::= path value
64 / 68
Summary - Type Safety coverage
update-expression ::=
[ SET action [, action] ... ]
[ REMOVE action [, action] ...]
[ ADD action [, action] ... ]
[ DELETE action [, action] ...]
set-action ::=
path = value
value ::=
operand
| operand '+' operand
| operand '-' operand
operand ::= path | function
function
list_append (list1, list2)
if_not_exists (path, value)
remove-action ::= path
add-action ::= path value
delete-action ::= path value
65 / 68
Summary - New Type Safe API
66 / 68
Learning More
github repo https://github.com/zio/zio-dynamodb
microsite https://zio.github.io/zio-dynamodb/
examples in own sbt module zio-dynamodb/examples
integration tests dynamodb/src/it/scala/zio/dynamodb/
last years talk introducing ZIO DynamoDB at Functional Scala 2021
67 / 68
Thanks
a big thank you to:
all the sponsors Ziverge, Scalac, UMATR, Coralogix, Conducktor,
Matechs for giving me this opportunity to talk about this library
Sandra and Agata for organising the conference
John for his mentorship during development
68 / 68

More Related Content

Similar to Introducing the ZIO DynamoDB Type Safe API

A (too) Short Introduction to Scala
A (too) Short Introduction to ScalaA (too) Short Introduction to Scala
A (too) Short Introduction to Scala
Riccardo Cardin
 
Code generation for alternative languages
Code generation for alternative languagesCode generation for alternative languages
Code generation for alternative languages
Rafael Winterhalter
 
Classes and objects
Classes and objectsClasses and objects
Classes and objects
Kamal Acharya
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
basic concepts
basic conceptsbasic concepts
basic concepts
Jetti Chowdary
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdf
NuttavutThongjor1
 
Quest 1 define a class batsman with the following specifications
Quest  1 define a class batsman with the following specificationsQuest  1 define a class batsman with the following specifications
Quest 1 define a class batsman with the following specifications
rajkumari873
 
Lecture 5
Lecture 5Lecture 5
Lecture 5
Muhammad Fayyaz
 
Boost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineeringBoost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineering
Miro Wengner
 
angular fundamentals.pdf
angular fundamentals.pdfangular fundamentals.pdf
angular fundamentals.pdf
NuttavutThongjor1
 
Assignment Java Programming 2
Assignment Java Programming 2Assignment Java Programming 2
Assignment Java Programming 2
Kaela Johnson
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
Giorgio Cefaro
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
eugenio pombi
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
Vadym Khondar
 
Dart for Java Developers
Dart for Java DevelopersDart for Java Developers
Dart for Java Developers
Yakov Fain
 
React native by example by Vadim Ruban
React native by example by Vadim RubanReact native by example by Vadim Ruban
React native by example by Vadim Ruban
Lohika_Odessa_TechTalks
 
Implementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord modelsImplementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord models
Kostyantyn Stepanyuk
 
Designing REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDesigning REST API automation tests in Kotlin
Designing REST API automation tests in Kotlin
Dmitriy Sobko
 
Deep Dive Into Swift
Deep Dive Into SwiftDeep Dive Into Swift
Deep Dive Into Swift
Sarath C
 
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
Dan Wahlin
 

Similar to Introducing the ZIO DynamoDB Type Safe API (20)

A (too) Short Introduction to Scala
A (too) Short Introduction to ScalaA (too) Short Introduction to Scala
A (too) Short Introduction to Scala
 
Code generation for alternative languages
Code generation for alternative languagesCode generation for alternative languages
Code generation for alternative languages
 
Classes and objects
Classes and objectsClasses and objects
Classes and objects
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
basic concepts
basic conceptsbasic concepts
basic concepts
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdf
 
Quest 1 define a class batsman with the following specifications
Quest  1 define a class batsman with the following specificationsQuest  1 define a class batsman with the following specifications
Quest 1 define a class batsman with the following specifications
 
Lecture 5
Lecture 5Lecture 5
Lecture 5
 
Boost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineeringBoost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineering
 
angular fundamentals.pdf
angular fundamentals.pdfangular fundamentals.pdf
angular fundamentals.pdf
 
Assignment Java Programming 2
Assignment Java Programming 2Assignment Java Programming 2
Assignment Java Programming 2
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
 
Dart for Java Developers
Dart for Java DevelopersDart for Java Developers
Dart for Java Developers
 
React native by example by Vadim Ruban
React native by example by Vadim RubanReact native by example by Vadim Ruban
React native by example by Vadim Ruban
 
Implementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord modelsImplementation of EAV pattern for ActiveRecord models
Implementation of EAV pattern for ActiveRecord models
 
Designing REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDesigning REST API automation tests in Kotlin
Designing REST API automation tests in Kotlin
 
Deep Dive Into Swift
Deep Dive Into SwiftDeep Dive Into Swift
Deep Dive Into Swift
 
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
Building the an End-to-End ASP.NET MVC 4, Entity Framework, HTML5, jQuery app...
 

Recently uploaded

Generating privacy-protected synthetic data using Secludy and Milvus
Generating privacy-protected synthetic data using Secludy and MilvusGenerating privacy-protected synthetic data using Secludy and Milvus
Generating privacy-protected synthetic data using Secludy and Milvus
Zilliz
 
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success StoryDriving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Safe Software
 
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
saastr
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
DanBrown980551
 
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptxOcean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
SitimaJohn
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
Daiki Mogmet Ito
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
Tomaz Bratanic
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
Chart Kalyan
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Tosin Akinosho
 
Recommendation System using RAG Architecture
Recommendation System using RAG ArchitectureRecommendation System using RAG Architecture
Recommendation System using RAG Architecture
fredae14
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
IndexBug
 
20240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 202420240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 2024
Matthew Sinclair
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Speck&Tech
 
UI5 Controls simplified - UI5con2024 presentation
UI5 Controls simplified - UI5con2024 presentationUI5 Controls simplified - UI5con2024 presentation
UI5 Controls simplified - UI5con2024 presentation
Wouter Lemaire
 
Main news related to the CCS TSI 2023 (2023/1695)
Main news related to the CCS TSI 2023 (2023/1695)Main news related to the CCS TSI 2023 (2023/1695)
Main news related to the CCS TSI 2023 (2023/1695)
Jakub Marek
 
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy SurveyTrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
Matthew Sinclair
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Wask
 
Artificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopmentArtificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopment
Octavian Nadolu
 
OpenID AuthZEN Interop Read Out - Authorization
OpenID AuthZEN Interop Read Out - AuthorizationOpenID AuthZEN Interop Read Out - Authorization
OpenID AuthZEN Interop Read Out - Authorization
David Brossard
 

Recently uploaded (20)

Generating privacy-protected synthetic data using Secludy and Milvus
Generating privacy-protected synthetic data using Secludy and MilvusGenerating privacy-protected synthetic data using Secludy and Milvus
Generating privacy-protected synthetic data using Secludy and Milvus
 
Driving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success StoryDriving Business Innovation: Latest Generative AI Advancements & Success Story
Driving Business Innovation: Latest Generative AI Advancements & Success Story
 
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
Deep Dive: AI-Powered Marketing to Get More Leads and Customers with HyperGro...
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
 
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptxOcean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
 
Recommendation System using RAG Architecture
Recommendation System using RAG ArchitectureRecommendation System using RAG Architecture
Recommendation System using RAG Architecture
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
 
20240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 202420240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 2024
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
 
UI5 Controls simplified - UI5con2024 presentation
UI5 Controls simplified - UI5con2024 presentationUI5 Controls simplified - UI5con2024 presentation
UI5 Controls simplified - UI5con2024 presentation
 
Main news related to the CCS TSI 2023 (2023/1695)
Main news related to the CCS TSI 2023 (2023/1695)Main news related to the CCS TSI 2023 (2023/1695)
Main news related to the CCS TSI 2023 (2023/1695)
 
TrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy SurveyTrustArc Webinar - 2024 Global Privacy Survey
TrustArc Webinar - 2024 Global Privacy Survey
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
 
Artificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopmentArtificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopment
 
OpenID AuthZEN Interop Read Out - Authorization
OpenID AuthZEN Interop Read Out - AuthorizationOpenID AuthZEN Interop Read Out - Authorization
OpenID AuthZEN Interop Read Out - Authorization
 

Introducing the ZIO DynamoDB Type Safe API

  • 2. Introducing the type-safe API for ZIO DynamoDB 2 / 68
  • 4. Talk Outline quick recap on using the AWS Java SDK where we got up to in last years presentation - what was missing? quick tour of the low level API introduction to the new Type-safe API show examples using the new API summary and wrap up 4 / 68
  • 5. Using AWS Java SDK recap 5 / 68
  • 6. AWS SDK example final case class Student( email: String, // partition key enrollmentDate: Option[Instant], payment: Payment ) sealed trait Payment object Payment { final case object DebitCard extends Payment final case object CreditCard extends Payment final case object PayPal extends Payment } creates a Student DynamoDB table serialises above model to the DynamoDB taking into account optional fields sum types integrates with AWS Java SDK and does Java interop does batched writes and reads ...what does the code look like?... 6 / 68
  • 7. Lots of bolierplate code! page 1 ... ... page 4!!! 240 lines of code! 7 / 68
  • 8. AWS SDK example Sumary - lots of tedious boilerplate code - not type safe - prone to run time errors 8 / 68
  • 10. ZIO DynamoDB solution val liveDynamoDBExecutorLayer = ... val errorOrListOfStudent = (for { avi = Student("avi@gmail.com", "maths", ...) adam = Student("adam@gmail.com", "english", ...) _ <- (put("student", avi) zip put("student", adam)).execute listOfErrorOrStudent <- forEach(List(avi, adam)) { st => get[Student]("student", PrimaryKey("email" -> st.email, "subject" -> st.subject) )}.execute } yield EitherUtil.collectAll(listOfErrorOrStudent)) .provideLayer(liveDynamoDBExecutorLayer) 10 / 68
  • 11. Tour of Low Level API AttributeValue's ProjectionExpressions's ConditionExpressions's UpdateExpressions's 11 / 68
  • 12. AttributeValue An AttributeValue is DynamoDB type and value pair that is stored in the database sealed trait AttributeValue final case class Binary(value: Iterable[Byte]) extends AttributeValue final case class BinarySet(value: Iterable[Iterable[Byte]]) extends AttributeValue final case class Bool(value: Boolean) extends AttributeValue // ... etc etc final case class String(value: ScalaString) extends AttributeValue final case class Number(value: BigDecimal) extends AttributeValue 12 / 68
  • 13. Item (AttrMap) A DynamoDB Item is record - a map of field name to AttributeValue. In the low level API it is modelled as an AttrMap final case class AttrMap(map: Map[String, AttributeValue]) type Item = AttrMap type PrimaryKey = AttrMap val aviItem1 = AttrMap(Map("email" -> AttributeValue.String("email"), "age" -> AttributeValue.Number(BigDecimal(21))) // using an apply method that takes a type class val aviItem2 = AttrMap("email" -> "avi@gmail.com", "age" -> 21) val bool = aviItem1 == aviItem2 // true 13 / 68
  • 14. DynamoDBQuery trait sealed trait DynamoDBQuery[+A] { def execute: ZIO[Has[DynamoDBExecutor], Exception, A] = ??? def zip[B](that: DynamoDBQuery[B]): DynamoDBQuery[(A, B)] = ??? ... def forEach[A, B](values: Iterable[A])(body: A => DynamoDBQuery[B]) : DynamoDBQuery[List[B]] } trait DynamoDBExecutor { def execute[A](query: DynamoDBQuery[A]): ZIO[Any, Throwable, A] } final case class GetItem(...) extends DynamoDBQuery[Option[Item]] // etc etc 14 / 68
  • 15. ProjectionExpression In DynamoDB a projection expression is a way of accessing the attribute email address.line1 tutors[1].surname In the API it is represented as a sealed trait sealed trait ProjectionExpression {...} final case class Root extends ProjectionExpression final case class MapElement(...) extends ProjectionExpression final case class ListElement(...) extends ProjectionExpression 15 / 68
  • 16. $ function (type unsafe) manually creating ProjectionExpression's using the constructors is painful ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42 16 / 68
  • 17. $ function (type unsafe) manually creating ProjectionExpression's using the constructors is painful ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42 so we introduced the $ function $("foo.bar.baz") === 42 && $("students[0].firstname") === "Avi" 17 / 68
  • 18. $ function (type unsafe) manually creating ProjectionExpression's using the constructors is painful ListElement(MapElement(MapElement(Root("foo"), "bar"), "baz"), 9) === 42 so we introduced the $ function $("foo.bar.baz") === 42 && $("students[0].firstname") === "Avi" which is really convenient but totally type unsafe 18 / 68
  • 19. Condition and Filter Expressions In DynamoDB a condition expression is a way specifying a condition that must hold for a PutItem, UpdateItem, or DeleteItem A very similar concept is a filter expression that applies to a DynamoDB Query In ZIO DynamoDB both Condition and Filter expressions concepts are modelled by: sealed trait ConditionExpression{...} 19 / 68
  • 20. Condition and Filter Expressions Syntax 20 / 68
  • 21. Update Expressions update-expression ::= [ SET action [, action] ... ] [ REMOVE action [, action] ...] [ ADD action [, action] ... ] [ DELETE action [, action] ...] set-action ::= path = value value ::= operand | operand '+' operand | operand '-' operand operand ::= path | function function list_append (list1, list2) if_not_exists (path, value) remove-action ::= path add-action ::= path value delete-action ::= path value 21 / 68
  • 22. Example of Low Level API val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) 22 / 68
  • 23. Example of Low Level API val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) private val program = for { _ <- (putItem("student", avi) zip putItem("student", adam)).execute } yield () 23 / 68
  • 24. Example of Low Level API val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) private val program = for { _ <- (putItem("student", avi) zip putItem("student", adam)).execute item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute } yield () 24 / 68
  • 25. Example of Low Level API val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) private val program = for { _ <- (putItem("student", avi) zip putItem("student", adam)).execute item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute _ <- updateItem("student", PrimaryKey("email" -> "avi@a.com")){ $("payment").set("Paypal") + $("age").add(1) }.execute } yield () 25 / 68
  • 26. Example of Low Level API val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) private val program = for { _ <- (putItem("student", avi) zip putItem("student", adam)).execute item <- getItem("student", PrimaryKey("email" -> "avi@a.com")).execute _ <- updateItem("student", PrimaryKey("email" -> "avi@a.com")) { $("payment").set("Paypal") + $("age").add(1) } _ <- deleteItem("student", PrimaryKey("email" -> "avi@a.com")).where( $("payment") === "Paypal" && $("age") > 21 ).execute } yield () 26 / 68
  • 27. Overview of Low Level APIs - Example val avi = AttrMap("email" -> "avi@a.com", "payment" -> "DebitCard", "age" -> 42) val adam = AttrMap("email" -> "adam@a.com", "payment" -> "DebitCard", "age" -> 21) private val program = for { _ <- (putItem("student", avi) zip putItem("student", adam)).execute item <- getItem("student", PrimaryKey("email" -> "a@b.com")).execute _ <- updateItem("student", PrimaryKey("email" -> "a@b.com")){ $("payment").set("Paypal") + $("age").add(1) } _ <- deleteItem("student", PrimaryKey("email" -> "a@b.com")).where( $("payment") === "Paypal" && $("age") > 21 ).execute student: Student = tediousCustomDeSerialisationCode(item) } yield student 27 / 68
  • 28. Type Safe Serialisation API using ZIO Schema ZIO Schema "Compositional, type-safe schema definitions, which enable auto- derivation of codecs and migrations." 28 / 68
  • 29. Type Safe Serialisation API using ZIO Schema final case class Student(email: String, subject: String, age: Int) object Student { implicit lazy val schema: Schema[Student] = DeriveSchema.gen[Student] } val avi = Student("avi@gmail.com", "maths", 21) val adam = Student("adam@gmail.com", "english", 21) 29 / 68
  • 30. Type Safe Serialisation API using ZIO Schema object Student { implicit lazy val schema: Schema[Student] = DeriveSchema.gen[Student] } val avi = Student("avi@gmail.com", "maths", 21) val adam = Student("adam@gmail.com", "english", 21) private val program = (for { _ <- (put("student", avi) zip put("student", adam)).execute student <- get("student", primaryKey("avi@gmail.com", "maths")) .execute.right } yield student == avi /* true */ ) type classes to derive codecs for serialisation to DynamoDB for put and get note that we are serialising case class directly this is a huge increase in usability! 30 / 68
  • 31. Codec customisations using annotations annotations for cutomising tagged union vs discrimated union enum storage names case class case class fields discriminator field enum values 31 / 68
  • 32. Where we left off in the last talk val avi: Student = get("student", primaryKey) put[Student]("student", Student("avi@gmail.com", ...),...) .where( $("addr.line1").contains("street") && $("addr.line2").beginsWith("f") ) updateItem("student", primaryKey) { $("firstname").set("Avinder") + $("groups").appendList(Chunk("group1")) }.where( $("email") === "avi@gmail.com" ) deleteItem("student", primaryKey) .where( $("age") > "18" && $("email") === "avi@gmail.com") ) querySomeItem("tableName1", limit = 10, $("email"), $("payment"), $("age")) .sortOrder(ascending = false) .whereKey(PartitionKey("email") === "avi@gmail.com") .filter( $("age") > 21 && $("subject").beginsWith("Math") ) Can you spot the run time error problem in the above code? 32 / 68
  • 34. Where we left off in the last talk 34 / 68
  • 35. Introducing the new type safe API 35 / 68
  • 36. Optics Provide a way of navigating your immutable data structure that reduces boilerplate Prism - access product data Lens - access sum type data Traversal - access a collection Drilling down into a field eg Student.email is a Scala language feature that is not a value Optics libraries take these language features and turn them into values that can be composed Typically libraries use use functions in the encoding (executable encoding) 36 / 68
  • 37. Reified Optics sealed trait Payment object Payment { final case object DebitCard extends Payment // ... implicit val schema = DeriveSchema.gen[Payment] } final case class Student( email: String, // partition key enrollDate: Instant, payment: Payment ) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, enrollDate, payment) = ProjectionExpression.accessors[Student] } 37 / 68
  • 38. Reified Optics Reified optics are not enough on their own - we needed extend this type safety to the rest of the API so we added phantom type parameters to our lower level representations: sealed trait ProjectionExpression[-From, +To] sealed trait ConditionExpression[-From] 38 / 68
  • 39. New type safe API the new type safe API uses reified optics to constrain expressions to the type we are working with 39 / 68
  • 40. New type safe API - Update Expressions val student = Student(...) object Student { val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student] } The update expressions here are typesafe. 40 / 68
  • 41. New type safe API - Condition Expressions val student = Student(...) object Student { val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student] } 41 / 68
  • 42. New type safe API - delete condition expressions val student = Student(...) object Student { val (email, enrollDate, payment, age) = ProjectionExpression.accessors[Student] } Elephant injection attack will not work! 42 / 68
  • 43. New Type-Safe API examples 43 / 68
  • 44. Example 1 - Optimistic locking 44 / 68
  • 45. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) 45 / 68
  • 46. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } 46 / 68
  • 47. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = ??? 47 / 68
  • 48. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = for { before <- get[Student]("student", primaryKey).execute.absolve } yield () 48 / 68
  • 49. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = for { before <- get[Student]("student", primaryKey).execute.absolve _ <- update[Student]("student", primaryKey) { ... }.execute } yield () 49 / 68
  • 50. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = for { before <- get[Student]("student", primaryKey).execute.absolve _ <- update[Student]("student", primaryKey) { f(before) + version.add(1) } ... } yield () 50 / 68
  • 51. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = for { before <- get[Student]("student", primaryKey).execute.absolve _ <- update[Student]("student", primaryKey) { f(before) + version.add(1) }.where(Student.version === before.version).execute.retry(policy) } yield () 51 / 68
  • 52. Optimistic locking example final case class Student(email: String, payment: Payment, age: Int, version: Long = 0) object Student { implicit val schema = DeriveSchema.gen[Student] val (email, payment, age, version) = ProjectionExpression.accessors[Student] } def optimisticUpdate(primaryKey: PrimaryKey)(f: Student => Action[Student]) = for { before <- get[Student]("student", primaryKey).execute.absolve _ <- update[Student]("student", primaryKey) { f(before) + version.add(1) }.where(Student.version === before.version).execute.retry(policy) } yield () for { _ <- optimisticUpdate(primaryKey("avi@gmail.com", "maths")) { student => if (student.age > 42) Student.payment.set(Payment.DebitCard) else Student.payment.set(Payment.CreditCard) }.execute } yield () 52 / 68
  • 53. Example 2 - Migration Pipeline Example 53 / 68
  • 54. Migration Pipeline Example final case class Student( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment ) final case class StudentCourses( email: String, courses: Set[String] ) final case class Student2( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment, courses: Set[String] ) 54 / 68
  • 55. Migration Pipeline Example final case class Student( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment ) final case class StudentCourses( email: String, courses: Set[String] ) final case class Student2( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment, courses: Set[String] ) 55 / 68
  • 56. Migration Pipeline Example final case class Student( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment ) final case class StudentCourses( email: String, courses: Set[String] ) final case class Student2( email: String, // partition key age: Int, enrollmentDate: Instant, payment: Payment, courses: Set[String] ) 56 / 68
  • 57. Migration Pipeline Example private val program = for { students <- scanAll[Student](tableName = "s") .parallel(16) .filter(Student.age > 21 && Student.enrollmentDate > aDate) .execute studentsAndCourses = batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email)) _ <- batchWriteFromStream(studentsAndCourses) { case (s, sc) => put(tableName = "Students2", Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses) ) }.runDrain } yield () 57 / 68
  • 58. Migration Pipeline Example private val program = for { students <- scanAll[Student](tableName = "s") .parallel(16) .filter(Student.age > 21 && Student.enrollmentDate > aDate) .execute studentsAndCourses = batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email)) _ <- batchWriteFromStream(studentsAndCourses) { case (s, sc) => put(tableName = "Students2", Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses) ) }.runDrain } yield () 58 / 68
  • 59. Migration Pipeline Example private val program = for { students <- scanAll[Student](tableName = "s") .parallel(16) .filter(Student.age > 21 && Student.enrollmentDate > aDate) .execute studentsAndCourses: ZStream[..., (Student, StudentCourses)] = batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email)) _ <- batchWriteFromStream(studentsAndCourses) { case (s, sc) => put(tableName = "Students2", Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses) ) }.runDrain } yield () 59 / 68
  • 60. Migration Pipeline Example private val program = for { students <- scanAll[Student](tableName = "s") .parallel(16) .filter(Student.age > 21 && Student.enrollmentDate > aDate) .execute studentsAndCourses: ZStream[..., (Student, StudentCourses)] = batchReadFromStream[...]("sc", students)(s => PrimaryKey("email" -> s.email)) _ <- batchWriteFromStream(studentsAndCourses) { case (s, sc) => put(tableName = "Students2", Student2(s.email, s.age, s.enrollmentDate, s.payment, sc.courses) ) }.runDrain } yield () 60 / 68
  • 62. Summary - Condition and Filter Expression 62 / 68
  • 63. Summary - Type Safety coverage 63 / 68
  • 64. Summary - Update Expressions update-expression ::= [ SET action [, action] ... ] [ REMOVE action [, action] ...] [ ADD action [, action] ... ] [ DELETE action [, action] ...] set-action ::= path = value value ::= operand | operand '+' operand | operand '-' operand operand ::= path | function function list_append (list1, list2) if_not_exists (path, value) remove-action ::= path add-action ::= path value delete-action ::= path value 64 / 68
  • 65. Summary - Type Safety coverage update-expression ::= [ SET action [, action] ... ] [ REMOVE action [, action] ...] [ ADD action [, action] ... ] [ DELETE action [, action] ...] set-action ::= path = value value ::= operand | operand '+' operand | operand '-' operand operand ::= path | function function list_append (list1, list2) if_not_exists (path, value) remove-action ::= path add-action ::= path value delete-action ::= path value 65 / 68
  • 66. Summary - New Type Safe API 66 / 68
  • 67. Learning More github repo https://github.com/zio/zio-dynamodb microsite https://zio.github.io/zio-dynamodb/ examples in own sbt module zio-dynamodb/examples integration tests dynamodb/src/it/scala/zio/dynamodb/ last years talk introducing ZIO DynamoDB at Functional Scala 2021 67 / 68
  • 68. Thanks a big thank you to: all the sponsors Ziverge, Scalac, UMATR, Coralogix, Conducktor, Matechs for giving me this opportunity to talk about this library Sandra and Agata for organising the conference John for his mentorship during development 68 / 68