<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: lee eggebroten</title>
    <description>The latest articles on DEV Community by lee eggebroten (@leggebroten).</description>
    <link>https://dev.to/leggebroten</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1385692%2F68d18436-4788-4d26-813d-fdcc4f82f4b0.jpeg</url>
      <title>DEV Community: lee eggebroten</title>
      <link>https://dev.to/leggebroten</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leggebroten"/>
    <language>en</language>
    <item>
      <title>Super simple validated structs in Elixir</title>
      <dc:creator>lee eggebroten</dc:creator>
      <pubDate>Sat, 20 Apr 2024 17:31:42 +0000</pubDate>
      <link>https://dev.to/leggebroten/super-simple-validated-structs-in-elixir-45a9</link>
      <guid>https://dev.to/leggebroten/super-simple-validated-structs-in-elixir-45a9</guid>
      <description>&lt;p&gt;&lt;strong&gt;I&lt;/strong&gt;n a prior article about a &lt;a href="https://dev.to/leggebroten/you-need-a-circle-of-trust-for-faster-and-safer-development-38lo"&gt;circle of trust&lt;/a&gt;, I discussed the substantial benefits of consistently using validated data structs as input parameters; &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html" rel="noopener noreferrer"&gt;Ecto.Changeset&lt;/a&gt; and &lt;a href="https://hexdocs.pm/plug/Plug.Conn.html" rel="noopener noreferrer"&gt;Plug.Conn&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;Even though there are clear benefits to this practice such as code that's easier to understand, easier to refactor, easier to test, with more useful feedback within IDEs, there can be resistance implementing it.  Common pushback usually centers around the boilerplate "noise" of creating and maintaining the struct's "constructor" modules. That resistance is what motivated me to write the macro this article highlights.&lt;/p&gt;

&lt;p&gt;This macro's goal is to extend &lt;a href="https://hexdocs.pm/typedstruct/TypedStruct.html" rel="noopener noreferrer"&gt;TypedStruct&lt;/a&gt; with moderately powerful field-level defaulting and validation.  The implementation imposes very little overhead (coding time or runtime), addresses the vast majority of use cases, and the created modules can be extended with your own functions for a very rich solution to data collection, validation, mutation, and parameter passing.&lt;/p&gt;

&lt;p&gt;The Hex documentation provides a Livebook implementation to demonstrate using the macro and its benefits.&lt;/p&gt;

