"Service pattern 2.0", how to "hide" implementation specific dependencies from service interface #7147
-
I bumped into this scenario and I am not sure how to structure the code around it. Say you have a workflow that depends on object workflowThatNeedsA {
val layer: ZLayer[Any, Nothing, A] = ???
def doStuff: ZIO[A, Nothing, Unit] = ???
} You also have a service that follows the "service pattern 2.0"
trait MyService {
def makeMeRich: ZIO[Any, Nothing, Unit]
}
object MyService {
def makeMeRich: ZIO[MyService, Nothing, Unit] = ZIO.serviceWithZIO[MyService](_.makeMeRich)
}
case class MyServiceImpl() extends MyService {
override def makeMeRich: ZIO[Any, Nothing, Unit] = workflowThatNeedsA.doStuff
} that won't compile because the signature has case class MyServiceImpl() extends MyService {
override def makeMeRich: ZIO[A, Nothing, Unit] = workflowThatNeedsA.doStuff
} This won't compile because trait MyService {
def makeMeRich: ZIO[A, Nothing, Unit]
} oh and now the accessor is wrong, too, says the compiler. Ok, fine let me "fix it": object MyService {
def makeMeRich: ZIO[MyService with A, Nothing, Unit] = ZIO.serviceWithZIO[MyService](_.makeMeRich)
} so now the compiler is happy so I just add my layer object MyServiceImpl {
val layer = ZLayer.fromFunction(() => MyServiceImpl())
} and now it all works and I can run the workflow with MyService.makeMeRich
.provide(MyServiceImpl.layer, worflowThatNeedsA.layer) But this is not that great. I had to expose the implementation dependencies on to the interface. What if I have another implementation of Ideally I would want to keep the interfaces and accessors clean, so the only place where this belongs to is in However I am not sure how to convey the idea of satisfying dependency A inside the method implementations. I thought about doing this: case class MyServiceImpl() extends MyService {
override def makeMeRich: ZIO[Any, Nothing, Unit] = workflowThatNeedsA.doStuff.provide(workflowThatNeedsA.layer)
} and that will, work. However this is still not what I want. Let's say that If my code was directly depending on case class MyServiceImpl(a: A) extends MyService {
override def makeMeRich: ZIO[A, Nothing, Unit] = a.blah() //direct interactoin with `A` object
} and then in layer object MyServiceImpl {
val layer = ZLayer.fromFunction(MyServiceImpl(_))
} but this is not the case. it's some workflow I use that needs that A.... What am I missing? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
@artur-jablonski You want to express object workflowThatNeedsA {
val layer: ZLayer[Any, Nothing, A] = ???
def doStuff: ZIO[A, Nothing, Unit] = ???
}
trait MyService {
def makeMeRich: ZIO[Any, Nothing, Unit]
}
object MyService {
def makeMeRich: ZIO[MyService, Nothing, Unit] =
ZIO.serviceWithZIO[MyService](_.makeMeRich)
}
case class MyServiceImpl(a : A) extends MyService {
override def makeMeRich: ZIO[Any, Nothing, Unit] =
workflowThatNeedsA.doStuff.provideEnvironment(ZEnvironment(a))
}
object MyServiceImpl {
val layer: ZLayer[A, Nothing, MyService] = ZLayer.fromFunction(MyServiceImpl(_))
} You will notice that there is a slight amount of boilerplate here in having to do trait A {
def doStuff: ZIO[Any, Nothing, Unit]
}
object A {
val layer: ZLayer[Any, Nothing, A] =
???
} Then you can express your implementation of case class MyServiceImpl(a : A) extends MyService {
override def makeMeRich: ZIO[Any, Nothing, Unit] =
a.doStuff
} |
Beta Was this translation helpful? Give feedback.
@artur-jablonski You want to express
A
as a dependency of your service implementation like the below.You will…