1
\$\begingroup\$

I created a small sample project using caliban, akka-persistence and ZIO. I'm not really happy with the wiring code:

    implicit val defaultRuntime: DefaultRuntime = new DefaultRuntime {}

    val counter = context.spawn(Counter("counter"), "counter")
    val service: CounterService = defaultRuntime.unsafeRun(CounterService.make(counter, scheduler, timeout))

    val graphQl = GraphQlApi(service)
    val routes = HttpRoutes(context.system, graphQl.interpreter)

    val serverBinding: Future[Http.ServerBinding] = Http()(untypedSystem).bindAndHandle(routes.counterRoutes(ec, defaultRuntime), "localhost", port)

The CounterService is wrapping the ask-calls to the persistent actor in a zio.Task:

class CounterService(counter: ActorRef[Counter.Command], scheduler: Scheduler, timeout: Timeout) {

  implicit val s: Scheduler = scheduler
  implicit val t: Timeout = timeout

  def getCounter: Task[Counter.Count] = {
    ZIO.fromFuture { ec =>
      counter ? GetCount
    }
  }
}


object CounterService {
  def make(countManager: ActorRef[Counter.Command], scheduler: Scheduler, timeout: Timeout): UIO[CounterService] = {
    for {
      manager <- ZIO.succeed(countManager)
    } yield new CounterService(manager, scheduler, timeout)
  }
}

For creating the Routes I need to pass the GraphQLInterpreter, the ExecutionContext and the zio.Runtime:

case class HttpRoutes(system: ActorSystem[Nothing], interpreter: GraphQLInterpreter[zio.ZEnv, _]) {

  def counterRoutes(ec: ExecutionContext, runtime: Runtime[zio.ZEnv]): Route = {
    path("graphql") {
      AkkaHttpAdapter.makeHttpService(interpreter)(ec, runtime)
    }
  }

}

The project compiles and I can execute graphql requests just fine. But I'm not satisfied (nor experienced) with wiring these libraries together. To me it feels like there is space for improvement on how to wire up these libraries but I'm kinda stuck.

Does anyone has some input on this vague question? Any other advice/feedback to my code is much appreciated as well :)

Full project source code can be found here: link to github project

\$\endgroup\$
2
\$\begingroup\$

I think you did a pretty good job overall.

You can simplify your code by making CounterService.make not return an effect since you only call ZIO.succeed in it. make can return directly a CounterService and that way no need to call unsafeRun in your main app.

I also think that the HttpRoutes class is not really necessary since it only has a single method counterRoutes that you call once. You could just put this function inside an object and ditch the intermediate class. The values that are already implicit (execution context, runtime) can be passed implicitly as well.

def counterRoutes(interpreter: GraphQLInterpreter[zio.ZEnv, _])(
  implicit ec: ExecutionContext, runtime: Runtime[zio.ZEnv]): Route = {
    path("graphql") {
      AkkaHttpAdapter.makeHttpService(interpreter)
    }
  }

routes.counterRoutes(graphQl.interpreter)
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Thanks for your input. I've applied the changes you suggested. There is one more question regarding the zio.Runtime: For larger projects, should I create a Runtime that provides my services to the GraphQLInterpreter for dependency injection? \$\endgroup\$ – TobiP Feb 21 at 18:10
  • \$\begingroup\$ Yes, that is correct. You need a Runtime for whatever environment R you require in your interpreter. By the way I just realized that you could reduce the code a bit more because Caliban accepts Futures directly in your schema, so you don’t need to wrap them all in Task. But if you’re interested I encourage you to also try a full ZIO version (without Akka), you’ll find it even shorter. \$\endgroup\$ – ghostdogpr Feb 22 at 1:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.