egze

egze

@JEG2 Hi. First of all, thank you for the book. I learned a lot.

I have a question about data validation. In the book you are doing it in the boundary layer. What if my validator has a bug and and invalid data gets through to the core? Is it OK if my core raises an error then? Or better to build the core that never breaks?

Example: A question can only accept answers “yes” and “no”. Every time you answer a question, it gets added to the list of responses in the quiz. But if the user somehow manages to answer “maybe”, which is invalid and the validator doesn’t catch it, we have options to: don’t add response to the list and return quiz unchanged, crash, return an error tuple.

Error tuple is not good for composition, not doing anything and returning the quiz is probably not what the user expected, and raising sounds quite good to me.

I hope I described the problem. What are your thoughts?

0
Post #32
OvermindDL1

OvermindDL1

It composes quite well with with and various other constructs. :slight_smile:

It really just depends on what ‘kind’ of error it should be though. Like should it be handleable first of all?

0
Post #33
egze

egze

It’s a good question if it should be handleable. I guess sometimes yes, sometimes no. This is the code I wrote today:

def answer_question(pitch, question_id, answer) do
  response = Response.new(pitch, question_id, answer)
  
  add_response(pitch, response)
end
  
defp add_response(pitch, %Response{valid: false}), do: pitch
defp add_response(pitch, %Response{valid: true} = response) do
  %Pitch{pitch | responses: Map.put(pitch.responses, response.question_id, response)}
end

Here only a valid response gets added to the map. We were debating with a co-worker if raising an error for the invalid response is a better approach than silently returning the pitch untouched. His argument was that the validation in the boundary layer can be buggy/incomplete and invalid data can leak into the core. He would then prefer to know about it, instead of silently returning the pitch.

In our case with questions and answers it is actually better like it is, because there is no handling of errors for this use-case. But I can also see his point of view, that if this was a more critical use-case, then it would be better to deal with it somehow.

0
Post #34
JEG2

JEG2

Author of Designing Elixir Systems with OTP

My preference would be to return a tagged tuple and not to swallow it. I would expect the frontend to turn it into a helpful notification for the user, even if that’s just “Something has gone wrong. We have been notified…”

I try to reserve raising errors for the cases where there’s no viable action left to take.

1
Post #35
egze

egze

But in the book you were never returning tuples from functional core, even when you were expecting user submitted data. Instead you validated in the boundary layer, but if it would somehow fail to validate correctly, then the core would have invalid state. Just trying to unterstand what strategies are better, not attacking your solution :slight_smile:

0
Post #36
JEG2

JEG2

Author of Designing Elixir Systems with OTP

I think I’m missing something in this discussion. Why would validation code at the boundaries be buggy while code deep in the core won’t be?

We almost surely don’t want to validate in both places, right? The two sources of truth would eventually drift apart.

Finally, there’s no reason a functional core can’t return errors. There’s a section, in the Boundaries chapter no less, about the value of treating errors as data. Consider how Phoenix controllers interact with contexts, for example.

Anyway, I feel like I’m not really grasping your query. What am I missing?

1
Post #37
egze

egze

That’s a good point about not wanting to validate in both places. I guess we were trying too hard to protect the system. And core is like a last chance to save the state from getting invalid. Wrong way to think about it. Totally makes sense that the validations will drift apart.

Now after this discussion, I’m planning to remove this function:

defp add_response(pitch, %Response{valid: false}), do: pitch

Also don’t check in add_response if the response in valid or not, but just add it. And the Validator in the boundary will take care of checking if we should call add_response in the first place.

1
Post #38
Fl4m3Ph03n1x

Fl4m3Ph03n1x

On page 154, in the section describing concurrency:

Concurrency

In the simplest terms, concurrency means doing more than one thing at the same time. In our venacular, it’s a micro concern.

I believe you mean vernacular instead :smiley:

A few lines later:

A client request can farm out six different database

Don’t you mean fan out? I never heard the term farm out but in case this is not a typo, I would really like to learn more about it!

1
Post #39
peerreynders

peerreynders OP

It’s as colloquialism

https://www.merriam-webster.com/dictionary/farm%20out

Specifically:

to turn over for performance by another usually under contract
farm out a job

So it’s often used to mean “outsourcing work”.

1
Post #40
Fl4m3Ph03n1x

Fl4m3Ph03n1x

Ahh, given the context this makes sense. Thanks !

1
Post #41