【Elixir 1.11】Phoenix Framework + DB開発をDockerでやる #1（コンテナ開発環境構築~CRUD実装~テスト）

この記事は、「[Elixir Advent Calendar 2020](https://qiita.com/advent-calendar/2020/elixir)」8日目の記事です。
きのうは@koyo-miyamuraさんの「[Elixirで動的計画法（DP）を実装してみた（Goとの比較つき）](https://qiita.com/koyo-miyamura/items/77388ff0d3e03c95897c)」でした！

# 概要

[Elixir](https://elixir-lang.org/)製のフレームワーク**[Phoenix](https://www.phoenixframework.org/)**によるWebアプリケーション開発を__Docker__でやる。

- シリーズ第1回の本記事では、  
  __Phoenix__コンテナと__DB（PostgreSQL）__コンテナとの連携確認をかねて、__CRUD__ Web UI実装からテストまで行います。
- 次回シリーズ第2回目で、CIパイプラインを導入します。
  - [【Elixir 1.11】Phoenix Framework + DB開発をDockerでやる #2（CIパイプライン導入）](https://qiita.com/im_miolab/items/f5f426985f968e686e85)

## 実行環境

| | バージョン | 備考 |
|:--|:--|:--|
| macOS | | |
| Docker (docker-compose) | | |
| __Elixir__ | 1.11.2 | compiled with Erlang/OTP 23 |
| __Phoenix__ | 1.5.7 | | 
| __PostgreSQL__ | 12.4 | |
| Node.js | 14.15.1 | |

## ディレクトリ構成（Phoenixプロジェクト作成前）

```terminal
.
├── .env
├── app
│   └── Dockerfile
├── db
│   └── Dockerfile
└── docker-compose.yml
```

### コード

```terminal:.env
PROJECT_NAME=my_app

POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=testdb
POSTGRES_PORT=5432
```

```dockerfile:app/Dockerfile
FROM elixir:1.11.2-slim

WORKDIR /usr/src/app

RUN apt-get update -y && \
  apt-get upgrade -y && \
  apt-get install -y \
  build-essential \
  curl \
  git \
  gzip \
  inotify-tools \
  ssh \
  sudo \
  tar

RUN apt-get install -y nodejs npm
RUN npm install -g n
RUN n 14.15.1
RUN apt-get purge -y nodejs npm

RUN yes | mix local.hex
RUN mix local.rebar --force
RUN mix archive.install hex phx_new 1.5.7 --force

RUN apt-get clean

CMD [ "mix", "phx.server" ]
```

```dockerfile:db/Dockerfile
FROM postgres:12.4-alpine

ENV LANG ja_JP.utf8
```

```yml:docker-compose.yml

version: "3"

networks:
  backend:
    driver: bridge

services:
  app:
    build: ./app
    volumes:
      - ./app:/usr/src/app
    ports:
      - 4000:4000
    stdin_open: true
    tty: true
    command:
      sh -c "cd ${PROJECT_NAME} && mix phx.server"
    networks:
      - backend

  db:
    build: ./db
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    ports:
      - ${POSTGRES_PORT}:5432
    volumes:
      - ./db/postgres/tmp/data:/var/lib/postgresql/data
    restart: always
    networks:
      - backend
```

# コンテナ開発環境構築

上記コードを、ご自分の開発環境に準備しましょう。
用意がととのったら、以下の手順で環境構築をしていきます。

## ビルド 〜 Phoenixプロジェクトディレクトリ作成

ビルドします。

```terminal:terminal
$ docker-compose build
```

ビルドが済んだら、
 
- appコンテナ
- dbコンテナ

がきちんと用意できているか確認します。

```terminal:terminal
$ docker-compose run --rm app elixir --version
Elixir 1.11.2 (compiled with Erlang/OTP 23)

$ docker-compose run --rm app mix archive
* hex-0.20.6
* phx_new-1.5.7

$ docker-compose run --rm db psql --version
psql (PostgreSQL) 12.4

$ docker-compose run --rm app bash -c "node --version && npm --version"
v14.15.1
6.14.8
```

大丈夫そうです。

つづいて、Phoenixプロジェクトを`my_app`という名前で作成していきます。

```terminal:terminal
$ docker-compose run --rm app mix phx.new my_app

    .
    .
（途中で表示される`Fetch and install dependencies? [Yn]`には、`Y`で進めます）

    .
    .
We are almost there! The following steps are missing:

    $ cd my_app

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
```

生成されたファイルの中身を書き換えます。

```elixir:app/my_app/config/dev.exs
# Configure your database
config :my_app, MyApp.Repo,
  username: "postgres",  # <-- update
  password: "password",  # <-- update
  database: "testdb",    # <-- update
  hostname: "db",        # <-- update
```

- `.env` で設定してある環境変数をつかっています。

コンテナ起動します。

```terminal:terminal
$ docker-compose up -d
```

きちんと起動しているか、確認します。

```terminal:terminal
$ docker-compose ps

        Name                       Command               State            Ports
----------------------------------------------------------------------------------------
................._app_1   sh -c cd my_app/ && mix ph ...   Up      0.0.0.0:4000->4000/tcp
................._db_1    docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
```

よさそうです。

ここで `mix ecto.create` やっておきます。

```terminal:terminal
$ docker-compose run --rm app bash -c "cd my_app && mix ecto.create"
```

コンテナを再起動します。

```terminal:terminal
$ docker-compose restart app
```

ここまでできたら、Webブラウザで「__[http://localhost:4000](http://localhost:4000)__」を確認しましょう。

以下のようなWelcome画面が表示されたらOKです！

<img width="687" alt="phx_init_page" src="https://user-images.githubusercontent.com/33124627/100324924-3c566300-300b-11eb-9f84-e5ff80c11a07.png">


- `docker-compose logs` をたたいて、ログ中にエラーが出ていないか念のため確認しておきましょう。

## CRUD WebUI実装

それでは、CRUD機能を実装していきます。

お題として、シンプルな__Usersリスト__を作っていきましょう。
まずは、取りあつかう情報をまとめてみます。

| | 項目名 |
| :-- | :-- |
| コンテキスト名 | Accounts |
| スキーマ名 | User |
| テーブル名 | users |
| カラム : 型 | name:string<br> email:string<br> bio:string |

こちらの内容にもとづいて、生成コマンド `mix phx.gen.html` を実行します。

```terminal:terminal
$ docker-compose exec app bash -c "cd my_app && \
mix phx.gen.html Accounts User users name:string email:string bio:string"
```

ルーティングを設定します。

```elixir:app/my_app/lib/my_app_web/router.ex
scope "/", MyAppWeb do
  pipe_through(:browser)

  get("/", PageController, :index)
  resources "/users", UserController  # --> add
end
```

マイグレーションを実行します。

```terminal:terminal
$ docker-compose exec app bash -c "cd my_app && mix ecto.migrate"
```

その後、コンテナを再起動しておきましょう。

```terminal:terminal
$ docker-compose restart app
```

ブラウザで「__[https://localhost:4000/users/new](https://localhost:4000/users/new)__」を確認します。

<img width="600px" alt="webui1" src="https://user-images.githubusercontent.com/33124627/100085123-00968e80-2e8f-11eb-9d2a-63a9f0ca14d6.png">

<img width="600px" alt="webui2" src="https://user-images.githubusercontent.com/33124627/100085143-07250600-2e8f-11eb-8bab-376015a398d6.png">

CRUD WebUIが爆誕したことを確認できました！

## テスト

テスト側の設定として、DB接続情報を更新します。

```elixir:app/my_app/config/test.exs
config :my_app, MyApp.Repo,
  username: "postgres",    # --> update
  password: "password",    # --> update
  database: "my_app_test#{System.get_env("MIX_TEST_PARTITION")}",
  hostname: "db",    # --> update
  pool: Ecto.Adapters.SQL.Sandbox
```

もしフォーマッターを入れてなかったら、このタイミングで`mix format`を走らせておきましょう。

```terminal:terminal
$ docker-compose exec app bash -c "cd my_app && mix format"
```

テスト実行します。

```terminal:terminal
$ docker-compose exec app bash -c "cd my_app && mix test"

Compiling 23 files (.ex)
Generated my_app app
...................

Finished in 0.9 seconds
19 tests, 0 failures

```

All Greenでパスしていることを確認できました！

# おわりに & 次回

今回、Phoenix + DBのDockerコンテナ開発環境を構築しました！
[次回](https://qiita.com/im_miolab/items/f5f426985f968e686e85)は、こちらのコンテナ開発環境にCIパイプラインを導入していきます。

明日の「Elixir Advent Calendar 2020」9日目は、@kobae964さんです。お楽しみに！

## お知らせ

### kokura.ex

北九州小倉のElixirコミュニティ__「kokura.ex」__は、福岡市で2017年から人気を博しているElixirコミュニティ「fukuoka.ex」が小倉にブランチした、新生__Elixirコミュニティ__です。

fukuoka.ex同様、

- __「高速処理性能」と「高い開発効率性」を両立できるプログラミング言語「Elixir」__
-  __そのWebアプリケーションフレームワーク「Phoenix」__

を、北九州を起点として広め、ワイワイ盛り上げていくコミュニティです。
これから先端技術をやりたい方や、最新のプログラミングを学びたい方、未来に向けてITに強くなりたい方など、技術への興味レベルが高い方や、プログラミングに関心が高い方のご参加を歓迎します。
__オンラインでのもくもく勉強会__を、__毎月ペースで開催__しておりますのでぜひどうぞ。
（はじめてElixirに触れる方も、お気軽にご参加ください！）

- イベントページ　__[fukuoka.ex](https://fukuokaex.connpass.com/)__  
  connpassイベント一覧より、__kokura.ex__のページをご覧いただけます

### fukuoka.ex アドベントカレンダー

「[fukuoka.ex Elixir／Phoenix Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fukuokaex)」もありますので、こちらもぜひどうぞ。

12/22の枠に自分も執筆参加してますので、よろしかったらご覧ください！