&lt;p&gt;I am pleased to introduce you to the &lt;code&gt;TypedStructCtor&lt;/code&gt; (where "ctor" is an abbreviation of &lt;a href="https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)" rel="noopener noreferrer"&gt;constructor&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  TypedStructCtor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/typed_struct_ctor/TypedStructCtor.html" rel="noopener noreferrer"&gt;TypedStructCtor&lt;/a&gt; is a TypedStruct plugin to add validating constructors to a TypedStruct module&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hexdocs.pm/typedstruct/TypedStruct.html" rel="noopener noreferrer"&gt;TypedStruct&lt;/a&gt; macro wraps field definitions to reduce boilerplate needed to define elixir structs and provides a &lt;a href="https://hexdocs.pm/typedstruct/TypedStruct.Plugin.html" rel="noopener noreferrer"&gt;plugin system&lt;/a&gt; enabling clients to extend the DSL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TypedStructCtor&lt;/code&gt; uses the &lt;code&gt;__changeset__&lt;/code&gt; "reflection" function added by the plugin &lt;a href="https://hexdocs.pm/typed_struct_ecto_changeset/TypedStructEctoChangeset.html" rel="noopener noreferrer"&gt;TypedStructEctoChangeset&lt;/a&gt; which enables Ecto.Changeset.cast on fields defined within the TypedStruct macro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out in Livebook
&lt;/h2&gt;

&lt;p&gt;Try the macro out in real time without having to install or write any of your own code&lt;/p&gt;

&lt;p&gt;To get started you need a running instance of &lt;a href="https://livebook.dev/" rel="noopener noreferrer"&gt;Livebook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://livebook.dev/run?url=https://github.com/withbelay/typed_struct_ctor/blob/main/try_it_out.livemd" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flivebook.dev%2Fbadge%2Fv1%2Fblue.svg" alt="Run in Livebook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rationale
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html" rel="noopener noreferrer"&gt;Ecto.Changeset&lt;/a&gt; is a great way to create validated structs.  However, if you've many validated structs they quickly become "noise", where writing these functions quickly become tedious, and bugs are easily introduced as struct changes are not easily overlooked.&lt;/p&gt;

&lt;p&gt;When the effort needed to write boilerplate tests for boilerplate code is factored in, it can be tempting to skip struct validation altogether.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Examples
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

    &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;AStruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;TypedStruct&lt;/span&gt;

      &lt;span class="n"&gt;typedstruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructEctoChangeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructCtor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# A required field with a default value provided by MFA tuple to return a UUID&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default_apply:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;

        &lt;span class="c1"&gt;# A required field with no default, meaning it must be provided to the constructor.&lt;/span&gt;
        &lt;span class="c1"&gt;# It's an `integer` with known `Ecto.cast` behavior, so for instance, string values are cast &lt;/span&gt;
        &lt;span class="c1"&gt;# for example string to integer&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:integer_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;

        &lt;span class="c1"&gt;# An optional field with no default, meaning it will only have a value if provided to the &lt;/span&gt;
        &lt;span class="c1"&gt;# constructor&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:some_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; 
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;AStruct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;some_string:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="c1"&gt;#Ecto.Changeset&amp;lt;&lt;/span&gt;
       &lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"36153915-bfd7-4067-85e1-03c9b0662582"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;some_string:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
       &lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;integer_field:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"can't be blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:required&lt;/span&gt;&lt;span class="p"&gt;]}],&lt;/span&gt;
       &lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="c1"&gt;#AStruct&amp;lt;&amp;gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;valid?:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
     &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# With `bang` notation and demonstrating Ecto's field cast (string to integer)&lt;/span&gt;
    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;AStruct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new!&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;some_string:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;integer_field:&lt;/span&gt; &lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStruct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;some_string:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;integer_field:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"2e28df41-c024-465e-901d-22c974f1d356"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The TypedStruct macro makes it much easier to define structs.  The TypedStructEctoChangeset plugin uses the field definitions to generate an Ecto.Changeset.cast function for fields in the struct.  And this plugin, TypedStructCtor, uses those &lt;code&gt;cast&lt;/code&gt; functions to generate validating constructors for the enclosing struct created by TypedStruct.&lt;/p&gt;

&lt;p&gt;This plugin adds 5 constructors, &lt;code&gt;new/0&lt;/code&gt;, &lt;code&gt;new/1&lt;/code&gt;, &lt;code&gt;new!/1&lt;/code&gt;, &lt;code&gt;from/2&lt;/code&gt;, and &lt;code&gt;from!/2&lt;/code&gt; to the given module.  &lt;/p&gt;

&lt;p&gt;Ecto &lt;code&gt;cast&lt;/code&gt; is called for all attributes provided to the constructors, defaults are applied where needed, and&lt;br&gt;
validation is performed.  &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;new&lt;/code&gt; functions return {:ok, struct} or {:error, changeset}, while the &lt;code&gt;new!&lt;/code&gt; functions return the struct, or raises if there were issues with &lt;code&gt;cast&lt;/code&gt; or validation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;new&lt;/code&gt; function takes an optional map of attributes, does &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4" rel="noopener noreferrer"&gt;Changeset.cast&lt;/a&gt; of all values matching the defined fields, adds defaults for fields missing values, validates any required fields, and finally calls &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html#apply_action/2" rel="noopener noreferrer"&gt;Changeset.apply_action&lt;/a&gt; to validate the changeset.  Returns &lt;code&gt;{:ok, &amp;lt;struct&amp;gt;}&lt;/code&gt; if everything is OK, &lt;code&gt;{:error, &amp;lt;changeset&amp;gt;}&lt;/code&gt; if there were issues with &lt;code&gt;cast&lt;/code&gt; or validation. Because it's necessary to properly handle mappable fields, if a struct is passed to the &lt;code&gt;new&lt;/code&gt; function, &lt;code&gt;{:error, :attributes_must_be_a_map}&lt;/code&gt; is returned; use one of the &lt;code&gt;from&lt;/code&gt; functions for that use case as described below.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;from&lt;/code&gt; functions are useful in messaging environments where a new message is created from a some set of values from a source message.  They are similar to the &lt;code&gt;new&lt;/code&gt; functions but accept a "base struct" as the first argument and a map of attributes as the second argument.  The base struct is mapped first to the field values, and the attributes are merged on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Required Fields
&lt;/h2&gt;

&lt;p&gt;By default, all fields are required when calling the constructors.  Meaning you'll get a changeset error if the field does not have a default, and you don't provide an attribute value for it in the constructor.&lt;/p&gt;

&lt;p&gt;You can override this by passing &lt;code&gt;required: false&lt;/code&gt; to the plugin&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

  &lt;span class="n"&gt;typedstruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructEctoChangeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructCtor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:this_field_is_not_required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or by passing &lt;code&gt;required: false&lt;/code&gt; to the &lt;code&gt;field&lt;/code&gt; definition.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

  &lt;span class="n"&gt;typedstruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructEctoChangeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructCtor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:this_field_is_not_required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Field-level Defaults
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;default&lt;/code&gt; and &lt;code&gt;default_apply&lt;/code&gt; can be provided to the &lt;code&gt;field&lt;/code&gt; definition to specify a default value for the field.&lt;/p&gt;

&lt;p&gt;Though you can specify both &lt;code&gt;default&lt;/code&gt; and &lt;code&gt;default_apply&lt;/code&gt; (an MFA tuple), only one will be used.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;default&lt;/code&gt; will be used with Elixir's struct syntax (e.g. &lt;code&gt;%AStruct{}&lt;/code&gt;).&lt;br&gt;
&lt;code&gt;default_apply&lt;/code&gt; will be invoked when one of the 5 constructor functions is used (e.g. &lt;code&gt;AStruct.new!()&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;default_apply&lt;/code&gt; function is short-circuited and will only be invoked if the given field was not present in the &lt;br&gt;
attributes.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

    &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;AStructWithDefaulting&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;TypedStruct&lt;/span&gt;

      &lt;span class="n"&gt;typedstruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructEctoChangeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructCtor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default_apply:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;SomeModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:some_function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"55"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithDefaulting&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithDefaulting&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;field:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;AStructWithDefaulting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new!&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithDefaulting&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;field:&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Mappable Fields
&lt;/h2&gt;

&lt;p&gt;As mentioned above, when using &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;from!&lt;/code&gt; functions, the first argument is a "source" struct whose matching-name fields will be copied first into the struct being constructed.  By default, all matching-name fields are copied, but the &lt;code&gt;mappable?&lt;/code&gt; boolean attribute can be used to specify which fields are not copied.  This is useful when you want the newly constructed struct to have different values for a field than the source struct such as &lt;code&gt;created_at&lt;/code&gt; or &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not mapping over the source struct values will mean the newly constructed struct will leave the new fields empty unless defaulted or provided as attributes to the constructor.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

    &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;TypedStruct&lt;/span&gt;

      &lt;span class="n"&gt;typedstruct&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructEctoChangeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TypedStructCtor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mappable?:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default_apply:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]})&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;mappable?:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default_apply:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utc_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]})&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# In the example below, the `id` and `created_at` fields are `mappable?: false` so they are not &lt;/span&gt;
    &lt;span class="c1"&gt;# copied from the source struct.  So in the new struct, `:reason` is copied from the source &lt;/span&gt;
    &lt;span class="c1"&gt;# struct, `:id` is provided in the attributes map, and `:created_at`, being nil after all the&lt;/span&gt;
    &lt;span class="c1"&gt;# copying is done, causes its default to be used instead, resulting in a new date.&lt;/span&gt;
    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;source_struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new!&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;reason:&lt;/span&gt; &lt;span class="s2"&gt;"because"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;reason:&lt;/span&gt; &lt;span class="s2"&gt;"because"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;created_at:&lt;/span&gt; &lt;span class="sx"&gt;~U[2023-11-18 04:57:16.754681Z]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"ffe94776-5d6e-4d84-9aeb-2862d874577f"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"id from attributes"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;reason:&lt;/span&gt; &lt;span class="s2"&gt;"because"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;created_at:&lt;/span&gt; &lt;span class="sx"&gt;~U[2023-11-18 04:57:16.766353Z]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"id from attributes"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;reason:&lt;/span&gt; &lt;span class="s2"&gt;"I said so"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;AStructWithMappableFields&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;reason:&lt;/span&gt; &lt;span class="s2"&gt;"I said so"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;created_at:&lt;/span&gt; &lt;span class="sx"&gt;~U[2023-11-18 04:57:16.772312Z]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="s2"&gt;"a09be86b-373a-48f0-9d74-faee10037421"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Because this plugin supports the interface defined by the &lt;code&gt;TypedStruct&lt;/code&gt; macro, installation assumes you've already added that dependency.&lt;/p&gt;

