# 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!
ところで私はAtomが死んでからメモ帳としてターミナル開いて素のvimを使ってるんですが、最近vim-plugの使い方を忘れてしまい、思い出しがてら新しいcolorschemeにしてみました。
redditで検索しておすすめされてたEverforest、使いやすいです。それまでicebergを使ってたので、VISUALモードが見にくかったんですよね。
閑話休題、関数型的なAPIの書き方の参考になるものを探します。が、なかなか難しいですね。
とりあえず、controllerを実装してる例って無いですね……
なんとなくのイメージで、Router Functionsのような例が出てくると思ったんですが違うようです。
もしかして関数型プログラミングの学習としてkotlinを選んだのは良くなかったのかもしれない。kotlinで学ぶ関数型言語みたいな書籍が無い(英語ならある)時点で察するべきか……
最初からやり直すならHaskell、scala、Elixir、Lispみたいので始めるべきだったかもしれません。
chatGPTさんに聞きつつやってますが、この人結構な確率で嘘をいうのであくまで参考程度に、いろんな資料を斜め読みしつつとりあえずで実装していきます。
なんとなくですが、
- 「関数そのものを変数に代入できる」
- 「数式っぽく扱うためにvoid関数を使わない」
- 「ラムダ式を多用しつつデータを変形させる」
- 「副作用を起こさない」
- 「メソッドチェーンっぽい書き方をする」
らへんなのかなと思います。
副作用云々はDDDでも触れるのでそれっぽい感じでいいんですかね……
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()
}
}
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)))
}
}
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)
}
}
package jp.gooye.toy.tamesi.repository
import org.springframework.data.repository.CrudRepository
interface TamesiDataRepository : CrudRepository<TamesiTableData, String>
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の接続まわりとユニットテストに手を出していきます。