Imitating Ecto's Architecture in a Personal Project

Hi Folks,

I am trying to put some of the ideas that I’ve been exposed to into practice in one of my applications.

One of those ideas is architecting the application to have a pure core and an imperative shell. The way that this manifests itself, at least to me, in practice is isolate impure functions and bubble them up to the surface so to speak. If our core is pure, then our code becomes easier to reason about, thanks to referential transparency and the substitution model.

It seems like many Elixir libraries hew closely to this idea of a pure core and an imperative shell. For example, in Ecto, an Ecto.Query struct is nothing more than a bag of data. It is only when this bag of data is passed into functions in Ecto.Repo that side effects occur. (I’m leaving out the part that the Ecto.Queryable protocol plays, since it is just a way to convert one bag of data into an Ecto.Query bag of data).

Anyways, I want to apply this same thinking to my application, which needs to reach out to an API. The application doesn’t care about the return value from the API, it just needs to send a message to the API so that the API can do some work. So my thinking is this:

  1. Create a new struct that contains all the information that HTTPoison needs to make the request. Headers, HTTP method, etc., etc.
  2. Create a new module that knows how to take that bag of data and make the request with HTTPoison.

The benefits of this is that my core remains pure. When testing, I can simply ensure that the values in my bag of data are what I expect them to be.

I guess the reason for my post is general comments on what I’m proposing to do, and also how folks would go about testing this. I can test the bag of data, that’s fine. However, for testing the actual side effect, it seems like Jose (in his Mocks and Explicit Contracts post) prefers to actually make a request, but I was wondering if I do this by creating a mock (used as a noun) for different environment, or if I should pass that in as a dependency. (Maybe even as a property on the bag of data). Or am I conflating a lot of different things here? (Jose seems to make a clear separation between these different strategies - i.e., different mocks, dependency injection, and defining a struct / protocol).

1 Like

It seems like you’re missing part of your post, as in the end it does not include ‘your thinking’.

But I completely agree. This architecture pattern seems very nice. In pure functional languages like e.g. Haskell, this is more-or-less enforced, because it is annoying to juggle IO around all the time, it is very natural to attempt to keep as much functions side-effect free as possible.

Please do tell more about your specific application needs, so we can discuss it in more detail :slight_smile:.

@qqwy - sorry I did miss something. Typing on phone hard. :wink: It should all be there now, except for the link to Jose’s post that I was talking about.

1 Like