[Ash Framework] Migration


최근 개인적인 개발을 할 때 Ash Framework 를 사용한다.
Ash Framework 홈페이지를 보면 아래와 같은 문구가 있다.
Model your domain, derive the reset
꽤나 잘 만든 문구라고 생각한다. 실제로 Ash 로 개발을 해보면 정말로 그렇다는 것을 느낀다.
Ash 는 단순한 코드로 복잡한 사용성을 커버할 수 있는 프레임워크이다.
Ash 로 개발하면 HTML 이나 SQL 같은 언어처럼 선언적인 코드를 작성하게 된다.
‘create_post 라는 함수는 Post 를 생성하는데 사용되는 함수이고, 생성 시 title, body
를 인자로 받는다’ 라고 Action 에 대한 정의를 하면, 자동으로 Post Resource 의 생성을 위한 함수, 검증을 위한 함수 등이 생성된다.
Ash Migration Support
Ash 의 Model your domain, derive the reset
문구가 적용되는 또 다른 부분이 있는데 바로 Migration 이다.
Ash 는 Migration 파일도 결국 Domain(Resource) 를 Source of Truth 로 삼아서 생성한다.
예로 Post 라는 Resource 를 하나 정의해보자. 단순한 예시를 위해 attributes 만 정의했다.
defmodule Foo.Blog.Post do
use Ash.Resource,
otp_app: :foo,
domain: Foo.Blog,
data_layer: AshPostgres.DataLayer
postgres do
table "posts"
repo Foo.Repo
end
attributes do
uuid_v7_primary_key :id
attribute :title, :string do
allow_nil? false
end
attribute :body, :string
create_timestamp :inserted_at
update_timestamp :updated_at
end
end
Ash migration 을 사용하면 migration 생성 시 Resource 에 대한 snapshot 을 같이 생성한다.mix ash.codegen create_post
를 루트에서 실행하면 migration 파일과 resource snapshot 파일이 같이 생성된다.
생성된 파일은 아래와 같다.
defmodule Foo.Repo.Migrations.CreatePost do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:posts, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true
add :title, :text, null: false
add :body, :text
add :inserted_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
end
end
def down do
drop table(:posts)
end
end
만약 추가로 category 가 필요하다면 아래와 같이 category 를 추가한다.
defmodule Foo.Blog.Post do
# ...
attributes do
# ...
attribute :category, :string do
allow_nil? false
end
end
end
그리고 mix ash.codegen add_category_to_post
를 실행하면 아래와 같이 delta 에 해당하는 부분만 migration 이 생성된다.
defmodule Foo.Repo.Migrations.CreatePost do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:posts) do
add :category, :text, null: false
end
end
def down do
alter table(:posts) do
remove :category
end
end
end
이정도만 되어도 충분이 좋은 듯한데, 여기서 더 마음에 드는 기능이 있다.mix ash.codegen --dev
명령어를 사용하면 개발 초기의 migration 파일을 병합하는 수고를 꽤나 줄일 수 있다.
실제로 개발 시 초기에는 migration 을 여러번 생성하게 된다. 특히 새로운 도메인을 시작하는 경우에는 migration 을 생각보다 자주변경했었는데, 나중에 이걸 하나로 병합는게 꽤 귀찮다. mix ash.codegen --dev
는 이런 경우에 하나로 합쳐주는 기능을 자동으로 지원하는 명령어다.
이전에 한 두단계에서 명령어를 mix ash.codegen --dev
로 변경해서 실행하면 실제 실행결과는 같다. 2개의 migration 이 생성되는건 동일한데 최종 개발이 마무리 된 이후 실제로 mix ash.codegen create_post
를 실행하면 아래와 같이 하나의 migration 으로 병합한다.
defmodule Foo.Repo.Migrations.CreatePost do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:posts, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v7()"), primary_key: true
add :title, :text, null: false
add :body, :text
add :category, :text, null: false
add :inserted_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
end
end
def down do
drop table(:posts)
end
end
매번 개발할 때 항상 있었으면 했던 기능인데 실제로 사용해보니 너무 편하다.
그리고 단순히 Migration file 만 병합하는게 아니다.
—dev 플래그로 생성된 migration 을 적용했다면, 병합된 파일로 다시 migration 을 할 때 기존 dev 로 적용된 것들은 전부 rollback 됨과 동시에 신규 migration 이 적용된다.
Subscribe to my newsletter
Read articles from Wonwoo Cho directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
