重构具有可变性和循环依赖性的域模型,以便为Scala提供良好的FP实践?

Mat*_*can 8 functional-programming scala immutability domain-model cyclic-reference

我来自OO背景(C#,javascript),而Scala是我第一次涉足FP.

由于我的背景,我无法很好地实现适合我的域问题的域模型,并且还符合FP的良好实践,例如代码中的最小可变性.

首先,简要描述我现在的域名问题.

  • 主域对象是: Event, Tournament, User, and Team
  • Teams 由...组成 Users
  • 双方Teams并Users可以参加Tournaments这发生在一个Event
  • Events由Users和组成Tournaments
  • 结果,统计数据,以及排名Teams和Users谁竞争,跨越Tournaments和Events将是一大特色.

鉴于问题的这种描述,我对域的最初想法是创建对象,其中双向循环关系是常态 - 类似于图形.我的想法是,能够访问任何给定对象的所有关联对象将为我提供最简单的路径,为我的数据编程视图,并操纵它.

case class User(
           email: String,
           teams: List[TeamUser],
           events: List[EventUser],
           tournaments: List[TournamentUser]) {
}
case class TournamentUser(
                     tournament: Tournament, 
                     user: User, 
                     isPresent: Boolean){
}
case class Tournament(
                 game: Game,
                 event: Event, 
                 users: List[TournamentUser], 
                 teams: List[TournamentTeam]) {
}
Run Code Online (Sandbox Code Playgroud)

然而,随着我进一步深入FP最佳实践,我发现我的思维过程与FP原则不相容.循环引用是不受欢迎的,似乎几乎不可能与不可变对象.

鉴于此,我现在正在努力解决如何重构我的域以满足良好FP的要求,同时仍然保持域中"真实世界对象"的常识组织.

我考虑过的一些选择

  • 使用lazy val和by-name引用 - 我的问题是,一旦域变得非常重要,这似乎变得无法管理
  • 使用单向关系 - 使用此方法虽然我被迫将一些域对象作为第二类对象,但只能通过其他对象访问.我该如何选择?它们对我来说似乎同样重要.此外,这将需要"反对谷物"构建查询,只是为了获得第二类对象的简单列表.
  • 使用间接并存储关系标识符列表 - 这会消除周期性依赖关系,但随后会产生更多复杂性,因为我必须编写额外的业务逻辑来模拟关系更新,并额外访问数据库以获得任何关系.

因此,我正在努力改变我的实现或原始模型,以实现我认为我需要的耦合,但对于Scala来说是"正确的方式".我该如何处理这个问题?

TL; DR - 当域似乎要求双向访问和核心的可变性时,如何使用良好的FP实践对域建模?

小智 2

假设您的域模型由数据库支持,在上面突出显示的情况下,我将使您的 User 类定义的“团队”、“事件”和“锦标赛”属性从数据库中检索适当的对象(您如果您担心过多的数据库调用,可以实施缓存策略)。它可能看起来像:

case class User(email: String)) {
    def teams = TeamService.getAllTeams.filter( { t => t.users.contains(this) } )
    //similar for events and tournaments
}
Run Code Online (Sandbox Code Playgroud)

另一种说法可能是,您的循环依赖项具有单个“权威”方向,而另一个方向的引用是据此计算的。这样,例如,当您将用户添加到锦标赛时,您的函数只需返回一个新的锦标赛对象(包含添加的用户),而不是一个新的锦标赛对象和一个新的用户对象。此外,锦标赛可以简单地包含用户/布尔元组的列表,而不是显式地对 TournamentUser 链接表进行建模。

另一种选择可能是使用Lenses来修改您的域模型,但我还没有在这种情况下实现它们。也许在 FP 方面有更多经验的人可以在这里谈谈它们的适用性。