IvanR

IvanR

Domo - model a business domain with type-safe structs and field type range checks

Domo makes your structure types work for data conformance validation. And it enables automatic range checking for field types at run-time.

Domo adds a new/1 constructor function to the structure module that builds an instance only if all fields conform to the struct’s t() type and all specified precondition functions for filed types return true.

That is useful for boundary data validation coming as decoded JSON from Jason or from :erlang.binary_to_term/1, and for validation when converting one core struct into another like in CQRS framework Commanded.

Domo makes it possible to validate model types in microservice setups between depending applications by sharing common modules with type definitions among codebases.

See typical usage in Readme.md via:

Run in Livebook

Shortly it looks like:

defmodule Customer do
  use Domo

  defstruct title: :none, name: "", age: 0

  @type title :: :mr | :ms | :dr | :none
  @type name :: String.t()

  @type age :: non_neg_integer()
  precond age: &(&1 < 300)

  @type t :: %__MODULE__{title: title(), name: name(), age: age()}
  precond t: &(String.length(&1.name) < 10)
end

iex(1)> Customer.new(title: :dr, name: "John", age: 25)           
{:ok, %Customer{age: 25, name: "John", title: :dr}}

iex(2)> Customer.new(title: :dr, name: nil, age: 25)              
{:error, [name: "Invalid value nil for field :name of %Customer{}. Expected the value \
matching the <<_::_*8>> type."]}

iex(3)> Customer.new(title: :dr, name: "Johnny be good", age: 25) 
{:error, [t: "Invalid value %Customer{age: 25, name: \"Johnny be good\", title: :dr}. \
Expected the value matching the Customer.t() type. And a true value from the precondition \
function \"&(String.length(&1.name) < 10)\" defined for Customer.t() type."]}

Example applications:

Domo main features are:

  • automatic generation of constructor and insurance functions validating struct’s fields conformance to @type t(), which are: new!/1, new/1, ensure_type!/1, and ensure_type/1
  • validation of nested structs referenced in the type spec
  • support for boolean precondition functions attached to the user-defined type or the whole struct’s t() type for values range check
  • validation of struct default values at compile-time
  • recompilation of dependencies when type definition changes

More information here:

Most Liked

IvanR

IvanR

The precond, which works only in association with the given type, is more like an extension to constrain the type’s values even more or to provide a custom error message.

The main difference in approaches is that Domo focuses on declarative constraint definition for structs with types combinations. And that the validation of nested structs is supported automatically just by @type spec. You can find an example illustrating that in the readme.

With Domo, it takes less code to have validation functions for structs. That can take less time to change them later.

IvanR

IvanR

1.5.8 - September 11, 2022

  • Domo generated constructor functions new!/1/0 and new/2/1/0 become overridable.
  • Improves Ecto changeset validation. Now the Domo.Changeset.validate_type/1 skips has_many and other assoc fields automatically, so the cast_assoc(:field) can be called to do the associations validation sequentually.

The one validate_type/1 call in the changeset gives a 1.5x slower execution than the bunch of equivalent Ecto’s validate_... calls.

At the same time, the execution duration of the pure Domo generated constructor function new!/1 is the same as of building a struct with Ecto changeset approach.

Comparison: 
Domo Album.new!/1                       5.31
Ecto.Changeset validate_.../1           5.21 - 1.02x slower +3.59 ms
Domo.Changeset validate_type/1          3.76 - 1.41x slower +77.93 ms

You can find all benchmark results in the README :victory_hand:

The overridable constructors open the remarkable feature of injecting the generated default values into a struct, keeping the values validated. Let the struct using Domo have a token field, then the generation of the default value can be done like that:

def new!([_|_] = fields) do
  super(Keyword.merge(fields, token: random_string(8)))
end

So no matter what random_string returns, the super function will ensure that it matches the type of the token field defined in t().

Magnificent! :slightly_smiling_face:

IvanR

IvanR

1.5.10 - November 16, 2022

  • Improve compatibility with ElixirLS. After running mix deps.update domo, please, remove .elixir_ls in your project directory and reopen VSCode.

Now struct type checking errors are in the VSCode view :tada:

Last Post!

dogweather

dogweather

That’s amazing, thank you.

Where Next?

Popular in Announcing Top

Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Thank...
New
tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
kelvinst
Hey everyone! Well, we made this lib a while ago and now we decided to finally go out and public with it! It’s a tool for creating and m...
New
pkrawat1
Hey guyz We at @aviabird are working on a payment library in elixir/phoenix. We are targeting March 2018 to add 56 Gateways to it. Have...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New
sasajuric
I’d like to announce a small library called boundaries. This is an experimental project which explores the idea of enforcing boundaries ...
New
martinthenth
Hello everybody :wave: Recently, some of my colleagues talked about database ids and uuids and their problems, and I remembered the pain...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44532 311
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
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

We're in Beta

About us Mission Statement