Reactive Messaging Patterns with F# and Akka.NET


Message Routing

For more details and full analysis of each pattern described in this section, please refer to Chapter 7 of Reactive Messaging Patterns with the Actor Model by Vaughn Vernon.

Sections

  1. Introduction
  2. Messaging with Actors
  3. Messaging Channels
  4. Message Construction
  5. Message Routing
  6. Message Transformation
  7. Message Endpoints
  8. System Management and Infrastructure

Content-Based Router

This pattern redirects a message to the right actor based on content inspection.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
let inventorySystemA (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "InventorySystemA: handling %A" message
        return! loop ()
    }
    loop ()

let inventorySystemX (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "InventorySystemX: handling %A" message
        return! loop ()
    }
    loop ()

let orderRouter (mailbox: Actor<_>) =
    let inventorySystemA = spawn mailbox.Context "inventorySystemA" inventorySystemA
    let inventorySystemX = spawn mailbox.Context "inventorySystemX" inventorySystemX

    let rec loop () = actor {
        let! orderPlaced = mailbox.Receive ()
        let (OrderPlaced order) = orderPlaced
        match order.OrderType with
        | "TypeABC" -> 
            printfn "OrderRouter: routing %A" orderPlaced
            inventorySystemA <! orderPlaced
        | "TypeXYZ" -> 
            printfn "OrderRouter: routing %A" orderPlaced
            inventorySystemX <! orderPlaced
        | _ -> printfn "OrderRouter: received unexpected message"
        return! loop ()
    }
    loop ()

let orderRouterRef = spawn system "orderRouter" orderRouter

let orderItem1 = { Id = "1"; ItemType = "TypeABC.4"; Description = "An item of type ABC.4."; Price = 29.95m }
let orderItem2 = { Id = "2"; ItemType = "TypeABC.1"; Description = "An item of type ABC.1."; Price = 99.95m }
let orderItem3 = { Id = "3"; ItemType = "TypeABC.9"; Description = "An item of type ABC.9."; Price = 14.95m }
let orderItemsOfTypeA = Map.ofList [(orderItem1.ItemType, orderItem1); (orderItem2.ItemType, orderItem2); (orderItem3.ItemType, orderItem3)]
orderRouterRef <! OrderPlaced({ Id = "123"; OrderType = "TypeABC"; OrderItems = orderItemsOfTypeA })

let orderItem4 = { Id = "4"; ItemType = "TypeXYZ.2"; Description = "An item of type XYZ.2."; Price = 74.95m }
let orderItem5 = { Id = "5"; ItemType = "TypeXYZ.1"; Description = "An item of type XYZ.1."; Price = 59.95m }
let orderItem6 = { Id = "6"; ItemType = "TypeXYZ.7"; Description = "An item of type XYZ.7."; Price = 29.95m }
let orderItem7 = { Id = "7"; ItemType = "TypeXYZ.5"; Description = "An item of type XYZ.5."; Price = 9.95m }
let orderItemsOfTypeX = Map.ofList [(orderItem4.ItemType, orderItem4); (orderItem5.ItemType, orderItem5); (orderItem6.ItemType, orderItem6); (orderItem7.ItemType, orderItem7)]
orderRouterRef <! OrderPlaced({ Id = "124"; OrderType = "TypeXYZ"; OrderItems = orderItemsOfTypeX })
Complete Code

Sections

Message Filter

The Message Filter pattern discards messages that should not be processed by a given actor.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
let inventorySystemA (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "InventorySystemA: handling %A" message
        return! loop ()
    }
    loop ()

let inventorySystemX (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "InventorySystemX: handling %A" message
        return! loop ()
    }
    loop ()

let inventorySystemXMessageFilter (actualInventorySystemX: IActorRef) (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! orderPlaced = mailbox.Receive ()
        let (OrderPlaced order) = orderPlaced
        match order.OrderType with
        | "TypeABC" -> actualInventorySystemX.Forward orderPlaced
        | _ -> printfn "InventorySystemXMessageFilter: filtering: %A" orderPlaced
        return! loop ()
    }
    loop ()

let inventorySystemARef = spawn system "inventorySystemA" inventorySystemA
let actualInventorySystemXRef = spawn system "inventorySystemX" inventorySystemX
let inventorySystemXRef = spawn system "inventorySystemXMessageFilter" <| inventorySystemXMessageFilter actualInventorySystemXRef

let orderItem1 = { Id = "1"; ItemType = "TypeABC.4"; Description = "An item of type ABC.4."; Price = 29.95m }
let orderItem2 = { Id = "2"; ItemType = "TypeABC.1"; Description = "An item of type ABC.1."; Price = 99.95m }
let orderItem3 = { Id = "3"; ItemType = "TypeABC.9"; Description = "An item of type ABC.9."; Price = 14.95m }
let orderItemsOfTypeA = Map.ofList [(orderItem1.ItemType, orderItem1); (orderItem2.ItemType, orderItem2); (orderItem3.ItemType, orderItem3)]
inventorySystemARef <! OrderPlaced({ Id = "123"; OrderType = "TypeABC"; OrderItems = orderItemsOfTypeA })
inventorySystemXRef <! OrderPlaced({ Id = "123"; OrderType = "TypeABC"; OrderItems = orderItemsOfTypeA })

let orderItem4 = { Id = "4"; ItemType = "TypeXYZ.2"; Description = "An item of type XYZ.2."; Price = 74.95m }
let orderItem5 = { Id = "5"; ItemType = "TypeXYZ.1"; Description = "An item of type XYZ.1."; Price = 59.95m }
let orderItem6 = { Id = "6"; ItemType = "TypeXYZ.7"; Description = "An item of type XYZ.7."; Price = 29.95m }
let orderItem7 = { Id = "7"; ItemType = "TypeXYZ.5"; Description = "An item of type XYZ.5."; Price = 9.95m }
let orderItemsOfTypeX = Map.ofList [(orderItem4.ItemType, orderItem4); (orderItem5.ItemType, orderItem5); (orderItem6.ItemType, orderItem6); (orderItem7.ItemType, orderItem7)]
inventorySystemARef <! OrderPlaced({ Id = "124"; OrderType = "TypeXYZ"; OrderItems = orderItemsOfTypeX })
inventorySystemXRef <! OrderPlaced({ Id = "124"; OrderType = "TypeXYZ"; OrderItems = orderItemsOfTypeX })
Complete Code

Sections

Dynamic Router

This pattern uses specific rules to determine how to route messages to different actors.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
90: 
91: 
92: 
93: 
94: 
95: 
96: 
97: 
let typeAInterested interestRouter (mailbox: Actor<TypeAMessage>) =
    interestRouter <! InterestedIn(typeof<TypeAMessage>.Name)

    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "TypeAInterested: received: %A" message
        return! loop ()
    }
    loop ()

let typeBInterested interestRouter (mailbox: Actor<TypeBMessage>) =
    interestRouter <! InterestedIn(typeof<TypeBMessage>.Name)

    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "TypeBInterested: received: %A" message
        return! loop ()
    }
    loop ()

let typeCInterested interestRouter (mailbox: Actor<TypeCMessage>) =
    interestRouter <! InterestedIn(typeof<TypeCMessage>.Name)

    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "TypeCInterested: received: %A" message
        interestRouter <! NoLongerInterestedIn(typeof<TypeCMessage>.Name)
        return! loop ()
    }
    loop ()

let typeCAlsoInterested interestRouter (mailbox: Actor<TypeCMessage>) =
    interestRouter <! InterestedIn(typeof<TypeCMessage>.Name)

    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "TypeCAlsoInterested: received: %A" message
        interestRouter <! NoLongerInterestedIn(typeof<TypeCMessage>.Name)
        return! loop ()
    }
    loop ()

let dunnoInterested (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        printfn "DunnoInterest: received undeliverable message: %A" message
        return! loop ()
    }
    loop ()

let typedMessageInterestRouter dunnoInterested (mailbox: Actor<_>) =
    let rec loop (interestRegistry: Map<string, IActorRef>) (secondaryInterestRegistry: Map<string, IActorRef>) = actor {
        let registerInterest messageType interested =
            interestRegistry
            |> Map.tryFind messageType
            |> Option.fold 
                (fun _ _ -> interestRegistry, Map.add messageType interested secondaryInterestRegistry) 
                (Map.add messageType interested interestRegistry, secondaryInterestRegistry)

        let unregisterInterest messageType wasInterested =
            interestRegistry
            |> Map.tryFind messageType
            |> Option.bind (fun i -> if i = wasInterested then Some (Map.remove messageType interestRegistry, secondaryInterestRegistry) else None)
            |> Option.bind (fun (p,s) -> s |> Map.tryFind messageType |> Option.map(fun i -> Map.add messageType i p,  Map.remove messageType secondaryInterestRegistry))
            |> Option.fold (fun _ i -> i) (interestRegistry, secondaryInterestRegistry)

        let sendFor message =
            let messageType = (message.GetType ()).Name
            interestRegistry
            |> Map.tryFind messageType
            |> Option.fold (fun _ i -> i) dunnoInterested
            |> fun i -> i <! message 

        let! message = mailbox.Receive ()
        match box message with
        | :? Registration as r ->
            match r with
            | InterestedIn messageType -> return! loop <|| registerInterest messageType (mailbox.Sender ())
            | NoLongerInterestedIn messageType -> return! loop <|| unregisterInterest messageType (mailbox.Sender ())
        | message -> 
            sendFor message
            return! loop interestRegistry secondaryInterestRegistry
    }
    loop Map.empty Map.empty