&lt;p&gt;While you can use the original &lt;a href="https://hex.pm/packages/typed_struct" rel="noopener noreferrer"&gt;typed_struct&lt;/a&gt; library, it seems to no longer be maintained.  However, there is a fork &lt;a href="https://hex.pm/packages/typedstruct" rel="noopener noreferrer"&gt;here&lt;/a&gt; that is quite active.&lt;/p&gt;

&lt;p&gt;The package can be installed by adding &lt;code&gt;typed_struct_ctor&lt;/code&gt; to your list of dependencies in &lt;code&gt;mix.exs&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# Choose either of the following `TypedStruct` libraries &lt;/span&gt;
    &lt;span class="c1"&gt;# both use the same name for the macro - `typedstruct` but&lt;/span&gt;
    &lt;span class="c1"&gt;# but are mutually exclusive:&lt;/span&gt;

    &lt;span class="c1"&gt;# The original, but no longer maintained library&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:typed_struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.3.0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;# Or the newer forked library&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:typedstruct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.5.2"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;# And add this library  &lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:typed_struct_ctor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.1.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>elixir</category>
      <category>programming</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>You need a Circle of Trust for faster and safer development</title>
      <dc:creator>lee eggebroten</dc:creator>
      <pubDate>Tue, 26 Mar 2024 19:56:57 +0000</pubDate>
      <link>https://dev.to/leggebroten/you-need-a-circle-of-trust-for-faster-and-safer-development-38lo</link>
      <guid>https://dev.to/leggebroten/you-need-a-circle-of-trust-for-faster-and-safer-development-38lo</guid>
      <description>&lt;p&gt;&lt;strong&gt;Do&lt;/strong&gt; you want faster development, less code, fewer tests, higher quality code, fewer production failures, and a better customer experience all at nearly no additional development cost? Consider building a Circle of Trust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdeo2pbzocmusjwl8ny3w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdeo2pbzocmusjwl8ny3w.jpeg" alt="Joined ropes" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O&lt;/strong&gt;ne of the fantastic principles enabled by &lt;a href="https://elixir-lang.org/"&gt;Elixir&lt;/a&gt; is the freedom to “&lt;a href="https://dev.to/jackmarchant/understanding-concurrency-in-elixir-24l8"&gt;let it crash&lt;/a&gt;”. We all recognize that it’s impossible to write perfect, failure-free code. And even if we could somehow, hardware failures and even radiation can still corrupt your state, creating situations you simply cannot recover from. So … let the process and its bad state die, and start over from a clean slate. Simple. Beautiful.&lt;/p&gt;

