Last year:
TechDays 2016
• Event-Driven Applications in F#
• Programming Quantum Computers in F# using
LIQUi|>
• Functional-first programming
language
• Perceived as being only for
math/science/fintech
• Has its own vocabulary and
design patterns
• Take a boring business domain
• Use familiar approaches
• Use F# constructs to solve known problems
• Fit nicely in Azure
• Show the benefits
Our Business
Domain:
Issue Tracker
• Types
• Functions
• Pipelines
Scenario 1:
Create an Issue
// Our first F# type
type IssueCreatedEvent = {
Id: string
Title: string
Description: string
}
type IssueId = Id of string
type IssueCreatedEvent = {
Id: IssueId
Title: string
Description: string
}
type CreateIssueCommand = {
Title: string
Description: string
}
let createIssue (command: CreateIssueCommand) = {
Id = generateId command
Title = command.Title
Description = command.Description
}
// createIssue: CreateIssueCommand -> IssueCreatedEvent
module Domain =
type IssueId = Id of string
type IssueCreatedEvent = {
Id: IssueId
Title: string
Description: string
}
type CreateIssueCommand = {
Title: string
Description: string
}
let createIssue (command: CreateIssueCommand) = {
Id = generateId command
Title = command.Title
Description = command.Description
}
[<DataContract>]
type NewIssueDTO = {
[<DataMember>] title: string
[<DataMember>] description: string
}
[<DataContract>]
type IssueEventLogDTO = {
[<DataMember>] id: string
[<DataMember>] created: NewIssueDTO
}
module SerDe =
let parseNewCommand (command: NewIssueDTO) = {
Title = command.title
Description = command.description
}
let initializeHistory (event: IssueCreatedEvent) =
let (Id idString) = event.Id
{ id = idString
created = { title = event.Title
description = event.Description }
}
module API =
let create (dto: NewIssueDTO) =
let command = SerDe.parseNewCommand dto
let event = Domain.createIssue command
let eventLogDTO = SerDe.initializeHistory event
eventLogDTO
module API =
let create =
SerDe.parseNewCommand
>> Domain.createIssue
>> SerDe.initializeHistory
type public IssuesApp() =
[<FunctionName("CreateIssue")>]
static member CreateIssue (
[<HttpTrigger("post")>]
request : NewIssueDTO,
[<DocumentDB("IssuesDB", "Issues")>]
documents: ICollector<IssueEventLogDTO>) =
let eventLogDTO = API.create request
documents.Add eventLogDTO
eventLogDTO.id
• 5 types
• 3 functions
• 1 pipeline
• 0 explicit I/O operations
Scenario 2:
Change an Issue
type User = User of string
type IssueAssignedToUserEvent = {
User: User
}
type IssueCommentedEvent = {
User: User
Comment: string
}
//type IssueClosedEvent = { }
//type IssueReopenedEvent = { }
type AssignIssueCommand = {
User: User
}
let assignIssue (command: AssignIssueCommand) = {
User = command.User
}
You shouldn’t assign an issue if it’s Done
type IssueStatus =
| New
| Done
type Result<TSuccess, TError> =
| Ok of TSuccess
| Error of TError
// Function type is:
// IssueStatus -> AssignIssueCommand -> Result<IssueAssignedEvent,string>
let assignIssue (status: IssueStatus) (command: AssignIssueCommand) =
match status with
| New -> Ok { User = command.User }
| Done -> Error "Can't assign closed issue"
let closeIssue (status: IssueStatus) command =
match status with
| New -> Ok ()
| Done -> Error "Issue is already closed"
let reopenIssue (status: IssueStatus) command =
match status with
| Done -> Ok ()
| New -> Error "Can't reopen open issue"
type ChangeIssueCommand =
| Assign of AssignIssueCommand
| AddComment of AddCommentCommand
| Close
| Reopen
type IssueChangedEvent =
| Assigned of IssueAssignedToUserEvent
| Commented of IssueCommentedEvent
| Closed
| Reopened
module Domain =
// let createIssue ...
let changeIssue (command: ChangeIssueCommand) status =
match command with
| Assign a -> assignIssue status a
| Comment c -> commentIssue status c
| Close -> closeIssue status
| Reopen -> reopenIssue status
type IssueEvents = {
Created: IssueCreatedEvent
Changes: IssueChangedEvent list
}
let apply status event =
match event with
| Closed -> Done
| Reopened -> New
| _ -> status
let replay (events: IssueEvents): IssueStatus =
List.fold apply New events.Changes
Full Flow of Change
module Domain =
// let createIssue ...
let changeIssue (command: Command) (events: IssueEvents) =
let status = replay events
match command with
| Assign a -> assignIssue status a
| Comment c -> commentIssue status c
| Close -> closeIssue status
| Reopen -> reopenIssue status
Service & Deployment Diagram
module API =
// let create = …
let change (req: IssueCommandDTO) (log: IssueEventLogDTO) =
let command = SerDe.parseCommand req
let eventLog = SerDe.parseHistory log
let result = Domain.change command eventLog
append log result
httpResponse result
let httpResponse result =
match result with
| Ok _ ->
new HttpResponseMessage(HttpStatusCode.OK)
| Error e ->
new HttpResponseMessage(
HttpStatusCode.BadRequest, Content = new StringContent(e))
// httpResponse: Result<T, string> -> HttpResponseMessage
type public IssuesApp() =
[<FunctionName("ChangeIssue")>]
static member ChangeIssue (
[<HttpTrigger("put")>]
request : IssueCommandDTO,
[<DocumentDB("IssuesDB", "Issues", Id = "{id}")>]
state: IssueEventLogDTO) =
API.change request state
• Concise types
• Choice types (discriminated union)
• Immutability by default
• Explicit concepts
• No nulls, no exceptions
• Make invalid state unrepresentable
• Type as documentation
• Types as guard
• Pure Functions
• Determinism
• Totality
• Testability
• Composability
• Functional Architecture
• No circular dependencies
• 10 years of open source
• Community contributions to Compiler, Tooling,
Libraries, Documentation
http://fsharp.org will get you started
Event Driven Applications in F#

