How to solve this circular dependency in my umbrella?

I have an umbrella project that contains three applications:

  • my_app (logic and database layer)
  • my_app_web (Phoenix application, depends on my_app)
  • my_app_grpc (gRPC endpoints, depends on my_app)

my_app_grpc sends messages to a GenServer that is inside my_app, which is fine. The problem is that this GenServer has to send callbacks to a gRPC stream. The solution I found without needing to add my_app_grpc as a dependency was to create a process inside my_app_grpc and just send a message to it through my_app, so all gRPC related work would be still inside its own application.

It works but I can’t test this message is being sent inside my_app tests, as the other applications are not running.

grpc_pid = Process.whereis(MyAppGrpc.Foo)
:erlang.trace(grpc_pid, true, [:receive])
assert_receive {:trace, ^grpc_pid, :receive, :my_message}

I just wanted to guarantee this message is being sent, as it’s a critical part of my system.

One way to break a circular compilation dependency is to bind the “reverse” arc of the graph at runtime - in this case, imagine you have this configuration in your umbrella:

config :my_app, foo_target: MyAppGrpc.Foo

In your tests inside my_app, you could override this to send to a “dummy” process that just echos the message back to the test process.

1 Like

I’m slightly bothered by sending messages to a process to obtain a callback. A “nice problem to have” but you don’t want that single genserver to bottleneck, plus it feels like you are “using genservers to structure your code” instead of using genservers to structure your processes.

Why not send a lambda as your callback hook. That way in your tests you can instrument whatever to make sure the callback happens, and in your grpc app, you send a lambda of a function that’s inside your grpc app and handle that internally.

2 Likes