&lt;p&gt;Sadly though, most failures we encounter are of our own making. Either our algorithms introduced the bug, or more likely, we didn’t account for certain state. That uncertainty of state invariably leads to overly defensive and hard-to-follow code, and frequent production failures that are difficult to debug. Which is only made worse when using Elixir’s super-efficient processes to improve performance because the logged stack traces can be nearly useless trying to locate the error source. “Awesome … an anonymous in-lined Erlang function inside a GenServer callback. Good luck finding that one!”.&lt;/p&gt;

&lt;p&gt;While test suites run during build/deploy to prove that anticipated problems are handled somewhere in our code, a Circle of Trust is a runtime concept like a Customs officer; always checking that the provided goods are in fact what we were told to expect. That way, if we’re given bad state from an outside source it’ll never reach our algorithms, and a full report can be created to make it easier to find the offender. And, like the Customs officer, you have only one place to go to define what is permitted, how sources should be packaged, and what is available in the shipment.&lt;/p&gt;

&lt;p&gt;A Circle of Trust has almost no real development cost. It’s easy to start, provides immediate rewards, and dividends increase the more you invest. Stay with me, I’ll clarify some of the substantial benefits below once I’ve explained what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  So … what IS a Circle of Trust?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt; Circle of Trust adapts the OO engineering principles of &lt;a href="https://stackify.com/oop-concept-for-beginners-what-is-encapsulation/"&gt;Encapsulation&lt;/a&gt;, and &lt;a href="https://medium.com/machine-words/separation-of-concerns-1d735b703a60"&gt;Separation of Concerns&lt;/a&gt; into Functional Programming using modules to &lt;strong&gt;validate&lt;/strong&gt; data that originated outside your control and provide &lt;strong&gt;accessors&lt;/strong&gt; for that data. This allows internal algorithms a level of trust for source data and a measure of isolation from structural changes source data may experience later. Once the module is created, move all defensive “what about this” code into that module’s validation routine.&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;Granted, some of the benefits of a Circle of Trust can be realized by more traditional &lt;a href="http://softwaretestingfundamentals.com/functional-testing/"&gt;functional&lt;/a&gt; or &lt;a href="https://www.guru99.com/interface-testing.html"&gt;interface&lt;/a&gt; test suites. But unless your system has super high performance requirements, the run time validation in a Circle of Trust can provide far more bang for your buck. And, if some validations do create bottlenecks, the costly portions can be moved to your interface test suite.&lt;/p&gt;

