persistent state
The persistent state is a container for your persistent objects where
the persistence information is stored. You might find database IDs,
optimistic locking counters, dirty flags, and created/modified columns
in there. While you will be able to see some of these things when
querying your database by hand, in your application, the persistent
state is a black box. But you can always get at your persistent object
with method get
:
val userState: PState[User] = getUserState()
val user: User = userState.get
Your persistent object is immutable and so is the PState
. You
“update” a persistent object by creating a new, modified object, for
instance with method copy
:
val newTitle = "Dr."
val updatedUser: User = user.copy(title = Some(newTitle))
But just as this operation would not cause any change to an immutable
collection that the user was in, neither will it cause any change to
the userState
. You can make the PState
aware of changes to your
aggregate using PState.modify
:
val updatedState: PState[User] = userState.modify(_.copy(title = Some(newTitle)))
Only isolated portions of your program will need to concern themselves with persistence state. For instance, suppose we have a domain service method to update a user’s score card:
trait UserService {
def updateScoreCard(user: User, event: PointScoredEvent): User
}
You can easily wrap a service call into PState.modify
:
val updatedState: PState[User] = userState.modify { user =>
userService.updateScoreCard(user, event)
}
We have just provided a guarantee - at virtually no cost - that
persistence concerns can not leak into the UserService
implementation.
What if our service method was an effectful method? Perhaps it has to hit another blocking service
somewhere to do its work, such as when hitting another
microservice in the application? In
this case, we would probably be wrapping it in a future-like construct in a reactive setting, or in
an IO monad construct in more of a functional setting. Let’s use scala.concurrent.Future
as an
example:
trait UserService {
def updateScoreCard(user: User, event: PointScoredEvent): Future[User]
}
We can change our above example to call modifyF
instead of modify
, like so:
val updatedState: Future[PState[User]] = userState.modifyF { user =>
userService.updateScoreCard(user, event)
}
The persistent state does not inherit the effect from the repository. This means you will have to
make an effect implicitly available when calling modifyF
, in the same way as when you created
your longevity context. (The persistent state could easily inherit the
effect from the repository, and perhaps it should. The advantage of inheriting the effect is that
the user would not need to supply the effect again when calling modifyF
. The disadvantage is that
the type gets more cumbersome. For instance, PState[User]
would become PState[Future, User]
.)