F# 如何设置非平凡数据持久性?

F# 如何设置非平凡数据持久性?,f#,functional-programming,F#,Functional Programming,一般来说,问题是:如何以良好的功能方式设置非平凡数据持久性? 我更喜欢那些有实际经验的人的回答,那些真正做过的人。但我也很高兴听到关于这个问题的任何其他想法 现在,让我用一些例子来阐明我的意思。 假设我有一些数据,我的程序需要处理和保存。喂,我们的老朋友员工: module Employees = type Employee = { Name: string; Age: int; /* etc. */ } module EmployeesPersistence = type Emplo

一般来说,问题是:如何以良好的功能方式设置非平凡数据持久性?
我更喜欢那些有实际经验的人的回答,那些真正做过的人。但我也很高兴听到关于这个问题的任何其他想法

现在,让我用一些例子来阐明我的意思。 假设我有一些数据,我的程序需要处理和保存。喂,我们的老朋友
员工

module Employees =
  type Employee = { Name: string; Age: int; /* etc. */ }

module EmployeesPersistence =
  type EmployeeId = ...

  let getEmployee: (id:EmployeeId -> Employee) = ...
  let updateEmployee: (id:EmployeeId -> c:Employee -> unit) = ...
  let newEmployee: (c:Employee -> EmployeeId) = ...
持久性函数是如何实现的其实并不重要,比如说,它们可以访问关系数据库、基于文档的数据库,甚至是磁盘上的文件。我现在不在乎

然后我有一个程序,可以对他们做一些事情:

module SomeLogic =
   let printEmployees emps = 
     let print { Name = name, Age = age } = printfn "%s %d" name age
     Seq.iter print emps
到目前为止,一切都很简单

现在,假设我有另一种数据,
部门
,它与
员工
相关,但需要单独存储(在单独的表、单独的集合、单独的文件中)。
为什么要独立?老实说,我真的不知道。我所知道的是,它需要有自己的标识,这样我就可以显示/更新它,而不必与任何特定员工有任何关系。从这一点,我推断存储必须是分开的,但我对其他方法持开放态度

所以我可以做和我对员工做的差不多的事情:

module Departments =
  type Department = { Name: string; /* etc. */ }

module DepartmentsPersistence =
  type DepartmentId = ...
  let getDepartment, updateDepartment, newDepartment = ...
但是现在,我该如何表达这种关系呢

尝试#1:忽略持久性。 按照“抽象所有事物”这一古老的伟大传统,让我们在设计数据模型时假装没有持久性。我们稍后再添加它。总有一天。它是“正交的”。这是“辅助”。或者类似的

module Employees =
  type Employee = { Name: string; Age: int; Department: Department }

module SomeLogic =
   let printEmployees emps = 
     let print { Name = name, Age = age, Department = { Name = dept } } = printfn "%s %d (%s)" name age dept
     Seq.iter print emps
这种方法意味着每次从持久性存储加载员工时,我都必须同时加载部门。浪费的然后,
Employee
-
Department
只是一种关系,但在实际应用中,我拥有的远不止这些。那么,我每次都要加载整个图表吗?太贵了

尝试2:拥抱坚持。 好的,因为我不能直接在
员工
中包含
部门
,所以我将包含部门的人工身份,以便在需要时加载它

module Employees =
  type Employee = { Name: string; Age: int; Department: DepartmentId }
现在,我的模型本身没有任何意义:我现在基本上是在为我的存储建模,而不是为我的域建模。
然后,需要部门的“逻辑”功能必须通过“loadDepartment”功能参数化:

module SomeLogic =
   let printEmployees loadDept emps = 
     let print { Name = name, Age = age, Department = deptId } = 
       let { Name = deptName } = loadDept deptId
       printfn "%s %d (%s)" name age deptName
     Seq.iter print emps
因此,我的函数本身也没有意义。持久性已经成为我程序中不可或缺的一部分,远远不是一个“正交概念”

尝试3:隐藏持久性。 好的,所以我不能直接包括部门,我也不喜欢包括部门Id,我该怎么办?
这里有一个想法:我可以包括一个部门的承诺

module Employees =
  type Employee = { Name: string; Age: int; Department: () -> Department }

module SomeLogic =
   let printEmployees emps = 
     let print { Name = name, Age = age, Department = dept } = printfn "%s %d (%s)" name age (dept()).Name
     Seq.iter print emps
到目前为止,这看起来是最干净的方式(顺便说一句,这就是ORMs所做的),但仍然不是没有问题。
首先,模型本身并没有确切的意义:一些组件是直接包含的,另一些是通过“承诺”包含的,没有任何立即明显的原因(换句话说,持久性泄漏)。
另一方面,有一个部门作为一个承诺是很好的阅读,但我如何更新?我想我可以用镜头来代替承诺,但这会变得更复杂

所以 人们实际上是如何做到这一点的?我应该在哪里妥协,什么可行,什么不可行?我真的必须妥协吗,难道没有一种“纯粹”的方式吗?

因为显然存在真正的数据驱动应用程序,所以必须有某种方式来完成这些事情,对吗?是吗?。

以及里面所有的
()
单元
可能会告诉你:这不是一个功能性问题(没有纯粹的方法),因为你到处都在处理副作用——问题可能是:我怎样才能在.net中隐藏这样的东西(因为F#与.net中的任何其他语言都没有什么不同)。因此,在ORM世界中,您所有的3种尝试都已经存在了:1)是急切加载,3)是延迟加载,2)忽略连接;)。。。至于这个问题,什么更好?很抱歉,这取决于你的情况,但也许我可以帮你一点我的意见:我认为DDD方法是个好主意-在你的域中搜索有界上下文,检查你是否可以将其中的聚合完全加载到内存中,并使用1-通常这没有问题,在您的示例中,这意味着:是的,您应该与您的员工一起加载部门信息,但在员工上下文中,您可能不应该同时加载该部门中的所有其他同事(因此,稍后您可能会在部门上下文中得到另一个
部门
模型)。我的重点是,我不想这样做“隐藏”。理想情况下,我希望我的模型独立于持久性。现实情况下,我希望有一种方法能很好地符合F#(ORM不符合这个标准)。答案是好的旧POCO(如果你想在这里使用简单的旧记录)-这在你的enterprisey OOP世界中也是一个常见的问题,这一次我认为那里的解决方案相当不错;)关于你的DDD建议:我在某个时候也考虑过这一点,正如你正确指出的,这会导致根据上下文为同一事物建立多个模型(即有员工或没有员工的部门)这样行吗,行吗?这不会随着时间的推移而爆发吗?