cdegroot

cdegroot

Should we adopt Dave's way of building applications as a series of components? (Dave's talk has now been added!)

Another point that Dave drives home in his course is that applications really are components, and you should treat them as such. Not as “full fledged applications”. We’ve been doing that umbrella-style before, but umbrellas are confusing and I always felt that even mix phx.new --no-ecto --no-html --no-brunch api generated a ton of fluff. In the same vein that files/modules are free and you can use them to separate api/otp glue/implementation, applications are free and you can - and I think should - use them to separate concerns. The reason therefore that I’m putting this here is that I feel it’s basically the same principle at work.

We’re setting up a new service, that will be responsible for various internal permissions related stuff. The “hard” part so far was whittling down the Phoenix generated code to something clean and manageable:

..../api$ find test lib config
test
test/support
test/support/conn_case.ex
test/test_helper.exs
lib
lib/api.ex
lib/api
lib/api/endpoint.ex
lib/api/router.ex
lib/api/user_policy_controller.ex
lib/api/application.ex
config
config/config.exs
config/deploy.exs
config/dev.exs
config/test.exs

no channels, no contexts, no mixing up of UI and business logic in a single OTP application. The sole controller:

defmodule Api.UserPolicyController do
  @moduledoc """
  This module contains the interface between the API and
  the business logic in the user_policies application..

  """
  use Api, :controller

  def get_for_user(conn, user_id) do
    {:ok, policy_document} = UserPolicies.get_policy_for(user_id)
    json conn, policy_document
  end
end

which calls out to a sibling app (using the “poncho” organization style):

.../user_policies$ find test lib config
test
test/test_helper.exs
lib
lib/user_policy.ex
lib/user_policies.ex
config
config/config.exs

I like this a lot better than the Phoenix default “we are Rails and toss everything together” mess. These are skeleton both currently applications, but it is immediately clear where things go when we start fleshing them out, there’s very nice separation of concerns, a random bypasser can pick an interest and only open that code, and so on. Frankly, with the biggest cost being a top level Makefile to drive CI builds (I’m settling on a pretty standard skeleton for that), I think it’s worth pursuing. At least experimenting with, to see how it feels. My gut says it feels much better, and after ~40 years of coding, I have learned to rely on my gut. YGMV, of course.

(and I’ll shuddup now with my heresies :wink: )


Edit: See post 20 and below for Dave’s talk on this topic and the discussion following the video.

546 20021 107

Most Liked

joeerl

joeerl

Creator of Erlang - Fondly Remembered

Excuse the longish post - I too watched Dave’s talk and it brings up some important points.

In this posting I want to talk about composabilty.

To me the unit of composabilty should be the process and NOT functions - to be clearer, of course functions should be composable but this problem is nicely solved ( F1 |> F2 |> F3 |> … :slight_smile:

I think the gold standard for composabily were unix pipes, and the beautiful way they could be composed in the shell.

a | b | c | d ...

The principle design idea was “the output of my program should be the input of your program”

This allows a b and c to all be written in different languages - but this has a few nasty problems:

  1. text flow across the boundaries
    so there is a lot of extra parsing and serialising involved
  2. if something in the middle fails (say c) there is no nice way to close the pipeline down

One excellent feature is that (say) b does not know who sends it inputs and does not know to whom the outputs should be sent.

Now consider Erlang - one of the above problems gets solved - text does not flow across the boundaries but Erlang messages. X ! M sends a message, receive M → … end receives a message so no parsing and serialising is involved and it’s very efficient.

Processes do not know where they get messages from (=good) but have to know where they send messages to (=bad).

A better way would be to use ports, call them in1, in2, in3 for inputs and out1, out2, out3 for outputs and control1, control1 for controls

We can now make a component - assume a process x that has an input in1 which doubles its input and sends the result to out1 - this is easy to write in erlang

   loop() ->
      receive 
          {in1, X} ->
              send(out1, 2*X),
              loop()
      end

Clever people can write this in Elixir as well :slight_smile:

All the component knows how to do is turn numbers on the in1 port into output on out1 but it does not know where in1 and out1 are.

Now we have to wire things up.

The pipe syntax X | Y | Z means “wire up the output of X to the input of Y” (and so on)

The important point is that a) components do not know where they get their inputs from and do not know where they send their outputs to and b) “wiring up” is NOT a part of the component.

