Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

I have an umbrella application that has several apps inside. These apps communicate and depend on each other.

So, when I am testing, because I am using mocks, I need to mock their interfaces. Now if you know about mocks or if you have read Jose Valim’s opinion on them (Mocks and explicit contracts « Plataformatec Blog) you know this brings an issue:

  • Using Mocks means I can deviate from the real implementation.

App Structure

Imagine I have an umbrella project with 2 apps:

  • cli (a command line interface)
  • manager (where all the logic is)

Umbrella Project structure

.
├── README.md
├── apps
│   ├── cli
│   └── manager
├── config
│   └── config.exs
├── mix.exs
└── mix.lock

Now, every time I change the manager’s Public API, I have to update the interface on cli, otherwise my tests in cli will pass even though nothing will then work:

cli app stucture

.
├── README.md
├── lib
│   ├── cli.ex
│   └── manager.ex # interface for the manager umbrella app
├── mix.exs
└── test
    ├── cli_test.exs
    ├── support
    └── test_helper.exs

Problems

So, following this pattern, I have a few issues:

  • every time create a new @type in the manager, I have to update all the interfaces that use it, so the projects using manager as a dependency can have access to the new types I created.
  • every time I change manager’s public API, I have to update the projects that use it as a dependency
  • I have a manager.ex file in my cli app, that is just an interface for the manager app. This is confusing, as people reading my code will not intuitively understand this is just an interface so i can create a mock that obeys it (mocks as nouns :D)

Possible solutions?

Given these issues I have considered the following option:

  • create a new app, called interfaces, where I define the interfaces of all umbrella apps.

This would have the following benefits:

  • Changes to the Public API of any app would always be reflected there.
  • All dialyzer @types would be there as well
  • Any project using the Interface app as a dependency would have immediate access to the most updated interfaces and type specs.
  • I would get rid of the manager.ex file because I would import it from the interface app

It would however have the following drawbacks:

  • Any umbrella app would be forced to have 2 dependencies: the interface app, and the app they want to use (in this case, cli would have to import both interface and manager)
  • They would have access to all interfaces and type specs, even if not needed
  • I would still have to change things in two places: the interface app, and the app that implements the interface.

So I am not really sure on how to deal with this issue.

How do you guys deal with this?

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

Doesn’t :cli have {:manager, in_umbrella: true}? Then :cli can use anything (public) in :manager.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

yes, it does. But I still have to create a manager.ex file that is an interface so I can use Mox.
And when you say public, you also mean @types ?

LostKobrakai

LostKobrakai

I guess we already had this conversation, but I personally would not mock internal dependencies in the first place. But manager.ex is one file in :cli, which belongs to :cli and is built to its needs. What exactly would you be copying from :manager and why?

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

manager.ex is a behaviour, that represents the public API of the manager app.
The manager app is an implementation of the manager.ex behaviour.

I have this behaviour so I can mock its implementation with Mox.

How would you solve this without mocks? Inject the dependencies directly?
You would end up with the same issue in the end, in the sense that a change in manager would always require another in cli.

I have a feeling this discussion comes up often in different ways because I have not yet cemented a solution :thinking: Or perhaps this is something more general and I am not seeing the full picture.

LostKobrakai

LostKobrakai

So either :cli depends on :manager and takes what it gets or :manager depends on :cli and implements the interface :cli prescribes. If you do both you’re in trouble. You don’t want circular dependencies.

Also what you’re seeing here is partly the cost of decoupling things, just that you’re not yet seeing any benefits out of that, because you’re only doing it for mocking and not for general decoupled parts in your system.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

So, a few questions:

  1. What do you think about the interface app idea? Overkill?
  2. Since you wouldn’t use mocks, would you inject dependencies directly? What would you do exactly?

This discussion is making me realize that perhaps the behaviour should be in the manager app instead of the cli app.

Good discussion!

LostKobrakai

LostKobrakai

If there’s no need on the manager side I’d test :cli by letting it call into :manager directly. No need to switch implementations without good reason. I also wouldn’t opt for an “interface” app yet. If you actually decouple those apps at some point (no hardcoded dependencies anymore) a shared interface might make sense.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

Those apps are decoupled because my manager can have different types of interfaces. cli is just one of them, I will eventually add a phoenix-live interface that talks to the manager.

This is way decoupling was something I did in this small project.

If I allow cli to call manager directly, then I will be doing end to end tests only, that is not my objective. Or perhaps I miss understood you?

LostKobrakai

LostKobrakai

Yeah, but :cli doesn’t need to be able to interact with multiple backends I imagine. :manager is the only one it interacts with. So no decouling in that direction.

So what? To me this is not a problem in itself. If there’s something in manager, which makes testing hard, this might change. But for that fact of the part making it hard, not because I want to not have end to end tests.

egze

egze

Don’t know if it helps, but here are my 2 cents how we solve it.

We have the following umbrella structure:

.
├── apps
│   ├── api_client
│   └── app

api_client exposes this function ApiClient.Companies.list
app uses the api_client app as a dependency (in_umbrella: true).
Now let’s say we are writing tests for app that do something with what ApiClient.Companies.list returns. First we create a wrapper for ApiClient inside the app. So we never call ApiClient.Companies.list outside of this wrapper directly. This is also where we introduce a new behaviour and use Mox to create a mock for it.

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews