最常见的问题是:如何以一种很好的功能方式设置非平凡的数据持久性?
我更喜欢那些有实际经验的人,实际上已经做过的人的答案。但我也很高兴听到有关这个问题的任何其他想法。
假设我有一些我的程序处理并需要保留的数据。说,我们的老朋友Employee
:
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
到目前为止如此直截了当。
现在,假设我有另一种数据Department
,它与Employee
有关,但需要独立存储(在单独的表中,单独的集合,单独的文件中)。登记/>
为何独立?说实话,我真的不知道。我做知道的是它需要有自己的身份,以便我可以显示/更新它而不需要任何特定的员工。由此,我推断存储必须是分开的,但我对其他方法持开放态度。
所以我可以做与Employees一样的事情:
module Departments =
type Department = { Name: string; /* etc. */ }
module DepartmentsPersistence =
type DepartmentId = ...
let getDepartment, updateDepartment, newDepartment = ...
但是现在,我该如何表达这种关系呢?
在“抽象所有事物”这个历史悠久的传统中,让我们设计我们的数据模型,同时假装没有持久性。我们稍后会添加它。有一天。它是“正交的”。这是“辅助”。或者其他一些。
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
只是一种关系,但在一个真实的应用程序中,我有更多的东西。那么,我每次都要加载整个图表呢?非常昂贵。
好的,因为我不能在Department
中直接包含Employee
,所以我会包含部门的人工身份,因此我可以在需要时加载它。
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
所以我现在的也的功能本身没有意义。持久性已成为我的计划中不可或缺的一部分,远非“正交概念”。
好的,所以我不能直接包括部门,我也不喜欢包括它的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
到目前为止,这看起来是最干净的方式(顺便说一句,这就是ORM所做的),但仍然没有问题。
首先,该模型本身并不合理:一些组件直接包含在内,另一些组件通过“promise”包含,没有任何明显的原因(换句话说,持久性通过泄漏)。登记/>
另一方面,将部门作为承诺可以阅读,但我该如何更新?我想我可以使用镜头代替承诺,但后来变得更加复杂。
人们如何实际做到这一点?我应该在哪里妥协,哪些有效,哪些无效?我是否真的必须妥协,是不是有“纯粹”的方式? 既然有明显真实的数据驱动应用程序,那么这些事情必须有某种方式完成,对吧?右?..