Elixir has a great method for wiring up functions X |> Y |> Z but the X,Y’s and Z’s are functions
NOT processes.

We can imagine components to be processes with inputs (in1, in2, in3, …) outputs (out1, out2, …) control ports (control1, control2, …) and error ports (error1, error2,…) - what are the error ports?

Error ports are for (guess what) errors - sending an atom to the in1 port of my doubling machine would result in an error message being sent to error1 (or something).

All of this can be nicely specified with some type system -

Machine M1 is

 in1 x N::integer -> out1 ! 2*N :: integer

etc. :slight_smile:

With this kind of structure software starts looking very much like hardware and we can make nice graphic tools to show how the components are wired up. The reason we do not program like this in sequential languages is because all the components MUST run in parallel (which is what chips do)

There is actually nothing new in the above - these ideas were first written down by John Paul Morrison in the early 1970’s (see Flow-based programming - Wikipedia) –

This (flow based programming) is one of those ideas we could (and should) revisit and cast into a modern form.

All of this means a bit of a re-think since most frameworks are structured on top of essentially sequential platforms.

Really we should be thinking in terms of “black boxes that send and receive messages” and “how to wire up the black boxes” and NOT functions with inputs and outputs, the latter problem is solved.

Think - “messages between components” and “what messages do I need in my protocol” NOT “input and output types” and “what functions and modules do I need”

(I called this Concurrency Oriented Programming a while back - but the term did not seem to latch on :slight_smile:

As Alan Kay said “the big idea is messaging”

Cheers

/Joe

josevalim

josevalim

Creator of Elixir

Sorry for being pedantic but, since you are asking someone else to build on their arguments, I would like to point out that you have not explained why any of the points above are true. I.e. it is not clear why the approach you mentioned above is easier to test, change, or reason about.

Just to make an absurd point, maybe the contract you define between those components pass all of the arguments between functions via the process dictionary. Maybe you do this:

# in the controller
Process.put(:product, product)
NewProductHandler.call()

# in the handler
def call() do
  product = Process.get(:product)

Now your code is broken between many files but it is still coupled and hard to test!

Every time somebody brings the argument that a module should only have a single function in their domain, I propose this: what if we remove Enum.map/2, Enum.reduce/3, etc and instead we introduce Enum.Mapper.call/2, Enum.Reducer.call/3 and so on?

To clarify: I am not saying breaking your software into multiple files is bad a thing but programmers clearly leverage different mechanisms to group functionality together. We need to identify when those mechanisms are measurably better (i.e. you can prove it leads to less coupling and better tests) and we need to identify when we feel the code is better.

Theoretically speaking, there is no difference between how easy to test or change is a big module MyApp with a 100 functions call_a, call_b, etc compared to 100 modules with a single function MyApp.A.call, MyApp.B.call, etc. There are, however, differences on how a developer understands and communicates with another developer in this codebase, which is what causes some people to feel one approach is superior than the other, but I can’t prove one is better than the other.

It is very important to make a distinction between proofs and feelings because they lead to very different discussions.

josevalim

josevalim

Creator of Elixir

I think it is important to clarify that, and I think Dave would agree, is that GenServer is not wrong. Rather we are wrong in using GenServer to build higher level components, often with domain/business logic, and we need higher level abstractions to solve those cases. When building low-level components, the distinction between client, server, and the low-level callbacks are important, but in your application domain you want to get rid of the bureaucracy and put the focus on the domain code. There are also other libraries attempting to tackle this particular issue.

I agree with Dave in many of the problems he raised: yes, we need to improve configuration (there was a whole other thread about it in the forum). Yes, it would be helpful to know if an application is stateful or is a singleton (and as I said in this thread, I don’t think looking at the supervision tree gives the complete answer). As Dave said, there is a lot to explore. :slight_smile:

Where Next?

Popular in Discussions Top

andre1sk
A big advantage to Elixir is all the distributed goodness but for many applications running on multiple nodes having integrated Etcd, Zoo...
New
blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New
CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
scouten
I’m looking for a host for the server part of a small (personal) side project that I’m working on. It’s currently written in Node.js and ...
New
griffinbyatt
Sobelow Sobelow is a security-focused static analysis tool for the Phoenix framework. For security researchers, it is a useful tool for g...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
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
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
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
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement