Rust で DDD を実践しながら API サーバーを実装・構築した（つもり）

Rust 勉強中の身ですので、何かしら作ってみようと思い立ち、 API サーバーを構築してみました。
自力で一から公開できるサーバーを構築したのは初めてでしたので、試行錯誤の過程を記事にします。

# 作ったもの

何の変哲もない API サーバーです。
成果物は [こちら](https://github.com/tsuchinoko0402/rust-web-api-server-sample) にアップしました。

- API サーバー
    - Rust で実装
        - Web フレームワーク（Actor）: [actix-web](https://actix.rs/)
        - ORM: [Diesel](https://diesel.rs/)
    - Docker イメージにして [Heroku](https://jp.heroku.com/) で稼働させる
- DB サーバー
    - [PostgreSQL](https://www.postgresql.org/) を利用する
    - [Heroku PostgreSQL](https://jp.heroku.com/postgres) で稼働させる

![作ったもの.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/369333b2-6619-99eb-5c2b-13d226dee241.png)

# 開発方針

上記のインフラ構成を目標として、以下の開発方針を軸として調査や検証を行いました。

- ローカルでの開発とサーバーへのデプロイはスムーズにできるようにする。
    - ローカルでテストや動作確認がスムーズにできるようにする：Docker の利用
    - デプロイも GitHub Actions を利用してできるようにする
- Rust のコードは DDD を実践できるアーキテクチャを採用する

# アーキテクチャの検討

一番四苦八苦したのはアーキテクチャでした。試行錯誤の過程を記載します。

## クリーンアーキテクチャでの実装と挫折

[かの有名な本](https://www.kadokawa.co.jp/product/301806000678/) も読んだことあったので、[こちらの記事](https://tech-blog.optim.co.jp/entry/2019/01/29/173000)を参考に下記の図を忠実に[^1]実装しようとしました。

[^1]: 「忠実な」の主な対象はユースケース層を指します。あちこちで見られるユースケース層の実装を見ると、ユースケースを「Output/Input Port」「Interactor」に分割して実装している（もしくは、名前を明示的にそうしている）ものがあまり見られなかったので、敢えて明示的にこれらを分割して実装しようとしてました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/350dbe14-c029-fe2f-4ccd-e0375a2529cf.png)

内側から実装を順調に進めていたのですが、フレームワーク＆ドライバー層の実装方法がわからず詰まってしまいました。詰まった理由としては、「フレームワーク」や「ドライバ」は Diesel や actix-web といった外部ライブラリとして提供されており、エントリポイント以外ではフレームワーク＆ドライバー層からインターフェイス層を呼び出す手立てがないためです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/ce480e9b-af58-e582-6034-b7c2d539b924.png)

実際に実装しようとすると、下記のようにインターフェース層からインフラ層を呼び出すという形しか出来ませんでした[^2]。

[^2]: もしかすると、上手いやり方が存在するのかもしれないのですが、調べきれていない/思いついていないです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/139ff8b2-9158-e3f6-7a74-383ab7a79625.png)

## オニオンアーキテクチャとの出会い

何か別のアプローチがないか調べていたところ、[こちらの記事](https://zenn.dev/streamwest1629/articles/no-clean_hello-onion-architecture) で紹介されていたオニオンアーキテクチャに出会いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/0ef66daf-977f-2d4f-eb60-6aec76656511.png)

これは DDD のヘキサゴナルアーキテクチャの「Application」部分をより具体化したものとして紹介されています。

![313px-Hexagonal_Architecture.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/88bc3673-5bf7-714c-7cd6-5ddfca1fd320.png)

### オニオンアーキテクチャの特徴

従来のレイヤードアーキテクチャは上位の層は直ぐ下の層しか呼び出せないのに対して、オニオンアーキテクチャはさらに下の層の呼び出しも許容しているのが特徴です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/699a0f13-8efb-fc50-9053-5a223845a2fa.png)

これにより、ドメインモデルやドメインサービス、ビジネスルールをインフラに依存せずに実装できます。

## 結局したかったことは何だっけ・・・？

ここまでいろいろ書籍を見返したりネットの記事を調べている中出、目的を見失いそうになったので、整理しました。結局、クリーンアーキテクチャとは、以下の SOLID 原則を満たしているアーキテクチャであることを再確認しました：

- S：SRP、単一責任の原則
- O：OCP、解放閉鎖の原則
- L：LSP、リスコフの置換原則
- I：ISP、インタフェース分離の原則
- D：DIP、依存性逆転の原則

オニオンアーキテクチャで API サーバーを実装するにあたっては、 SOLID 原則を満たしているかを注意しながら実装を進めました。

# 実装内容

アイデアとしては Rust でヘキサゴナルアーキテクチャを実装している [こちらも記事](https://alexis-lozano.com/hexagonal-architecture-in-rust-1/) を参考に、ポケモンの情報を CRUD 操作できる API です。動作例は以下の通りです：

```shell
$ curl -X GET localhost:8080/pokemon
{"message":"FAILURE Get Pokemon List","type":"get_pokemon_list_error"}

$ curl -X POST -H "Content-Type: application/json" -d '{"number":1, "name":"test_name", "types": [ "Fire" ]}' localhost:8080/pokemon
SUCCESS Register Pokemon
$ curl -X POST -H "Content-Type: application/json" -d '{"number":2, "name":"test_name2", "types": [ "Water", "Electric" ]}' localhost:8080/pokemon
SUCCESS Register Pokemon
$ curl -X GET localhost:8080/pokemon
[{"number":1,"name":"test_name","types":["Fire"]},{"number":2,"name":"test_name2","types":["Water","Electric"]}]

$ curl -X GET localhost:8080/pokemon/1
{"number":1,"name":"test_name","types":["Fire"]}

$ curl -X PUT -H "Content-Type: application/json" -d '{"number":1, "name":"test_name2", "types": [ "Water" ]}' localhost:8080/pokemon/1
SUCCESS Update Pokemon: no 1
$ curl -X GET localhost:8080/pokemon/1
{"number":1,"name":"test_name2","types":["Water"]}

$ curl -X DELETE localhost:8080/pokemon/1
SUCCESS Delete Pokemon: no 1
$ curl -X GET localhost:8080/pokemon
[{"number":2,"name":"test_name2","types":["Water","Electric"]}]
```

アーキテクチャとしては、先ほどのオニオンアーキテクチャをベースに実装していきました。

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/0c7c61ac-a3dc-e57f-108f-b4d8f4050c2e.png)

## ドメインモデル層

値オブジェクトやエンティティとビジネスルール、リポジトリを実装します（`/server/src/domain/models`）。今回は、ドメインサービスに相当するものがありませんでしたが、ある場合は` /server/src/domain/services` に実装します。
ポケモンオブジェクトは以下のようにします（`/server/src/domain/models/pokemon/pokemon.rs`）：

```rust
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Pokemon {
    pub number: PokemonNumber,
    pub name: PokemonName,
    pub types: PokemonTypes,
}

impl Pokemon {
    pub fn new(number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Self {
        Self {
            number,
            name,
            types,
        }
    }
}
```

番号や名前、タイプについては値オブジェクトとして定義します。
例えば、番号は以下のようにします（`/server/src/domain/models/pokemon/pokemon_number.rs`）：

```rust
use std::convert::TryFrom;

/// ポケモンの図鑑 No を表す。
#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Debug)]
pub struct PokemonNumber(i32);

/// ポケモンの図鑑 No の振る舞い：u16 から PokemonNumber への変換。
/// 現時点でポケモンの図鑑 No は898 までなので、
/// それ以上にならないように決めている。
impl TryFrom<i32> for PokemonNumber {
    type Error = ();

    fn try_from(n: i32) -> Result<Self, Self::Error> {
        if n > 0 && n < 899 {
            Ok(Self(n))
        } else {
            Err(())
        }
    }
}

/// 図鑑 No から u16 への変換処理の振る舞いを定義。
impl From<PokemonNumber> for i32 {
    fn from(n: PokemonNumber) -> Self {
        n.0
    }
}
```

番号は構造体として定義するものの、プロパティは1つしかないので、タプル構造体として定義します。
採用する整数型ですが、 ORM の Diesel と PostgreSQL で扱う型の変換の都合上、 `i32` を使います。
一般的な整数から `PokemonNumber` への変換の際にはチェックをしており、 `Result` 型で返します。

リポジトリについては基本的な CRUD 操作と存在確認を定義します（`/server/src/domain/models/pokemon/pokemon_repository.rs`）：

```rust
use anyhow::Result;

/// Pokemon のリポジトリインタフェース
pub trait PokemonRepository {
    /// 番号からポケモンを探す
    fn find_by_number(&self, number: &PokemonNumber) -> Result<Pokemon>;

    /// ポケモン一覧を表示する
    fn list(&self) -> Result<Vec<Pokemon>>;

    /// オブジェクトを永続化（保存）する振る舞い
    fn insert(&self, pokemon: &Pokemon) -> Result<()>;

    /// オブジェクトを再構築する振る舞い
    fn update(&self, pokemon: &Pokemon) -> Result<()>;

    /// オブジェクトを永続化（破棄）する振る舞い
    fn delete(&self, number: &PokemonNumber) -> Result<()>;

    /// 作成したポケモンの重複確認を行う。
    fn exists(&self, pokemon: &Pokemon) -> bool {
        match self.find_by_number(&pokemon.number) {
            Ok(_) => true,
            Err(_) => false,
        }
    }
}
```

## アプリケーションサービス層

いわゆる、ユースケースを定義します。ジェネリクスを利用して DI（依存性の注入）をおこなっています。
以下は、指定された番号のポケモンを取り出してくるユースケースの実装の例です（`/server/src/application/pokemon_get_service.rs`）：

```rust
use anyhow::Result;
use std::convert::TryFrom;

/// アプリケーションサービスの構造体。
/// generics でリポジトリへの依存を表し、trait 境界を定義することで、DI を行う。
pub struct PokemonGetService<T>
where
    T: PokemonRepository,
{
    pokemon_repository: T,
}

/// アプリケーションサービスの振る舞いを定義。
impl<T: PokemonRepository> PokemonGetService<T> {
    /// コンストラクタ
    pub fn new(pokemon_repository: T) -> Self {
        Self { pokemon_repository }
    }

    /// 取得処理の実行。
    pub fn handle(&self, no: i32) -> Result<PokemonData> {
        let number = PokemonNumber::try_from(no).unwrap();
        match self.pokemon_repository.find_by_number(&number) {
            Ok(value) => Ok(PokemonData::new(value)),
            Err(_) => Err(anyhow::anyhow!(
                "取得しようとしたポケモンが存在しません: no {:?}",
                number
            )),
        }
    }
}
```

外部からドメインオブジェクトを直接扱わないように DTO（Data Transfer Object）も定義しています（`/server/src/domain/application/pokemon_data.rs`）：

```rust
use crate::domain::models::pokemon::pokemon::Pokemon;
use getset::Getters;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;

#[derive(Serialize, Deserialize, Clone, Getters, PartialEq, Eq, Debug)]
pub struct PokemonData {
    #[getset(get = "pub with_prefix")]
    number: i32,
    #[getset(get = "pub with_prefix")]
    name: String,
    #[getset(get = "pub with_prefix")]
    types: Vec<String>,
}

impl PokemonData {
    pub fn new(source: Pokemon) -> Self {
        Self {
            number: source.number.try_into().unwrap(),
            name: source.name.try_into().unwrap(),
            types: source.types.try_into().unwrap(),
        }
    }
}
```

## インフラ層

DB とのやりとりと Actor を定義します。外部ライブラリを利用して、 Adaptor としての役割を担います。

### DB とのやりとり：Diesel を利用

Diesel の使いから、DB のマイグレーションについては（手前味噌ですが）[私が過去に書いた記事](https://www.zakioka.net/blog/rust-diesel-sample)を参考にしていただければと思います。

下層であるドメイン層のリポジトリの実装を行います。
これにより、依存性の逆転を実現しています：

ソースコードのうち、ポケモンデータの取得処理の部分を示します（`/server/src/infra/diesel/pokemon/pokemon_repository.rs`）：

```rust
use anyhow::{Context, Result};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use std::convert::TryInto;

/// Diesel が直接利用するデータモデル。
#[derive(Debug, Queryable, Clone)]
pub struct PokemonEntity {
    pub no: i32,
    pub name: String,
    pub type_: Vec<String>,
}

#[derive(Debug, Insertable)]
#[table_name = "pokemon"]
pub struct NewPokemon {
    pub no: i32,
    pub name: String,
    pub type_: Vec<String>,
}

/// Pokemon の振る舞い： PokemonEntity から Pokemon への変換処理。
impl From<PokemonEntity> for Pokemon {
    fn from(entity: PokemonEntity) -> Pokemon {
        Pokemon {
            number: entity.no.try_into().unwrap(),
            name: entity.name.try_into().unwrap(),
            types: entity.type_.try_into().unwrap(),
        }
    }
}

pub struct PokemonRepositoryImpl {
    pub pool: Box<Pool<ConnectionManager<PgConnection>>>,
}

impl PokemonRepository for PokemonRepositoryImpl {
    ...（略）

    /// 引数で渡した図鑑 No のポケモンを返却する
    fn find_by_number(&self, number: &PokemonNumber) -> Result<Pokemon> {
        let conn = self.pool.get().context("failed to get connection")?;
        let target_num: i32 = number.clone().try_into().unwrap();
        match pokemon
            .filter(pokemon::no.eq(target_num))
            .load::<PokemonEntity>(&conn)
        {
            Ok(result) => match result.get(0) {
                Some(value) => Ok(Pokemon::from(value.clone())),
                None => Err(anyhow::anyhow!("Not Found Pokemon number:{}", target_num)),
            },
            Err(e) => Err(anyhow::anyhow!(e)),
        }
    }
    ...（略）
}
```

各メソッド内でコネクションプールからコネクションを取得して利用するようにします。

### 外部からのリクエストの受付（Actor）：actix-web の利用

受け付けた HTTP リクエストを actix-web を利用して処理するようにします。
リクエストの JSON データを格納する型を以下のように定義します（`/server/src/infra/actix/request.rs`）：

```rust
use std::convert::TryInto;
use crate::domain::models::pokemon::pokemon::Pokemon;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Deserialize, Serialize)]
pub struct PokemonRequest {
    pub number: i32,
    pub name: String,
    pub types: Vec<String>,
}

impl PokemonRequest {
    pub fn of(&self) -> Pokemon {
        Pokemon::new(
            self.number.try_into().unwrap(),
            self.name.clone().try_into().unwrap(),
            self.types.clone().try_into().unwrap(),
        )
    }
}
```

（今記事で扱っているポケモンデータの取得処理ではこれは使いません）。
リクエストのパス、メソッドに応じた処理は以下のように実装できます（`/server/src/infra/actix/handler.rs`）：

```rust
use crate::infra::actix::request::PokemonRequest;
use actix_web::{delete, get, post, put, web, web::Json, HttpResponse, Responder};
use serde::Serialize;

#[derive(Serialize)]
struct ErrorResponse {
    message: String,
    r#type: String,
}

...（略）

#[get("/pokemon/{number}")]
async fn get_pokemon(
    data: web::Data<RequestContext>,
    path_params: web::Path<(i32,)>,
) -> impl Responder {
    let pokemon_application = PokemonGetService::new(data.pokemon_repository());
    let no = path_params.into_inner().0.into();
    match pokemon_application.handle(no) {
        Ok(pokemon) => HttpResponse::Ok().json(pokemon),
        Err(_) => {
            let response = ErrorResponse {
                message: format!("FAILURE Get Pokemon: no {:?}", no),
                r#type: "get_pokemon_list_error".to_string(),
            };
            HttpResponse::InternalServerError().json(response)
        }
    }
}

...（略）
```

エラーについては、メッセージとタイプを定義して、レスポンスで返却できるようにしています。
サーバーの起動処理は以下のように実装できます（`/server/src/infra/actix/router.rs`）：

```rust
use super::handlers;
use crate::{config::CONFIG, domain::models::pokemon::pokemon_repository::PokemonRepository};
use actix_web::{middleware::Logger, App, HttpServer};
use diesel::{
    r2d2::{ConnectionManager, Pool},
    PgConnection,
};

#[actix_web::main]
pub async fn run() -> std::io::Result<()> {
    dotenv::dotenv().ok();
    let port = std::env::var("PORT")
        .ok()
        .and_then(|val| val.parse::<u16>().ok())
        .unwrap_or(CONFIG.server_port);

    HttpServer::new(|| {
        App::new()
            .data(RequestContext::new())
            .wrap(Logger::default())
            .service(handlers::health)
            .service(handlers::post_pokemon)
            .service(handlers::get_pokemon)
            .service(handlers::update_pokemon)
            .service(handlers::delete_pokemon)
            .service(handlers::get_pokemon_list)
    })
    .bind(format!("{}:{}", CONFIG.server_address, port))?
    .run()
    .await
}

#[derive(Clone)]
pub struct RequestContext {
    pool: Pool<ConnectionManager<PgConnection>>,
}

impl RequestContext {
    pub fn new() -> RequestContext {
        let manager = ConnectionManager::<PgConnection>::new(&CONFIG.database_url);
        let pool = Pool::builder()
            .build(manager)
            .expect("Failed to create DB connection pool.");
        RequestContext { pool }
    }

    pub fn pokemon_repository(&self) -> impl PokemonRepository {
        use crate::infra::diesel::pokemon_repository::PokemonRepositoryImpl;

        PokemonRepositoryImpl {
            pool: Box::new(self.pool.to_owned()),
        }
    }
}
```

これで、`main` 関数から `run` 関数を呼び出すと、Web サーバーが起動します。

## 補足：環境変数の値取り出しについて

Rust の [dotenv](https://crates.io/crates/dotenv) を利用し、 `.env` ファイルに書かれた環境変数を読み出すようにしていますが、アプリ起動時に環境変数を static 変数 `CONFIG` に格納するようにしています。

詳しい方法は（手前味噌ですが）[私が過去に書いたメモ](https://www.zakioka.net/memo/rust/environment_variable/#dotenv)をご参照ください。

# ローカルでの動作確認

DB とのやりとりを含むコードですので、そのまま `cargo run` しただけでは動作確認ができません。 DB とのやりとりも含めた動作確認ができる環境を Docker（docker-compose）を用いて構築します。

## DB 設定のための `Dockerfile`

`/db/Dockerfile` に DB のための設定を書きます：

```dockerfile
ENV LANG ja_JP.utf8
FROM postgres:14-alpine AS db
```

`/db/docker-entrypoint-initdb.d` のディレクトリの下に DB 立ち上げ直後に実行する SQL を置いておきます[^3]。

[^3]: 今回は、DB 立ち上げ時にロール root とデータベース root がないよ、というエラーメッセージが出るのに対応するための SQL を置いてます。なぜこのエラーが出るのかはよく分かってません。

```sql
CREATE ROLE root WITH LOGIN;
CREATE DATABASE root;
```

## API サーバのための `Dockerfile`

`/server/Dockerfile` に API サーバを立ち上げるための設定を書きます：

- マルチステージビルドを利用します。
    - 開発環境（`develop-stage`）ではローカル環境でサーバーを稼働させるためのコマンド `cargo-watch` 、Diesel で PostgreSQL を扱う際に必要な `libpg-dev` 、Diesel でマイグレーションなどを行うのに必要なコマンド `diesel_cli` をインストールします。また、本番環境のために成果物をコピーしておきます。
    - ビルド環境（`build-stage`）では、開発環境を用いて本番環境向けのビルドを実行します。
    - 本番環境（`production-stage`） では、ビルド環境の成果物をコピーしてきて実行します。

```dockerfile
# 開発環境
FROM rust:1.57.0 as develop-stage
WORKDIR /app
RUN cargo install cargo-watch
RUN apt install -y libpq-dev
RUN cargo install diesel_cli
COPY . .

# ビルド環境
FROM develop-stage as build-stage
RUN update-ca-certificates
RUN cargo build --release

# 本番環境
FROM rust:1.57.0-slim-buster as production-stage
RUN apt-get update
RUN apt-get install libpq-dev -y
COPY --from=build-stage /app/target/release/actix_web_sample .
CMD ["./actix_web_sample"]
```

## `docker-compose.yml` の記述

DB とサーバーの両方をローカル環境で立ち上げるために、 `docker-composed.yml` ﻿を用意します。

- DB は `pg_isready` コマンドでヘルスチェックを行い、正常に起動していることを確認します。
- DB の環境変数は後に紹介する `.env` ファイルの内容に合わせて設定します。
- サーバーは DB が正常に起動したことを確認してから起動します。
- 起動の際、diesel によるマイグレーション（テーブル作成）と `cargo watch` によるアプリ起動を行います。
    - cargo watch により、ソースの保存を自動で検知して再ビルドしてくれるため、開発が楽になります。

```dockerfile
version: '3.7'
services:
  server:
    build:
      context: ./server
    target: 'develop-stage'
    ports:
      - "8080:8080"
    depends_on:
      db:
    condition: service_healthy
    volumes:
      - ./server:/app
      - cargo-cache:/usr/local/cargo/registry
      - target-cache:/app/target
    command: /bin/sh -c "diesel setup && cargo watch -x run"
    tty: true
  
   db:
     build:
       context: ./db
     ports:
       - '5432:5432'
     environment:
       POSTGRES_USER: admin
       POSTGRES_PASSWORD: password
       POSTGRES_DB: postgres
     volumes:
       - ./db/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
     healthcheck:
       test: ["CMD-SHELL", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "postgres"]
       interval: 10s
       timeout: 5s
       retries: 5
       
  volumes:
    cargo-cache:
    target-cache:
```

## `.env` の用意

ローカルで立ち上げる際に利用する環境変数の設定を記述します。

```plaintext
SERVER_PORT=8080
DATABASE_URL=postgres://admin:password@db:5432/postgres
SERVER_ADDRESS=0.0.0.0
```

## ローカル開発環境でのサーバ＆DB立ち上げ

`docker-compose` を用いてローカル環境を立ち上げます。
前述の通り、開発中は立ち上げっぱなしにしておけば、Rust のコードを変更した場合でも自動的にビルドして反映してくれます。

```
docker-compose up -d
```

# Heroku へのデプロイ

実装した API サーバーを Heroku にデプロイします。Heroku のアカウントは作成済みで、 Heroku CLI もインストール済みであるとします。

## Heroku の設定

コマンドラインから Heroku の設定をします。アプリの追加と、 PostgreSQL のアドオンを追加します。
PostgreSQL のアドオンを追加すると自動的にアプリの環境変数 DATABASE_URL が追加されます。

```shell
# Heroku に CLI でログイン（ブラウザが立ち上がるのでログインを行う）
$ heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/XXXXX
Logging in... done
Logged in as hoge@mail.com
heroku create rust-web-api-server-sample

# アプリの作成
$ heroku create rust-web-api-sample
Creating ⬢ rust-web-api-sample... done
https://rust-web-api-sample.herokuapp.com/ | https://git.heroku.com/rust-web-api-sample.git

# アプリに PostgreSQL のアドオン（Hobby プラン）を追加
$ heroku addons:create heroku-postgresql:hobby-dev --app rust-web-api-sample
Creating heroku-postgresql:hobby-dev on ⬢ rust-web-api-sample... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-contoured-25420 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
```

## GitHub Actions の設定

ここから、手でコマンドをポチポチ打ってデプロイをしても良いのですが、せっかくなので、 GitHub Action でデプロイを自動化します。GitHub のリポジトリに push したら Heroku にデプロイするように設定します。

以下のように `.github/workflows/deploy-to-heroku.yml` を作成します：

- [AkhileshNS/heroku-deploy](https://github.com/AkhileshNS/heroku-deploy) を利用してデプロイします。
- Heroku へのデプロイは Docker を利用する設定にしています。
- あらかじめ、リポジトリのシークレットに以下を設定しておきます：
    - `HEROKU_API_KEY`: Heroku の Account Settings 画面 -> API Key で取得できる API キーを指定
    - `HEROKU_EMAIL`: Heroku のアカウントで使用しているメールアドレスを指定
- 起動したサーバーの IP アドレスは環境変数で設定します。
- 環境変数の設定は `heroku config:set` コマンドで行います。
    - Github Actions 内で heroku コマンドを利用するためには Heroku へのログイン処理を行う必要があるのですが、heroku-deploy を一度でも使うことで Heroku へのログインを行なってくれます。
    - このツールを使ってログイン処理だけ行うということもできます（`justlogin` オプション）
- ヘルスチェックも入れています。

```yaml
name: Deploy to heroku

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: "rust-web-api-server-sample"
          heroku_email: ${{secrets.HEROKU_EMAIL}}
          buildpack: "https://github.com/emk/heroku-buildpack-rust.git"
          usedocker: true
          appdir: server
          healthcheck: "https://rust-web-api-server-sample.herokuapp.com/health"
          checkstring: "Ok"
          rollbackonhealthcheckfailed: true
      - run: |
          heroku config:set SERVER_ADDRESS=0.0.0.0
```

これでデプロイが成功したら、 `https://rust-web-api-server-sample.herokuapp.com/` が利用できるようになります。

## 補足：Heroku へのデプロイにあたってのポート指定の環境変数について

Heroku でのサーバー起動の際のポートの指定は、環境変数 `PORT` の値を使用するようにすればOKですので、以下のように書けばOKです。

```rust
 let port = std::env::var("PORT")
        .ok()
        .and_then(|val| val.parse::<u16>().ok())
        .unwrap_or(CONFIG.server_port)
```

今回は、ローカルでの起動も考慮に入れているので、環境変数 `PORT` の指定がなければ、 `.env` ファイルの `SERVER_PORT` の値を利用するという設定にしています。

# まとめ

- 一から Web API サーバーを初めて実装してみました。
- アーキテクチャについては色々悩みましたが、 SOLID 原則を満たし、かつ、実装の方法が分かりやすかったオニオンアーキテクチャを採用しました。
- ローカル開発からデプロイまで、 Docker を利用することでラクに環境構築をすることができました。
- 今後はこの方法・実装方法をテンプレートとして利用できればと思います。

# 追記1: 「これって DDD やなくない？」（2022/01/25）

Twitter で下記のコメントいただきました：

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">このような実装を基盤にドメイン知識(制約や振る舞い)を反映していくか、そもそもCRUDしかありませんというならDDDとか言わずにDOAに振り切ったほうがシンプルな設計になると思います。中途半端な状況が余計混乱を生むというのは経験があります。</p>&mdash; かとじゅん (@j5ik2o) <a href="https://twitter.com/j5ik2o/status/1485766341336264706?ref_src=twsrc%5Etfw">January 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

ご指摘の通り、タイトルには「DDD を実践」と書いたのですが、ドメインルールの実装がまだまだ甘いのでこれは DDD ではないです。本文で

> 今回は、ドメインサービスに相当するものがありません

と書いた通りです。

ユースケースやビジネスルールを明確に定義することが実際の場面では必要になってくると思います。

# 追記2: 似たような実践例(2022/01/25)

似たような実践例がついこないだのアドベントカレンダーに投稿されていたということをお聞きしましたので紹介します：

[Rust の新しい HTTP サーバーのクレート Axum をフルに活用してサーバーサイドアプリケーション開発をしてみる - Don't Repeat Yourself](https://blog-dry.com/entry/2021/12/26/002649)

Web アプリフレームワークには新しめの [Axum](https://github.com/tokio-rs/axum) を使ってたり、アーキテクチャとしては [Explicit Architecture](https://herbertograca.com/tag/explicit-architecture/) を採用しているなどの違いはあるものの、かなり参考になる記事でした。

これを参考に、リファクタリングしてみようかと思います。

# 参考資料

- [実践Rust入門　[言語仕様から開発手法まで]](https://gihyo.jp/book/2019/978-4-297-10559-4)
    - Rust に関する基礎知識、Web サーバーの実装方法など、基本的なこと全般的に参考にしています。
- 私が過去に書いた記事：これまでに色々調べたり試したことを活用しました：
    - [docker-compose で DB とアプリサーバを立ち上げ、接続する](https://www.zakioka.net/blog/docker-compose-db-for-app/)
    - [Rust の ORM：Diesel を使ってみる](https://www.zakioka.net/blog/rust-diesel-sample)
    - [API サーバを Rust で実装する　〜DDD/オニオンアーキテクチャによる実装検討〜](https://www.zakioka.net/blog/api-server-on-rust-architecture/)
    - [API サーバを Rust で実装する　〜ローカル開発からデプロイまで〜](https://www.zakioka.net/blog/api-server-on-rust-develop-deploy/)
- [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.kadokawa.co.jp/product/301806000678/)
    - ソフトウェアのアーキテクチャに関する考え方を学びました。
- オニオンアーキテクチャの元ネタ（Jeffrey Palermo 氏のブログ）
    - [The Onion Architecture : part 1](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)
    - [The Onion Architecture : part 2](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-2/)
    - [The Onion Architecture : part 3](https://jeffreypalermo.com/2008/08/the-onion-architecture-part-3/)
    - [The Onion Architecture : part 4 – After Four Years](https://jeffreypalermo.com/2013/08/onion-architecture-part-4-after-four-years/)
- [クリーンアーキわからんかった人のためのクリーンじゃないけどクリーンみたいなオニオンに見せかけたSOLIDの話](https://zenn.dev/streamwest1629/articles/no-clean_like-clean_its-onion-solid)
    - クリーンアーキテクチャの実装に挫折したときに参考にしました。
- [イラストで理解するSOLID原則](https://qiita.com/baby-degu/items/d058a62f145235a0f007)
    - SOLID 原則の理解のために参考にしました。
- [[DDD]ドメイン駆動 + オニオンアーキテクチャ概略](https://qiita.com/little_hand_s/items/2040fba15d90b93fc124)
    - クリーン、ヘキサゴナル、オニオンの各アーキテクチャの位置付けの整理に参考にしました。
- [実践ドメイン駆動設計](https://www.shoeisha.co.jp/book/detail/9784798131610)
    - DDD について初めて学んだ本です。
- [ドメイン駆動設計入門 ボトムアップでわかる！ドメイン駆動設計の基本](https://www.shoeisha.co.jp/book/detail/9784798150727)
    - 上記の本で理解しにくかった箇所の理解を深めるのに大変役立ちました。
- 今回、実装したもののアイデアを参考にした記事はこちらです。ヘキサゴナルアーキテクチャで Web API サーバーを実装しています；
    - [Hexagonal architecture in Rust #1 - Domain](https://alexis-lozano.com/hexagonal-architecture-in-rust-1/)
    - [Hexagonal architecture in Rust #2 - In-memory repository](https://alexis-lozano.com/hexagonal-architecture-in-rust-2/)
    - [Hexagonal architecture in Rust #2 - In-memory repository](https://alexis-lozano.com/hexagonal-architecture-in-rust-2/)
    - [Hexagonal architecture in Rust #3 - HTTP API](https://alexis-lozano.com/hexagonal-architecture-in-rust-3/)
    - [Hexagonal architecture in Rust #4 - Refactoring](https://alexis-lozano.com/hexagonal-architecture-in-rust-4/)
    - [Hexagonal architecture in Rust #5 - Remaining use-cases](https://alexis-lozano.com/hexagonal-architecture-in-rust-5/)
    - [Hexagonal architecture in Rust #7 - Long-lived repositories](https://alexis-lozano.com/hexagonal-architecture-in-rust-7/)
- [DDDのパターンをRustで表現する ~ Value Object編 ~](https://caddi.tech/archives/1373)
    - 値オブジェクトの実装方法について参考にしました。
- [Rustでクリーンアーキテクチャを組む時DIするサンプルコード](https://qiita.com/kawadumax/items/0875eda8a89f5d24d3c3)
    - DI を実現するための具体的な書き方について参考にしました。
- [RustでたのしいDI オニオンアーキテクチャとDependency Injectionをいい感じに実現したい](https://docs.google.com/presentation/d/1GpsRpeI8unwoZBvA3-K8t2_Q_dj4fsgdZpoyo-VmdAY/edit#slide=id.p)
    - タイトル的にやりたいことがドンピシャでした。
    - DI の実装パターンについて参考にしました。結局、今回はシンプルな「1. Static Dispatch」を採用しました。
- [Docker によるデプロイ - Heroku](https://devcenter.heroku.com/ja/categories/deploying-with-docker)
    - Heroku へのデプロイ方法に関するマニュアルです。
    - 初めのうちは手元でこれらのコマンドを実行してデプロイをしましたが、のちに下の記事を参考に GitHubActions に移行しました。
- [Deploy to Heroku · Actions · GitHub Marketplace · GitHub](https://github.com/marketplace/actions/deploy-to-heroku)
    - GitHub Actions で Heroku にデプロイするためのプラグインのマニュアルです。
- [Github ActionsでGolangプロジェクトをHerokuに自動デプロイしてみた](https://blog.tanebox.com/archives/630/)
    - 上記プラグインの利用に際して参考にしました。
- [How to deploy Rust on Heroku (with Docker)](https://kerkour.com/deploy-rust-on-heroku-with-docker/)
    - プラグインを使わずにデプロイする方法としてはこちらが使えるかなと考えました。