What does stateful service refer to in this case?
Do you mean that it will execute a side effect when an object is constructed?
For this, a better idea would be to have a method that runs the side effect when your application is starting. Instead of running it during construction.
Or maybe you are saying that it holds a mutable state inside the service? As long as the internal mutable state is not exposed, it should be alright. You just need to provide a pure (referentially transparent) method to communicate with the service.
To expand on my second point:
Let's say we're constructing an in memory db.
class InMemoryDB(private val hashMap: ConcurrentHashMap[String, String]) {
def getId(s: String): IO[String] = ???
def setId(s: String): IO[Unit] = ???
}
object InMemoryDB {
def apply(hashMap: ConcurrentHashMap[String, String]) = new InMemoryDB(hashMap)
}
IMO, this doesn't need to be effectful, since the same thing is happening
if you make a network call. Although, you need to make sure that there is only one instance of this class.
If you're using Ref
from cats-effect, what I would normally do, is to flatMap
the ref at the entry point, so your class doesn't have to be effectful.
object Effectful extends IOApp {
class InMemoryDB(storage: Ref[IO, Map[String, String]]) {
def getId(s: String): IO[String] = ???
def setId(s: String): IO[Unit] = ???
}
override def run(args: List[String]): IO[ExitCode] = {
for {
storage <- Ref.of[IO, Map[String, String]](Map.empty[String, String])
_ = app(storage)
} yield ExitCode.Success
}
def app(storage: Ref[IO, Map[String, String]]): InMemoryDB = {
new InMemoryDB(storage)
}
}
OTOH, if you're writing a shared service or a library which is dependent on a stateful object (let's say multiple concurrency primitives) and you don't want your users to care what to initialize.
Then, yes, it has to be wrapped in an effect. You could use something like, Resource[F, MyStatefulService]
to make sure that everything is closed properly. Or just F[MyStatefulService]
if there's nothing to close.