使用它登录功能或功能?

时间:2014-07-15 09:58:46

标签: f#

它是最好(我知道没有银弹,但通过使用一个可能有一些优势) - 登录调用函数,或函数调用它?

示例:

方法1


module MongoDb =
   let tryGetServer connectionString =
      try
         let server = new MongoClient(connectionString).GetServer()
         server.Ping()
         Some server
      with _ -> None

用法:

match MongoDb.tryGetServer Config.connectionString with
| None ->
    logger.Information "Unable to connect to the database server."
    // ... code ...
| Some srv ->
    logger.Information "Successfully connected to the database server."
    // ... code ...

方法2


module MongoDb =
    let tryGetServer connectionString =
        try
            let server = new MongoClient(connectionString).GetServer()
            server.Ping()
            Some server
        with _ -> None

    let tryGetServerLogable connectionString logger =
        match tryGetServer connectionString with
        | None -> 
            logger.Information "Unable to connect to the database server."
            None
        | Some srv -> 
            logger.Information "Successfully connected to the database server."
            Some srv

用法:

match MongoDb.tryGetServerLogable Config.connectionString logger with
| None ->
    // ... code ...
| Some srv ->
    // ... code ...

2 个答案:

答案 0 :(得分:5)

方法2 更好。一般来说,日志记录是跨领域关注,因此它最好与实现细节分离。交叉问题最好通过作文来解决;在OOD,this can be done with Decorators or Interceptors。在FP中,我们有时可以从OOD中学习,因为许多the principles translate from objects to closures

然而,我不是逐字逐句地使用方法2 ,而是更喜欢这样的事情:

module MongoDb =
    let tryGetServer connectionString =
        try
            let server = MongoClient(connectionString).GetServer()
            server.Ping()
            Some server
        with _ -> None

请注意,MongoDb模块不了解日志记录。这遵循Single Responsibility Principle,这在函数式编程中也很有价值。

tryGetServer函数具有以下签名:

string -> MongoServer option

现在您可以定义一个与MongoDb模块完全分离的日志记录功能:

module XyzLog =
    type Logger() =
        member this.Information message = ()

    let tryGetServer f (logger : Logger) connectionString  =
        match f connectionString with
        | None ->
            logger.Information "Unable to connect to the database server."
            None
        | Some srv ->
            logger.Information "Successfully connected to the database server."
            Some srv

在这里,您可以想象XyzLog是特定日志记录模块的占位符,使用Serilog,Log4Net,NLog,您自己的自定义日志记录框架或类似...

f参数是一个带有通用签名'a -> 'b option的函数,其中MongoDb.tryGetServer是一个特殊化。

这意味着您现在可以像这样定义部分应用的功能

let tgs = XyzLog.tryGetServer MongoDb.tryGetServer (XyzLog.Logger())

函数tgs 具有签名

string -> MongoServer option

因此,依赖具有此签名的函数的任何客户端都可以互换使用MongoDb.tryGetServertgs,而不知道其中的差异。

这使您可以相互独立地改变您的想法或重构MongoDb.tryGetServer和您的日志记录基础结构。

答案 1 :(得分:4)

有一种更通用的方法可以实现横切关注点,例如使用函数式语言进行日志记录。我的例子来自异步服务库(想想ASP.NET MVC和ActionFilters),但同样适用于此。如Mark所述,函数tryGetServer的类型为string -> MongoServer option。假设我们将其抽象为:

type Service<'a, 'b> = 'a -> 'b option

然后假设我们也有如下类型:

type Filter<'a, 'b> = 'a -> Service<'a, 'b> -> 'b option

过滤器是一个函数,它取值'aService<'a, 'b>,然后返回与Service<'a, 'b>函数相同类型的值。最简单的过滤器是一个函数,它只是将它直接接收的'a传递给服务,并返回它从服务中获取的值。更有趣的过滤器是在接收来自服务的输出之后打印日志消息的功能。

let loggingFilter (connStr:string) (tryGetServer:string -> MongoServer option) : Filter<string, MongoServer option> =
  let server = tryGetServer connStr 
  match tryGetServer connStr with
  | Some _ ->
    logger.Information "Successfully connected to the database server."
    server
  | None -> 
    logger.Information "Unable to connect to the database server."
    server

然后,如果您定义了以下内容:

type Continuation<'a,'r> = ('a  -> 'r) -> 'r

module Continuation =     

    let bind (m:Continuation<'a, 'r>) k c = m (fun a -> k a c)

module Filter =

    /// Composes two filters into one which calls the first one, then the second one.
    let andThen (f2:Filter<_,,_>) (f1:Filter<_,_>) : Filter<_,_> = fun input -> Continuation.bind (f1 input) f2

    /// Applies a filter to a service returning a filtered service.
    let apply (service:Service<_,_>) (filter:Filter<_,_>) : Service<_,_> = fun input -> filter input service

    /// The identity filter which passes the input directly to the service and propagates the output.
    let identity : Filter<_,_> = fun (input:'Input) (service:Service<_,_>) -> service input

您可以将过滤器应用于服务并返回原始服务类型,但现在可以进行日志记录:

let tryGetServerLogable = Filter.apply tryGetServer loggingFilter

为什么要这么麻烦?那么,现在你可以组合过滤器了。例如,您可以添加一个过滤器来衡量创建连接所需的时间,然后您可以使用Filter.andThen将它们组合在一起。我最初制作的要点是here

另一种需要考虑的方法是使用writer monad。使用编写器monad,您可以将日志消息的实际打印推迟到某个明确定义的点,但仍具有相似的组合特征。

相关问题