F# 如何将函数类型作为业务需求公开,而不公开其异步依赖关系?
我更喜欢将业务需求建模为代码库中的功能类型:F# 如何将函数类型作为业务需求公开,而不公开其异步依赖关系?,f#,domain-driven-design,F#,Domain Driven Design,我更喜欢将业务需求建模为代码库中的功能类型: type Subscribe = SubscribeRequest -> Result<SubscribedCourier,ErrorDescription> 问题: 当实现单元测试时,上面的函数是很好的。但是,当上述函数需要依赖外部系统来完成其任务时,我认为该函数需要对该函数签名使用异步修饰符 因此,我现在有义务将我的函数类型更新为以下内容: type Subscribe = SubscribeRequest -> Asy
type Subscribe = SubscribeRequest -> Result<SubscribedCourier,ErrorDescription>
问题:
当实现单元测试时,上面的函数是很好的。但是,当上述函数需要依赖外部系统来完成其任务时,我认为该函数需要对该函数签名使用异步修饰符
因此,我现在有义务将我的函数类型更新为以下内容:
type Subscribe = SubscribeRequest -> Async<Result<SubscribedCourier,ErrorDescription>>
虽然上面的函数类型对于返回异步结果类型是正确的,但是对于函数如何执行,它现在是一个有漏洞的抽象。我真的只想通过功能类型指定业务需求,并让我的功能映射到这些功能类型,就像它们是联系人一样
问题:
总之,如何在不公开异步依赖项的情况下将函数类型公开为业务需求?我不知道为什么要为函数定义类型别名,但我同意,如果要实现,业务逻辑不应该是异步的。在F#中,我认为观察是有意义的。除非执行I/O,否则很少需要返回异步工作流 我认为应该保留域模型,因此,正如您所写 您通常可以通过重构到一个不纯净的纯不纯净三明治来解决这个问题。这里似乎也是这样。据我所知,将消息实际放入队列的工作看起来完全是通用的。我想您可以将其提取到一个helper函数中,如下所示:
let subscribe : Publish.Subscribe =
fun request ->
async {
let subscribed = request |> toSubscribedCourier
let json = JsonConvert.SerializeObject subscribed
let buffer = Encoding.ASCII.GetBytes(json)
let message = Message(buffer)
let topicClient = new TopicClient("MyConnectionString","Subscription.subscribed")
do! topicClient.SendAsync(message) |> Async.AwaitTask
return Ok subscribed
}
let send x =
let json = JsonConvert.SerializeObject x
let buffer = Encoding.ASCII.GetBytes json
let message = Message buffer
let topicClient = new TopicClient ("MyConnectionString", "Subscription.subscribed")
do! topicClient.SendAsync message |> Async.AwaitTask
return Ok x
(我还没有尝试编译这个,所以可能会有一些小问题。)
现在,您可以将三明治创建为简单的组合:
let sandwich = toSubscribedCourier >> send
(同样,这可能无法编译,但希望能让人明白这一点。)
有可能在到Subscribed Courier
中几乎没有逻辑,但那只是事实。正如我在书中所写:
一旦你去除了所有偶然的复杂性,你就发现了本质的复杂性
也许没有太多的领域逻辑,但这一事实只有在你开始将纯函数与非纯行为分开后才被揭示出来。我不确定我是否遵循。在我看来,你有三个选择。1.保持原样,2。返回一个异步,3。创建返回的AsyncResult类型。如果使用一个,只需同步调用它来隐藏异步内容。我不确定我是否认为隐藏它异步有那么糟糕。我认为Scott Wlashin在他的书中有一个很好的例子。你不想在这里隐藏
Async
类型,重要的是让代码自上而下保持异步。当你说“业务需求”时,我假设你指的是“域模型”。保持域模型代码不受Async
的影响是合理的,因为Async代码通常“起作用”,并且域代码没有副作用。所以我觉得你这里的代码很好。@Tomasjanson我创建了一个AsyncResult类型:类型AsyncResult=Async我想我理解你的推理。我决定保持代码的原样,这是因为对于我试图完成的事情来说,复杂度增加了(投资回报率很低)。
let sandwich = toSubscribedCourier >> send