2024-03-14に投稿

DBを起動してマルチコンテナにしてみるぞ

PostgreSQLを起動する

    # The commented out section below is an example of how to define a PostgreSQL
    # database that your application can use. `depends_on` tells Docker Compose to
    # start the database before your application. The `db-data` volume persists the
    # database data between container restarts. The `db-password` secret is used
    # to set the database password. You must create `db/password.txt` and add
    # a password of your choosing to it before running `docker compose up`.

と言われているのでせっかくなのでPostgreSQLを使用します。

Learning centerの"Multi-container applications"が参考になるかも。
こちらのサンプルのcompose.yamlを見ます。

どうやらservicesのすぐ下に複数定義するとMulti-containerになるようですね。
サービスとコンテナの言葉の違い的なものがよくわからなかったのでchatGPTに聞いたらこんな感じでした。ほんとかどうか理解するには知識が足りない。

*Dockerにおける「service」とは、コンテナーの実行単位を指します。コンテナーは、アプリケーションやサービスの独立した実行環境を提供するための軽量な仮想化技術です。そして、Docker Composeを使って複数のコンテ ナーをまとめて定義する場合、各コンテナーは1つ以上のサービスとして定義されます。

具体的には、Docker Composeの設定ファイル(通常はdocker-compose.yml)で、services セクションの下に複数のサービスが定義されます。各サービスの定義には、そのサービスに関連するイメージ、ポートの公開、環境変 数の設定などが含まれます。そして、各サービスは1つ以上のコンテナーを起動します。つまり、コンテナーは実際にリソースを提供する実体であり、サービスはそのコンテナーの定義や管理を担当します。

要するに、Dockerにおける「service」とは、論理的なグループ化や管理の単位であり、そのサービスによって実行される1つ以上のコンテナーを指します。一方で、コンテナーは実際にリソースを提供する単位です。*

特に何も考えずにコメントどおりに depends_on の項目をコメントアウト、同様にservice直下のdbの項目もコメントアウトします。

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/

# Here the instructions define your application as a service called "app".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
  app:
    container_name: tamesikotlin20240122comp # この行を追加してコンテナ名を指定する
    build:
      context: .
    #  target: final
    # If your application exposes a port, uncomment the following lines and change
    # the port numbers as needed. The first number is the host port and the second
    # is the port inside the container.
    ports:
      - 8081:8080
    depends_on:
      db:
        condition: service_healthy

    # The commented out section below is an example of how to define a PostgreSQL
    # database that your application can use. `depends_on` tells Docker Compose to
    # start the database before your application. The `db-data` volume persists the
    # database data between container restarts. The `db-password` secret is used
    # to set the database password. You must create `db/password.txt` and add
    # a password of your choosing to it before running `docker compose up`.
  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

起動確認をします。
今回はバックグラウンド起動を試すために-dを実行します。

$ docker compose up -d --build
Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /host_mnt/Users/********/IdeaProjects/tamesi/db/password.txt
zsh: exit 1     docker compose up -d --build

おっとよく読んでなかった、パスワード指定するためにファイル作れと書いてありますね 作って再実行。

 ✔ Container tamesi-db-1               Healthy
 ✔ Container tamesikotlin20240122comp  Started

起動に成功した様子。接続してみたいと思います。
クライアントはDBeavweを使います。

$ brew install dbeaver-community

docker-compose.yamlの方に設定を加え、ポート5432を公開。

  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    # ここの下2行を追加
    ports:
      - 5432:5432

起動して接続確認。

$ docker compose up -d --build

OK!

kotlinの関数型言語の描き方がわからない

ところで私はAtomが死んでからメモ帳としてターミナル開いて素のvimを使ってるんですが、最近vim-plugの使い方を忘れてしまい、思い出しがてら新しいcolorschemeにしてみました。
redditで検索しておすすめされてたEverforest、使いやすいです。それまでicebergを使ってたので、VISUALモードが見にくかったんですよね。

閑話休題、関数型的なAPIの書き方の参考になるものを探します。が、なかなか難しいですね。

