SlideShare a Scribd company logo
1 of 19
Event Sourcing in the
Functional World
A practical journey of using F#
and event sourcing
● Why Event Sourcing?
● Domain Design: Event Storming
● Primitives, Events, Commands, and
Aggregates
● Domain Logic as Pure Functions
● Serialization and DTOs
● Error Handling: Railway-Oriented
Programming
● Cosmos DB: Event / Aggregate Store
● Strong(-er) Consistency at Scale
● Assembling the Pipeline: Dependency
Rejection
● Change Feed: Eventually Consistent Read
Models https://fsharpforfunandprofit.com/
Why Event
Sourcing?
• We need a reliable audit log
• We need to know the the
state of the system at a
given point in time
• We believe it helps us to build
more scalable systems*
(your mileage may vary)
“Event Sourcing -
Step by step in
F#”
https://medium.com/@dzoukr/event
-sourcing-step-by-step-in-f-
be808aa0ca18
“Scaling Event-
Sourcing at Jet”
https://medium.com/@eulerfx/scalin
g-event-sourcing-at-jet-
9c873cac33b8
Event
Storming
Form a shared mental
model, avoid
technical jargon
Focus on business
events, not data
structures
Discover Events,
Commands, and
Domain Aggregates
Primitives
The modelling
building blocks
Simple Wrappers
type FirstName = FirstName of string
type LastName = LastName of string
type MiddleName = MiddleName of string
Wrappers with guarded access (recommended)
type EmailAddress = private EmailAddress of string
[<RequireQualifiedAccess>]
module EmailAddress =
let create email =
if Regex.IsMatch(email, ".*?@(.*)")
then email |> EmailAddress |> Ok
else Error "Incorrect email format"
let value (EmailAddress e) = e
Composition
type ContactDetails = { Name: Name; Email: EmailAddress }
and Name =
{ FirstName: FirstName
MiddleName: MiddleName option
LastName: LastName }
Events
What happened
to the system?
Different event types are just DU cases
type VaccinationEvent =
| ContactRegistered of ContactDetails
| AppointmentCreated of VaccinationAppointment
| AppointmentCanceled of AppointmentId
| VaccineAdministered of AppointmentId
| ObservationComplete of AppointmentId * ObservationStatus
| SurveySubmitted of AppointmentId * SurveyResult
Metadata:
● Timestamp
● Aggregate Id
● Sequence No.
● Correlation Id
● Causation Id
● Grouping
attributes
and VaccinationAppointment =
{ Id: AppointmentId
Vaccine: VaccineType
Hub: VaccinationHub
Date: DateTime }
and AppointmentId = string
and VaccineType = Pfizer | Moderna | AstraZeneca
and ObservationStatus =
| NoAdverseReaction
| AdverseReaction of ReactionKind
and VaccinationHub = exn
and ReactionKind = exn
and SurveyResult = exn
type Event<'D, 'M> = { Id: Guid; Data: 'D; Metadata: 'M }
What caused
the change?
(Sometimes)
Who and why
requested the
change?
Commands Commands are intents, and reflect actions.
Naming: usually “VerbNoun”
type VaccinationCommand =
| RegisterContact of ContactDetails
| CreateAppointment of VaccinationAppointment
| CancelAppointment of AppointmentId
| AdministerVaccine of AppointmentId
* ObservationStatus option
| SubmitSurvey of AppointmentId * SurveyResult
Valid commands generate events
let toEvents c = function
| RegisterContact cd -> [ ContactRegistered cd ]
| CreateAppointment a -> [ AppointmentCreated a ]
| CancelAppointment appId -> [ AppointmentCanceled appId ]
| AdministerVaccine (appId, None) ->
[ VaccineAdministered appId ]
| AdministerVaccine (appId, Some status) ->
[ VaccineAdministered appId
ObservationComplete (appId, status) ]
| SubmitSurvey (appId, s) -> [ SurveySubmitted (appId, s) ]
Event.Metadata.CausationId = Command.Id
“Designing with
types: Making
illegal states
unrepresentable”
https://fsharpforfunandprofit.com/po
sts/designing-with-types-making-
illegal-states-unrepresentable/
Domain
Aggregates
Aggregates should have enough information to power
business rules
type VaccineRecipient =
{ Id: Guid
ContactDetails: ContactDetails
RegistrationDate: DateTime
State: VaccineRecipientState }
and VaccineRecipientState =
| Registered
| Booked of VaccinationAppointment nlist
| InProcess of VaccinationInProcess
| FullyVaccinated of VaccinationResult nlist
and VaccinationInProcess =
{ Administered: VaccinationResult nlist
Booked: VaccinationAppointment nlist }
and VaccinationResult = VaccinationAppointment
* ObservationStatus option
* SurveyResult option
and 'T nlist = NonEmptyList<'T>
and NonEmptyList<'T> = 'T list
Pure functions
Domain
Logic
Event sourcing in a nutshell
type Folder<'Aggregate, 'Event> =
'Aggregate -> 'Event list -> 'Aggregate
type Handler<'Aggregate, 'Command, 'Event> =
'Aggregate -> 'Command -> 'Aggregate * 'Event list
Add error handling, separate “create” and “update”
let create newId timestamp event =
match event with
| ContactRegistered c ->
{ Id = newId; ContactDetails = c;
RegistrationDate = timestamp; State = Registered } |> Ok
| _ -> Error "Aggregate doesn't exist"
let update aggregate event =
match aggregate.State, event with
| Registered, AppointmentCreated apt ->
{ aggregate with State = Booked [ apt ] } |> Ok
| Booked list, AppointmentCreated apt ->
{ aggregate with State = Booked (list @ [apt] ) } |> Ok
| _, _ -> "Exercise left to the reader" |> Error
“Serializing your
domain model”
https://fsharpforfunandprofit.
com/posts/serializing-your-
domain-model/
DTOs
DTOs reflect unvalidated input outside of our control
type NameDto =
{ FirstName: string
MiddleName: string // can be null :(
LastName: string }
module NameDto =
let toDomain (name: NameDto) =
({ FirstName = name.FirstName |> FirstName
MiddleName = name.MiddleName
|> Option.ofObj |> Option.map MiddleName
LastName = name.LastName |> LastName }: Name) |> Ok
You might need to apply versioning to your DTOs
type VaccinationEventDto =
| AppointmentCreated of VaccinationAppointment
| AppointmentCreatedV2 of VaccinationAppointment
* Confirmation
Events are stored forever, design them carefully!
Error
Handling
Error Handling is tedious
type ContactDetailsDto = { Name: NameDto; Email: string }
module ContactDetailsDto =
let toDomainTheBoringWay contact =
let nameResult = contact.Name |> NameDto.toDomain
match (nameResult) with
| Ok name ->
let emailResult = contact.Email |> EmailAddress.create
match emailResult with
| Ok email ->
({ Name = name; Email = email }: ContactDetails) |> Ok
| Error e -> Error e
| Error e -> Error e
“Railway Oriented
Programming”
https://fsharpforfunand
profit.com/rop/
FsToolkit.ErrorHandling helps
module ContactDetailsDto =
let toDomain contact = result {
let! name = contact.Name |> NameDto.toDomain
let! email = contact.Email |> EmailAddress.create
return ({ Name = name; Email = email }: ContactDetails)
}
There and back
again
Libraries:
FSharp.Json
https://github.com/vsaprono
v/FSharp.Json
Thoth.Json (works
with Fable!)
https://thoth-
org.github.io/Thoth.Json/
Serialization FSharp.Json can handle wrapper types and options
let record: Name =
{ FirstName = FirstName "John"
MiddleName = None
LastName = LastName "Smith" }
record |> Json.serialize |> printfn "%s"
{
"FirstName": "John",
"MiddleName": null,
"LastName": "Smith"
}
JSON -> DTO can fail
let tryParse<'Data> s =
try
Json.deserialize<'Data> s |> Ok
with
| x -> sprintf "Cannot deserialize: %s" x.Message |> Error
JSON -> Domain? Compose using ROP
let readContacts contactsString =
contactsString
|> tryParse<ContactDetailsDto>
|> Result.bind ContactDetailsDto.toDomain
CosmosDB:
Event /
Aggregate Store
Persistence
Define partition strategy in the code, not DB
let! response =
database.CreateContainerIfNotExistsAsync(
containerName, "/partitionKey")
module VaccinationRecipientKeyStrategy =
let toPartitionKey streamId =
$"VaccinationRecipient-{streamId}"
Partition boundaries determine consistency
Stream 1
(partition 1)
Event 1 Event 2 Event 3
Aggregate
Stream 2
(partition 2)
Event 1 Event 2
Aggregate
Defining Entities
in F#
Persistence Useful DB Wrappers
module Db =
type PartitionKey = PartitionKey of string
type Id = Id of string
type ETag = ETag of string
type EntityType = Event | AggregateRoot
Use JSON serializer specifics to map to DB fields
type PersistentEntity<'Payload> =
{ [<JsonField("partitionKey")>]
PartitionKey: Db.PartitionKey
[<JsonField("id")>] Id: Db.Id
[<JsonField("etag")>] ETag: Db.ETag option
Type: EntityType
Payload: 'Payload }
Concrete serialized types
module VaccinationPersistence =
type Event = PersistentEntity<VaccinationEvent>
type Aggregate = PersistentEntity<VaccineRecipient>
CosmosDB
Transactions
Persistence Use Batch API to add events + “upsert” the aggregate
module CosmosDb =
let createBatch (Db.PartitionKey key) (container: Container) =
container.CreateTransactionalBatch(PartitionKey(key))
let createAsString (payload: string)
(batch: TransactionalBatch) =
new MemoryStream(Encoding.UTF8.GetBytes(payload))
|> batch.CreateItemStream
let replaceAsString (Db.Id id) (payload: string)
(Db.ETag etag)
(batch: TransactionalBatch) =
let stream = new MemoryStream
(Encoding.UTF8.GetBytes(payload))
let options = TransactionalBatchItemRequestOptions()
options.IfMatchEtag <- etag
batch.ReplaceItemStream(id, stream, options)
let executeBatch (batch: TransactionalBatch) = taskResult {
let! response = batch.ExecuteAsync()
if (response.IsSuccessStatusCode) then
return! Ok ()
else
return! Error (response.StatusCode, response.ErrorMessage)
}
We use tasks for an easier
interop with Azure SDKs
Optimistic
concurrency
“Dependency
Rejection”
https://blog.ploeh.dk/2017/01/
27/from-dependency-
injection-to-dependency-
rejection/
“Reinventing the
Transaction
Script”
https://fsharpforfunandprofit.
com/transactionscript/
Assembling
the Pipeline
The Impure - Pure - Impure “sandwich”
• Read data
• Deserialize DTOs from JSON
• Convert DTOs to Domain Models
• Generate commands
• Run the handler
• Convert Results to DTOs
• Serialize DTOs to JSON
• Persist the output
Do not do I/O in pure code, return directives instead
module VaccinationExample =
type ErrorMessage = string
type DomainEvent = Event<VaccinationEvent, EventMetadata>
type HandlerResult =
| NoChange
| InvalidCommand of ErrorMessage
| Conflict of ErrorMessage
| CreateAggregate of VaccineRecipient * DomainEvent list
| UpdateAggregate of VaccineRecipient * DomainEvent list
type Handler = VaccineRecipient option
-> VaccinationCommand -> HandlerResult
Using
CosmosDB to
build read
models
Change
Feed
You can use either:
• Azure Functions CosmosDBTrigger (simpler)
• or ChangeFeed Processor running in AppServices
(more control)
You will need to provision:
• A separate Lease collection in CosmosDB
• The target storage (DB, blobs, SQL DB, etc)
Leases ensure events will be processed in sync, but...
Events might come in several partitions at once!
let groups = docs
|> Seq.groupBy
(fun d ->
d.GetPropertyValue<string> "partitionKey")
for (pk, group) in groups do // process by partitions
You have to handle errors (make your own DLQ, etc)
You can reprocess events from the beginning!
Questions?
Thank you
flarehr.com.au

More Related Content

What's hot

Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Scott Wlaschin
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...Philip Schwarz
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8RichardWarburton
 
Functional and Event Driven - another approach to domain modeling
Functional and Event Driven - another approach to domain modelingFunctional and Event Driven - another approach to domain modeling
Functional and Event Driven - another approach to domain modelingDebasish Ghosh
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Scott Wlaschin
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Scott Wlaschin
 
Pipeline oriented programming
Pipeline oriented programmingPipeline oriented programming
Pipeline oriented programmingScott Wlaschin
 
Functional and Algebraic Domain Modeling
Functional and Algebraic Domain ModelingFunctional and Algebraic Domain Modeling
Functional and Algebraic Domain ModelingDebasish Ghosh
 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsPhilip Schwarz
 
webpack 101 slides
webpack 101 slideswebpack 101 slides
webpack 101 slidesmattysmith
 
Retour opérationnel sur la clean architecture
Retour opérationnel sur la clean architectureRetour opérationnel sur la clean architecture
Retour opérationnel sur la clean architectureRomainKuzniak
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1José Paumard
 
Promises, promises, and then observables
Promises, promises, and then observablesPromises, promises, and then observables
Promises, promises, and then observablesStefan Charsley
 
Clean Code II - Dependency Injection
Clean Code II - Dependency InjectionClean Code II - Dependency Injection
Clean Code II - Dependency InjectionTheo Jungeblut
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...Philip Schwarz
 

What's hot (20)

Kleisli Composition
Kleisli CompositionKleisli Composition
Kleisli Composition
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
 
Functional and Event Driven - another approach to domain modeling
Functional and Event Driven - another approach to domain modelingFunctional and Event Driven - another approach to domain modeling
Functional and Event Driven - another approach to domain modeling
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)
 