&lt;p&gt;Author’s side note: I would be surprised if you found a formal software related definition for “Circle of Trust”. It is a term our Atlas team at &lt;a href="https://www.geekwire.com/2019/hidden-legacy-aquantive-microsofts-6b-loss-became-win-seattle-tech-industry/"&gt;aQuantive&lt;/a&gt; started using to refer to the concept of runtime validation and encapsulation of source data. Even though we had substantial unit and interface test suites, adopting this principle provided all the benefits described here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhlqppz1a8vu1jp5kaag.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhlqppz1a8vu1jp5kaag.jpeg" alt="software" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L&lt;/strong&gt;et’s have an example applied to incoming data from a simple “Contact Us” form a customer might use to inquire about something. To shorten things up a bit I’ve left off most &lt;code&gt;@spec&lt;/code&gt; and &lt;code&gt;@doc&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;Here I’ll use &lt;a href="https://hexdocs.pm/ecto/Ecto.html"&gt;Ecto&lt;/a&gt; to validate that required fields were present and mutate data types where needed, provide declarative &lt;code&gt;valid?&lt;/code&gt;, if data is invalid &lt;code&gt;errors&lt;/code&gt; clearly identifies what was wrong, and finally it provides accessors to those fields either as a collection from &lt;code&gt;fields&lt;/code&gt; or individually such as &lt;code&gt;email&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;

  &lt;span class="n"&gt;embedded_schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:request_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="ss"&gt;subject:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="ss"&gt;request_date:&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nv"&gt;@valid_subjects&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"General Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Order Status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Compliment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Complaint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Other"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;~w(email subject message request_date)a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;~w(email subject message)a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;~r/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_inclusion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@valid_subjects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;valid?:&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;struct!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;subject:&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;request_date&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;request_date:&lt;/span&gt; &lt;span class="n"&gt;request_date&lt;/span&gt;&lt;span class="p"&gt;}}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_date&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its unit test file clarifies expectations from validation and accessors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUsTest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ExUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Case&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"cast/1 when all data is valid"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:valid_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"valid? returns true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"email matches source data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"subject matches source data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"message matches source data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"request_date is converted to Date type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="sx"&gt;~D[2019-10-28]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"fields returns expected struct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fields_is_as_expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"cast/1 when missing required fields"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:missing_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"valid? returns false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"reports missing required fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
               &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"can't be blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:required&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
               &lt;span class="ss"&gt;subject:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"can't be blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:required&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
               &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"can't be blank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:required&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
             &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"fields returns expected struct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fields_is_as_expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"cast/1 when invalid subject"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:valid_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:invalid_subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"valid? returns false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"reports missing required fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
               &lt;span class="ss"&gt;subject:&lt;/span&gt;
                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"is invalid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:inclusion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="ss"&gt;enum:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"General Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Order Status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Compliment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Complaint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Other"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                  &lt;span class="p"&gt;]}&lt;/span&gt;
             &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"fields returns expected struct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fields_is_as_expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"cast/1 when invalid request_date"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:valid_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:invalid_request_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:execute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"valid? returns false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"reports missing required fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
               &lt;span class="ss"&gt;request_date:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"is invalid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;validation:&lt;/span&gt; &lt;span class="ss"&gt;:cast&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
             &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"fields returns expected struct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fields_is_as_expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;fields_is_as_expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_date&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="sx"&gt;~D[2019-10-28]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;request_date:&lt;/span&gt; &lt;span class="n"&gt;expected_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;subject:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;email:&lt;/span&gt; &lt;span class="s2"&gt;"foo@bar.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;subject:&lt;/span&gt; &lt;span class="s2"&gt;"Compliment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;request_date:&lt;/span&gt; &lt;span class="s2"&gt;"2019-10-28"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;missing_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="p"&gt;%{}}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;invalid_subject&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;put_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"this subject is not in approved list"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;invalid_request_date&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;put_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:request_date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"10-28-2019"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now an example of how we might use it.&lt;/p&gt;

&lt;p&gt;Given the form data from the customer’s request, use the &lt;code&gt;ContactUs.cast&lt;/code&gt; function to fully validate the data. If data from the form is not &lt;code&gt;valid?&lt;/code&gt; we can use &lt;code&gt;errors&lt;/code&gt; to clearly inform the user what was wrong (or perhaps log if source data is from an external API). If state is OK, we can use accessors (like &lt;code&gt;ContactUs.email(contact_data))&lt;/code&gt; to obtain form data without worrying about where it is or about possible future minor structural changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Sends emails from our `Contact Us` form.
  """&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;View&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Bamboo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Generates and sends an email from a contact_us form submission.
  """&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send_contact_us_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_us_attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;contact_us_attrs&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;valid?:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contact_data&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;contact_data&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;contact_us_email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver_now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contact_data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;errors:&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@doc&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;contact_us_email&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contact_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;new_email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"customerservice@my_company.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"contact-form@email.my_company.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Reply-To"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ContactUs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"contact_us_email.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"left as an exercise for the reader"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73d5zxsggdx2y60v6esb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73d5zxsggdx2y60v6esb.jpeg" alt="Lending a hand" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F&lt;/strong&gt;inally, let’s explore some ways a Circle of Trust can lend a hand.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Failures happen. Support Agile’s &lt;a href="https://dzone.com/articles/fail-fast-principle-in-software-development"&gt;Fail Fast principle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ensures contracts. We’re all working together. That’s both good and bad. It can be difficult keeping a growing number of developers in synch. You’ll identify problems with production pushes/rollouts sooner.&lt;/li&gt;
