クラウドネイティブを目指してファイルI/Oを切り離すレポジトリを作った

# オンプレ Docker-Compose から GCP へ移行したいけど、クラウドネイティブじゃないんだなぁ

皆さん、ローカル/Single Instance環境からGCPへの移行、苦労していませんか？

例えば SQL Database や、ローカルデータをアプリケーションデータと密結合にしていると、Kubernetes や、Google App Engine などのクラウドネイティブ環境へ移行するのが大変辛くなります。

実際、私は画像をローカルに保存してそれに合わせた処理をしていたところ、Google Cloud Storage への移行が大変つらいことになっています。

今回は、開発時にそういったガバをなくすために、そもそもアプリケーションと I/O 処理を **Docker-Compose 上で** 分離することを目指し、簡単な File I/O のための Dockerfile を作ります。

# つまりどうするってことだってばよ

Docker-Compose ファイルがこうあるとします。

```yaml
version: "3"
services:
  api:
    build: .
    ports:
      - 8080:8080

# api 内に PostgreSQL と Local File の処理がくっついている
```

これを

```yaml
version: "3"
services:

  # DB を切り離します
  dev_db:
    build: containers/postgres
    ports:
      - 5565:5432
    volumes:
      - "dev_db_volume:/var/lib/postgresql/data"
    environment:
      POSTGRES_USER: sample-user
      POSTGERS_PASSWORD: sample-password
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
      POSTGRES_DB: picpage_db
    restart: always

  # Local File を外部に写します
  local_fs:
    build: containers/local_fs
    ports:
      - 5577:5577
    volumes:
      - "./fstorage:/local_fs"
  
  # api は上2つを依存に含めることで参照します。
  api:
    build: .
    ports:
      - 8080:8080
    depends_on:
      - dev_db

volumes:
  dev_db_volumes
```

# local_fs を書こう、書かねばならぬ
DB に関しては PostgreSQL などあるのでちょうど良いんですが、Cloud Storage の代替となるものがなかったので、書きます。ついでに golang の勉強もちょっとします。


## 出来たもの
https://github.com/MokkeMeguru/golang-local-fs

使い方は以下アプリディレクトリに対して、こんな感じ (詳細はREADMEへ)

```
/app
  |- containers
  |- docker-compose.yml
  `- app.py など
```

```shell
mkdir containers
git clone https://github.com/MokkeMeguru/golang-local-fs containers/local_fs
docker-compose build
docker-compose run --service-port local_fs

# Env:
# (env.Env) {
#  LocalFileRoot: (string) (len=9) "/local_fs",
#  OverWriteFile: (bool) false
# }

# [GIN] 2021/04/14 - 18:15:17 | 200 |     432.861µs |      172.24.0.1 | GET      "/files/samplebra.png"                                                                             
# [GIN] 2021/04/14 - 18:15:26 | 200 |     675.812µs |      172.24.0.1 | GET      "/files/samplebra.png"                                                                             
                                                                         
```

実際にブラウザ (~localhost:5577~) で画像を確認すると、こんな感じに見えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147520/054bd6be-5cee-e0a8-5903-f5cf401ad604.png)

# 締め
このレポジトリを使ってくれ、というよりかは、どうせ開発するなら、 **ステートフル環境とステートレス環境は早めに分離しておけ** 、ということです。

オブジェクト指向プログラミングが強力な世の中では、同じドメインのオブジェクト型 (ex. ファイル) が、クラウドとローカルで噛み合わなくなることがあります。それへの対策として、はじめから分離することを前提にコーディングを行うことが重要です。




終わり

# Notice
特に Clean Architecture のタグをつけている理由は、 **いくら外部 I/O を外側に追いやっても、内部オブジェクトがどうしよもなく外部に依存してしまうことがある** ということへの注意喚起です…

例えば、画像ファイルの場合、ローカルでは (ファイルですから) `java.io.File` として管理したいと思います。ところが、Google Cloud Storage の利用を考えると、 `java.io.InputStream` の方が適切となります。

なぜかというと、~java.io.File~ はストレージ上にあるファイルを指し示すのですが、 Google Cloud Storage が提供するのは、 `InputStream` で表されるメモリの上の()バイト列だからです。これを一旦 tempFile へ I/O するのは非効率ですし、tempFile の有効期限などの問題も面倒となります。

上記例のため、たとえどんなアーキテクチャをこねくり回しても、クラウドとローカルの差をはじめから意識して設計・コーディングを行う必要性があると言えます。

~~あと、今回作った local_fs レポジトリは golang の Clean Architecture をベースに作っています。とはいえオーバテクノロジーなのでだいぶ削っていますが~~