let dunnoInterestedRef = spawn system "dunnoInterested" dunnoInterested
let typedMessageInterestRouterRef = spawn system "typedMessageInterestRouter" <| typedMessageInterestRouter dunnoInterestedRef
let typeAInterestedRef = spawn system "typeAInterest" <| typeAInterested typedMessageInterestRouterRef
let typeBInterestedRef = spawn system "typeBInterest" <| typeBInterested typedMessageInterestRouterRef
let typeCInterestedRef = spawn system "typeCInterest" <| typeCInterested typedMessageInterestRouterRef
let typeCAlsoInterestedRef = spawn system "typeCAlsoInterested" <| typeCAlsoInterested typedMessageInterestRouterRef

typedMessageInterestRouterRef <! TypeAMessage("Message of TypeA.")
typedMessageInterestRouterRef <! TypeBMessage("Message of TypeB.")
typedMessageInterestRouterRef <! TypeCMessage("Message of TypeC.")
typedMessageInterestRouterRef <! TypeCMessage("Another message of TypeC.")
typedMessageInterestRouterRef <! TypeDMessage("Message of TypeD.")
Complete Code

Sections

Recipient List

This pattern routes messages to a list of recipient actors based on their content.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
let mountaineeringSuppliesOrderProcessor (mailbox: Actor<_>) =
    let rec loop interestRegistry = actor {
        let calculateRecipientList (rfq: RequestForQuotation) = 
            interestRegistry
            |> Map.toList
            |> List.filter (fun (_, v) -> 
                let (Money lowTotalRetail) = v.LowTotalRetail
                let (Money highTotalRetail) = v.HighTotalRetail
                rfq.TotalRetailPrice >= lowTotalRetail && rfq.TotalRetailPrice <= highTotalRetail)
            |> List.map (fun (_, v) -> v.QuoteProcessor)

        let dispatchTo rfq (recipientList: IActorRef list) =
            recipientList
            |> List.iter (fun recipient ->
                rfq.RetailItems
                |> List.iter (fun retailItem -> 
                    printfn "OrderProcessor: %s item: %s to: %s" rfq.RfqId retailItem.ItemId <| recipient.Path.ToString ()
                    recipient <! { RfqId = rfq.RfqId; ItemId = retailItem.ItemId; RetailPrice = Money retailItem.RetailPrice; OrderTotalRetailPrice = Money rfq.TotalRetailPrice }))

        let! message = mailbox.Receive ()
        match box message with
        | :? PriceQuoteInterest as interest -> return! loop <| Map.add interest.Path interest interestRegistry 
        | :? PriceQuote as priceQuote -> printfn "OrderProcessor: received: %A" priceQuote
        | :? RequestForQuotation as rfq -> 
            let recipientList = calculateRecipientList rfq
            dispatchTo rfq recipientList
        | message -> printfn "OrderProcessor: unexpected: %A" message
        return! loop interestRegistry 
    }
    loop Map.empty

let budgetHikersPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 1.00m; HighTotalRetail = Money 1000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.02m
        elif (orderTotalRetailPrice <= 399.99m) then 0.03m
        elif (orderTotalRetailPrice <= 499.99m) then 0.05m
        elif (orderTotalRetailPrice <= 799.99m) then 0.07m
        else 0.075m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let highSierraPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 100.00m; HighTotalRetail = Money 10000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 150.00m) then 0.015m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let mountainAscentPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 70.00m; HighTotalRetail = Money 5000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 99.99m) then 0.01m
        elif (orderTotalRetailPrice <= 199.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.0475m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let pinnacleGearPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 250.00m; HighTotalRetail = Money 500000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 299.99m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.0175m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 1199.99m) then 0.035m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        elif (orderTotalRetailPrice <= 7999.99m) then 0.05m
        else 0.06m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let rockBottomOuterwearPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 0.50m; HighTotalRetail = Money 7500.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.06m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.07m
        elif (orderTotalRetailPrice <= 5999.99m) then 0.075m
        else 0.08m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let orderProcessorRef = spawn system "orderProcessor" mountaineeringSuppliesOrderProcessor
let budgetHikersRef = spawn system "budgetHikersPriceQuotes" <| budgetHikersPriceQuotes orderProcessorRef
let highSierraRef = spawn system "highSierra" <| highSierraPriceQuotes orderProcessorRef
let mountainAscentRef = spawn system "mountainAscent" <| mountainAscentPriceQuotes orderProcessorRef
let pinnacleGearRef = spawn system "pinnacleGear" <| pinnacleGearPriceQuotes orderProcessorRef
let rockBottomOuterwearRef = spawn system "rockBottomOuterwear" <| rockBottomOuterwearPriceQuotes orderProcessorRef

orderProcessorRef <! { RfqId = "123"; RetailItems = [ { ItemId = "1"; RetailPrice = 29.95m }; { ItemId = "2"; RetailPrice = 99.95m }; { ItemId = "3"; RetailPrice = 14.95m } ] }
orderProcessorRef <! { RfqId = "125"; RetailItems = [ { ItemId = "4"; RetailPrice = 39.99m }; { ItemId = "5"; RetailPrice = 199.95m }; { ItemId = "6"; RetailPrice = 149.95m }; { ItemId = "7"; RetailPrice = 724.99m } ] }
orderProcessorRef <! { RfqId = "129"; RetailItems = [ { ItemId = "8"; RetailPrice = 119.99m }; { ItemId = "9"; RetailPrice = 499.95m }; { ItemId = "10"; RetailPrice = 519.00m }; { ItemId = "11"; RetailPrice = 209.50m } ] }
orderProcessorRef <! { RfqId = "135"; RetailItems = [ { ItemId = "12"; RetailPrice = 0.97m }; { ItemId = "13"; RetailPrice = 9.50m }; { ItemId = "14"; RetailPrice = 1.99m } ] }
orderProcessorRef <! { RfqId = "140"; RetailItems = [ { ItemId = "15"; RetailPrice = 107.50m }; { ItemId = "16"; RetailPrice = 9.50m }; { ItemId = "17"; RetailPrice = 599.99m }; { ItemId = "18"; RetailPrice = 249.95m }; { ItemId = "19"; RetailPrice = 789.99m } ] }
Complete Code

Sections

Splitter

The Splitter pattern decomposes a message into smaller ones.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
let orderItemTypeAProcessor (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! TypeAItemOrdered orderItem = mailbox.Receive ()
        printfn "OrderItemTypeAProcessor: handling %A" orderItem
        return! loop ()
    }
    loop ()

let orderItemTypeBProcessor (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! TypeBItemOrdered orderItem = mailbox.Receive ()
        printfn "OrderItemTypeBProcessor: handling %A" orderItem
        return! loop ()
    }
    loop ()

let orderItemTypeCProcessor (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! TypeCItemOrdered orderItem = mailbox.Receive ()
        printfn "OrderItemTypeCProcessor: handling %A" orderItem
        return! loop ()
    }
    loop ()

let orderRouter (mailbox: Actor<_>) =
    let orderItemTypeAProcessor = spawn mailbox.Context "orderItemTypeAProcessor" orderItemTypeAProcessor
    let orderItemTypeBProcessor = spawn mailbox.Context "orderItemTypeBProcessor" orderItemTypeBProcessor
    let orderItemTypeCProcessor = spawn mailbox.Context "orderItemTypeCProcessor" orderItemTypeCProcessor

    let rec loop () = actor {
        let! OrderPlaced order = mailbox.Receive ()
        order.OrderItems 
        |> Map.iter (fun k orderItem -> 
            match orderItem.ItemType with
            | "TypeA" -> 
                printfn "OrderRouter: routing %A" orderItem
                orderItemTypeAProcessor <! TypeAItemOrdered(orderItem)
            | "TypeB" -> 
                printfn "OrderRouter: routing %A" orderItem
                orderItemTypeBProcessor <! TypeBItemOrdered(orderItem)
            | "TypeC" -> 
                printfn "OrderRouter: routing %A" orderItem
                orderItemTypeCProcessor <! TypeCItemOrdered(orderItem)
            | _ -> printfn "OrderRouter: received unexpected message")
        return! loop ()
    }
    loop ()