Event Driven Applications in F#

  • 2.
  • 4.
    • Event-Driven Applicationsin F# • Programming Quantum Computers in F# using LIQUi|>
  • 5.
    • Functional-first programming language •Perceived as being only for math/science/fintech • Has its own vocabulary and design patterns
  • 6.
    • Take aboring business domain • Use familiar approaches • Use F# constructs to solve known problems • Fit nicely in Azure • Show the benefits
  • 7.
  • 13.
  • 14.
  • 15.
    // Our firstF# type type IssueCreatedEvent = { Id: string Title: string Description: string }
  • 16.
    type IssueId =Id of string type IssueCreatedEvent = { Id: IssueId Title: string Description: string }
  • 18.
    type CreateIssueCommand ={ Title: string Description: string }
  • 19.
    let createIssue (command:CreateIssueCommand) = { Id = generateId command Title = command.Title Description = command.Description } // createIssue: CreateIssueCommand -> IssueCreatedEvent
  • 20.
    module Domain = typeIssueId = Id of string type IssueCreatedEvent = { Id: IssueId Title: string Description: string } type CreateIssueCommand = { Title: string Description: string } let createIssue (command: CreateIssueCommand) = { Id = generateId command Title = command.Title Description = command.Description }
  • 24.
    [<DataContract>] type NewIssueDTO ={ [<DataMember>] title: string [<DataMember>] description: string } [<DataContract>] type IssueEventLogDTO = { [<DataMember>] id: string [<DataMember>] created: NewIssueDTO }
  • 25.
    module SerDe = letparseNewCommand (command: NewIssueDTO) = { Title = command.title Description = command.description } let initializeHistory (event: IssueCreatedEvent) = let (Id idString) = event.Id { id = idString created = { title = event.Title description = event.Description } }
  • 26.
    module API = letcreate (dto: NewIssueDTO) = let command = SerDe.parseNewCommand dto let event = Domain.createIssue command let eventLogDTO = SerDe.initializeHistory event eventLogDTO
  • 27.
    module API = letcreate = SerDe.parseNewCommand >> Domain.createIssue >> SerDe.initializeHistory
  • 30.
    type public IssuesApp()= [<FunctionName("CreateIssue")>] static member CreateIssue ( [<HttpTrigger("post")>] request : NewIssueDTO, [<DocumentDB("IssuesDB", "Issues")>] documents: ICollector<IssueEventLogDTO>) = let eventLogDTO = API.create request documents.Add eventLogDTO eventLogDTO.id
  • 31.
    • 5 types •3 functions • 1 pipeline • 0 explicit I/O operations
  • 32.
  • 34.
    type User =User of string type IssueAssignedToUserEvent = { User: User } type IssueCommentedEvent = { User: User Comment: string } //type IssueClosedEvent = { } //type IssueReopenedEvent = { }
  • 35.
    type AssignIssueCommand ={ User: User } let assignIssue (command: AssignIssueCommand) = { User = command.User }
  • 36.
    You shouldn’t assignan issue if it’s Done
  • 37.
  • 39.
    type Result<TSuccess, TError>= | Ok of TSuccess | Error of TError
  • 40.
    // Function typeis: // IssueStatus -> AssignIssueCommand -> Result<IssueAssignedEvent,string> let assignIssue (status: IssueStatus) (command: AssignIssueCommand) = match status with | New -> Ok { User = command.User } | Done -> Error "Can't assign closed issue"
  • 41.
    let closeIssue (status:IssueStatus) command = match status with | New -> Ok () | Done -> Error "Issue is already closed" let reopenIssue (status: IssueStatus) command = match status with | Done -> Ok () | New -> Error "Can't reopen open issue"
  • 42.
    type ChangeIssueCommand = |Assign of AssignIssueCommand | AddComment of AddCommentCommand | Close | Reopen type IssueChangedEvent = | Assigned of IssueAssignedToUserEvent | Commented of IssueCommentedEvent | Closed | Reopened
  • 43.
    module Domain = //let createIssue ... let changeIssue (command: ChangeIssueCommand) status = match command with | Assign a -> assignIssue status a | Comment c -> commentIssue status c | Close -> closeIssue status | Reopen -> reopenIssue status
  • 45.
    type IssueEvents ={ Created: IssueCreatedEvent Changes: IssueChangedEvent list }
  • 46.
    let apply statusevent = match event with | Closed -> Done | Reopened -> New | _ -> status let replay (events: IssueEvents): IssueStatus = List.fold apply New events.Changes
  • 47.
  • 48.
    module Domain = //let createIssue ... let changeIssue (command: Command) (events: IssueEvents) = let status = replay events match command with | Assign a -> assignIssue status a | Comment c -> commentIssue status c | Close -> closeIssue status | Reopen -> reopenIssue status
  • 50.
  • 51.
    module API = //let create = … let change (req: IssueCommandDTO) (log: IssueEventLogDTO) = let command = SerDe.parseCommand req let eventLog = SerDe.parseHistory log let result = Domain.change command eventLog append log result httpResponse result
  • 52.
    let httpResponse result= match result with | Ok _ -> new HttpResponseMessage(HttpStatusCode.OK) | Error e -> new HttpResponseMessage( HttpStatusCode.BadRequest, Content = new StringContent(e)) // httpResponse: Result<T, string> -> HttpResponseMessage
  • 53.
    type public IssuesApp()= [<FunctionName("ChangeIssue")>] static member ChangeIssue ( [<HttpTrigger("put")>] request : IssueCommandDTO, [<DocumentDB("IssuesDB", "Issues", Id = "{id}")>] state: IssueEventLogDTO) = API.change request state
  • 55.
    • Concise types •Choice types (discriminated union) • Immutability by default • Explicit concepts • No nulls, no exceptions • Make invalid state unrepresentable • Type as documentation • Types as guard
  • 56.
    • Pure Functions •Determinism • Totality • Testability • Composability
  • 57.
    • Functional Architecture •No circular dependencies
  • 58.
    • 10 yearsof open source • Community contributions to Compiler, Tooling, Libraries, Documentation http://fsharp.org will get you started

Editor's Notes

  • #7 Build a bridge from usual to unusual, Broaden toolbox
  • #8 I said I want it to be boring! Developers are domain experts.