F#中的存储库模式

yan*_*nta 12 .net f#

我正在研究原型使用文档数据库(目前MongoDB,可能会改变),并发现.NET驱动程序有点痛苦,所以我想我会用Repository模式抽象数据访问.这应该可以很容易地将你正在使用的任何驱动程序(NoRM,mongodb-csharp,simple-mongob)替换为你的杀手f#mongodb驱动程序,它在准备就绪时不会吮吸.

我的问题是关于添加操作.这将对数据库产生一些副作用,因此对All的后续调用将会有所不同.我应该关心吗?在C#传统上我不会,但我觉得在F#我应该.

这是通用存储库接口:

type IRepository<'a> =
    interface
        abstract member All : unit -> seq<'a>

        // Add has a side-effect of modifying the database
        abstract member Add : 'a -> unit
    end
Run Code Online (Sandbox Code Playgroud)

以下是MongoDB实现的外观:

type Repository<'b when 'b : not struct>(server:MongoDB.IMongo,database) =
    interface IRepository<'b> with

        member x.All() =
            // connect and return all

        member x.Add(document:'b) =
            // add and return unit
Run Code Online (Sandbox Code Playgroud)

在整个应用程序中,我将使用IRepository,从而可以轻松更改驱动程序和可能的数据库.

调用All很好,但是添加我希望的是而不是返回单元,返回一个新的存储库实例.就像是:

        // Add has a side-effect of modifying the database
        // but who cares as we now return a new repository
        abstract member Add : 'a -> IRepository<'a>
Run Code Online (Sandbox Code Playgroud)

问题是如果我调用Get,然后添加,原始存储库仍然会返回所有文档.例:

let repo1 = new Repository<Question>(server,"killerapp") :> IRepository<Question>
let a1 = repo1.All() 
let repo2 = repo1.Add(new Question("Repository pattern in F#"))
let a2 = repo2.All()
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望a1和a2的长度不同,但它们与数据库中的长度相同.应用程序工作,用户可以问他们的问题,但程序员不知道为什么它返回一个新的IRepository.

那么我应该尝试在类型设计中处理Add对数据库的副作用吗?其他人如何解决这个问题,您是使用Repository还是这样的接口类还是有更好的功能方法?

Tim*_*son 5

看起来您正在将不变性应用于影响外部世界状态的函数。无论 F# 实现如何,您认为它在 MongoDB 级别如何工作?您将如何防止repo1看到repo2所做的任何更改?如果其他进程影响数据库,会发生什么情况——在这种情况下同时执行repo1并更改?repo2

换句话说,想象一下System.Console这样的实现。如果Console.Out.WriteLine始终返回一个新的不可变对象,它将如何与 的调用交互Console.In.ReadLine

编辑 tl;dr:不要这样做。有时副作用是好的。