let splitter (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        return! loop ()
    }
    loop ()

let orderRouterRef = spawn system "orderRouter" orderRouter
let orderItem1 = { Id = "1"; ItemType = "TypeA"; Description = "An item of type A."; Price = Money 23.95m }
let orderItem2 = { Id = "2"; ItemType = "TypeB"; Description = "An item of type B."; Price = Money 99.95m }
let orderItem3 = { Id = "3"; ItemType = "TypeC"; Description = "An item of type C."; Price = Money 14.95m }
let orderItems = Map.ofList [ (orderItem1.Id, orderItem1); (orderItem2.Id, orderItem2); (orderItem3.Id, orderItem3) ]
    
orderRouterRef <! OrderPlaced({ OrderItems = orderItems })
Complete Code

Sections

Aggregator

The Aggregator pattern creates a single message from multiple ones.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 
157: 
158: 
159: 
160: 
161: 
162: 
163: 
164: 
165: 
166: 
167: 
168: 
169: 
170: 
171: 
172: 
173: 
174: 
175: 
176: 
let priceQuoteAggregator (mailbox: Actor<_>) =
    let rec loop fulfilledPriceQuotes = actor {
        let! message = mailbox.Receive ()
        match message with
        | RequiredPriceQuotesForFulfillment(rfqId, quotesRequested) as message ->
            printfn "PriceQuoteAggregator: required fulfilled: %A" message
            return! loop (fulfilledPriceQuotes |> Map.add rfqId ({RfqId = rfqId; QuotesRequested = quotesRequested; PriceQuotes = []; Requester = mailbox.Sender () }))
        | PriceQuoteFulfilled priceQuoteFulfilled ->
            printfn "PriceQuoteAggregator: fulfilled price quote: %A" priceQuoteFulfilled
            let previousFulfillment = fulfilledPriceQuotes |> Map.find priceQuoteFulfilled.RfqId
            let currentPriceQuotes = previousFulfillment.PriceQuotes @ [priceQuoteFulfilled]
            let currentFulfillment = { previousFulfillment with PriceQuotes = currentPriceQuotes }
            if (currentPriceQuotes.Length >= currentFulfillment.QuotesRequested) then
                currentFulfillment.Requester <! currentFulfillment
                return! loop (fulfilledPriceQuotes |> Map.remove priceQuoteFulfilled.RfqId)
            else return! loop (fulfilledPriceQuotes |> Map.add priceQuoteFulfilled.RfqId currentFulfillment)
    }
    loop Map.empty

let mountaineeringSuppliesOrderProcessor priceQuoteAggregator (mailbox: Actor<_>) =
    let rec loop interestRegistry = actor {
        let calculateRecipientList (rfq: RequestForQuotation) = 
            interestRegistry
            |> Map.toList
            |> List.filter (fun (_, v) -> 
                let (Money lowTotalRetail) = v.LowTotalRetail
                let (Money highTotalRetail) = v.HighTotalRetail
                rfq.TotalRetailPrice >= lowTotalRetail && rfq.TotalRetailPrice <= highTotalRetail)
            |> List.map (fun (_, v) -> v.QuoteProcessor)

        let dispatchTo rfq (recipientList: IActorRef list) =
            recipientList
            |> List.iter (fun recipient ->
                rfq.RetailItems
                |> List.iter (fun retailItem -> 
                    printfn "OrderProcessor: %s item: %s to: %s" rfq.RfqId retailItem.ItemId <| recipient.Path.ToString ()
                    recipient <! { RfqId = rfq.RfqId; ItemId = retailItem.ItemId; RetailPrice = Money retailItem.RetailPrice; OrderTotalRetailPrice = Money rfq.TotalRetailPrice }))

        let! message = mailbox.Receive ()
        match box message with
        | :? PriceQuoteInterest as interest -> return! loop <| Map.add interest.Path interest interestRegistry 
        | :? PriceQuote as priceQuote -> 
            priceQuoteAggregator <! PriceQuoteFulfilled priceQuote
            printfn "OrderProcessor: received: %A" priceQuote
        | :? RequestForQuotation as rfq -> 
            let recipientList = calculateRecipientList rfq
            priceQuoteAggregator <! RequiredPriceQuotesForFulfillment(rfq.RfqId, recipientList.Length * rfq.RetailItems.Length)
            dispatchTo rfq recipientList
        | :? QuotationFulfillment as fulfillment -> printfn "OrderProcessor: received: %A" fulfillment
        | message -> printfn "OrderProcessor: unexpected: %A" message
        return! loop interestRegistry 
    }
    loop Map.empty

let budgetHikersPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 1.00m; HighTotalRetail = Money 1000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.02m
        elif (orderTotalRetailPrice <= 399.99m) then 0.03m
        elif (orderTotalRetailPrice <= 499.99m) then 0.05m
        elif (orderTotalRetailPrice <= 799.99m) then 0.07m
        else 0.075m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let highSierraPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 100.00m; HighTotalRetail = Money 10000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 150.00m) then 0.015m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let mountainAscentPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 70.00m; HighTotalRetail = Money 5000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 99.99m) then 0.01m
        elif (orderTotalRetailPrice <= 199.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.0475m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let pinnacleGearPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 250.00m; HighTotalRetail = Money 500000.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 299.99m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.0175m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 1199.99m) then 0.035m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        elif (orderTotalRetailPrice <= 7999.99m) then 0.05m
        else 0.06m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let rockBottomOuterwearPriceQuotes interestRegistrar (mailbox: Actor<RequestPriceQuote>) =
    interestRegistrar <! { Path = mailbox.Self.Path.ToString (); QuoteProcessor = mailbox.Self; LowTotalRetail = Money 0.50m; HighTotalRetail = Money 7500.00m }
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.06m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.07m
        elif (orderTotalRetailPrice <= 5999.99m) then 0.075m
        else 0.08m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let priceQuoteAggregatorRef = spawn system "priceQuoteAggregator" priceQuoteAggregator
let orderProcessorRef = spawn system "orderProcessor" <| mountaineeringSuppliesOrderProcessor priceQuoteAggregatorRef
let budgetHikersRef = spawn system "budgetHikersPriceQuotes" <| budgetHikersPriceQuotes orderProcessorRef
let highSierraRef = spawn system "highSierra" <| highSierraPriceQuotes orderProcessorRef
let mountainAscentRef = spawn system "mountainAscent" <| mountainAscentPriceQuotes orderProcessorRef
let pinnacleGearRef = spawn system "pinnacleGear" <| pinnacleGearPriceQuotes orderProcessorRef
let rockBottomOuterwearRef = spawn system "rockBottomOuterwear" <| rockBottomOuterwearPriceQuotes orderProcessorRef

orderProcessorRef <! { RfqId = "123"; RetailItems = [ { ItemId = "1"; RetailPrice = 29.95m }; { ItemId = "2"; RetailPrice = 99.95m }; { ItemId = "3"; RetailPrice = 14.95m } ] }
orderProcessorRef <! { RfqId = "125"; RetailItems = [ { ItemId = "4"; RetailPrice = 39.99m }; { ItemId = "5"; RetailPrice = 199.95m }; { ItemId = "6"; RetailPrice = 149.95m }; { ItemId = "7"; RetailPrice = 724.99m } ] }
orderProcessorRef <! { RfqId = "129"; RetailItems = [ { ItemId = "8"; RetailPrice = 119.99m }; { ItemId = "9"; RetailPrice = 499.95m }; { ItemId = "10"; RetailPrice = 519.00m }; { ItemId = "11"; RetailPrice = 209.50m } ] }
orderProcessorRef <! { RfqId = "135"; RetailItems = [ { ItemId = "12"; RetailPrice = 0.97m }; { ItemId = "13"; RetailPrice = 9.50m }; { ItemId = "14"; RetailPrice = 1.99m } ] }
orderProcessorRef <! { RfqId = "140"; RetailItems = [ { ItemId = "15"; RetailPrice = 107.50m }; { ItemId = "16"; RetailPrice = 9.50m }; { ItemId = "17"; RetailPrice = 599.99m }; { ItemId = "18"; RetailPrice = 249.95m }; { ItemId = "19"; RetailPrice = 789.99m } ] }
Complete Code

Sections

Resequencer

This pattern ensures that messages are processed in the right order.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
type SequencedMessage =  SequencedMessage of correlationId: string * index: int * total: int
type ResequencedMessages = { DispatchableIndex: int; SequencedMessages: SequencedMessage [] } with
    member this.AdvancedTo(dispatchableIndex: int) = { this with DispatchableIndex = dispatchableIndex }

