重构 OOP "decorator" 以释放 monad 结构
Refactoring an OOP "decorator" to Free monad structure(s)
我有一些“遗留”Scala 代码(Java-like),它执行一些数据访问。有一个装饰器可以跟踪 DAO 方法(收集指标)的使用情况,如下所示:
class TrackingDao(tracker: Tracker) extends Dao {
def fetchById(id: UUID, source: String): Option[String] = {
tracker.track("fetchById", source) {
actualFetchLogic(...)
}
}
...
}
我正在尝试将其建模为一个免费的 monad。我为 DAO 操作定义了以下代数:
sealed trait DBOp[A]
case class FetchById(id: UUID) extends DBOp[Option[String]]
...
我看到两个选项:
a) 我可以制作两个采用 DBOp
的解释器,一个执行实际的数据访问,另一个执行跟踪,然后将它们组合在一起或
b) 我使 Tracking 成为显式代数,并使用 Coproduct 在相同的 for
组合中使用它们 OR
c) 完全不同的东西!
第一个选项看起来更像是 "decorator" 方法,它与 DBOp
相关联,第二个是更通用的解决方案,但需要显式调用 'tracking' 代数。
此外,请注意原始 fetchById
调用中的 source
参数:它仅用于跟踪。我宁愿将它从 API.
中删除
这是实际问题:我如何为跟踪建模?
从你的问题中还不完全清楚,但如果跟踪是一种环境效应,当你执行数据库访问时应该 "happen" 而 source
只是用于跟踪目的的参数,你可以根本不必用您的自由语言提及它。例如,您可以使用您现在拥有的 ADT 并将其解释为 (Tracker, Source, OtherStuff) => IO[A]
,所以您得到的是一个函数,一旦您给它一个 Tracker
和源等等,它就会生成一个程序来进行数据库访问否则你需要(例如数据库连接),并且跟踪实现对解释器来说是完全私有的。这让您编写数据库程序时根本不用考虑跟踪。
另一方面,如果您 确实 需要讨论业务逻辑中的跟踪,那么我们可能需要更多关于拥有多个 Tracker
意味着什么的信息s 和 source
s 以及它们是如何引入和使用的。可能需要副产品或扩展语言或嵌套语言来处理您需要表达的内容。
正如我们行业中的所有事物一样,直接的答案是 "it depends" :)。由于 "tracking" 在这里是一个模糊的概念(我不知道域的详细信息),我会说你有两种可能的情况(或者至少我看到两种)
a) "tracking" 是您的业务词汇表的一个元素
如果跟踪是一个单独的关注点,它是您的企业使用的词汇表的一部分,那么我会使用一个单独的代数来表示该关注点。与此类似的东西是 "authentication & authorization" - 尽管这是一个 "low-level" 问题,但它仍然是业务语言的一部分 ("As admin I want to...") 我会在这里使用单独的代数
b) "tracking" 是某些 'debugging', 'logging'
的机制
如果跟踪不是语言的一部分,而是您为维护而保留的机器元素,那么我会将其保留在它所属的位置 - 机器。我会选择一个解释器,它会对 'tracking'(记录、调试)这些不同的调用产生副作用。
换句话说,如果现在您没有测试 "if I do this business thingy, then this should be tracked" 的单一测试,那么我绝对会选择选项 b) 这里
我有一些“遗留”Scala 代码(Java-like),它执行一些数据访问。有一个装饰器可以跟踪 DAO 方法(收集指标)的使用情况,如下所示:
class TrackingDao(tracker: Tracker) extends Dao {
def fetchById(id: UUID, source: String): Option[String] = {
tracker.track("fetchById", source) {
actualFetchLogic(...)
}
}
...
}
我正在尝试将其建模为一个免费的 monad。我为 DAO 操作定义了以下代数:
sealed trait DBOp[A]
case class FetchById(id: UUID) extends DBOp[Option[String]]
...
我看到两个选项:
a) 我可以制作两个采用 DBOp
的解释器,一个执行实际的数据访问,另一个执行跟踪,然后将它们组合在一起或
b) 我使 Tracking 成为显式代数,并使用 Coproduct 在相同的 for
组合中使用它们 OR
c) 完全不同的东西!
第一个选项看起来更像是 "decorator" 方法,它与 DBOp
相关联,第二个是更通用的解决方案,但需要显式调用 'tracking' 代数。
此外,请注意原始 fetchById
调用中的 source
参数:它仅用于跟踪。我宁愿将它从 API.
这是实际问题:我如何为跟踪建模?
从你的问题中还不完全清楚,但如果跟踪是一种环境效应,当你执行数据库访问时应该 "happen" 而 source
只是用于跟踪目的的参数,你可以根本不必用您的自由语言提及它。例如,您可以使用您现在拥有的 ADT 并将其解释为 (Tracker, Source, OtherStuff) => IO[A]
,所以您得到的是一个函数,一旦您给它一个 Tracker
和源等等,它就会生成一个程序来进行数据库访问否则你需要(例如数据库连接),并且跟踪实现对解释器来说是完全私有的。这让您编写数据库程序时根本不用考虑跟踪。
另一方面,如果您 确实 需要讨论业务逻辑中的跟踪,那么我们可能需要更多关于拥有多个 Tracker
意味着什么的信息s 和 source
s 以及它们是如何引入和使用的。可能需要副产品或扩展语言或嵌套语言来处理您需要表达的内容。
正如我们行业中的所有事物一样,直接的答案是 "it depends" :)。由于 "tracking" 在这里是一个模糊的概念(我不知道域的详细信息),我会说你有两种可能的情况(或者至少我看到两种)
a) "tracking" 是您的业务词汇表的一个元素
如果跟踪是一个单独的关注点,它是您的企业使用的词汇表的一部分,那么我会使用一个单独的代数来表示该关注点。与此类似的东西是 "authentication & authorization" - 尽管这是一个 "low-level" 问题,但它仍然是业务语言的一部分 ("As admin I want to...") 我会在这里使用单独的代数
b) "tracking" 是某些 'debugging', 'logging'
的机制如果跟踪不是语言的一部分,而是您为维护而保留的机器元素,那么我会将其保留在它所属的位置 - 机器。我会选择一个解释器,它会对 'tracking'(记录、调试)这些不同的调用产生副作用。
换句话说,如果现在您没有测试 "if I do this business thingy, then this should be tracked" 的单一测试,那么我绝对会选择选项 b) 这里