Boost Fusion Library
Boost Fusion LibraryBoost Fusion Library
Boost Fusion Library
 
Second Level Cache in JPA Explained
Second Level Cache in JPA ExplainedSecond Level Cache in JPA Explained
Second Level Cache in JPA Explained
 
Pipeline oriented programming
Pipeline oriented programmingPipeline oriented programming
Pipeline oriented programming
 
Functional and Algebraic Domain Modeling
Functional and Algebraic Domain ModelingFunctional and Algebraic Domain Modeling
Functional and Algebraic Domain Modeling
 
Vulkan 1.1 Reference Guide
Vulkan 1.1 Reference GuideVulkan 1.1 Reference Guide
Vulkan 1.1 Reference Guide
 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets Cats
 
webpack 101 slides
webpack 101 slideswebpack 101 slides
webpack 101 slides
 
Retour opérationnel sur la clean architecture
Retour opérationnel sur la clean architectureRetour opérationnel sur la clean architecture
Retour opérationnel sur la clean architecture
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1
 
Promises, promises, and then observables
Promises, promises, and then observablesPromises, promises, and then observables
Promises, promises, and then observables
 
Clean Code II - Dependency Injection
Clean Code II - Dependency InjectionClean Code II - Dependency Injection
Clean Code II - Dependency Injection
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
 