let chaosRouter consumer (mailbox: Actor<_>) =
    let random = Random()
    let rec loop () = actor {
        let! SequencedMessage(correlationId, index, total) as sequencedMessage = mailbox.Receive ()
        let millis = random.Next 100
        printfn "ChaosRouter: delaying delivery of %A for %i milliseconds" sequencedMessage millis
        let duration = TimeSpan.FromMilliseconds (float millis)
        mailbox.Context.System.Scheduler.ScheduleTellOnce (duration, consumer, sequencedMessage)
        return! loop ()
    }
    loop ()

let resequencerConsumer actualConsumer (mailbox: Actor<_>) =
    let rec loop resequenced = actor {
        let dummySequencedMessages count = [| for _ in 1 .. count -> SequencedMessage("", -1, count) |]
        
        let resequence sequencedMessage resequenced = 
            let (SequencedMessage(correlationId, index, total)) = sequencedMessage
            let resequenced = 
                resequenced
                |> Map.tryFind correlationId
                |> Option.fold 
                    (fun _ v -> resequenced) 
                    (resequenced |> Map.add correlationId ( { DispatchableIndex = 1; SequencedMessages = dummySequencedMessages total }))
            resequenced
            |> Map.find correlationId
            |> fun m -> m.SequencedMessages.[index - 1] <- sequencedMessage
            resequenced

        let rec dispatchAllSequenced correlationId resequenced =
            let resequencedMessage = resequenced |> Map.find correlationId
            let dispatchableIndex = 
                resequencedMessage.SequencedMessages 
                |> Array.filter (fun (SequencedMessage(_, index, _) as sequencedMessage) -> index = resequencedMessage.DispatchableIndex)
                |> Array.fold (fun dispatchableIndex sequencedMessage -> 
                        actualConsumer <! sequencedMessage
                        dispatchableIndex + 1) 
                        resequencedMessage.DispatchableIndex
            let resequenced = resequenced |> Map.add correlationId (resequencedMessage.AdvancedTo dispatchableIndex)
            if resequencedMessage.SequencedMessages |> Array.exists (fun (SequencedMessage(_, index, _)) -> index = dispatchableIndex) then dispatchAllSequenced correlationId resequenced
            else resequenced

        let removeCompleted correlationId resequenced = 
            resequenced
            |> Map.find correlationId
            |> fun resequencedMessages -> 
                let (SequencedMessage(_,_,total)) = resequencedMessages.SequencedMessages.[0]
                if resequencedMessages.DispatchableIndex > total then
                    printfn "ResequencerConsumer: removed completed: %s" correlationId
                    resequenced |> Map.remove correlationId
                else resequenced

        let! SequencedMessage(correlationId, index, total) as unsequencedMessage = mailbox.Receive ()
        printfn "ResequencerConsumer: received: %A" unsequencedMessage
        let resequenced = 
            resequenced
            |> resequence unsequencedMessage
            |> dispatchAllSequenced correlationId
            |> removeCompleted correlationId
        return! loop resequenced
    }
    loop Map.empty

let sequencedMessageConsumer (mailbox: Actor<SequencedMessage>) =
    let rec loop () = actor {
        let! sequencedMessage = mailbox.Receive ()
        printfn "SequencedMessageConsumer: received: %A" sequencedMessage
        return! loop ()
    }
    loop ()

let sequencedMessageConsumerRef = spawn system "sequencedMessageConsumer" sequencedMessageConsumer
let resequencerConsumerRef = spawn system "resequencerConsumer" <| resequencerConsumer sequencedMessageConsumerRef
let chaosRouterRef = spawn system "chaosRouter" <| chaosRouter resequencerConsumerRef

[1 .. 5] |> List.iter (fun index -> chaosRouterRef <! SequencedMessage("ABC", index, 5))
[1 .. 5] |> List.iter (fun index -> chaosRouterRef <! SequencedMessage("XYZ", index, 5))
Complete Code

Sections

Composed Message Processor

This pattern handles composite messages by splitting them, routing the parts and then aggregating the results.

1: 
// No code example

Sections

Scatter-Gather

This pattern broadcasts a message to multiple actors and then aggregates the results.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 
157: 
158: 
159: 
160: 
161: 
162: 
163: 
164: 
165: 
166: 
167: 
168: 
169: 
170: 
171: 
172: 
173: 
174: 
175: 
176: 
177: 
178: 
179: 
180: 
181: 
182: 
183: 
184: 
185: 
186: 
187: 
188: 
189: 
190: 
191: 
192: 
193: 
194: 
195: 
196: 
197: 
198: 
199: 
200: 
201: 
202: 
203: 
204: 
let priceQuoteAggregator (mailbox: Actor<_>) =
    let rec loop fulfilledPriceQuotes = actor {
        let bestPriceQuotationFrom (quotationFulfillment: QuotationFulfillment) =
            let bestPrices = 
                quotationFulfillment.PriceQuotes
                |> List.groupBy (fun priceQuote -> priceQuote.ItemId)
                |> List.map (fun (itemId, quotes) -> 
                    quotes 
                    |> List.maxBy (fun quote ->  
                        let (Money discount) = quote.DiscountPrice
                        discount))
            { RfqId = quotationFulfillment.RfqId; PriceQuotes = bestPrices }
        
        let quoteBestPrice (quotationFulfillment: QuotationFulfillment) = 
            fulfilledPriceQuotes 
            |> Map.tryFind quotationFulfillment.RfqId
            |> Option.map (fun q -> quotationFulfillment.Requester <! bestPriceQuotationFrom quotationFulfillment)
            |> Option.fold (fun _ _ -> fulfilledPriceQuotes |> Map.remove quotationFulfillment.RfqId) fulfilledPriceQuotes

        let priceQuoteRequestTimedOut rfqId = 
            fulfilledPriceQuotes 
            |> Map.tryFind rfqId
            |> Option.fold (fun _ _ -> quoteBestPrice (fulfilledPriceQuotes |> Map.find rfqId)) fulfilledPriceQuotes

        let priceQuoteRequestFulfilled (priceQuoteFulfilled: PriceQuote) =
            let previousFulfillment = fulfilledPriceQuotes |> Map.find priceQuoteFulfilled.RfqId
            let currentPriceQuotes = previousFulfillment.PriceQuotes @ [priceQuoteFulfilled]
            let currentFulfillment = { previousFulfillment with PriceQuotes = currentPriceQuotes }
            if (currentPriceQuotes.Length >= currentFulfillment.QuotesRequested) then quoteBestPrice currentFulfillment
            else fulfilledPriceQuotes |> Map.add priceQuoteFulfilled.RfqId currentFulfillment

        let! message = mailbox.Receive ()
        match message with
        | RequiredPriceQuotesForFulfillment(rfqId, quotesRequested) as message ->
            printfn "PriceQuoteAggregator: required fulfilled: %A" message
            let duration = TimeSpan.FromSeconds 2.
            mailbox.Context.System.Scheduler.ScheduleTellOnce (duration, mailbox.Self, PriceQuoteTimedOut rfqId)
            return! loop (fulfilledPriceQuotes |> Map.add rfqId ({RfqId = rfqId; QuotesRequested = quotesRequested; PriceQuotes = []; Requester = mailbox.Sender () }))
        | PriceQuoteFulfilled priceQuote -> 
            printfn "PriceQuoteAggregator: fulfilled price quote: %A" priceQuote
            return! loop <| priceQuoteRequestFulfilled priceQuote
        | PriceQuoteTimedOut rfqId -> return! loop <| priceQuoteRequestTimedOut rfqId
    }
    loop Map.empty

let mountaineeringSuppliesOrderProcessor priceQuoteAggregator (mailbox: Actor<_>) =
    let rec loop subscribers = actor {
        let dispatch rfq =
            subscribers
            |> Map.toList
            |> List.iter (fun (_, (SubscribeToPriceQuoteRequests(quoterId, quoteProcessor) as subscriber)) ->
                rfq.RetailItems
                |> List.iter (fun retailItem -> 
                    printfn "OrderProcessor: %s item: %s to: %s" rfq.RfqId retailItem.ItemId quoterId
                    quoteProcessor <! { RfqId = rfq.RfqId; ItemId = retailItem.ItemId; RetailPrice = Money retailItem.RetailPrice; OrderTotalRetailPrice = Money rfq.TotalRetailPrice }))

        let! message = mailbox.Receive ()
        match box message with
        | :? SubscribeToPriceQuoteRequests as subscriber -> 
            let (SubscribeToPriceQuoteRequests(quoterId, quoteProcessor)) = subscriber
            return! loop <| Map.add quoteProcessor.Path.Name subscriber subscribers 
        | :? PriceQuote as priceQuote -> 
            priceQuoteAggregator <! PriceQuoteFulfilled priceQuote
            printfn "OrderProcessor: received: %A" priceQuote
        | :? RequestForQuotation as rfq -> 
            priceQuoteAggregator <! RequiredPriceQuotesForFulfillment(rfq.RfqId, subscribers.Count * rfq.RetailItems.Length)
            dispatch rfq
        | :? BestPriceQuotation as bestPriceQuotation -> printfn "OrderProcessor: received: %A" bestPriceQuotation
        | message -> printfn "OrderProcessor: unexpected: %A" message
        return! loop subscribers 
    }
    loop Map.empty

