はじめに
Ash Framworkの作者のZachさんが来日されて、Ash Frameworkについての講演をされました。
この講演の直前まで、Ash Frameworkについては全く知識がなかったのですが、講演に参加して話を伺って、いままで聞いたことのない切口で、とても面白いとおもいました。
詳しい事は、Zachさんの講演スライドを見ていただくのが良いと思います。
このうち、ポイントになる2点を紹介したいと思います。
この記事のプログラムは、Zachさんの講演スライドにあるコードをみやすいようにテキスト化したものです。
コードよりデータを重視
Zachさんの講演スライドの最初の方に、Data > Code という言葉が出てきます。
Data > Codeとは、データを重視するという意味だと思います。
次の例を見てみましょう。
defmodule InputValidator do
def validate_agename(input) do
case Map.get(input, :name) do
nil -> {:error, :name, "Name is required"}
value when is_binary(value) -> :ok
_ -> {:error, :name, "Name must be a string"}
end
end
@rules [
&validate_name/1,
&validate_age/1,
&validate_email/1
]
end
このコードは、入力値を検証するモジュールです。このモジュールは、入力値を検証するための関数を定義しています。よく見られるパターンですね。
Dataを中心に考えた場合は、次のようになります。
defmodule InputValidator do
@rules [
{:name, &is_binary/1, "Name must be a string"},
{:age, &(&1 > 18), "Age must be 18 or older"},
{:email, &String.contains?(&1, "@"), "Email must contain an @"}
]
end
検証のルールをデータとして定義しているのです。
もちろんこのデータだけでは、検証を行うことはできません。検証を行うための関数も必要です。
defmodule InputValidator do
@rules [ ... ]
def validate_one_field(field, value) do
Enum.reduce_while(@rules, :ok, fn {rule_field, validator, error}, :ok ->
if rule_field != field || validator.(value) do
{:cont, :ok}
else
{:halt, {:error, error}}
end
end)
end
end
このように、データと検証のルールを定義することで、Validatorを実現する事ができます。
手書きよりも自動生成
Zachさんの講演スライドでは、次に、Derive > Handwrite という言葉がでてきます。
手書きのコードよりも自動生成を重視するという事だと思います。
まずは、手書きの例を見てみましょう。
defmodule MyForm do
use MyAppWeb, :live_component
def render(assigns) do
~H"""
<.form for={@form}>
...
<.input field={@form[:age]} />
<.field_tip>
Age must be greater than 18.
</.field_tip>
</.form>
"""
end
end
このコードは、よくある、フォームを表示するコンポーネントです。:ageのフィールドについての説明(field_tip)を表示しています。
これに対して、自動生成の例を見てみましょう。
defmodule MyForm do
use MyAppWeb, :live_component
def render(assigns) do
~H"""
<.form for={@form}>
...
<.input field={@form[:age]} />
<%= for requirement <- InputValidator.requirements(:age) do %>
<.field_tip>
<%= requirement %>
</.field_tip>
<% end %>
</.form>
"""
end
end
この例には、Age must be greater than 18.
という文字列はなく、InputValidator.requirements(:age)から取得していて、自動生成されています。
さらに、:ageだけでなく、他のフィールドについても生成するコードを書くこともできます。
defmodule MyForm do
use MyAppWeb, :live_component
def render(assigns) do
~H"""
<.form for={@form}>
<%= for field <- InputValidator.fields() do %>
<.input field={@form[field]} />
<%= for requirement <- InputValidator.requirements(field) do %>
<.field_tip>
<%= requirement %>
</.field_tip>
<% end %>
<% end %>
</.form>
"""
end
end
この例は、Web画面の例ですが、モデリングする対象は、Webサーバだけではなく、データベース等も対象となっています。
この方法で、モデリングすると、データベースの種類がかわっても、モデリングのデータ部分は変わらず、処理の部分がデータベースによって変わるだけという事になります。
まとめ
Zachさんの講演スライドには、まだポイントになることがあるんですが、
- Data > Code
- Derive > Handwrite
この2つAsh Frameworkの特徴をよく表していると思いました。
わたしも、類似したバリデーションがたくさんあった時に、パラメータと共通化した関数にしてみた事は、ありますが、これを突き詰めて、開発対象のモデリングにまで広げた所が、Ash Frameworkの凄い所だと思いました。
このAsh Framworkは、現在すでにバージョン3.4.47までリリースされています。詳しくは、以下のリンクを参照してください。
今後AIによる開発が進んでくると、何を作るのかを表現する事が重要になってくると思います。その時に、Ash Frameworkのような思想が役立つのかもしれないなぁ。