Drools rule Concepts
Drools rule ConceptsDrools rule Concepts
Drools rule Concepts
 

Similar to Event sourcing in the functional world (22 07-2021)

Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenchesLukas Smith
 
Introducing the WSO2 Complex Event Processor
Introducing the WSO2 Complex Event ProcessorIntroducing the WSO2 Complex Event Processor
Introducing the WSO2 Complex Event ProcessorWSO2
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the TrenchesJonathan Wage
 
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"Andrii Dembitskyi "Events in our applications Event bus and distributed systems"
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"Fwdays
 
DDD, CQRS, ES lessons learned
DDD, CQRS, ES lessons learnedDDD, CQRS, ES lessons learned
DDD, CQRS, ES lessons learnedQframe
 
Complex Event Processor 3.0.0 - An overview of upcoming features
Complex Event Processor 3.0.0 - An overview of upcoming features Complex Event Processor 3.0.0 - An overview of upcoming features
Complex Event Processor 3.0.0 - An overview of upcoming features WSO2
 
NET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxNET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxpetabridge
 
Oleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoCOleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoCOleksandr Valetskyy
 
N Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeN Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeAnton Kulyk
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every dayVadym Khondar
 
Online Meetup: Why should container system / platform builders care about con...
Online Meetup: Why should container system / platform builders care about con...Online Meetup: Why should container system / platform builders care about con...
Online Meetup: Why should container system / platform builders care about con...Docker, Inc.
 
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive AppsJorge Ortiz
 