let budgetHikersPriceQuotes priceQuoteRequestPublisher (mailbox: Actor<RequestPriceQuote>) =
    let quoterId = mailbox.Self.Path.Name
    priceQuoteRequestPublisher <! SubscribeToPriceQuoteRequests(quoterId, mailbox.Self)

    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.02m
        elif (orderTotalRetailPrice <= 399.99m) then 0.03m
        elif (orderTotalRetailPrice <= 499.99m) then 0.05m
        elif (orderTotalRetailPrice <= 799.99m) then 0.07m
        else 0.075m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let highSierraPriceQuotes priceQuoteRequestPublisher (mailbox: Actor<RequestPriceQuote>) =
    let quoterId = mailbox.Self.Path.Name
    priceQuoteRequestPublisher <! SubscribeToPriceQuoteRequests(quoterId, mailbox.Self)
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 150.00m) then 0.015m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        if orderTotalRetailPrice < 1000.00m then
            let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
            mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        else printfn "BudgetHikersPriceQuotes: ignoring: %A" rpq
        return! loop ()
    }
    loop ()

let mountainAscentPriceQuotes priceQuoteRequestPublisher (mailbox: Actor<RequestPriceQuote>) =
    let quoterId = mailbox.Self.Path.Name
    priceQuoteRequestPublisher <! SubscribeToPriceQuoteRequests(quoterId, mailbox.Self)
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 99.99m) then 0.01m
        elif (orderTotalRetailPrice <= 199.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.0475m
        else 0.05m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let pinnacleGearPriceQuotes priceQuoteRequestPublisher (mailbox: Actor<RequestPriceQuote>) =
    let quoterId = mailbox.Self.Path.Name
    priceQuoteRequestPublisher <! SubscribeToPriceQuoteRequests(quoterId, mailbox.Self)
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 299.99m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.0175m
        elif (orderTotalRetailPrice <= 499.99m) then 0.02m
        elif (orderTotalRetailPrice <= 999.99m) then 0.03m
        elif (orderTotalRetailPrice <= 1199.99m) then 0.035m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.04m
        elif (orderTotalRetailPrice <= 7999.99m) then 0.05m
        else 0.06m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
        mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        return! loop ()
    }
    loop ()

let rockBottomOuterwearPriceQuotes priceQuoteRequestPublisher (mailbox: Actor<RequestPriceQuote>) =
    let quoterId = mailbox.Self.Path.Name
    priceQuoteRequestPublisher <! SubscribeToPriceQuoteRequests(quoterId, mailbox.Self)
    
    let discountPercentage orderTotalRetailPrice =
        if (orderTotalRetailPrice <= 100.00m) then 0.015m
        elif (orderTotalRetailPrice <= 399.99m) then 0.02m
        elif (orderTotalRetailPrice <= 499.99m) then 0.03m
        elif (orderTotalRetailPrice <= 799.99m) then 0.04m
        elif (orderTotalRetailPrice <= 999.99m) then 0.05m
        elif (orderTotalRetailPrice <= 2999.99m) then 0.06m
        elif (orderTotalRetailPrice <= 4999.99m) then 0.07m
        elif (orderTotalRetailPrice <= 5999.99m) then 0.075m
        else 0.08m
    
    let rec loop () = actor {
        let! rpq = mailbox.Receive ()
        let (Money retailPrice) = rpq.RetailPrice
        let (Money orderTotalRetailPrice) = rpq.OrderTotalRetailPrice
        if orderTotalRetailPrice < 1000.00m then
            let discount = discountPercentage (orderTotalRetailPrice * retailPrice)
            mailbox.Sender () <! { RfqId = rpq.RfqId; ItemId = rpq.ItemId; RetailPrice = rpq.RetailPrice; DiscountPrice = Money (retailPrice - discount) }
        else printfn "RockBottomOuterwearPriceQuotes: ignoring: %A" rpq
        return! loop ()
    }
    loop ()

let priceQuoteAggregatorRef = spawn system "priceQuoteAggregator" priceQuoteAggregator
let orderProcessorRef = spawn system "orderProcessor" <| mountaineeringSuppliesOrderProcessor priceQuoteAggregatorRef
let budgetHikersRef = spawn system "budgetHikersPriceQuotes" <| budgetHikersPriceQuotes orderProcessorRef
let highSierraRef = spawn system "highSierra" <| highSierraPriceQuotes orderProcessorRef
let mountainAscentRef = spawn system "mountainAscent" <| mountainAscentPriceQuotes orderProcessorRef
let pinnacleGearRef = spawn system "pinnacleGear" <| pinnacleGearPriceQuotes orderProcessorRef
let rockBottomOuterwearRef = spawn system "rockBottomOuterwear" <| rockBottomOuterwearPriceQuotes orderProcessorRef

orderProcessorRef <! { RfqId = "123"; RetailItems = [ { ItemId = "1"; RetailPrice = 29.95m }; { ItemId = "2"; RetailPrice = 99.95m }; { ItemId = "3"; RetailPrice = 14.95m } ] }
orderProcessorRef <! { RfqId = "125"; RetailItems = [ { ItemId = "4"; RetailPrice = 39.99m }; { ItemId = "5"; RetailPrice = 199.95m }; { ItemId = "6"; RetailPrice = 149.95m }; { ItemId = "7"; RetailPrice = 724.99m } ] }
orderProcessorRef <! { RfqId = "129"; RetailItems = [ { ItemId = "8"; RetailPrice = 119.99m }; { ItemId = "9"; RetailPrice = 499.95m }; { ItemId = "10"; RetailPrice = 519.00m }; { ItemId = "11"; RetailPrice = 209.50m } ] }
orderProcessorRef <! { RfqId = "135"; RetailItems = [ { ItemId = "12"; RetailPrice = 0.97m }; { ItemId = "13"; RetailPrice = 9.50m }; { ItemId = "14"; RetailPrice = 1.99m } ] }
orderProcessorRef <! { RfqId = "140"; RetailItems = [ { ItemId = "15"; RetailPrice = 107.50m }; { ItemId = "16"; RetailPrice = 9.50m }; { ItemId = "17"; RetailPrice = 599.99m }; { ItemId = "18"; RetailPrice = 249.95m }; { ItemId = "19"; RetailPrice = 789.99m } ] }
Complete Code

Sections

Routing Slip

This pattern allows to split one large procedure into smaller sequential steps that are handled by different actors. On each step, the specified actor can attach additional information to the message and send it to the next one.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
type RegistrationProcess = { ProcessId: string; ProcessSteps: ProcessStep list; CurrentStep: int } with
    static member Create (processId, processSteps) = { ProcessId = processId; ProcessSteps = processSteps; CurrentStep = 0 }
    member this.IsCompleted with get () = this.CurrentStep >= this.ProcessSteps.Length
    member this.NextStep () = 
        if (this.IsCompleted) then failwith "Process had already completed."
        else this.ProcessSteps |> List.item this.CurrentStep
    member this.StepCompleted () = { this with CurrentStep = this.CurrentStep + 1 }
type RegisterCustomer = { RegistrationData: RegistrationData; RegistrationProcess: RegistrationProcess } with
    member this.Advance () = 
        let advancedProcess = this.RegistrationProcess.StepCompleted ()
        if not advancedProcess.IsCompleted then (advancedProcess.NextStep ()).Processor <! { this with RegistrationProcess = advancedProcess }
        else ()

let creditChecker (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! registerCustomer = mailbox.Receive ()
        let federalTaxId = registerCustomer.RegistrationData.CustomerInformation.FederalTaxId
        printfn "CreditChecker: handling register customer to perform credit check: %s" federalTaxId
        registerCustomer.Advance ()
    }
    loop ()

let contactKeeper (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! registerCustomer = mailbox.Receive ()
        let contactInfo = registerCustomer.RegistrationData.ContactInformation
        printfn "ContactKeeper: handling register customer to keep contact information: %A" contactInfo
        registerCustomer.Advance ()
    }
    loop ()

let customerVault (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! registerCustomer = mailbox.Receive ()
        let customerInformation = registerCustomer.RegistrationData.CustomerInformation
        printfn "CustomerVault: handling register customer to create a new custoner: %A" customerInformation
        registerCustomer.Advance ()
    }
    loop ()

let servicePlanner (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! registerCustomer = mailbox.Receive ()
        let serviceOption = registerCustomer.RegistrationData.ServiceOption
        printfn "ServicePlanner: handling register customer to plan a new customer service: %A" serviceOption
        registerCustomer.Advance ()
    }
    loop ()

