Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
@theburningmonk
tame
|> (cloud <| complexity)
|> with
|> (fsharp >> powered >> DSLs)
Principal Engineer
image by nerovivo license : https://creativecommons.org/licenses/by-sa/2.0/
@theburningmonk
Under-Abstraction
@theburningmonk
Oversimplification
@theburningmonk
Impedance Mismatch
@theburningmonk
Amazon
DynamoDB
Amazon
SimpleWorkflow
Amazon
CloudWatch
CASE STUDY
@theburningmonk
F# DSLs. Awesome!
@theburningmonk
Amazon
DynamoDB
Amazon
SimpleW
Amazon
CloudWatch
CASE STUDY
@theburningmonk
@theburningmonk
managed
key-value store
@theburningmonk
redundancy
9-9s guarantee
@theburningmonk
great performance
@theburningmonk
name your
throughput
@theburningmonk
@theburningmonk
can be changed on-the-fly
@theburningmonk
@theburningmonk
infinitely scalable
(but you still have to pay for it)
@theburningmonk
Hash Key
Range Key
@theburningmonk
Query:
given a hash key
filter on
range key, or
local secondary index
@theburningmonk
Hash Key Range Key
Local Secondary Index
Global Secondary Index
@theburningmonk
Scan:
FULL TABLE search
(performance + cost concern)
@theburningmonk
Hash Key Range Key
Local Secondary Index
Global Secondary Index
Hash Key Range Key
Local Secondary Index
Who are the TOP 3 players in “Starship
X” with a score of at least 1000?
Global S...
@theburningmonk
@theburningmonk
select GameTitle, UserId, TopScore
from GameScores
where GameTitle = “Starship X”
and TopScore >= 1000
ord...
DynamoDB.SQL
github.com/fsprojects/DynamoDb.SQL
@theburningmonk
GOAL
Disguise
complexity
@theburningmonk
GOAL
Prevent
abstraction leak
@theburningmonk
GOAL
SELECT UserId, TopScore
FROM GameScore
WHERE GameTitle CONTAINS “Zelda”
ORDER DESC
LIMIT 3
WITH (NoCo...
@theburningmonk
Query
AST
Execution
F# & FParsec*
*www.quanttec.com/fparsec
External DSL
via
@theburningmonk
@theburningmonk
SELECT * FROM GameScore
Abstract Syntax Tree (AST)
FParsec
@theburningmonk
SELECT * FROM GameScore
keyword keyword
* | attribute, attribute, …
table name
@theburningmonk
SELECT * FROM GameScore
type Attributes =
| Asterisk
| Attributes of string[]
@theburningmonk
SELECT * FROM GameScore
type Query =
{
Attributes : Attributes
Table : string
}
@theburningmonk
SELECT * FROM GameScore
Parser for “SELECT” keyword
pSelect
@theburningmonk
SELECT * FROM GameScore
pSelect
let pSelect = skipStringCI "select"
@theburningmonk
SELECT * FROM GameScore
pSelect
let pSelect = skipStringCI "select"
matches the string “select” (Case
Inse...
@theburningmonk
SELECT * FROM GameScore
pFrom
let pFrom = skipStringCI "from"
@theburningmonk
SELECT * FROM GameScore
Parser for a string that
represents the table name
pTableName
@theburningmonk
SELECT * FROM GameScore
pTableName
let isTableName = isLetter <||> isDigit
let pTableName =
many1Satisfy i...
@theburningmonk
SELECT * FROM GameScore
pTableName
let isTableName = isLetter <||> isDigit
let pTableName =
many1Satisfy i...
@theburningmonk
SELECT * FROM GameScore
pTableName
let isTableName = isLetter <||> isDigit
let pTableName =
many1Satisfy i...
@theburningmonk
SELECT * FROM GameScore
pTableName
let isTableName = isLetter <||> isDigit
let pTableName =
many1Satisfy i...
@theburningmonk
SELECT * FROM GameScore
pTableName
let isTableName = isLetter <||> isDigit
let pTableName =
many1Satisfy i...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeName
UserId, GameTitle, TopScore, …
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeName
UserId, GameTitle, TopScore, …
let pAsterisk = stringCI...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeName
UserId, GameTitle, TopScore, …
let pAsterisk = stringCI...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeName
UserId, GameTitle, TopScore, …
let isAttributeName = is...
@theburningmonk
SELECT * FROM GameScore
UserId, GameTitle, TopScore, …
pAttributeName pCommapAsterisk
*
let pComma = skipS...
@theburningmonk
SELECT * FROM GameScore
UserId, GameTitle, TopScore, …
pAttributeName pCommapAsterisk
*
let pAttributeName...
@theburningmonk
SELECT * FROM GameScore
UserId, GameTitle, TopScore, …
pAttributeName pCommapAsterisk
*
let pAttributeName...
@theburningmonk
SELECT * FROM GameScore
UserId, GameTitle, TopScore, …
pAttributeName pComma
sepBy1
pAttributeNames
pAster...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeNames
UserId, GameTitle, TopScore, …
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeNames
UserId, GameTitle, TopScore, …
let pAttribute =
pAster...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeNames
UserId, GameTitle, TopScore, …
let pAttribute =
pAster...
@theburningmonk
SELECT * FROM GameScore
pAsterisk
*
pAttributeNames
UserId, GameTitle, TopScore, …
choice
pAttribute
@theburningmonk
SELECT * FROM GameScore
pAttribute
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
let pQuery =
tuple4 pSelect pAttribute
pFrom pTa...
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
let pQuery =
tuple4 pSelect pAttribute
pFrom pTa...
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
let pQuery =
tuple4 pSelect pAttribute
pFrom pTa...
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
let pQuery =
tuple4 pSelect pAttribute
pFrom pTa...
@theburningmonk
SELECT * FROM GameScore
pAttribute pTableNamepFrompSelect
tuple4
pQuery
@theburningmonk
@theburningmonk
< 50 lines of code
@theburningmonk
Amazing
F# + FParsec
=
@theburningmonk
Recap
@theburningmonk
@theburningmonk
select GameTitle, UserId, TopScore
from GameScores
where GameTitle = “Starship X”
and TopScore >= 1000
ord...
@theburningmonk
Amazon
DynamoDB
Amazon
SimpleWorkflow
Amazon
CloudWatch
CASE STUDY
@theburningmonk
@theburningmonk
Decision Worker
@theburningmonk
Decision Worker
Poll
@theburningmonk
Decision Worker
Decision
Task
@theburningmonk
Decision WorkerDecide
@theburningmonk
Activity Worker
Decision Worker
@theburningmonk
Activity Worker
Decision Worker
Poll
@theburningmonk
Activity Worker
Decision Worker
Activity
Task
@theburningmonk
Activity Worker
Decision Worker
Complete
@theburningmonk
Workers can run from
anywhere
@theburningmonk
@theburningmonk
input = “Yan”
result = “Hello Yan!”
Start
Finish
Activity
@theburningmonk
image by Ryan Hageman license : https://creativecommons.org/licenses/by-sa/2.0/
SWF-based Application
API
Heartbeats
Error
Handling
Polling
API
Activity
Worker
Decision
Worker
Heartbeats
Error
Handling
Polling
API
Activity
Worker
Decision
Worker
Heartbeats
Error
Handling
Polling
API
Boilerplate
– Kris Jordan
“Good simplicity is less with
leverage, not less with less.
Good simplicity is complexity
disguised, not com...
http://bit.ly/1pOLeKl
Start
Finish
Activity
?
@theburningmonk
the workflow is
implied by decision
worker logic…
@theburningmonk
instead..
@theburningmonk
the workflow should
drive decision
worker logic
@theburningmonk
the workflow should
driveautomate
decision worker logic
Amazon
.SimpleWorkflow
.Extensions
github.com/fsprojects/Amazon.SimpleWorkflow.Extensions
@theburningmonk
GOAL
Remove
boilerplates
@theburningmonk
GOAL
Code that matches
the way you think
@theburningmonk
Start
Finish
Activity
Start
Finish
Activity
@theburningmonk
Workflows can be
nested
input
result
@theburningmonk
Recap
Activity
Worker
Decision
Worker
Heartbeats
Error
Handling
Polling
API
@theburningmonk
Amazon
DynamoDB
Amazon
SimpleW
Amazon
CloudWatch
CASE STUDY
@theburningmonk
@theburningmonk
@theburningmonk
@theburningmonk
wanna find
correlations?
@theburningmonk
wanna find
correlations?
you can DIY it!
;-)
@theburningmonk
“what latencies spiked
at the same time as
payment service?”
Amazon
.CloudWatch
.Selector
github.com/fsprojects/Amazon.CloudWatch.Selector
@theburningmonk
Find metrics whose 5
min average exceeded
1 second during last
12 hours
@theburningmonk
cloudWatch.Select(
unitIs “milliseconds” +
average (>) 1000.0
@ last 12 hours
|> intervalOf 5 minutes)
@theburningmonk
cloudWatch.Select(“
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hours
at intervalOf 5 minutes...
@theburningmonk
“did any cache
nodes’ CPU spike
yesterday?”
@theburningmonk
cloudWatch.Select(
namespaceLike “elasticache” +
nameLike “cpu” +
max (>) 80.0
@ last 24 hours
|> interval...
@theburningmonk
cloudWatch.Select(
namespaceLike “elasticache” +
nameLike “cpu” +
max (>) 80.0
@ last 24 hours
|> interval...
@theburningmonk
@theburningmonk
@theburningmonk
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type Query =
{
Filter : Filter
TimeFrame : TimeFrame
Period : Period option
}
@theburningmonk
Query
Internal
DSL
External
DSL
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type MetricTerm = Namespace | Name
type Filter =
| MetricFilter of MetricTerm * (string -> bool)
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type MetricTerm = Namespace | Name
type Unit = | Unit
type Filter =
| MetricFilter of MetricTerm * (string...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type MetricTerm = Namespace | Name
type Unit = | Unit
type StatsTerm =
| Average | Min | Max | Sum | Sampl...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type MetricTerm = Namespace | Name
type Unit = | Unit
type StatsTerm =
| Average | Min | Max | Sum | Sampl...
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type TimeFrame =
| Last of TimeSpan
| Since of DateTime
| Between of DateTime * DateTime
@theburningmonk
namespaceIs ‘JustEat’ and
nameLike ‘cpu’ and
unitIs ‘milliseconds’ and
average > 1000.0
duringLast 12 hour...
@theburningmonk
type Period = | Period of TimeSpan
@theburningmonk
Active Patterns
a primer on
@theburningmonk
allow patterns to be
abstracted away into
named functions
@theburningmonk
Single-Case Patterns
@theburningmonk
let (|Float|) input =
match Double.TryParse input with
| true, n -> n
| _ ->
failwithf “not a float [%s]” ...
@theburningmonk
let (|Float|) input =
match Double.TryParse input with
| true, n -> n
| _ ->
failwithf “not a float [%s]” ...
@theburningmonk
let (|Float|) input =
match Double.TryParse input with
| true, n -> n
| _ ->
failwithf “not a float [%s]” ...
@theburningmonk
match someString with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
@theburningmonk
let (|Float|) input =
match Double.TryParse input with
| true, n -> n
match someString with
| Float 42.0 -...
@theburningmonk
let (|Float|) input =
match Double.TryParse input with
| true, n -> n
match someString with
| Float 42.0 -...
@theburningmonk
match someString with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
le...
@theburningmonk
match someString with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
le...
@theburningmonk
match “42” with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
@theburningmonk
match “boo” with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
Error!!!
@theburningmonk
Partial Patterns
@theburningmonk
let (|Float|_|) input =
match Double.TryParse input with
| true, n -> Some n
| _ -> None
@theburningmonk
let (|Float|_|) input =
match Double.TryParse input with
| true, n -> Some n
| _ -> None
Float : string ->...
@theburningmonk
match “boo” with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
| _ -> ...
@theburningmonk
match “boo” with
| Float 42.0 -> “ftw”
| Float 11.0 -> “palprime”
| Float x -> sprintf “just %f” x
| _ -> ...
@theburningmonk
Multi-Case Patterns
@theburningmonk
let (|Prime|NotPrime|NaN|) input =
match Double.TryParse input with
| true, n when isPrime n -> Prime n
| ...
@theburningmonk
let (|Prime|NotPrime|NaN|) input =
match Double.TryParse input with
| true, n when isPrime n -> Prime n
| ...
@theburningmonk
let (|Prime|NotPrime|NaN|) input =
match Double.TryParse input with
| true, n when isPrime n -> Prime n
| ...
@theburningmonk
let (|Prime|NotPrime|NaN|) input =
match Double.TryParse input with
| true, n when isPrime n -> Prime n
| ...
@theburningmonk
let (|Prime|NotPrime|NaN|) input =
match Double.TryParse input with
| true, n when isPrime n -> Prime n
| ...
@theburningmonk
match someString with
| Prime n -> …
| NotPrime n -> …
| NaN -> …
@theburningmonk
match someString with
| Prime n & Float 11.0 -> …
| Prime n -> …
| Float 42.0 | Float 8.0 -> …
| NotPrime ...
@theburningmonk
match someString with
| Prime n & Float 11.0 -> …
| Prime n -> …
| Float 42.0 | Float 8.0 -> …
| NotPrime ...
@theburningmonk
match someString with
| Prime (IsPalindrome n) -> …
| Prime (IsEven n) -> …
| _ -> …
@theburningmonk
Tokenise
(string -> string list)
@theburningmonk
[
“namespaceIs”; “‘JustEat’”; “and”;
“nameLike”; “‘cpu’”; “and”;
…
]
@theburningmonk
Visual Studio time…
@theburningmonk
Parse Filter
(string list -> Filter * string list)
@theburningmonk
let rec loop acc = function
| NamespaceIs (filter, tl)
| NamespaceLike (filter, tl)
| NameIs (filter, tl) | N...
@theburningmonk
let (|NamespaceIs|_|) = function
| StringCI "NamespaceIs"::QuotedString ns::tl
-> (eqFilter MetricFilter N...
@theburningmonk
Parse
(string list -> Query)
@theburningmonk
let parse (input : string) =
input
|> tokenize
|> parseFilter
|> parseTimeFrame
|> parsePeriod
@theburningmonk
Amazing
F#
=
@theburningmonk
usable from anywhere you
can run F# code
e.g. F# REPL, executable, ..
Internal DSL
@theburningmonk
useful for building tools
e.g. CLI, …
External DSL
@theburningmonk
@theburningmonk
@theburningmonk
@theburningmonk
Recap
@theburningmonk
@theburningmonk
@theburningmonk
Amazon
DynamoDB
Amazon
SimpleWorkflow
Amazon
CloudWatch
CASE STUDY
@theburningmonk
theburningmonk.com
github.com/theburningmonk
is hiring :-)
http://tech.just-eat.com/jobs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Tame cloud complexity with F#-powered DSLs
Upcoming SlideShare
Loading in …5
×

Tame cloud complexity with F#-powered DSLs

18,108 views

Published on

In this talk, I discussed the different forms of complexities that can arise when integrating with APIs, and how DSLs can be used to tackle these complexities. I demonstrated that F# can be a very effective tool for creating both internal and external DSLs using both FParsec and active patterns.

Published in: Technology
  • Be the first to comment

Tame cloud complexity with F#-powered DSLs

  1. 1. @theburningmonk tame |> (cloud <| complexity) |> with |> (fsharp >> powered >> DSLs)
  2. 2. Principal Engineer
  3. 3. image by nerovivo license : https://creativecommons.org/licenses/by-sa/2.0/
  4. 4. @theburningmonk Under-Abstraction
  5. 5. @theburningmonk Oversimplification
  6. 6. @theburningmonk Impedance Mismatch
  7. 7. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  8. 8. @theburningmonk F# DSLs. Awesome!
  9. 9. @theburningmonk Amazon DynamoDB Amazon SimpleW Amazon CloudWatch CASE STUDY
  10. 10. @theburningmonk
  11. 11. @theburningmonk managed key-value store
  12. 12. @theburningmonk redundancy 9-9s guarantee
  13. 13. @theburningmonk great performance
  14. 14. @theburningmonk name your throughput
  15. 15. @theburningmonk
  16. 16. @theburningmonk can be changed on-the-fly
  17. 17. @theburningmonk
  18. 18. @theburningmonk infinitely scalable (but you still have to pay for it)
  19. 19. @theburningmonk
  20. 20. Hash Key Range Key
  21. 21. @theburningmonk Query: given a hash key filter on range key, or local secondary index
  22. 22. @theburningmonk
  23. 23. Hash Key Range Key Local Secondary Index Global Secondary Index
  24. 24. @theburningmonk Scan: FULL TABLE search (performance + cost concern)
  25. 25. @theburningmonk
  26. 26. Hash Key Range Key Local Secondary Index Global Secondary Index
  27. 27. Hash Key Range Key Local Secondary Index Who are the TOP 3 players in “Starship X” with a score of at least 1000? Global Secondary Index
  28. 28. @theburningmonk
  29. 29. @theburningmonk select GameTitle, UserId, TopScore from GameScores where GameTitle = “Starship X” and TopScore >= 1000 order desc limit 3 with (NoConsistentRead, Index(GameTitleIndex, true))
  30. 30. DynamoDB.SQL github.com/fsprojects/DynamoDb.SQL
  31. 31. @theburningmonk GOAL Disguise complexity
  32. 32. @theburningmonk GOAL Prevent abstraction leak
  33. 33. @theburningmonk GOAL SELECT UserId, TopScore FROM GameScore WHERE GameTitle CONTAINS “Zelda” ORDER DESC LIMIT 3 WITH (NoConsistentRead)
  34. 34. @theburningmonk Query AST Execution
  35. 35. F# & FParsec* *www.quanttec.com/fparsec External DSL via
  36. 36. @theburningmonk
  37. 37. @theburningmonk SELECT * FROM GameScore Abstract Syntax Tree (AST) FParsec
  38. 38. @theburningmonk SELECT * FROM GameScore keyword keyword * | attribute, attribute, … table name
  39. 39. @theburningmonk SELECT * FROM GameScore type Attributes = | Asterisk | Attributes of string[]
  40. 40. @theburningmonk SELECT * FROM GameScore type Query = { Attributes : Attributes Table : string }
  41. 41. @theburningmonk SELECT * FROM GameScore Parser for “SELECT” keyword pSelect
  42. 42. @theburningmonk SELECT * FROM GameScore pSelect let pSelect = skipStringCI "select"
  43. 43. @theburningmonk SELECT * FROM GameScore pSelect let pSelect = skipStringCI "select" matches the string “select” (Case Insensitive) and ignores it
  44. 44. @theburningmonk SELECT * FROM GameScore pFrom let pFrom = skipStringCI "from"
  45. 45. @theburningmonk SELECT * FROM GameScore Parser for a string that represents the table name pTableName
  46. 46. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  47. 47. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  48. 48. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  49. 49. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName
  50. 50. @theburningmonk SELECT * FROM GameScore pTableName let isTableName = isLetter <||> isDigit let pTableName = many1Satisfy isTableName parses a sequence of one or more chars that satisfies the predicate function
  51. 51. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, …
  52. 52. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let pAsterisk = stringCIReturn "*" Asterisk
  53. 53. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let pAsterisk = stringCIReturn "*" Asterisk matches the specified string and return the given value
  54. 54. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeName UserId, GameTitle, TopScore, … let isAttributeName = isLetter <||> isDigit let pAttributeName = many1Satisfy isAttributeName
  55. 55. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pComma = skipStringCI ","
  56. 56. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pAttributeNames = sepBy1 pAttributeName pComma
  57. 57. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pCommapAsterisk * let pAttributeNames = sepBy1 pAttributeName pComma parses one or more occurrences of pAttributeName separated by pComma
  58. 58. @theburningmonk SELECT * FROM GameScore UserId, GameTitle, TopScore, … pAttributeName pComma sepBy1 pAttributeNames pAsterisk *
  59. 59. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, …
  60. 60. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … let pAttribute = pAsterisk <|> pAttributeNames
  61. 61. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … let pAttribute = pAsterisk <|> pAttributeNames
  62. 62. @theburningmonk SELECT * FROM GameScore pAsterisk * pAttributeNames UserId, GameTitle, TopScore, … choice pAttribute
  63. 63. @theburningmonk SELECT * FROM GameScore pAttribute
  64. 64. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect
  65. 65. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  66. 66. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  67. 67. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table })
  68. 68. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect let pQuery = tuple4 pSelect pAttribute pFrom pTableName |>> (fun (_, attributes, _, table) -> { Attributes = attributes Table = table }) Query
  69. 69. @theburningmonk SELECT * FROM GameScore pAttribute pTableNamepFrompSelect tuple4 pQuery
  70. 70. @theburningmonk
  71. 71. @theburningmonk < 50 lines of code
  72. 72. @theburningmonk Amazing F# + FParsec =
  73. 73. @theburningmonk Recap
  74. 74. @theburningmonk
  75. 75. @theburningmonk select GameTitle, UserId, TopScore from GameScores where GameTitle = “Starship X” and TopScore >= 1000 order desc limit 3 with (NoConsistentRead, Index(GameTitleIndex, true))
  76. 76. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  77. 77. @theburningmonk
  78. 78. @theburningmonk Decision Worker
  79. 79. @theburningmonk Decision Worker Poll
  80. 80. @theburningmonk Decision Worker Decision Task
  81. 81. @theburningmonk Decision WorkerDecide
  82. 82. @theburningmonk Activity Worker Decision Worker
  83. 83. @theburningmonk Activity Worker Decision Worker Poll
  84. 84. @theburningmonk Activity Worker Decision Worker Activity Task
  85. 85. @theburningmonk Activity Worker Decision Worker Complete
  86. 86. @theburningmonk Workers can run from anywhere
  87. 87. @theburningmonk
  88. 88. @theburningmonk input = “Yan” result = “Hello Yan!” Start Finish Activity
  89. 89. @theburningmonk
  90. 90. image by Ryan Hageman license : https://creativecommons.org/licenses/by-sa/2.0/
  91. 91. SWF-based Application
  92. 92. API
  93. 93. Heartbeats Error Handling Polling API
  94. 94. Activity Worker Decision Worker Heartbeats Error Handling Polling API
  95. 95. Activity Worker Decision Worker Heartbeats Error Handling Polling API Boilerplate
  96. 96. – Kris Jordan “Good simplicity is less with leverage, not less with less. Good simplicity is complexity disguised, not complexity denied.”
  97. 97. http://bit.ly/1pOLeKl
  98. 98. Start Finish Activity ?
  99. 99. @theburningmonk the workflow is implied by decision worker logic…
  100. 100. @theburningmonk instead..
  101. 101. @theburningmonk the workflow should drive decision worker logic
  102. 102. @theburningmonk the workflow should driveautomate decision worker logic
  103. 103. Amazon .SimpleWorkflow .Extensions github.com/fsprojects/Amazon.SimpleWorkflow.Extensions
  104. 104. @theburningmonk GOAL Remove boilerplates
  105. 105. @theburningmonk GOAL Code that matches the way you think
  106. 106. @theburningmonk Start Finish Activity
  107. 107. Start Finish Activity
  108. 108. @theburningmonk Workflows can be nested
  109. 109. input result
  110. 110. @theburningmonk Recap
  111. 111. Activity Worker Decision Worker Heartbeats Error Handling Polling API
  112. 112. @theburningmonk Amazon DynamoDB Amazon SimpleW Amazon CloudWatch CASE STUDY
  113. 113. @theburningmonk
  114. 114. @theburningmonk
  115. 115. @theburningmonk
  116. 116. @theburningmonk wanna find correlations?
  117. 117. @theburningmonk wanna find correlations? you can DIY it! ;-)
  118. 118. @theburningmonk “what latencies spiked at the same time as payment service?”
  119. 119. Amazon .CloudWatch .Selector github.com/fsprojects/Amazon.CloudWatch.Selector
  120. 120. @theburningmonk Find metrics whose 5 min average exceeded 1 second during last 12 hours
  121. 121. @theburningmonk cloudWatch.Select( unitIs “milliseconds” + average (>) 1000.0 @ last 12 hours |> intervalOf 5 minutes)
  122. 122. @theburningmonk cloudWatch.Select(“ unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes”)
  123. 123. @theburningmonk “did any cache nodes’ CPU spike yesterday?”
  124. 124. @theburningmonk cloudWatch.Select( namespaceLike “elasticache” + nameLike “cpu” + max (>) 80.0 @ last 24 hours |> intervalOf 15 minutes)
  125. 125. @theburningmonk cloudWatch.Select( namespaceLike “elasticache” + nameLike “cpu” + max (>) 80.0 @ last 24 hours |> intervalOf 15 minutes) Regex
  126. 126. @theburningmonk
  127. 127. @theburningmonk
  128. 128. @theburningmonk
  129. 129. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  130. 130. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes Filters
  131. 131. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes TimeFrame
  132. 132. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes Period
  133. 133. @theburningmonk type Query = { Filter : Filter TimeFrame : TimeFrame Period : Period option }
  134. 134. @theburningmonk Query Internal DSL External DSL
  135. 135. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  136. 136. @theburningmonk type MetricTerm = Namespace | Name type Filter = | MetricFilter of MetricTerm * (string -> bool)
  137. 137. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  138. 138. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool)
  139. 139. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  140. 140. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type StatsTerm = | Average | Min | Max | Sum | SampleCount type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool) | StatsFilter of StatsTerm * (float -> bool)
  141. 141. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  142. 142. @theburningmonk type MetricTerm = Namespace | Name type Unit = | Unit type StatsTerm = | Average | Min | Max | Sum | SampleCount type Filter = | MetricFilter of MetricTerm * (string -> bool) | UnitFilter of Unit * (string -> bool) | StatsFilter of StatsTerm * (float -> bool) | CompositeFilter of Filter * Filter
  143. 143. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  144. 144. @theburningmonk type TimeFrame = | Last of TimeSpan | Since of DateTime | Between of DateTime * DateTime
  145. 145. @theburningmonk namespaceIs ‘JustEat’ and nameLike ‘cpu’ and unitIs ‘milliseconds’ and average > 1000.0 duringLast 12 hours at intervalOf 5 minutes
  146. 146. @theburningmonk type Period = | Period of TimeSpan
  147. 147. @theburningmonk Active Patterns a primer on
  148. 148. @theburningmonk allow patterns to be abstracted away into named functions
  149. 149. @theburningmonk Single-Case Patterns
  150. 150. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input
  151. 151. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input
  152. 152. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n | _ -> failwithf “not a float [%s]” input Float : string -> float
  153. 153. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  154. 154. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  155. 155. @theburningmonk let (|Float|) input = match Double.TryParse input with | true, n -> n match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  156. 156. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x let (|Float|) input = match Double.TryParse input with | true, n -> n
  157. 157. @theburningmonk match someString with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x let (|Float|) input = match Double.TryParse input with | true, n -> n
  158. 158. @theburningmonk match “42” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x
  159. 159. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x Error!!!
  160. 160. @theburningmonk Partial Patterns
  161. 161. @theburningmonk let (|Float|_|) input = match Double.TryParse input with | true, n -> Some n | _ -> None
  162. 162. @theburningmonk let (|Float|_|) input = match Double.TryParse input with | true, n -> Some n | _ -> None Float : string -> float option
  163. 163. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x | _ -> “not a float”
  164. 164. @theburningmonk match “boo” with | Float 42.0 -> “ftw” | Float 11.0 -> “palprime” | Float x -> sprintf “just %f” x | _ -> “not a float”
  165. 165. @theburningmonk Multi-Case Patterns
  166. 166. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  167. 167. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  168. 168. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  169. 169. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  170. 170. @theburningmonk let (|Prime|NotPrime|NaN|) input = match Double.TryParse input with | true, n when isPrime n -> Prime n | true, n -> NotPrime n | _ -> NaN
  171. 171. @theburningmonk match someString with | Prime n -> … | NotPrime n -> … | NaN -> …
  172. 172. @theburningmonk match someString with | Prime n & Float 11.0 -> … | Prime n -> … | Float 42.0 | Float 8.0 -> … | NotPrime n -> … | NaN -> …
  173. 173. @theburningmonk match someString with | Prime n & Float 11.0 -> … | Prime n -> … | Float 42.0 | Float 8.0 -> … | NotPrime n -> … | NaN -> …
  174. 174. @theburningmonk match someString with | Prime (IsPalindrome n) -> … | Prime (IsEven n) -> … | _ -> …
  175. 175. @theburningmonk Tokenise (string -> string list)
  176. 176. @theburningmonk [ “namespaceIs”; “‘JustEat’”; “and”; “nameLike”; “‘cpu’”; “and”; … ]
  177. 177. @theburningmonk Visual Studio time…
  178. 178. @theburningmonk Parse Filter (string list -> Filter * string list)
  179. 179. @theburningmonk let rec loop acc = function | NamespaceIs (filter, tl) | NamespaceLike (filter, tl) | NameIs (filter, tl) | NameLike (filter, tl) | UnitIs (filter, tl) | Average (filter, tl) | Sum (filter, tl) | Min (filter, tl) | Max (filter, tl) | SampleCount (filter, tl) -> match tl with | And tl -> loop (filter::acc) tl | _ -> flatten (filter::acc), tl | _ -> failwith “No filters?!?!?”
  180. 180. @theburningmonk let (|NamespaceIs|_|) = function | StringCI "NamespaceIs"::QuotedString ns::tl -> (eqFilter MetricFilter Namespace ns, tl) |> Some | _ -> None let (|NamespaceLike|_|) = function | StringCI "NamespaceLike"::QuotedString pattern::tl -> (regexFilter MetricFilter Namespace pattern, tl) |> Some | _ -> None
  181. 181. @theburningmonk Parse (string list -> Query)
  182. 182. @theburningmonk let parse (input : string) = input |> tokenize |> parseFilter |> parseTimeFrame |> parsePeriod
  183. 183. @theburningmonk Amazing F# =
  184. 184. @theburningmonk usable from anywhere you can run F# code e.g. F# REPL, executable, .. Internal DSL
  185. 185. @theburningmonk useful for building tools e.g. CLI, … External DSL
  186. 186. @theburningmonk
  187. 187. @theburningmonk
  188. 188. @theburningmonk
  189. 189. @theburningmonk Recap
  190. 190. @theburningmonk
  191. 191. @theburningmonk
  192. 192. @theburningmonk Amazon DynamoDB Amazon SimpleWorkflow Amazon CloudWatch CASE STUDY
  193. 193. @theburningmonk theburningmonk.com github.com/theburningmonk
  194. 194. is hiring :-) http://tech.just-eat.com/jobs

×