&lt;li&gt;Enables better error messaging for known-bad conditions. “Hey, we aren’t supposed to be getting a &lt;code&gt;nil&lt;/code&gt; image URL and we got one”. We can log something sane rather than a huge pattern match failure somewhere inside of an anonymous callback with no idea where the call originated from.&lt;/li&gt;
&lt;li&gt;Having context and source data provides the opportunity for better customer feedback on failure rather than “oops, something went wrong”.&lt;/li&gt;
&lt;li&gt;Makes testing easier. Because you know that certain data states can never happen, you don’t have test for those cases.&lt;/li&gt;
&lt;li&gt;Makes testing easier #2. Data-modules can help create more resilient tests. By abstracting data state to these modules, the tests can be shielded from change when underlying data formats change.&lt;/li&gt;
&lt;li&gt;Reduces production code. Because you can filter out invalid data-states, you don’t have to write code to handle them.&lt;/li&gt;
&lt;li&gt;Coding is faster and safer. The declarative nature of these data modules makes writing both production and test code &lt;strong&gt;much&lt;/strong&gt; faster and safer.&lt;/li&gt;
&lt;li&gt;Refactoring is easier. It should be obvious that it’s simpler to update and test an accessor than to find all client code directly accessing source data. “Their API just changed &lt;code&gt;id&lt;/code&gt; to &lt;code&gt;user_id&lt;/code&gt;" … yeah, that’ll be a fun grep and Code Review.”&lt;/li&gt;
&lt;li&gt;Shared business logic. These modules provide a convenient place for common code. For instance, rather than having date format logic sprinkled everywhere in your code, do it here.&lt;/li&gt;
&lt;li&gt;When combined with &lt;a href="https://aaronrenner.io/2019/09/18/application-layering-a-pattern-for-extensible-elixir-application-design.html"&gt;Application Layering&lt;/a&gt;, you’ll have an appropriate place to provide “living” documentation about your architecture, and its expectations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.martinfowler.com/bliki/Yagni.html"&gt;YAGNI&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/KISS_principle"&gt;KISS&lt;/a&gt;. We should avoid over-engineered and unneeded “what if” investments in our code. And sometimes the best way to reduce code complexity is to do a better job of &lt;a href="https://thoughtbot.com/blog/better-domain-modeling-in-elixir-with-sum-types"&gt;domain modeling&lt;/a&gt;. However, it is quite rare that you’ll fully understand your data domain right away. A Circle of Trust makes it far easier to make these additions later when things are clearer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjupnldfjs1cshg8hopx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjupnldfjs1cshg8hopx.jpeg" alt="Path up mountain" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T&lt;/strong&gt;here are many paths to good code. I hope you can see that adding a Circle of Trust allows for faster development, less code, fewer tests, higher quality code, fewer failures, and more helpful error messages. All at the negligible cost of writing a module that encapsulates the data parsing and validation you’re already doing.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>learning</category>
      <category>coding</category>
      <category>development</category>
    </item>
    <item>
      <title>Feature Guides enable the best of "Full Stack" and domain expertise</title>
      <dc:creator>lee eggebroten</dc:creator>
      <pubDate>Tue, 26 Mar 2024 15:05:39 +0000</pubDate>
      <link>https://dev.to/leggebroten/feature-guides-enable-the-best-of-full-stack-and-domain-expertise-45pl</link>
      <guid>https://dev.to/leggebroten/feature-guides-enable-the-best-of-full-stack-and-domain-expertise-45pl</guid>
      <description>&lt;p&gt;&lt;strong&gt;W&lt;/strong&gt;ant more dependable feature delivery, happier developers, increased velocity and performance, all at a lower overall cost? Consider horizontally aligned teams utilizing Feature Guides.&lt;/p&gt;

&lt;p&gt;When it comes to software development there really are only two ways to organize your team; Full Stack (vertically aligned feature teams), or domain based (no, not heap).&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Jack of all trades and (usually) master of none
&lt;/h3&gt;