module ServiceRegistry =
    let contactKeeper (system: ActorSystem) (id: string) = spawn system (sprintf "contactKeeper-%s" id) contactKeeper
    let creditChecker (system: ActorSystem) (id: string) = spawn system (sprintf "creditChecker-%s" id) creditChecker
    let customerVault (system: ActorSystem) (id: string) = spawn system (sprintf "customerVault-%s" id) customerVault
    let servicePlanner (system: ActorSystem) (id: string) = spawn system (sprintf "servicePlanner-%s" id) servicePlanner

let processId = (Guid.NewGuid ()).ToString ()
let step1 = { Name = "create_customer"; Processor = ServiceRegistry.customerVault system processId }
let step2 = { Name = "set_up_contact_info"; Processor = ServiceRegistry.contactKeeper system processId }
let step3 = { Name = "select_service_plan"; Processor = ServiceRegistry.servicePlanner system processId }
let step4 = { Name = "check_credit"; Processor = ServiceRegistry.creditChecker system processId }
let registrationProcess = RegistrationProcess.Create (processId, [ step1; step2; step3; step4 ])
let registrationData = { 
    CustomerInformation = { Name = "ABC, Inc."; FederalTaxId = "123-45-6789" }
    ContactInformation = { PostalAddress = { Address1 = "123 Main Street"; Address2 = "Suite 100"; City = "Boulder"; State = "CO"; ZipCode = "80301" }
                           Telephone = Telephone "303-555-1212" }
    ServiceOption = { Id = "99-1203"; Description = "A description of 99-1203." } }
let registerCustomer = { RegistrationData = registrationData; RegistrationProcess = registrationProcess }

(registrationProcess.NextStep ()).Processor <! registerCustomer
Complete Code

Sections

Process Manager

This pattern splits a large process into smaller steps that may not be sequential and may not be known at design time. The Process Manager maintains the state of the process and calculates each step.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
module Process =
    let processOf processId processes = processes |> Map.find processId
    let startProcess processId ``process`` self processes =
        self <! ProcessStarted(processId, ``process``)
        processes |> Map.add processId ``process``
    let stopProcess processId self processes =
        let ``process`` = processOf processId processes
        self <! ProcessStopped(processId, ``process``)
        processes |> Map.remove processId

let loanRateQuote loanRateQuoteId taxId amount termInMonths loanBroker (mailbox: Actor<_>) =
    let rec loop bankLoanRateQuotes expectedLoanRateQuotes creditRatingScore = actor {
        let quotableCreditScore score = score > 399

        let bestBankLoanRateQuote () = bankLoanRateQuotes |> List.minBy (fun bankLoanRateQuote -> bankLoanRateQuote.InterestRate)

        let! message = mailbox.Receive () 
        match message with
        | StartLoanRateQuote expectedLoanRateQuotes -> 
            loanBroker <! LoanRateQuoteStarted(loanRateQuoteId, taxId)
            return! loop bankLoanRateQuotes expectedLoanRateQuotes creditRatingScore
        | EstablishCreditScoreForLoanRateQuote(loanRateQuoteId, taxId, creditRatingScore) -> 
            if quotableCreditScore creditRatingScore then loanBroker <! CreditScoreForLoanRateQuoteEstablished(loanRateQuoteId, taxId, creditRatingScore, amount, termInMonths)
            else loanBroker <! CreditScoreForLoanRateQuoteDenied(loanRateQuoteId, taxId, amount, termInMonths, creditRatingScore)
            return! loop bankLoanRateQuotes expectedLoanRateQuotes creditRatingScore
        | RecordLoanRateQuote(bankId, bankLoanRateQuoteId, interestRate) -> 
            let bankLoanRateQuote = { BankId = bankId; BankLoanRateQuoteId = bankLoanRateQuoteId; InterestRate = interestRate }
            loanBroker <! LoanRateQuoteRecorded(loanRateQuoteId, taxId, bankLoanRateQuote)
            if bankLoanRateQuotes |> List.length >= expectedLoanRateQuotes then loanBroker <! LoanRateBestQuoteFilled(loanRateQuoteId, taxId, amount, termInMonths, creditRatingScore, bestBankLoanRateQuote ())
            else ()
            return! loop <| bankLoanRateQuotes @ [bankLoanRateQuote] <| expectedLoanRateQuotes <| creditRatingScore
        | TerminateLoanRateQuote -> 
            loanBroker <! LoanRateQuoteTerminated(loanRateQuoteId, taxId)
            return! loop bankLoanRateQuotes expectedLoanRateQuotes creditRatingScore
    }
    loop [] 0 0

let loanBroker creditBureau banks (mailbox: Actor<_>) =
    let rec loop processes = actor {
        let! message = mailbox.Receive ()
        match message with
        | BankLoanRateQuoted(bankId, bankLoanRateQuoteId, loadQuoteReferenceId, taxId, interestRate) ->
            printfn "%A" message
            Process.processOf loadQuoteReferenceId processes <! RecordLoanRateQuote(bankId, bankLoanRateQuoteId, interestRate)
            return! loop processes
        | CreditChecked(creditProcessingReferenceId, taxId, score) ->
            printfn "%A" message
            Process.processOf creditProcessingReferenceId processes <! EstablishCreditScoreForLoanRateQuote(creditProcessingReferenceId, taxId, score)
            return! loop processes
        | CreditScoreForLoanRateQuoteDenied(loanRateQuoteId, taxId, amount, termInMonths, score) ->
            printfn "%A" message
            Process.processOf loanRateQuoteId processes <! TerminateLoanRateQuote
            let denied = BestLoanRateDenied(loanRateQuoteId, taxId, amount, termInMonths, score)
            printfn "Would be sent to original requester: %A" denied
            return! loop processes
        | CreditScoreForLoanRateQuoteEstablished(loanRateQuoteId, taxId, score, amount, termInMonths) ->
            printfn "%A" message
            banks |> List.iter (fun bank -> bank <! QuoteLoanRate(loanRateQuoteId, taxId, score, amount, termInMonths))
            return! loop processes
        | LoanRateBestQuoteFilled(loanRateQuoteId, taxId, amount, termInMonths, creditScore, bestBankLoanRateQuote) ->
            printfn "%A" message
            let best = BestLoanRateQuoted(bestBankLoanRateQuote.BankId, loanRateQuoteId, taxId, amount, termInMonths, creditScore, bestBankLoanRateQuote.InterestRate)
            printfn "Would be sent to original requester: %A" best
            return! loop <| Process.stopProcess loanRateQuoteId mailbox.Self processes 
        | LoanRateQuoteRecorded(loanRateQuoteId, taxId, bankLoanRateQuote) ->
            printfn "%A" message
            return! loop processes
        | LoanRateQuoteStarted(loanRateQuoteId, taxId) ->
            printfn "%A" message
            creditBureau <! CheckCredit(loanRateQuoteId, taxId)
            return! loop processes
        | LoanRateQuoteTerminated(loanRateQuoteId, taxId) ->
            printfn "%A" message
            return! loop <| Process.stopProcess loanRateQuoteId mailbox.Self processes 
        | ProcessStarted(processId, ``process``) ->
            printfn "%A" message
            ``process`` <! StartLoanRateQuote(banks.Length)
            return! loop processes
        | ProcessStopped (_,_) -> printfn "%A" message
        | QuoteBestLoanRate(taxId, amount, termInMonths) -> 
            let loanRateQuoteId = (Guid.NewGuid ()).ToString () // LoanRateQuote.id
            let loanRateQuote = spawn mailbox.Context "loanRateQuote" <| loanRateQuote loanRateQuoteId taxId amount termInMonths mailbox.Self
            return! loop <| Process.startProcess loanRateQuoteId loanRateQuote mailbox.Self processes
    }
    loop Map.empty

let creditBureau (mailbox: Actor<_>) =
    let creditRanges = [300; 400; 500; 600; 700]
    let randomCreditRangeGenerator = Random()
    let randomCreditScoreGenerator = Random()
    
    let rec loop () = actor {
        let! (CheckCredit(creditProcessingReferenceId, taxId)) = mailbox.Receive ()
        let range = creditRanges |> List.item (randomCreditRangeGenerator.Next 5)
        let score = range + randomCreditScoreGenerator.Next 20
        mailbox.Sender () <! CreditChecked(creditProcessingReferenceId, taxId, score)        
        return! loop ()
    }
    loop ()

let bank bankId primeRate ratePremium (mailbox: Actor<_>) =
    let randomDiscount = Random()
    let randomQuoteId = Random()
    let calculateInterestRate amount months creditScore = 
        let creditScoreDiscount = creditScore / 100.0m / 10.0m - ((randomDiscount.Next 5 |> decimal) * 0.05m)
        primeRate + ratePremium + ((months / 12.0m) / 10.0m) - creditScoreDiscount
    
    let rec loop () = actor {
        let! (QuoteLoanRate(loadQuoteReferenceId, taxId, creditScore, amount, termInMonths)) = mailbox.Receive ()
        let interestRate = calculateInterestRate amount (decimal termInMonths) (decimal creditScore)
        mailbox.Sender () <! BankLoanRateQuoted(bankId, (randomQuoteId.Next 1000).ToString (), loadQuoteReferenceId, taxId, interestRate)
        return! loop ()
    }
    loop ()

