yordisprieto
What is your take about the “primitive obsession” code smell in Elixir?
Do you prefer to create structs that wrap the native values or wraps other values like Decimal.t() that are a bit more complex?
Or do you try to use the primitive values as much as you can?
I am particularly interested in those situations where you only have one value in your struct, since
defmodule Something do
// value is either a native type or some other struct
@enforce_keys [:value]
defstruct [:value]
end
// another example
defmodule Amount do
@enforce_keys [:value]
defstruct [:value]
def new(value) do
%__MODULE__{value: Decimal.new(value)}
end
end
Recently coming back to revisit my code, I find myself refactoring a bit and introducing more value objects, that brings a bit more clarity to my code compared to old code.
Some of the value objects (structs) are there to lift up the meaning of the native type or nested structs. While others check some invariants behind the factory function to make sure I don’t have garbage values.
What are your general thoughts about the topic?
Trending in Discussions
Other Trending Topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 10 of 30 Posts
ityonemo
You will take a performance, and a readability hit. IMO the only reason to do this if you are have some sort of common strategy that traps the module associated with the struct, e.g. a protocol, or something where you are doing:
presumably somewhere, you have some structs that get sent to above function that have more than one map field, in addition to the one-field structs.
ityonemo
by readability hit, I mean both in terms of your code, and in terms of when you’re debugging and inspecting stuff, not to mention if somehow your error logs happen to have erlang formatting instead of elixir formatting… Structs are not pretty in erlang formatting. I guess I feel a better strategy to lift up the meaning of primitive data types is to use good variable names. I once had a project where my linter would reject my code i If I had generic variable names like “value” “result”, “x”, etc. I would like to have a static linter some day that can infer types and will require that certain variables have suffixes, e.g. _id must be a uuid and vice versa.
dimitarvp
In the general case I am completely with you here.
Just definitely don’t obsess. There are many cases where just passing integers and Strings is completely fine.
But I find myself using value objects for another reason as well: libraries like
domo/typed_structcan help you with better enforcing contracts at runtime plus give you better error messages in case of a violation.al2o3cr
Can you post an example of code that uses this approach? In isolation, it doesn’t seem like this is anything more than a
Decimalwith extra steps.yordisprieto
They are two main cases thou,
In this case, it doesn’t seem to have much value, but I would argue that you shouldn’t care if I am dealing with the value a Decimal.t() or a string.
In this case, the value object doesn’t exist simply to lift up the meaning and hide the underline type, but also to make sure that the invariants check are correct, therefore, people don’t have to figure out what it means to be “PositiveAmount”.
I hope that helps.
yordisprieto
As far as I can remember, we ended up with the following value objects after tackling the “Primitive Obsession” and “Parse, Dot no Validate.”
Also, some value objects may not make sense to you, probably because you are missing out on the context and the why based on that given context. In the end, structs are just a way to provide a map of an identity; such an identity allows you to make assumptions about it. Sometimes, they are valuable due to invariants, and other times, they are beneficial due to assumptions made instead of validating all over the place. Context > Consistency
What to avoid is being too obsessive about creating wrapping functions instead of calling Map directly, focusing primarily on factory functions to ensure invariants above everything, and having less to do with hiding fundamental values. I wish I could find Rich Hickey’s video in which he talks about this.
dimitarvp
I might not be reading you well but why is “Parse, Don’t Validate” an anti-pattern?
yordisprieto
I am not sure what I wrote or how it could be interpreted! Maybe I misspoke or said backward … I meant to say, “It is good to parse instead of validate.”
Changing the wording, just in case, let the user decide what it is an anti-pattern
English is too hard sometimes
Oddman
Value objects are there to enforce contraints and in DDD-land: business invariants. The point is to ensure that you have valid data and structures at all times. It also makes it MUCH easier to ensure you’re always working with that data by declaring your struct dependencies defined in those modules.
I use them everywhere in my game I’m building, including for IDs. IDs might seem like a bit of an “obsessive” approach, until you make one simple mistake where you pass the wrong id as an argument, and poison data. Do that once, and you very quickly see the value in having IDs as value objects.
But not everything needs to be. Value objects have their real value in encapsulating logic that validates the values they’ll hold. They’re also good for pairing related data together, such as an Address or phone number (country code, area code, number.etc.). You can’t have a valid phone without those details, so grouping it together makes a lot of sense.
yordisprieto
Ask me how I know
… now a days I have VO for IDs as well