An Introduction To CQRS
An Introduction To CQRSAn Introduction To CQRS
An Introduction To CQRSNeil Robbins
 
Event Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BEEvent Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BEAndrzej Ludwikowski
 
Overview of Zookeeper, Helix and Kafka (Oakjug)
Overview of Zookeeper, Helix and Kafka (Oakjug)Overview of Zookeeper, Helix and Kafka (Oakjug)
Overview of Zookeeper, Helix and Kafka (Oakjug)Chris Richardson
 
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor WSO2
 
Hack an ASP .NET website? Hard, but possible!
Hack an ASP .NET website? Hard, but possible! Hack an ASP .NET website? Hard, but possible!
Hack an ASP .NET website? Hard, but possible! Vladimir Kochetkov
 
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)mircodotta
 

Similar to Event sourcing in the functional world (22 07-2021) (20)

Symfony2 - from the trenches
Symfony2 - from the trenchesSymfony2 - from the trenches
Symfony2 - from the trenches
 
Introducing the WSO2 Complex Event Processor
Introducing the WSO2 Complex Event ProcessorIntroducing the WSO2 Complex Event Processor
Introducing the WSO2 Complex Event Processor
 
dSS API by example
dSS API by exampledSS API by example
dSS API by example
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"Andrii Dembitskyi "Events in our applications Event bus and distributed systems"
Andrii Dembitskyi "Events in our applications Event bus and distributed systems"
 