let creditBureauRef = spawn system "creditBureau" creditBureau
let bank1Ref = spawn system "bank1" <| bank "bank1" 2.75m 0.30m
let bank2Ref = spawn system "bank2" <| bank "bank2" 2.73m 0.31m
let bank3Ref = spawn system "bank3" <| bank "bank3" 2.80m 0.29m
let loanBrokerRef = spawn system "loanBroker" <| loanBroker creditBureauRef [bank1Ref; bank2Ref; bank3Ref]

loanBrokerRef <! QuoteBestLoanRate("111-11-1111", 100000, 84)
Complete Code

Sections

Message Broker

This pattern decouples receivers from senders and controls the flow of messages.

1: 
// No code example

Sections

val inventorySystemA : mailbox:'a -> 'b

Full name: messagerouting.inventorySystemA
val mailbox : 'a
val loop : (unit -> 'a)
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val inventorySystemX : mailbox:'a -> 'b

Full name: messagerouting.inventorySystemX
val orderRouter : mailbox:'a -> 'b

Full name: messagerouting.orderRouter
val inventorySystemA : obj
val inventorySystemX : obj
val orderRouterRef : obj

Full name: messagerouting.orderRouterRef
val orderItem1 : obj

Full name: messagerouting.orderItem1
val orderItem2 : obj

Full name: messagerouting.orderItem2
val orderItem3 : obj

Full name: messagerouting.orderItem3
val orderItemsOfTypeA : Map<obj,obj>

Full name: messagerouting.orderItemsOfTypeA
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val orderItem4 : obj

Full name: messagerouting.orderItem4
val orderItem5 : obj

Full name: messagerouting.orderItem5
val orderItem6 : obj

Full name: messagerouting.orderItem6
val orderItem7 : obj

Full name: messagerouting.orderItem7
val orderItemsOfTypeX : Map<obj,obj>

Full name: messagerouting.orderItemsOfTypeX
val inventorySystemXMessageFilter : actualInventorySystemX:'a -> mailbox:'b -> 'c

Full name: messagerouting.inventorySystemXMessageFilter
val actualInventorySystemX : 'a
val inventorySystemARef : obj

Full name: messagerouting.inventorySystemARef
val actualInventorySystemXRef : obj

Full name: messagerouting.actualInventorySystemXRef
val inventorySystemXRef : obj

Full name: messagerouting.inventorySystemXRef
val typeAInterested : interestRouter:obj -> mailbox:'a -> 'b

Full name: messagerouting.typeAInterested
val interestRouter : obj
val typeof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typeof
val typeBInterested : interestRouter:obj -> mailbox:'a -> 'b

Full name: messagerouting.typeBInterested
val typeCInterested : interestRouter:obj -> mailbox:'a -> 'b

Full name: messagerouting.typeCInterested
val typeCAlsoInterested : interestRouter:obj -> mailbox:'a -> 'b

Full name: messagerouting.typeCAlsoInterested
val dunnoInterested : mailbox:'a -> 'b

Full name: messagerouting.dunnoInterested
val typedMessageInterestRouter : dunnoInterested:'a -> mailbox:'b -> 'c

Full name: messagerouting.typedMessageInterestRouter
val dunnoInterested : 'a
val loop : ('a -> 'b -> 'c)
val interestRegistry : 'a
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val secondaryInterestRegistry : 'a
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
module Option

from Microsoft.FSharp.Core
val fold : folder:('State -> 'T -> 'State) -> state:'State -> option:'T option -> 'State

Full name: Microsoft.FSharp.Core.Option.fold
val add : key:'Key -> value:'T -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.add
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind
union case Option.Some: Value: 'T -> Option<'T>
val remove : key:'Key -> table:Map<'Key,'T> -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.remove
union case Option.None: Option<'T>
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
val dunnoInterestedRef : obj

Full name: messagerouting.dunnoInterestedRef
val typedMessageInterestRouterRef : obj

Full name: messagerouting.typedMessageInterestRouterRef
val typeAInterestedRef : obj

Full name: messagerouting.typeAInterestedRef
val typeBInterestedRef : obj

Full name: messagerouting.typeBInterestedRef
val typeCInterestedRef : obj

Full name: messagerouting.typeCInterestedRef
val typeCAlsoInterestedRef : obj

Full name: messagerouting.typeCAlsoInterestedRef
val mountaineeringSuppliesOrderProcessor : mailbox:'a -> 'b

Full name: messagerouting.mountaineeringSuppliesOrderProcessor
val loop : ('a -> 'b)
val toList : table:Map<'Key,'T> -> ('Key * 'T) list (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.toList
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val budgetHikersPriceQuotes : interestRegistrar:obj -> mailbox:'a -> 'b

Full name: messagerouting.budgetHikersPriceQuotes
val interestRegistrar : obj
val discountPercentage : (decimal -> decimal)
val orderTotalRetailPrice : decimal
val highSierraPriceQuotes : interestRegistrar:obj -> mailbox:'a -> 'b

Full name: messagerouting.highSierraPriceQuotes
val mountainAscentPriceQuotes : interestRegistrar:obj -> mailbox:'a -> 'b

Full name: messagerouting.mountainAscentPriceQuotes
val pinnacleGearPriceQuotes : interestRegistrar:obj -> mailbox:'a -> 'b

Full name: messagerouting.pinnacleGearPriceQuotes
val rockBottomOuterwearPriceQuotes : interestRegistrar:obj -> mailbox:'a -> 'b

Full name: messagerouting.rockBottomOuterwearPriceQuotes
val orderProcessorRef : obj

Full name: messagerouting.orderProcessorRef
val budgetHikersRef : obj

Full name: messagerouting.budgetHikersRef
val highSierraRef : obj

Full name: messagerouting.highSierraRef
val mountainAscentRef : obj

Full name: messagerouting.mountainAscentRef
val pinnacleGearRef : obj

Full name: messagerouting.pinnacleGearRef
val rockBottomOuterwearRef : obj

Full name: messagerouting.rockBottomOuterwearRef
val orderItemTypeAProcessor : mailbox:'a -> 'b

Full name: messagerouting.orderItemTypeAProcessor
val orderItemTypeBProcessor : mailbox:'a -> 'b

Full name: messagerouting.orderItemTypeBProcessor
val orderItemTypeCProcessor : mailbox:'a -> 'b

Full name: messagerouting.orderItemTypeCProcessor
val orderItemTypeAProcessor : obj
val orderItemTypeBProcessor : obj
val orderItemTypeCProcessor : obj
val iter : action:('Key -> 'T -> unit) -> table:Map<'Key,'T> -> unit (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.iter
val splitter : mailbox:'a -> 'b

Full name: messagerouting.splitter
val orderItems : Map<obj,obj>

Full name: messagerouting.orderItems
val priceQuoteAggregator : mailbox:'a -> 'b

Full name: messagerouting.priceQuoteAggregator
val fulfilledPriceQuotes : 'a
val find : key:'Key -> table:Map<'Key,'T> -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.find
val mountaineeringSuppliesOrderProcessor : priceQuoteAggregator:'a -> mailbox:'b -> 'c

Full name: messagerouting.mountaineeringSuppliesOrderProcessor
val priceQuoteAggregator : 'a
val priceQuoteAggregatorRef : 'a

Full name: messagerouting.priceQuoteAggregatorRef
val budgetHikersRef : 'a

Full name: messagerouting.budgetHikersRef
val highSierraRef : 'a

Full name: messagerouting.highSierraRef
val mountainAscentRef : 'a

Full name: messagerouting.mountainAscentRef
val pinnacleGearRef : 'a

Full name: messagerouting.pinnacleGearRef
val rockBottomOuterwearRef : 'a

Full name: messagerouting.rockBottomOuterwearRef
Multiple items
union case SequencedMessage.SequencedMessage: correlationId: string * index: int * total: int -> SequencedMessage

--------------------
type SequencedMessage = | SequencedMessage of correlationId: string * index: int * total: int

Full name: messagerouting.SequencedMessage
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
type ResequencedMessages =
  {DispatchableIndex: int;
   SequencedMessages: SequencedMessage [];}
  member AdvancedTo : dispatchableIndex:int -> ResequencedMessages

Full name: messagerouting.ResequencedMessages
ResequencedMessages.DispatchableIndex: int
ResequencedMessages.SequencedMessages: SequencedMessage []
val this : ResequencedMessages
member ResequencedMessages.AdvancedTo : dispatchableIndex:int -> ResequencedMessages

Full name: messagerouting.ResequencedMessages.AdvancedTo
val dispatchableIndex : int
val chaosRouter : consumer:'a -> mailbox:'b -> 'c

Full name: messagerouting.chaosRouter
val consumer : 'a
val random : 'a
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
namespace System
val resequencerConsumer : actualConsumer:'a -> mailbox:'b -> 'c

Full name: messagerouting.resequencerConsumer
val actualConsumer : 'a
val resequenced : 'a
module Array

from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> array:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.filter
val fold : folder:('State -> 'T -> 'State) -> state:'State -> array:'T [] -> 'State

Full name: Microsoft.FSharp.Collections.Array.fold
val exists : predicate:('T -> bool) -> array:'T [] -> bool

Full name: Microsoft.FSharp.Collections.Array.exists
val sequencedMessageConsumer : mailbox:'a -> 'b

Full name: messagerouting.sequencedMessageConsumer
val sequencedMessageConsumerRef : 'a

Full name: messagerouting.sequencedMessageConsumerRef
val resequencerConsumerRef : 'a

Full name: messagerouting.resequencerConsumerRef
val chaosRouterRef : 'a (requires member ( <! ))

Full name: messagerouting.chaosRouterRef
val index : int
val groupBy : projection:('T -> 'Key) -> list:'T list -> ('Key * 'T list) list (requires equality)

Full name: Microsoft.FSharp.Collections.List.groupBy
val maxBy : projection:('T -> 'U) -> list:'T list -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.List.maxBy
val subscribers : 'a
val budgetHikersPriceQuotes : priceQuoteRequestPublisher:'a -> mailbox:'p -> 'q (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.budgetHikersPriceQuotes
val priceQuoteRequestPublisher : 'a (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))
val quoterId : 'a
val highSierraPriceQuotes : priceQuoteRequestPublisher:'a -> mailbox:'p -> 'q (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.highSierraPriceQuotes
val mountainAscentPriceQuotes : priceQuoteRequestPublisher:'a -> mailbox:'p -> 'q (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.mountainAscentPriceQuotes
val pinnacleGearPriceQuotes : priceQuoteRequestPublisher:'a -> mailbox:'p -> 'q (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.pinnacleGearPriceQuotes
val rockBottomOuterwearPriceQuotes : priceQuoteRequestPublisher:'a -> mailbox:'p -> 'q (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.rockBottomOuterwearPriceQuotes
val orderProcessorRef : 'a (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagerouting.orderProcessorRef
type RegistrationProcess =
  {ProcessId: string;
   ProcessSteps: obj;
   CurrentStep: int;}
  member NextStep : unit -> 'a0
  member StepCompleted : unit -> RegistrationProcess
  member IsCompleted : bool
  static member Create : processId:string * processSteps:'a0 -> RegistrationProcess

Full name: messagerouting.RegistrationProcess
RegistrationProcess.ProcessId: string
RegistrationProcess.ProcessSteps: obj
RegistrationProcess.CurrentStep: int
static member RegistrationProcess.Create : processId:string * processSteps:'a0 -> RegistrationProcess

Full name: messagerouting.RegistrationProcess.Create
val processId : string
val processSteps : 'a
val this : RegistrationProcess
member RegistrationProcess.IsCompleted : bool

Full name: messagerouting.RegistrationProcess.IsCompleted
member RegistrationProcess.NextStep : unit -> 'a0

Full name: messagerouting.RegistrationProcess.NextStep
property RegistrationProcess.IsCompleted: bool
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val item : index:int -> list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.item
member RegistrationProcess.StepCompleted : unit -> RegistrationProcess

Full name: messagerouting.RegistrationProcess.StepCompleted
type RegisterCustomer =
  {RegistrationData: obj;
   RegistrationProcess: RegistrationProcess;}
  member Advance : unit -> unit

Full name: messagerouting.RegisterCustomer
RegisterCustomer.RegistrationData: obj
Multiple items
RegisterCustomer.RegistrationProcess: RegistrationProcess

--------------------
type RegistrationProcess =
  {ProcessId: string;
   ProcessSteps: obj;
   CurrentStep: int;}
  member NextStep : unit -> 'a0
  member StepCompleted : unit -> RegistrationProcess
  member IsCompleted : bool
  static member Create : processId:string * processSteps:'a0 -> RegistrationProcess

Full name: messagerouting.RegistrationProcess
val this : RegisterCustomer
member RegisterCustomer.Advance : unit -> unit

Full name: messagerouting.RegisterCustomer.Advance
val advancedProcess : RegistrationProcess
RegisterCustomer.RegistrationProcess: RegistrationProcess
member RegistrationProcess.StepCompleted : unit -> RegistrationProcess
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
member RegistrationProcess.NextStep : unit -> 'a0
val creditChecker : mailbox:'a -> 'b

Full name: messagerouting.creditChecker
val contactKeeper : mailbox:'a -> 'b

Full name: messagerouting.contactKeeper
val customerVault : mailbox:'a -> 'b

Full name: messagerouting.customerVault
val servicePlanner : mailbox:'a -> 'b

Full name: messagerouting.servicePlanner
val contactKeeper : system:'a -> id:string -> 'b

Full name: messagerouting.ServiceRegistry.contactKeeper
val system : 'a
val id : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val creditChecker : system:'a -> id:string -> 'b

Full name: messagerouting.ServiceRegistry.creditChecker
val customerVault : system:'a -> id:string -> 'b

Full name: messagerouting.ServiceRegistry.customerVault
val servicePlanner : system:'a -> id:string -> 'b

Full name: messagerouting.ServiceRegistry.servicePlanner
val processId : string

Full name: messagerouting.processId
val step1 : 'a

Full name: messagerouting.step1
module ServiceRegistry

from messagerouting
val step2 : 'a

Full name: messagerouting.step2
val step3 : 'a

Full name: messagerouting.step3
val step4 : 'a

Full name: messagerouting.step4
val registrationProcess : RegistrationProcess

Full name: messagerouting.registrationProcess
static member RegistrationProcess.Create : processId:string * processSteps:'a0 -> RegistrationProcess
val registrationData : 'a

Full name: messagerouting.registrationData
val registerCustomer : RegisterCustomer

Full name: messagerouting.registerCustomer
val processOf : processId:'a -> processes:Map<'a,'b> -> 'b (requires comparison)

Full name: messagerouting.Process.processOf
val processId : 'a (requires comparison)
val processes : Map<'a,'b> (requires comparison)
val startProcess : processId:'a -> process:'b -> self:'c -> processes:Map<'a,'b> -> Map<'a,'b> (requires comparison and member ( <! ))

Full name: messagerouting.Process.startProcess
val self : 'a (requires member ( <! ))
val stopProcess : processId:'a -> self:'b -> processes:Map<'a,'d> -> Map<'a,'d> (requires comparison and member ( <! ))

Full name: messagerouting.Process.stopProcess
val loanRateQuote : loanRateQuoteId:'a -> taxId:'b -> amount:'c -> termInMonths:'d -> loanBroker:'e -> mailbox:'f -> 'g

Full name: messagerouting.loanRateQuote
val loanRateQuoteId : 'a
val taxId : 'a
val amount : 'a
val termInMonths : 'a
val loanBroker : 'a
val loop : ('a -> 'b -> 'c -> 'd)
val bankLoanRateQuotes : 'a
val expectedLoanRateQuotes : 'a
val creditRatingScore : 'a
val minBy : projection:('T -> 'U) -> list:'T list -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.List.minBy
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val loanBroker : creditBureau:'a -> banks:'b -> mailbox:'c -> 'd

Full name: messagerouting.loanBroker
val creditBureau : 'a
val banks : 'a
val processes : 'a
module Process

from messagerouting
val creditBureau : mailbox:'a -> 'b

Full name: messagerouting.creditBureau
val creditRanges : int list
val randomCreditRangeGenerator : 'a
val randomCreditScoreGenerator : 'a
val bank : bankId:'a -> primeRate:decimal -> ratePremium:decimal -> mailbox:'b -> 'c

Full name: messagerouting.bank
val bankId : 'a
val primeRate : decimal
val ratePremium : decimal
val randomDiscount : 'a
val randomQuoteId : 'a
val calculateInterestRate : ('a -> decimal -> decimal -> decimal)
val months : decimal
val creditScore : decimal
val creditScoreDiscount : decimal
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
val creditBureauRef : 'a

Full name: messagerouting.creditBureauRef
val bank1Ref : 'a

Full name: messagerouting.bank1Ref
val bank2Ref : 'a

Full name: messagerouting.bank2Ref
val bank3Ref : 'a

Full name: messagerouting.bank3Ref
val loanBrokerRef : 'a (requires member ( <! ))

Full name: messagerouting.loanBrokerRef
F# Project
Fork me on GitHub