【问题标题】:Defining a Message Passing domain with very many message types定义具有非常多消息类型的消息传递域
【发布时间】:2019-01-02 20:00:05
【问题描述】:

到目前为止,我看到的大多数 F# 消息传递示例都使用 2-4 种消息类型,并且能够利用模式匹配将每个消息定向到其适当的处理函数。

对于我的应用程序,我需要数百种独特的消息类型,因为它们的处理性质和所需参数不同。到目前为止,每种消息类型都是其自己的记录类型,并附加了一个标记接口,因为在一个可区分的联合中包含数百种类型并不是很漂亮——而且这些类型的模式匹配也不是。因此,我目前正在使用反射来查找正确的消息处理函数。

有没有更好、更实用的方法来做到这一点?也许更聪明的方式来定义这样一个域?我想在编译时尽可能多地执行正确性,但目前我正在查找基于自定义属性的处理程序函数,并在运行时检查它们的签名。

据我所知,我无法使用 .NET 自定义属性强制执行函数签名,而且由于类型太多无法实际进行模式匹配,因此(据我所知)我不能使用单个通用消息处理函数任何一个。我尝试使用通用包装函数作为所有处理程序的“接口”,并且只将自定义属性附加到这个,但这并没有授予包装函数属性并通过基于该属性的反射使它们可见(我'我对 .NET 很陌生)。

我考虑过将处理函数作为成员附加到它们各自的记录类型的可能性,这将避免反射的需要并在编译时强制执行一些额外的正确性。但是,将所有这些功能都呈现在客户端并没有多大意义。

【问题讨论】:

    标签: reflection f# rabbitmq domain-driven-design message-queue


    【解决方案1】:

    这个问题有点宽泛,但我会试一试。

    让我们从类型开始。我们的消息将有一个类型和一个内容。您可以添加更多字段,例如messageId、sender、receiver 等。

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

    同样,我们的处理程序类型将类型和处理程序函数配对。

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

    我们希望在某个地方注册所有处理程序及其类型。字典是理想的,因为它速度很快:

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

    唯一的问题是字典不能有泛型类型,所以这里的所有处理程序都是MessageHandler&lt;obj&gt; 类型。因此,我们需要能够将&lt;'T&gt; 消息和处理程序转换为&lt;obj&gt; 消息和处理程序并返回。这里我们有一个辅助函数:

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

    以及将处理函数注册为&lt;obj&gt;处理函数的函数:

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

    我们可以为任何类型注册处理程序:

    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 )
    

    这是我们的通用消息处理程序:

    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
    

    创建消息:

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

    并像这样测试它:

    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;}"
    

    【讨论】:

    • 非常有趣的方法,它肯定比我的反射实现更能检查编译时间的正确性。非常感谢!我刚刚测试了一下,并尝试添加一些我自己的应用程序功能。明天必须继续,因为现在是我住的地方凌晨 4 点,但这看起来很有希望。
    • 很高兴你喜欢 :)
    猜你喜欢
    • 2012-10-12
    • 2019-09-04
    • 2012-06-03
    • 2012-06-12
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多