とりあえず、controllerを実装してる例って無いですね……
なんとなくのイメージで、Router Functionsのような例が出てくると思ったんですが違うようです。
もしかして関数型プログラミングの学習としてkotlinを選んだのは良くなかったのかもしれない。kotlinで学ぶ関数型言語みたいな書籍が無い(英語ならある)時点で察するべきか……
最初からやり直すならHaskell、scala、Elixir、Lispみたいので始めるべきだったかもしれません。

なんとなくで書いてみる

chatGPTさんに聞きつつやってますが、この人結構な確率で嘘をいうのであくまで参考程度に、いろんな資料を斜め読みしつつとりあえずで実装していきます。
なんとなくですが、
- 「関数そのものを変数に代入できる」
- 「数式っぽく扱うためにvoid関数を使わない」
- 「ラムダ式を多用しつつデータを変形させる」
- 「副作用を起こさない」
- 「メソッドチェーンっぽい書き方をする」
らへんなのかなと思います。

副作用云々はDDDでも触れるのでそれっぽい感じでいいんですかね……

Controller

RESTFul APIを目指してこんな感じ

package jp.gooye.toy.tamesi.controller;

import jp.gooye.toy.tamesi.model.TamesiDataResource
import jp.gooye.toy.tamesi.model.TamesiResponse
import jp.gooye.toy.tamesi.service.TamesiDataService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class TamesiController(
    private val service: TamesiDataService
) {

    @GetMapping("/tamesi")
    fun welcome() = TamesiResponse(message = "hello world!!")

    @PostMapping("/save")
    fun create(tamesiDataResource: TamesiDataResource): TamesiDataResource {
        return service.save(tamesiDataResource)
    }

    @GetMapping("/get")
    fun read(@PathVariable id: String): TamesiDataResource {
        // 後で投げる例外は修正する
        return service.findById(id).orElseThrow()
    }

}

Service

package jp.gooye.toy.tamesi.service

import jp.gooye.toy.tamesi.model.TamesiDataResource
import jp.gooye.toy.tamesi.repository.TamesiDataRepository
import org.springframework.stereotype.Service
import java.util.Optional

@Service
class TamesiDataService(
    val repository: TamesiDataRepository,
    val tableDataFactory: TamesiTableDataFactory,
    val resourceFactory: TamesiResourceFactory
) {
    fun findById(id: String): Optional<TamesiDataResource> {
        return repository.findById(id).map { resourceFactory.from(it) }

    }

    fun save(resource: TamesiDataResource): TamesiDataResource {
        return resourceFactory.from(repository.save(tableDataFactory.from(resource)))
    }
}

Factory

package jp.gooye.toy.tamesi.service

import jp.gooye.toy.tamesi.model.TamesiDataResource
import jp.gooye.toy.tamesi.repository.TamesiTableData

class TamesiResourceFactory {
    fun from(data: TamesiTableData): TamesiDataResource {
        return TamesiDataResource(data.name, data.age)
    }
}
package jp.gooye.toy.tamesi.service

import jp.gooye.toy.tamesi.model.TamesiDataResource
import jp.gooye.toy.tamesi.repository.TamesiTableData

class TamesiTableDataFactory {

    fun from(resource: TamesiDataResource): TamesiTableData {
        return TamesiTableData(null, resource.name, resource.age)
    }
}

Repository

package jp.gooye.toy.tamesi.repository

import org.springframework.data.repository.CrudRepository

interface TamesiDataRepository : CrudRepository<TamesiTableData, String>

data

package jp.gooye.toy.tamesi.repository

import jakarta.persistence.Id
import jakarta.persistence.Table

@Table(name = "tamesi")
data class TamesiTableData(
    @Id
    val id: String?,
    val name: String,
    val age: Int
)

とりあえず形にしましたが、これだけでは動きません。次回、DBの接続まわりとユニットテストに手を出していきます。


gooye-g

初心者

所有者限定モードのためこのボードには投稿できません
コメント