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.
Event, Tournament, User, and Team
Teams
are made up of Users
Teams
and Users
can attend Tournaments
which take place at an Event
Events
consist of Users
and Tournaments
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
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With