Refactoring domain model with mutability and cyclical dependencies to work for Scala with good FP practices?
Asked Answered
B

1

8

I come from an OO background(C#, javascript) and Scala is my first foray into FP.

Because of my background I am having trouble realizing a domain model that fits my domain problem well and also complies with good practices for FP such as minimal mutability in code.

First, a short description of my domain problem as it is now.

  • Main domain objects are: Event, Tournament, User, and Team
  • Teams are made up of Users
  • Both Teams and Users can attend Tournaments which take place at an Event
  • Events consist of Users and Tournaments
  • Scores, stats, and rankings for Teams and Users who compete across Tournaments and Events will be a major feature.

Given this description of the problem my initial idea for the domain is create objects where bidirectional, cyclic relationships are the norm -- something akin to a graph. My line of thinking is that being able to access all associated objects for any given given object will offer me the easiest path for programming views for my data, as well as manipulating it.

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]) {
}

However as I have dived further into FP best practices I have found that my thought process is incompatible with FP principles. Circular references are frowned upon and seem to be almost an impossibility with immutable objects.

Given this, I am now struggling with how to refactor my domain to meet the requirements for good FP while still maintaining a common sense organization of the "real world objects" in the domain.

Some options I've considered

  • Use lazy val and by-name references -- My qualm with this is that seems to become unmanageable once the domain becomes non-trivial
  • Use uni-directional relationships instead -- With this method though I am forced to relegate some domain objects as second class objects which can only be accessed through other objects. How would I choose? They all seem equally important to me. Plus this would require building queries "against the grain" just to get a simple list of the second class objects.
  • Use indirection and store a list of identifiers for relationships -- This removes cyclical dependencies but then creates more complexity because I would have to write extra business logic to emulate relationship updates and make extra trips to the DB to get any relationship.

So I'm struggling with how to alter either my implementation or my original model to achieve the coupling I think I need but in "the right way" for Scala. How do I approach this problem?

TL;DR -- How do I model a domain using good FP practices when the domain seems to call for bidirectional access and mutability at its core?

Baseboard answered 10/7, 2014 at 13:23 Comment(0)
P
2

Assuming that your domain model is backed by a database, in the case you highlighted above, I would make the "teams," "events," and "tournaments" properties of your User class defs that retrieve the appropriate objects from the database (you could implement a caching strategy if you're concerned about excessive db calls). It might look something like:

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

Another way of saying this might be that your cyclic dependencies have a single "authoritative" direction, while references in the other direction are calculated from this. This way, for example, when you add a user to a tournament, your function only has to return a new tournament object (with the added user), rather than a new tournament object and a new user object. Also, rather than explicitly modeling the TournamentUser linking table, Tournament could simply contain a list of User/Boolean tuples.

Another option might be to use Lenses to modify your domain model, but I haven't implemented them in a situation like this. Maybe someone with more experience in FP could speak to their applicability here.

Plashy answered 3/8, 2014 at 3:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.