DDD, CQRS, ES lessons learned
DDD, CQRS, ES lessons learnedDDD, CQRS, ES lessons learned
DDD, CQRS, ES lessons learned
 
Complex Event Processor 3.0.0 - An overview of upcoming features
Complex Event Processor 3.0.0 - An overview of upcoming features Complex Event Processor 3.0.0 - An overview of upcoming features
Complex Event Processor 3.0.0 - An overview of upcoming features
 
NET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptxNET Systems Programming Learned the Hard Way.pptx
NET Systems Programming Learned the Hard Way.pptx
 
Oleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoCOleksandr Valetskyy - DI vs. IoC
Oleksandr Valetskyy - DI vs. IoC
 
N Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React NativeN Things You Don't Want to Repeat in React Native
N Things You Don't Want to Repeat in React Native
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
 
Postman On Steroids
Postman On SteroidsPostman On Steroids
Postman On Steroids
 
Online Meetup: Why should container system / platform builders care about con...
Online Meetup: Why should container system / platform builders care about con...Online Meetup: Why should container system / platform builders care about con...
Online Meetup: Why should container system / platform builders care about con...
 
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive Apps
 
An Introduction To CQRS
An Introduction To CQRSAn Introduction To CQRS
An Introduction To CQRS
 
Event Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BEEvent Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BE
 
Overview of Zookeeper, Helix and Kafka (Oakjug)
Overview of Zookeeper, Helix and Kafka (Oakjug)Overview of Zookeeper, Helix and Kafka (Oakjug)
Overview of Zookeeper, Helix and Kafka (Oakjug)
 
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor
WSO2 Product Release Webinar - Introducing the WSO2 Complex Event Processor
 
Hack an ASP .NET website? Hard, but possible!
Hack an ASP .NET website? Hard, but possible! Hack an ASP .NET website? Hard, but possible!
Hack an ASP .NET website? Hard, but possible!
 
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
 

Recently uploaded

Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...
Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...
Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...Dr.Costas Sachpazis
 
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...Call Girls in Nagpur High Profile
 
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...RajaP95
 
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...Dr.Costas Sachpazis
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024Mark Billinghurst
 
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130Suhani Kapoor
 
Processing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxProcessing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxpranjaldaimarysona
 
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerStudy on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerAnamika Sarkar
 
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...ranjana rawat
 
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...ZTE
 
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Serviceranjana rawat
 
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur Escorts
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur EscortsCall Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur Escorts
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur EscortsCall Girls in Nagpur High Profile
 
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).pptssuser5c9d4b1
 
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝soniya singh
 
Software Development Life Cycle By Team Orange (Dept. of Pharmacy)
Software Development Life Cycle By  Team Orange (Dept. of Pharmacy)Software Development Life Cycle By  Team Orange (Dept. of Pharmacy)
Software Development Life Cycle By Team Orange (Dept. of Pharmacy)Suman Mia
 
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsHigh Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsCall Girls in Nagpur High Profile
 
Introduction to IEEE STANDARDS and its different types.pptx
Introduction to IEEE STANDARDS and its different types.pptxIntroduction to IEEE STANDARDS and its different types.pptx
Introduction to IEEE STANDARDS and its different types.pptxupamatechverse
 
Microscopic Analysis of Ceramic Materials.pptx
Microscopic Analysis of Ceramic Materials.pptxMicroscopic Analysis of Ceramic Materials.pptx
Microscopic Analysis of Ceramic Materials.pptxpurnimasatapathy1234
 

Recently uploaded (20)

Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...
Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...
Structural Analysis and Design of Foundations: A Comprehensive Handbook for S...
 
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...
High Profile Call Girls Nashik Megha 7001305949 Independent Escort Service Na...
 
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...
IMPLICATIONS OF THE ABOVE HOLISTIC UNDERSTANDING OF HARMONY ON PROFESSIONAL E...
 
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...
Sheet Pile Wall Design and Construction: A Practical Guide for Civil Engineer...
 
IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024IVE Industry Focused Event - Defence Sector 2024
IVE Industry Focused Event - Defence Sector 2024
 
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
 
