Reactive Messaging Patterns with F# and Akka.NET


Messaging Channels

For more details and full analysis of each pattern described in this section, please refer to Chapter 5 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

Point-to-Point Channel

The Point-to-Point Channel enables one actor to send messages to another specific actor. When using Akka.NET this is achieved using the actor's reference.

 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: 
let actorA actorB (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        return! loop ()
    }
    actorB <! "Hello, from actor A!"
    actorB <! "Hello again, from actor A!"
    loop ()

let actorB (mailbox: Actor<string>) =
    let rec loop hello helloAgain goodbye goodbyeAgain = actor {
        let! message = mailbox.Receive ()
        let hello = hello + (if message.Contains "Hello" then 1 else 0)
        let helloAgain = helloAgain + (if message.Contains "Hello again" then 1 else 0)
        assertion (hello = 0 || hello > helloAgain)
        let goodbye = goodbye + (if message.Contains "Goodbye" then 1 else 0)
        let goodbyeAgain = goodbyeAgain + (if message.Contains "Goodbye again" then 1 else 0)
        assertion (goodbye = 0 || goodbye > goodbyeAgain)
        printfn "ActorB: received %s" message
        return! loop hello helloAgain goodbye goodbyeAgain
    }
    loop 0 0 0 0 

let actorC actorB (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        return! loop ()
    }
    actorB <! "Goodbye, from actor C!"
    actorB <! "Goodbye again, from actor C!"
    loop ()

let actorBRef = spawn system "actorB" actorB
let actorARef = spawn system "actorA" <| actorA actorBRef
let actorCRef = spawn system "actorC" <| actorC actorBRef
Complete Code

Sections

Publish-Subscribe Channel

This pattern allows actors to publish messages that can be delivered to multiple (subscribed) actors.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let quoteListener (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! { Market = Market m; Ticker = Symbol t; Price = Money p } = mailbox.Receive ()
        printfn "QuoteListener: PricedQuoted received, market: %s, ticker: %s, price: %M" m t p 
        return! loop ()
    }
    loop () 

let quoteListenerRef = spawn system "quoteListenerRef" quoteListener
subscribe typeof<PricedQuoted> quoteListenerRef system.EventStream
publish { Market = Market("quotes/NASDAQ"); Ticker = Symbol "MSFT"; Price = Money(37.16m) } system.EventStream
Complete Code

Sections

Datatype Channel

When actors receive typed messages, they are using a Datatype Channel. In cases when they receive serialized messages, specific actors can be used to create typed ones.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
let productQueriesChannel (mailbox: Actor<_>) =
    let translateToProductQuery message = message |> Encoding.UTF8.GetString |> ProductQuery
    
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        let (ProductQuery value) = message |> translateToProductQuery
        printfn "ProductQueriesChannel: ProductQuery received, value: %s" <| value
        return! loop ()
    }
    loop ()

let productQueriesChannelRef = spawn system "productQueriesChannel" productQueriesChannel

productQueriesChannelRef <! Encoding.UTF8.GetBytes "test query"
Complete Code

Sections

Invalid Message Channel

The Invalid Message Channel pattern provides an approach to handle unsupported messages.

 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: 
type InvalidMessage<'a> = { Sender: IActorRef; Receiver: IActorRef; Message: 'a }

let invalidMessageChannel (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! { Sender = s; Receiver = r; Message = m } = mailbox.Receive ()
        printfn "InvalidMessageChannel: InvalidMessage received, message: %A" m
        return! loop ()
    }
    loop ()

let authenticator (nextFilter: IActorRef) (invalidMessageChannel: IActorRef) (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        match box message with
        | :? ProcessIncomingOrder as message ->
            let (ProcessIncomingOrder(bytes)) = message 
            let text = Encoding.Default.GetString bytes
            printfn "Decrypter: processing %s" text
            let orderText = text.Replace ("(encryption)", String.Empty)
            nextFilter <! ProcessIncomingOrder(Encoding.Default.GetBytes orderText)
        | invalid -> invalidMessageChannel <! { Sender = mailbox.Sender (); Receiver = mailbox.Self; Message = invalid }
        return! loop () 
    }
    loop ()

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

let invalidMessageChannelRef = spawn system "invalidMessageChannel" invalidMessageChannel
let nextFilterRef = spawn system "nextFilter" nextFilter
let authenticatorRef = spawn system "authenticator" <| authenticator nextFilterRef invalidMessageChannelRef

authenticatorRef <! "Invalid message"
Complete Code

Sections

Dead Letter Channel