&lt;p&gt;Vertically aligned development, is seductive. In theory, it puts into the hands of the Product Manager, direct control of all the developer resources needed to complete tasks throughout the company's technical domains. These might include: UI, middleware, server, database, distribution, monitoring, and DevOps.&lt;/p&gt;

&lt;p&gt;Thus, theory goes, this full access to the means of production guarantees quality products, on time, on budget, with an ever increasing velocity. And, for a short time this is true. With clear, focused, business objectives its easy to prioritize a team's skills and resources to achieve goals. Unfortunately, it fails for one simple reason; humans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2rv2e6q1qkcmu0vwps.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2rv2e6q1qkcmu0vwps.jpeg" alt="People contemplating canyon, wide and deep" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mastering all the skill proficiency requirements for Full Stack is more than can be expected of any but a handful. Consider this non-inclusive list of some of the languages, libraries, and frameworks a developer would need proficiency with (not just familiarity) to be truly "Full Stack";&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;Slime/transpilers&lt;/li&gt;
&lt;li&gt;Javascript&lt;/li&gt;
&lt;li&gt;Ajax&lt;/li&gt;
&lt;li&gt;JQuery/Selectors&lt;/li&gt;
&lt;li&gt;Bootstrap&lt;/li&gt;
&lt;li&gt;Underscore&lt;/li&gt;
&lt;li&gt;Angular/Phoenix/React/Rails&lt;/li&gt;
&lt;li&gt;REST&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;MySql/Postgres&lt;/li&gt;
&lt;li&gt;Kafka/RabbitMQ&lt;/li&gt;
&lt;li&gt;Phoenix/Phoenix Live&lt;/li&gt;
&lt;li&gt;Elixir/Ruby/C#&lt;/li&gt;
&lt;li&gt;plus all associated test frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;T&lt;/strong&gt;hat's a lot to learn, but &lt;strong&gt;mastering&lt;/strong&gt; something requires far more than mere &lt;strong&gt;proficiency&lt;/strong&gt; with its syntax.&lt;/p&gt;

&lt;p&gt;Like any natural human language, each software language, library, and framework has its own idioms, dialects, and phraseology that can take varying degrees of time to master. And, like natural language, lacking that fluency usually produces poor, and sometimes disastrous end results. Also, software languages, tools, frameworks, and paradigms evolve rapidly, requiring constant attention to maintain fluency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8w33e5txky3g2hjsqft.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8w33e5txky3g2hjsqft.jpeg" alt="long view of canyon" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  But wait, it gets worse
&lt;/h3&gt;

&lt;p&gt;Each domain of a product's technical stack has considerations beyond the language chosen to implement it. For instance, adding a column to a database is a simple thing. But the consequences of doing it in the wrong place can have large impacts on your business that are difficult to unravel. Additionally, proper testing techniques for all those languages can vary substantially and doing it wrong can hide failures, and even hamstring new features in production code.&lt;/p&gt;

&lt;p&gt;Granted, some of these negatives can be countered with skill sharing and consistent, high quality code reviews. But again, this kind of diligence is difficult to achieve in the long term when deadlines rear their ugly heads.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.linkedin.com/business/learning/blog/learner-engagement/see-the-industries-with-the-highest-turnover-and-why-it-s-so-hi"&gt;LinkedIn&lt;/a&gt;, the turnover rate for software developers is the highest of any sector at over 13%. How much knowledge sharing, code consistency, architectural direction, and quality will be quantitatively passed along when average tenure for all but the highest paying companies is about 2 years?&lt;/p&gt;

&lt;h3&gt;
  
  
  You mean we gotta pay them too?
&lt;/h3&gt;

&lt;p&gt;It probably goes without saying, but with the greater number of fluency requirements, the harder it will be to find and evaluate qualified employees. Correspondingly, their pay expectations will be higher, and the more likely they'll be hired away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnp2w2fkp5l552426scv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnp2w2fkp5l552426scv.jpeg" alt="Man contemplating canyon" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's bad, what do you propose?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Domain (horizontal) alignment
&lt;/h3&gt;

&lt;p&gt;Generally speaking horizontal development is organized around domain competencies, and the extended experience needed to make wise decisions for the long term health of the code base.&lt;/p&gt;

&lt;p&gt;UI, for instance, needs language skills specific to their target devices (desktop, iOS, Android), and browsers. This domain also requires a unique set of experience-based skills on how to make the customer interface responsive, reliable, and efficient. Due to all the devices and browsers, potential corner cases are plentiful and only experience can avoid them. Given that your UI is usually the first contact your customer has with your product, the need for experience in this domain is obvious.&lt;/p&gt;