Processing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxProcessing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptx
 
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube ExchangerStudy on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
Study on Air-Water & Water-Water Heat Exchange in a Finned Tube Exchanger
 
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
 
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
 
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...
ZXCTN 5804 / ZTE PTN / ZTE POTN / ZTE 5804 PTN / ZTE POTN 5804 ( 100/200 GE Z...
 
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service
(RIA) Call Girls Bhosari ( 7001035870 ) HI-Fi Pune Escorts Service
 
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur Escorts
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur EscortsCall Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur Escorts
Call Girls Service Nagpur Tanvi Call 7001035870 Meet With Nagpur Escorts
 
DJARUM4D - SLOT GACOR ONLINE | SLOT DEMO ONLINE
DJARUM4D - SLOT GACOR ONLINE | SLOT DEMO ONLINEDJARUM4D - SLOT GACOR ONLINE | SLOT DEMO ONLINE
DJARUM4D - SLOT GACOR ONLINE | SLOT DEMO ONLINE
 
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt
247267395-1-Symmetric-and-distributed-shared-memory-architectures-ppt (1).ppt
 
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝
Model Call Girl in Narela Delhi reach out to us at 🔝8264348440🔝
 
Software Development Life Cycle By Team Orange (Dept. of Pharmacy)
Software Development Life Cycle By  Team Orange (Dept. of Pharmacy)Software Development Life Cycle By  Team Orange (Dept. of Pharmacy)
Software Development Life Cycle By Team Orange (Dept. of Pharmacy)
 
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsHigh Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
 
Introduction to IEEE STANDARDS and its different types.pptx
Introduction to IEEE STANDARDS and its different types.pptxIntroduction to IEEE STANDARDS and its different types.pptx
Introduction to IEEE STANDARDS and its different types.pptx
 
Microscopic Analysis of Ceramic Materials.pptx
Microscopic Analysis of Ceramic Materials.pptxMicroscopic Analysis of Ceramic Materials.pptx
Microscopic Analysis of Ceramic Materials.pptx
 

Event sourcing in the functional world (22 07-2021)

  • 1. Event Sourcing in the Functional World A practical journey of using F# and event sourcing
  • 2. ● Why Event Sourcing? ● Domain Design: Event Storming ● Primitives, Events, Commands, and Aggregates ● Domain Logic as Pure Functions ● Serialization and DTOs ● Error Handling: Railway-Oriented Programming ● Cosmos DB: Event / Aggregate Store ● Strong(-er) Consistency at Scale ● Assembling the Pipeline: Dependency Rejection ● Change Feed: Eventually Consistent Read Models https://fsharpforfunandprofit.com/
  • 3. Why Event Sourcing? • We need a reliable audit log • We need to know the the state of the system at a given point in time • We believe it helps us to build more scalable systems* (your mileage may vary) “Event Sourcing - Step by step in F#” https://medium.com/@dzoukr/event -sourcing-step-by-step-in-f- be808aa0ca18 “Scaling Event- Sourcing at Jet” https://medium.com/@eulerfx/scalin g-event-sourcing-at-jet- 9c873cac33b8
  • 4. Event Storming Form a shared mental model, avoid technical jargon Focus on business events, not data structures Discover Events, Commands, and Domain Aggregates
  • 5. Primitives The modelling building blocks Simple Wrappers type FirstName = FirstName of string type LastName = LastName of string type MiddleName = MiddleName of string Wrappers with guarded access (recommended) type EmailAddress = private EmailAddress of string [<RequireQualifiedAccess>] module EmailAddress = let create email = if Regex.IsMatch(email, ".*?@(.*)") then email |> EmailAddress |> Ok else Error "Incorrect email format" let value (EmailAddress e) = e Composition type ContactDetails = { Name: Name; Email: EmailAddress } and Name = { FirstName: FirstName MiddleName: MiddleName option LastName: LastName }
  • 6. Events What happened to the system? Different event types are just DU cases type VaccinationEvent = | ContactRegistered of ContactDetails | AppointmentCreated of VaccinationAppointment | AppointmentCanceled of AppointmentId | VaccineAdministered of AppointmentId | ObservationComplete of AppointmentId * ObservationStatus | SurveySubmitted of AppointmentId * SurveyResult Metadata: ● Timestamp ● Aggregate Id ● Sequence No. ● Correlation Id ● Causation Id ● Grouping attributes and VaccinationAppointment = { Id: AppointmentId Vaccine: VaccineType Hub: VaccinationHub Date: DateTime } and AppointmentId = string and VaccineType = Pfizer | Moderna | AstraZeneca and ObservationStatus = | NoAdverseReaction | AdverseReaction of ReactionKind and VaccinationHub = exn and ReactionKind = exn and SurveyResult = exn type Event<'D, 'M> = { Id: Guid; Data: 'D; Metadata: 'M }
  • 7. What caused the change? (Sometimes) Who and why requested the change? Commands Commands are intents, and reflect actions. Naming: usually “VerbNoun” type VaccinationCommand = | RegisterContact of ContactDetails | CreateAppointment of VaccinationAppointment | CancelAppointment of AppointmentId | AdministerVaccine of AppointmentId * ObservationStatus option | SubmitSurvey of AppointmentId * SurveyResult Valid commands generate events let toEvents c = function | RegisterContact cd -> [ ContactRegistered cd ] | CreateAppointment a -> [ AppointmentCreated a ] | CancelAppointment appId -> [ AppointmentCanceled appId ] | AdministerVaccine (appId, None) -> [ VaccineAdministered appId ] | AdministerVaccine (appId, Some status) -> [ VaccineAdministered appId ObservationComplete (appId, status) ] | SubmitSurvey (appId, s) -> [ SurveySubmitted (appId, s) ] Event.Metadata.CausationId = Command.Id
  • 8. “Designing with types: Making illegal states unrepresentable” https://fsharpforfunandprofit.com/po sts/designing-with-types-making- illegal-states-unrepresentable/ Domain Aggregates Aggregates should have enough information to power business rules type VaccineRecipient = { Id: Guid ContactDetails: ContactDetails RegistrationDate: DateTime State: VaccineRecipientState } and VaccineRecipientState = | Registered | Booked of VaccinationAppointment nlist | InProcess of VaccinationInProcess | FullyVaccinated of VaccinationResult nlist and VaccinationInProcess = { Administered: VaccinationResult nlist Booked: VaccinationAppointment nlist } and VaccinationResult = VaccinationAppointment * ObservationStatus option * SurveyResult option and 'T nlist = NonEmptyList<'T> and NonEmptyList<'T> = 'T list
  • 9. Pure functions Domain Logic Event sourcing in a nutshell type Folder<'Aggregate, 'Event> = 'Aggregate -> 'Event list -> 'Aggregate type Handler<'Aggregate, 'Command, 'Event> = 'Aggregate -> 'Command -> 'Aggregate * 'Event list Add error handling, separate “create” and “update” let create newId timestamp event = match event with | ContactRegistered c -> { Id = newId; ContactDetails = c; RegistrationDate = timestamp; State = Registered } |> Ok | _ -> Error "Aggregate doesn't exist" let update aggregate event = match aggregate.State, event with | Registered, AppointmentCreated apt -> { aggregate with State = Booked [ apt ] } |> Ok | Booked list, AppointmentCreated apt -> { aggregate with State = Booked (list @ [apt] ) } |> Ok | _, _ -> "Exercise left to the reader" |> Error
  • 10. “Serializing your domain model” https://fsharpforfunandprofit. com/posts/serializing-your- domain-model/ DTOs DTOs reflect unvalidated input outside of our control type NameDto = { FirstName: string MiddleName: string // can be null :( LastName: string } module NameDto = let toDomain (name: NameDto) = ({ FirstName = name.FirstName |> FirstName MiddleName = name.MiddleName |> Option.ofObj |> Option.map MiddleName LastName = name.LastName |> LastName }: Name) |> Ok You might need to apply versioning to your DTOs type VaccinationEventDto = | AppointmentCreated of VaccinationAppointment | AppointmentCreatedV2 of VaccinationAppointment * Confirmation Events are stored forever, design them carefully!
  • 11. Error Handling Error Handling is tedious type ContactDetailsDto = { Name: NameDto; Email: string } module ContactDetailsDto = let toDomainTheBoringWay contact = let nameResult = contact.Name |> NameDto.toDomain match (nameResult) with | Ok name -> let emailResult = contact.Email |> EmailAddress.create match emailResult with | Ok email -> ({ Name = name; Email = email }: ContactDetails) |> Ok | Error e -> Error e | Error e -> Error e “Railway Oriented Programming” https://fsharpforfunand profit.com/rop/ FsToolkit.ErrorHandling helps module ContactDetailsDto = let toDomain contact = result { let! name = contact.Name |> NameDto.toDomain let! email = contact.Email |> EmailAddress.create return ({ Name = name; Email = email }: ContactDetails) }
  • 12. There and back again Libraries: FSharp.Json https://github.com/vsaprono v/FSharp.Json Thoth.Json (works with Fable!) https://thoth- org.github.io/Thoth.Json/ Serialization FSharp.Json can handle wrapper types and options let record: Name = { FirstName = FirstName "John" MiddleName = None LastName = LastName "Smith" } record |> Json.serialize |> printfn "%s" { "FirstName": "John", "MiddleName": null, "LastName": "Smith" } JSON -> DTO can fail let tryParse<'Data> s = try Json.deserialize<'Data> s |> Ok with | x -> sprintf "Cannot deserialize: %s" x.Message |> Error JSON -> Domain? Compose using ROP let readContacts contactsString = contactsString |> tryParse<ContactDetailsDto> |> Result.bind ContactDetailsDto.toDomain
  • 13. CosmosDB: Event / Aggregate Store Persistence Define partition strategy in the code, not DB let! response = database.CreateContainerIfNotExistsAsync( containerName, "/partitionKey") module VaccinationRecipientKeyStrategy = let toPartitionKey streamId = $"VaccinationRecipient-{streamId}" Partition boundaries determine consistency Stream 1 (partition 1) Event 1 Event 2 Event 3 Aggregate Stream 2 (partition 2) Event 1 Event 2 Aggregate
  • 14. Defining Entities in F# Persistence Useful DB Wrappers module Db = type PartitionKey = PartitionKey of string type Id = Id of string type ETag = ETag of string type EntityType = Event | AggregateRoot Use JSON serializer specifics to map to DB fields type PersistentEntity<'Payload> = { [<JsonField("partitionKey")>] PartitionKey: Db.PartitionKey [<JsonField("id")>] Id: Db.Id [<JsonField("etag")>] ETag: Db.ETag option Type: EntityType Payload: 'Payload } Concrete serialized types module VaccinationPersistence = type Event = PersistentEntity<VaccinationEvent> type Aggregate = PersistentEntity<VaccineRecipient>
  • 15. CosmosDB Transactions Persistence Use Batch API to add events + “upsert” the aggregate module CosmosDb = let createBatch (Db.PartitionKey key) (container: Container) = container.CreateTransactionalBatch(PartitionKey(key)) let createAsString (payload: string) (batch: TransactionalBatch) = new MemoryStream(Encoding.UTF8.GetBytes(payload)) |> batch.CreateItemStream let replaceAsString (Db.Id id) (payload: string) (Db.ETag etag) (batch: TransactionalBatch) = let stream = new MemoryStream (Encoding.UTF8.GetBytes(payload)) let options = TransactionalBatchItemRequestOptions() options.IfMatchEtag <- etag batch.ReplaceItemStream(id, stream, options) let executeBatch (batch: TransactionalBatch) = taskResult { let! response = batch.ExecuteAsync() if (response.IsSuccessStatusCode) then return! Ok () else return! Error (response.StatusCode, response.ErrorMessage) } We use tasks for an easier interop with Azure SDKs Optimistic concurrency
  • 16. “Dependency Rejection” https://blog.ploeh.dk/2017/01/ 27/from-dependency- injection-to-dependency- rejection/ “Reinventing the Transaction Script” https://fsharpforfunandprofit. com/transactionscript/ Assembling the Pipeline The Impure - Pure - Impure “sandwich” • Read data • Deserialize DTOs from JSON • Convert DTOs to Domain Models • Generate commands • Run the handler • Convert Results to DTOs • Serialize DTOs to JSON • Persist the output Do not do I/O in pure code, return directives instead module VaccinationExample = type ErrorMessage = string type DomainEvent = Event<VaccinationEvent, EventMetadata> type HandlerResult = | NoChange | InvalidCommand of ErrorMessage | Conflict of ErrorMessage | CreateAggregate of VaccineRecipient * DomainEvent list | UpdateAggregate of VaccineRecipient * DomainEvent list type Handler = VaccineRecipient option -> VaccinationCommand -> HandlerResult
  • 17. Using CosmosDB to build read models Change Feed You can use either: • Azure Functions CosmosDBTrigger (simpler) • or ChangeFeed Processor running in AppServices (more control) You will need to provision: • A separate Lease collection in CosmosDB • The target storage (DB, blobs, SQL DB, etc) Leases ensure events will be processed in sync, but... Events might come in several partitions at once! let groups = docs |> Seq.groupBy (fun d -> d.GetPropertyValue<string> "partitionKey") for (pk, group) in groups do // process by partitions You have to handle errors (make your own DLQ, etc) You can reprocess events from the beginning!