The Dead Letter Channel receives all the messages that didn't reach the desired destination. Any actor can subscribe to the Dead Letter provided by Akka.NET.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let sysListener (mailbox: Actor<DeadLetter>) = 
    let rec loop () = actor {
        let! deadLetter = mailbox.Receive ()
        printfn "SysListner, DeadLetter received: %A" deadLetter.Message
        return! loop ()
    }
    loop ()

let sysListenerRef = spawn system "sysListener" sysListener
subscribe typeof<DeadLetter> sysListenerRef system.EventStream

let deadActorRef = select "akka://system/user/deadActor" system
deadActorRef <! "Message to dead actor"
Complete Code

Sections

Guaranteed Delivery

This pattern ensures that all the messages are received by the destination actor.

1: 
// TBD: Akka Persistence is not fully supported yet

Sections

Channel Adapter

This pattern exposes a messaging interface and connects it to the application.

 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: 
let stockTrader (tradingBus: IActorRef) (buyerService: BuyerService) (sellerService: SellerService) (mailbox: Actor<_>) =
    let applicationId = mailbox.Self.Path.Name
    tradingBus <! RegisterCommandHandler(applicationId, "ExecuteBuyOrder", mailbox.Self)
    tradingBus <! RegisterCommandHandler(applicationId, "ExecuteSellOrder", mailbox.Self)
    
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        match message with
        | ExecuteBuyOrder(i, s, q, p) -> 
            let result = buyerService.PlaceBuyOrder (i, s, q, p)
            tradingBus <! TradingNotification("BuyOrderExecuted", BuyOrderExecuted(result.PortfolioId, result.OrderId, result.Symbol, result.Quantity, result.TotalCost))
        | ExecuteSellOrder(i, s, q, p) -> 
            let result = buyerService.PlaceBuyOrder (i, s, q, p)
            tradingBus <! TradingNotification("SellOrderExecuted", SellOrderExecuted(result.PortfolioId, result.OrderId, result.Symbol, result.Quantity, result.TotalCost))
        return! loop ()
    }
    loop ()

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

let tradingBusRef = spawn system "tradingBus" tradingBus
let stockTraderRef = spawn system "stockTrader" (stockTrader <| tradingBusRef <| new BuyerService() <| new SellerService ())

stockTraderRef <! ExecuteBuyOrder("1", Symbol "S1", 5, Money 10m)
stockTraderRef <! ExecuteSellOrder("2", Symbol "S2", 3, Money 8m)
Complete Code

Sections

Message Bridge

The Message Bridge pattern integrates two applications using different messaging technologies.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
type RabbitMQTextMessage = RabbitMQTextMessage of string

let inventoryProductAllocationBridge (mailbox: Actor<_>) =
    let translatedToInventoryProductAlloction = sprintf "Inventory product alloction for %s"
    let acknowledgeDelivery (RabbitMQTextMessage textMessage) = printfn "InventoryProductAllocationBridge: acknowledged '%s'" textMessage

    let rec loop () = actor {
        let! message = mailbox.Receive ()
        let (RabbitMQTextMessage textMessage) = message
        printfn "InventoryProductAllocationBridge: received '%s'" textMessage
        let inventoryProductAllocation = translatedToInventoryProductAlloction textMessage
        printfn "InventoryProductAllocationBridge: translated '%s'" inventoryProductAllocation
        acknowledgeDelivery message
        return! loop ()
    }
    loop ()

let inventoryProductAllocationBridgeRef = spawn system "inventoryProductAllocationBridge" inventoryProductAllocationBridge

inventoryProductAllocationBridgeRef <! RabbitMQTextMessage "Rabbit test message"
Complete Code

Sections

Message Bus

The Message Bus pattern connects different applications in a decoupled fashion.

 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: 