&lt;p&gt;"Platform" or "server side" code has similar domain specific concerns. Mistakes here are easy to make, expensive to fix, and can have a deep and potentially fatal effect on the product. Experience in this domain helps companies anticipate and design for concurrency demands, response times, memory requirements, reliability, architectural separation, and scaling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqmfnzrvzydzlt241nh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqmfnzrvzydzlt241nh.jpeg" alt="man on edge of canyon" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of domain alignment
&lt;/h3&gt;

&lt;p&gt;Given this, there are several benefits horizontally aligned organizations have over those that are vertically aligned. Generally these benefits are due to reduced breadth of proficiency requirements, increased depth in those proficiencies, and more applied long-term experience. Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier to find (and evaluate) qualified, like-minded employees.&lt;/li&gt;
&lt;li&gt;Greater focus on domain concerns, yields higher quality of code&lt;/li&gt;
&lt;li&gt;Higher code quality enables increased velocity and improved customer value and reliability&lt;/li&gt;
&lt;li&gt;Better appreciation of domain specific architectural issues decreases likelihood of introducing costly design flaws.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One subtle benefit that shouldn't be overlooked is the human component. While Full Stack development frequently suffers from &lt;a href="https://en.wikipedia.org/wiki/Tragedy_of_the_commons"&gt;Tragedy of the Commons&lt;/a&gt;, the more localized code base of horizontal organizations benefit from pride of ownership.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;imply put, it's much easier to fully understand your domain and become beneficially invested to make "your" code base better.&lt;/p&gt;

&lt;h3&gt;
  
  
  So why isn't everybody horizontally aligned?
&lt;/h3&gt;

&lt;p&gt;Using traditional business processes, it can be difficult to predictably provide customer value when you have to drive requirements down through all those layers. Each layer has its own domain-specific concerns, deadlines, and delivery requirements. This is usually complicated by conflicting priorities and schedules of internal customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt;o, how do you get the predictable delivery benefits of Full Stack and the performance, reliability, and long-term velocity benefits of horizontal?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7x8g7bqcc9hflfnd4ys.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7x8g7bqcc9hflfnd4ys.jpeg" alt="guide on mule climbing out of canyon" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You need Feature Guides of course
&lt;/h3&gt;

&lt;p&gt;Create a development-focused role mirroring the Product Manager's Business focus. This person needs to work closely with the Product Manager to understand the business needs, assist them in clarifying missing technical aspects, work with domain-teams to identify any long or short term impacts on architecture, ensure any needed preliminary designs or prototypes are created, solicit input on broad-based implementation strategies and any architectural changes, coordinate with domain teams to appropriately prioritize work within their schedules, and finally track the progress, quality and completeness of the required work.&lt;/p&gt;

&lt;p&gt;In some organizations these responsibilities might fall to the Development Manager, but personal experience shows much greater benefit for the organization if it's delegated to the developers themselves. Such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Human element of "owning" features is a powerful motivator&lt;/li&gt;
&lt;li&gt;Identifies developers ready for more responsibility&lt;/li&gt;
&lt;li&gt;Provides a "safer" way for developers to "try their hand" in different domains and languages they might have interest in&lt;/li&gt;
&lt;li&gt;Opportunity to groom and assess likely leadership candidates; which are always in short supply&lt;/li&gt;
&lt;li&gt;Builds a more thorough understanding of the overall architecture&lt;/li&gt;
&lt;li&gt;Cross-team coordination improves ties within the whole development organization&lt;/li&gt;
&lt;li&gt;Attributable ownership leads to greater opportunity for recognition and improved morale&lt;/li&gt;
&lt;li&gt;Project success rates provide more quantifiable metrics for performance evaluation&lt;/li&gt;
&lt;li&gt;Greater breadth of interactions improves the quality of peer reviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It&lt;/strong&gt; should be obvious that the seniority of the Feature Guide would be relative to the breadth and corporate impact of the feature. The Guide's primary responsibility is not necessarily do all (if any) of the work themselves but rather to coordinate and facilitate the technical details of predictably releasing high quality customer value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft56gy7o8sh1z9icwc8x3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft56gy7o8sh1z9icwc8x3.jpeg" alt="Graveyard" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nothing is black and white
&lt;/h3&gt;

&lt;p&gt;When a company or product is in its infancy, the smaller teams, reduced domain responsibilities, and need for an immediate marketable product (whatever the cost), then Full Stack can provide that focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;H&lt;/strong&gt;owever, the when the company first feels the unwieldy nature of all the accumulated tech debt and talks begin of the first re-write, they should switch to Horizontal and Feature Guides or it's likely the "new thing" is going to look just like the "old thing".&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
