Modeling domain with types in Elixir

Yes! I’ve learned that Dialyzer has @opaque directive.

If you mark a type as @opaque, you can use it inside the module it is defined. Outside the module, you can pass the opaque type around, but you can’t access its fields. This directive renders @imetallica solution great! E.g.

defmodule Email do
  defstcut :email
  @opaque t() :: %__MODULE__{email: string()}

  @spec validate(String.t()) :: t()
  def validate(string_email) do
    #... validations
    # if this is the only place you return that struct, dialyzer will make sure nobody else creates or modifies the struct
    %__MODULE__{email: string_email)
  end

  @spec send(t()) :: :ok | :error
  def send(%__MODULE__{email: string_email}) do
    #access to the field is OK because you can use @opaque type fields in the same module
    :ok
  end
end

def OtherModule do
  def test() do
    valid_email = Email.validate("email@example.com")
    %Email{email | email: "something_broken"} #dialyzer will complain because the type is opaque
  end
end

Combined with other approaches from this talk: https://www.youtube.com/watch?v=XGeK9q6yjsg
it gives me a pretty nice domain-driven design :slight_smile:

4 Likes