let tradingBus (mailbox: Actor<_>) =
    let rec loop commandHandlers notificationInterests = actor {
        let dispatchCommand commandId command =
            commandHandlers 
            |> Map.tryFind commandId
            |> Option.map (fun hs -> hs |> List.iter (fun (CommandHandler(_, h)) -> h <! command))
            |> ignore

        let dispatchNotification notificationId notification =
            notificationInterests 
            |> Map.tryFind notificationId
            |> Option.map (fun hs -> hs |> List.iter (fun (NotificationInterest(_, i)) -> i <! notification))
            |> ignore

        let registerCommandHandler commandId applicationId handler =
            let commandHandler = CommandHandler(applicationId, handler)
            commandHandlers
            |> Map.tryFind commandId
            |> Option.fold (fun _ hs -> commandHandler :: hs) [commandHandler]
            |> fun hs -> Map.add commandId hs commandHandlers

        let registerNotificationInterest notificationId applicationId interested =
            let notificationInterest = NotificationInterest(applicationId, interested)
            notificationInterests
            |> Map.tryFind notificationId
            |> Option.fold (fun _ is -> notificationInterest :: is) [notificationInterest]
            |> fun is -> Map.add notificationId is notificationInterests

        let! message = mailbox.Receive ()
        match message with
        | RegisterCommandHandler(applicationId, commandId, handler) -> 
            return! loop (registerCommandHandler commandId applicationId handler) notificationInterests
        | RegisterNotificationInterest(applicationId, notificationId, interested) -> 
            return! loop commandHandlers (registerNotificationInterest notificationId applicationId interested)
        | TradingCommand(commandId, command) -> dispatchCommand commandId command
        | TradingNotification(notificationId, notification) -> dispatchNotification notificationId notification
        | Status -> 
            printfn "TradingBus: STATUS: %A" commandHandlers
            printfn "TradingBus: STATUS: %A" notificationInterests
        return! loop commandHandlers notificationInterests
    }
    loop Map.empty Map.empty

let marketAnalysisTools tradingBus (mailbox: Actor<_>) =
    let applicationId = mailbox.Self.Path.Name
    tradingBus <! RegisterNotificationInterest(applicationId, "BuyOrderExecuted", mailbox.Self)
    tradingBus <! RegisterNotificationInterest(applicationId, "SellOrderExecuted", mailbox.Self)
    
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        match message with
        | BuyOrderExecuted _ as executed -> printfn "MarketAnalysisTools: adding holding: %A" executed
        | SellOrderExecuted _ as executed -> printfn "MarketAnalysisTools: adjusting holding: %A" executed
        return! loop () 
    }
    loop ()

let portfolioManager tradingBus (mailbox: Actor<_>) =
    let applicationId = mailbox.Self.Path.Name
    tradingBus <! RegisterNotificationInterest(applicationId, "BuyOrderExecuted", mailbox.Self)
    tradingBus <! RegisterNotificationInterest(applicationId, "SellOrderExecuted", mailbox.Self)
    
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        match message with
        | BuyOrderExecuted _ as executed -> printfn "PortfolioManager: adding holding: %A" executed
        | SellOrderExecuted _ as executed -> printfn "PortfolioManager: adjusting holding: %A" executed
        return! loop () 
    }
    loop ()

let stockTrader tradingBus (mailbox: Actor<_>) =
    let applicationId = mailbox.Self.Path.Name
    tradingBus <! RegisterCommandHandler(applicationId, "ExecuteBuyOrder", mailbox.Self)
    tradingBus <! RegisterCommandHandler(applicationId, "ExecuteSellOrder", mailbox.Self)
    
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        match message with
        | ExecuteBuyOrder(portfolioId, symbol, quantity, price) as buy -> 
            printfn "StockTrader: buying for: %A" buy
            tradingBus <! TradingNotification("BuyOrderExecuted", BuyOrderExecuted(portfolioId, symbol, quantity, price))
        | ExecuteSellOrder(portfolioId, symbol, quantity, price) as sell ->
            printfn "StockTrader: selling for: %A" sell
            tradingBus <! TradingNotification("BuyOrderExecuted", SellOrderExecuted(portfolioId, symbol, quantity, price))
        return! loop () 
    }
    loop ()

let tradingBusRef = spawn system "tradingBus" tradingBus
let marketAnalysisToolsRef = spawn system "marketAnalysisTools" <| marketAnalysisTools tradingBusRef
let portfolioManagerRef = spawn system "portfolioManager" <| portfolioManager tradingBusRef
let stockTraderRef = spawn system "stockTrader" <| stockTrader tradingBusRef

tradingBusRef <! Status
tradingBusRef <! TradingCommand("ExecuteBuyOrder", ExecuteBuyOrder("p123", "MSFT", 100, Money 31.85m))
tradingBusRef <! TradingCommand("ExecuteSellOrder", ExecuteSellOrder("p456", "MSFT", 200, Money 31.80m))
tradingBusRef <! TradingCommand("ExecuteBuyOrder", ExecuteBuyOrder("p789", "MSFT", 100, Money 31.83m))
Complete Code

Sections

val actorA : actorB:obj -> mailbox:'a -> 'b

