sbuttgereit

sbuttgereit

Dependencies and programmatically avoiding circular references

This is going to be an oddball question and not really something that others are going to commonly encounter. I’m also pretty sure that what I’m going to be asking about is simply not possible… but as I prove time and time again that I don’t know everything: here I am, question in hand.

The Short, Less Clear Version

I have an Elixir project which is library-like and will be used in several other Elixir projects. This library project will also have some dependencies on other Elixir projects I’ve also built. Some of those dependencies could benefit from the library itself and so would be great to include in those projects; but of course, this creates circular references between projects. So the question becomes this: is it possible to programmatically exclude a project’s dependencies at compile time (or deps.get time which seems more likely to be what I need).

The Long, Possibly More Clear Version

(Note that I’ll be simplifying some things here for the purposes of discussion)

My application is built up from a fair (and growing) number of library-like Elixir projects which are dedicated to narrowly defined feature sets; I’ll call these “Components” because they aren’t really true libraries like you might find via Hex. Some of these Components define runtime services which the top-level application ultimately will be responsible for starting and supervising. At the end of the day, however loosely, these Components are intended to work together and therefore can assume that some of these services will have been started and supervised by that top-level application.

In terms of building a running application which I can build successfully into releases, all of this works fine and as intended. There is no issue of circular dependencies in the normal course or even threat thereof; this is avoided by design.

However… during REPL aided development and during testing, having some of these services up-and-running in some minimal way can be very helpful (or in the case of testing actually essential). Up to this point I have, in each Component that would be helped in this way, manually defined helper functions to get the requisite services loaded/started. This works, but results in a lot of boilerplate, duplicating the same configurations, supervisor setups, childspecs etc. across components where this needs to happen. I hate that. So, I think to myself, why not create yet another Component that’s only available when Mix.env() is either :dev or :test encapsulating these helper common services? I’ll still have a little boilerplate, but it will be much minimized.

This utility Component also works well much of the time, but there are some cases where this can end up in circular dependencies. Consider the following scenario:

I have the following hypothetical Components:

  • Db - functionality to build, load, start, etc. involving databases.

  • Flags - Feature Flag-like functionality backed by an ETS table fronted by a GenServer holding runtime-manageable configurations. Depends on Db for persistence.

  • DevUtils - the :dev/:test only support Component which provides and extends features from both Db and Flags for REPL aided development and testing. Depends on Db and Flags.

  • Features - higher level business logic. Depends on Db, Flags, and DevUtils (only :dev/:test).

  • App - the top-level application. Depends on Db, Flags, Features, and DevUtils (only :dev/:test)`.

As defined above, things work OK. But notice that Flags depends on Db. This means maintaining Flags would benefit from the Db supporting parts of DevUtils and there’s the rub: DevUtils already depends on Flags. Trying to make DevUtils a dependency of Flags results in a circular dependency.

The Question

Given the above, ideally, I’d love for DevUtils, while being added as a dependency to Flags, to see that fact and to not try to use Flags as its own dependency, thus avoiding the circular dependency. So… simply put, is there a way to do that?

Naturally, I’ve tried a few things hoping it might work (for example optional: true in relevant dependencies)… but the more typical mix.exs dependency exclusions or conditional compilation methods I expect won’t work because I’m relatively sure it’s already too late by the time you get to compilation where those things would matter; the following seems to suggest as much:

While I expect that’s still true and that I take its meaning correctly, that quote is nonetheless pretty old and on the off-chance things have changed or I’m missing something, I thought I’d ask.

This is nothing urgent and I have acceptable “plan B” approaches which avoid the circular dependencies, but all my alternates are less clean than having a singular DevUtils package which supports all the development support scenarios I’d like it to.

Most Liked Responses

al2o3cr

al2o3cr

I don’t have any immediate solutions to suggest, but in general I’ve noticed that if I’m building phrases to label subparts of a thing it may mean there’s a name missing.

In your situation, it sounds like the “Db-supporting parts of DevUtils” and the “Flags-supporting parts of DevUtils” might be happier as separate packages.

Where Next?

Popular in Questions Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => "XXX...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement