Defining a Message Passing domain with very many message types

佐手、 提交于 2021-02-07 18:05:59

问题


Most F# Message Passing examples I've seen so far are working with 2-4 message types, and are able to utilize pattern matching to direct each message to its proper handler function.

For my application, I need hundreds of unique message types due to the different nature of their handling and required parameters. So far, each message type is its own record type with a marker interface attached, because including hundreds of types in a single discriminated union would not be very pretty - and neither would the pattern matching of these be. As a result, I'm currently using reflection to find the correct handler functions of messages.

Is there a better, and more functional way of doing this? Perhaps even a smarter way to define such a domain? I'd like to enforce as much correctness as possible at compile time, but currently I'm finding the handler functions based on a custom attribute, as well as checking their signature at run time.

As far as I know, I cannot enforce a function's signature with a .NET custom attribute, and since there are too many types to realistically pattern match, I can't (to my knowledge) use a single generic message handler function either. I tried using a generic wrapper function as an "interface" for all handlers, and only attaching the custom attribute to this one, but this didn't grant the wrapped functions the attribute and make them visible through reflection based on that attribute (I'm very new to .NET).

I have thought about the possibility of attaching the handler functions to their respective record type as a member, which would circumvent the need for reflection and enforce some additional correctness at compile time. However, it doesn't make much sense to have all those functions present client side.


回答1:


The question is a little broad but I will give it a try.

Lets start with the types. Our message will have a type and a content. You can add more fields like messageId, sender, receiver, etc.

type MessageType = MessageType of string
type Message<'T> = {
    messageType : MessageType
    message     : 'T
}

Similarly our handler type will pair both type and handler function.

type HandlerResult      = Result<string, string>
type MessageHandler<'T> = {
    messageType : MessageType
    handlerF    : Message<'T> -> HandlerResult
}

We want to have someplace to register all handlers with their types. A Dictionary is ideal because it is fast:

let Handlers = System.Collections.Generic.Dictionary<MessageType, MessageHandler<obj>>()

The only thing is the dictionary cannot have a generic type so all handlers here are going to be of type MessageHandler<obj>. Because of that we need to be able to convert <'T> messages and handlers to <obj> messages and handlers and back. Se here we have a helper function:

let ofMessageGen (msg: Message<obj>) : Message<_> = {
    messageType =       msg.messageType
    message     = unbox msg.message
}

and a function to register the handler function as an <obj> handler:

let registerHandler (handlerF:Message<'T> -> HandlerResult) = 
    let handler = {
        messageType = MessageType <| (typeof<'T>).FullName
        handlerF    = ofMessageGen >> handlerF
    }
    Handlers.Add(handler.messageType, handler )

With that we can register handlers for any types:

registerHandler  (fun msg -> sprintf "String message: %s" msg.message |> Ok )
registerHandler  (fun msg -> sprintf "int    message: %d" msg.message |> Ok )
registerHandler  (fun msg -> sprintf "float  message: %f" msg.message |> Ok )

and here is our generic message handler:

let genericHandler (msg:Message<obj>) : HandlerResult =
    match Handlers.TryGetValue msg.messageType with
    | false, _       -> Error <| sprintf "No Handler for message: %A" msg
    | true , handler -> handler.handlerF msg

to create a message:

let createMessage (m:'T) = {
    messageType = MessageType <| (typeof<'T>).FullName
    message     = box m
}

and test it like this:

createMessage "Hello" |> genericHandler |> printfn "%A" 
createMessage 123     |> genericHandler |> printfn "%A" 
createMessage 123.4   |> genericHandler |> printfn "%A" 
createMessage true    |> genericHandler |> printfn "%A" 

// Ok "String message: Hello"
// Ok "int    message: 123"
// Ok "float  message: 123.400000"    
// Error
//  "No Handler for message: {messageType = MessageType "System.Boolean";
// message = true;}"


来源:https://stackoverflow.com/questions/54012421/defining-a-message-passing-domain-with-very-many-message-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!