Full name: messagingchannels.actorA
val actorB : obj
val mailbox : 'a
val loop : (unit -> 'a)
val actorB : mailbox:'a -> 'b

Full name: messagingchannels.actorB
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 loop : ('a -> 'b -> 'c -> 'd -> 'e)
val hello : 'a
val helloAgain : 'a
val goodbye : 'a
val goodbyeAgain : 'a
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

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

Full name: messagingchannels.actorC
val actorBRef : obj

Full name: messagingchannels.actorBRef
val actorARef : obj

Full name: messagingchannels.actorARef
val actorCRef : obj

Full name: messagingchannels.actorCRef
val quoteListener : mailbox:'a -> 'b

Full name: messagingchannels.quoteListener
val quoteListenerRef : obj

Full name: messagingchannels.quoteListenerRef
val typeof<'T> : System.Type

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

Full name: messagingchannels.productQueriesChannel
val translateToProductQuery : ('a -> 'b)
val message : 'a
val productQueriesChannelRef : obj

Full name: messagingchannels.productQueriesChannelRef
type InvalidMessage<'a> =
  {Sender: obj;
   Receiver: obj;
   Message: 'a;}

Full name: messagingchannels.InvalidMessage<_>
InvalidMessage.Sender: obj
InvalidMessage.Receiver: obj
InvalidMessage.Message: 'a
val invalidMessageChannel : mailbox:'a -> 'b

Full name: messagingchannels.invalidMessageChannel
val authenticator : nextFilter:'a -> invalidMessageChannel:'b -> mailbox:'c -> 'd

Full name: messagingchannels.authenticator
val nextFilter : 'a
val invalidMessageChannel : 'a
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
module String

from Microsoft.FSharp.Core
val nextFilter : mailbox:'a -> 'b

Full name: messagingchannels.nextFilter
val invalidMessageChannelRef : obj

Full name: messagingchannels.invalidMessageChannelRef
val nextFilterRef : obj

Full name: messagingchannels.nextFilterRef
val authenticatorRef : obj

Full name: messagingchannels.authenticatorRef
val sysListener : mailbox:'a -> 'b

Full name: messagingchannels.sysListener
val sysListenerRef : 'a

Full name: messagingchannels.sysListenerRef
val deadActorRef : 'a (requires member ( <! ))

Full name: messagingchannels.deadActorRef
val stockTrader : tradingBus:'a -> buyerService:'d -> sellerService:'e -> mailbox:'f -> 'g (requires member ( <! ) and member ( <! ))

Full name: messagingchannels.stockTrader
val tradingBus : 'a (requires member ( <! ) and member ( <! ))
val buyerService : 'a
val sellerService : 'a
val applicationId : 'a
val tradingBus : mailbox:'a -> 'b

Full name: messagingchannels.tradingBus
val tradingBusRef : 'a

Full name: messagingchannels.tradingBusRef
val stockTraderRef : 'a (requires member ( <! ) and member ( <! ))

Full name: messagingchannels.stockTraderRef
Multiple items
union case RabbitMQTextMessage.RabbitMQTextMessage: string -> RabbitMQTextMessage

--------------------
type RabbitMQTextMessage = | RabbitMQTextMessage of string

Full name: messagingchannels.RabbitMQTextMessage
val inventoryProductAllocationBridge : mailbox:'a -> 'b

Full name: messagingchannels.inventoryProductAllocationBridge
val translatedToInventoryProductAlloction : (string -> string)
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val acknowledgeDelivery : (RabbitMQTextMessage -> unit)
val textMessage : string
val inventoryProductAllocationBridgeRef : 'a (requires member ( <! ))

Full name: messagingchannels.inventoryProductAllocationBridgeRef
val loop : ('a -> 'b -> 'c)
val commandHandlers : 'a
val notificationInterests : 'a
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 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 map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
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 iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
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 empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
val marketAnalysisTools : tradingBus:'a -> mailbox:'o -> 'p (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagingchannels.marketAnalysisTools
val tradingBus : 'a (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))
val portfolioManager : tradingBus:'a -> mailbox:'o -> 'p (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagingchannels.portfolioManager
val stockTrader : tradingBus:'a -> mailbox:'o -> 'p (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagingchannels.stockTrader
val tradingBusRef : 'a (requires member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ) and member ( <! ))

Full name: messagingchannels.tradingBusRef
val marketAnalysisToolsRef : 'a

Full name: messagingchannels.marketAnalysisToolsRef
val portfolioManagerRef : 'a

Full name: messagingchannels.portfolioManagerRef
val stockTraderRef : 'a

Full name: messagingchannels.stockTraderRef
F# Project
Fork me on GitHub