すきま風

勉強したことのメモとか

Spring Boot 2.4.0 でMultiple Propertyファイルを適用させる

Spring Boot 2.4になって、application.ymlの書き方がちょっと変わったのでメモ。色々便利になったようなのですが、k8sとか使っていないのであんまり恩恵がない 😑


基本設定をapplication-base.ymlに記述し、環境ごとの差異をそれぞれのapplication-xxx.ymlに記述します。

application.yml

# default
spring:
  config:
    import:
      - classpath:application-base.yml
---
spring:
  config:
    activate:
      on-profile: development
    import:
      - classpath:application-base.yml

application-base.yml

すべての環境で共通の設定を記述する

server:
  port: 8081
  shutdown: graceful

application-development.yml

some.property.label: development

application-default.yml

local起動の場合 (特にprofileを指定しない場合) defaultになります。

some.property.label: default

configuration

テスト用に適当なConfigurationを用意

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperty::class)
class SomeConfiguration(
    private val prop: SomeProperty
) {
    fun prop() = prop.label
}

@ConfigurationProperties("some.property")
@ConstructorBinding
class SomeProperty(
    val label: String = ""
)
@RestController
class ConfigTestController(
    val config: SomeConfiguration
) {
    @GetMapping("test")
    fun test(): Mono<String> = mono {
        config.prop()
    }
}

コンテナにします

$ ./gradlew bootBuildImage

defaultで起動

$ docker run -m "1024M" -it -p 8081:8081 --rm demo:0.0.1-SNAPSHOT

$ curl http://localhost:8081/test
-> default

developmentで起動

$ docker run -m "1024M" -it -p 8081:8081 -e "SPRING_PROFILES_ACTIVE=development" --rm demo:0.0.1-SNAPSHOT
-> The following profiles are active: development


$ curl http://localhost:8081/test
-> development

上書きしたい場合

以前と同様に、後からimportしたproperty fileが一番強くて、設定をoverrideします。

application-override.yml

server:
  port: 8080

application.yml

---
spring:
  config:
    activate:
      on-profile: development
    import:
      - classpath:application-base.yml
      - classpath:application-override.yml
$ docker run -m "1024M" -it -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=development" --rm demo:0.0.1-SNAPSHOT

-> Netty started on port(s): 8080

前より書き方が面倒になった気もするが、これであっているのだろうか 😑
profile.groupsを使うよりはこっちのほうがまだ楽だと思うけど、情報待ちデス

参考

github.com

goroutineを使ってdatabaseに並列でInsertするサンプル

databaseにgoroutineで並行にinsertするサンプルプログラムです。仕事で必要になって実験しました。

database 接続

sql.Open() を使ってDB Instanceを取得します。DB Instanceは並行安全で、このInstanceがConnection Poolを管理するので Singletonにしてアプリケーション全体で共通利用することにします。

// database.go

// かんたんsingleton
var singletonDB *sql.DB
var lock sync.Mutex

func Connect() *sql.DB {
    lock.Lock()
    defer lock.Unlock()
    if singletonDB != nil {
        return singletonDB
    }
    // サンプルなのでerr無視
    singletonDB, _ = sql.Open(
        "postgres",
        fmt.Sprintf(
            "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
            os.Getenv("host"),
            os.Getenv("port"),
            os.Getenv("user"),
            os.Getenv("password"),
            os.Getenv("dbname"),
        ),
    )

    // connection poolingの設定をする
    singletonDB.SetMaxOpenConns(10)
    singletonDB.SetConnMaxIdleTime(10 * time.Second)
    singletonDB.SetConnMaxLifetime(10 * time.Second)

    return singletonDB
}

goroutine fan-out

Sample tableを適当に用意してInsertしていきます。PrepareするとDB InstanceはConnectionを発行して、Stmt Instanceを返します。 Stmtも並行安全なので、goroutineで共用します。自分のLocal環境のテストでは結果は変わらなかったのですが、 きちんとした環境ではgoroutineごとにStmtを発行 (= goroutineごとにConnectionをもつ) ほうが早いかもしれません。ただ、Transaction管理とかしだすと辛いと思います。
あと、公式の説明によるとgoではDB InstanceをCloseするということは基本行わないらしいのでStmtのCloseしかしていません。StmtをCloseするとConnectionはIdleになります。

func () Insert(entityCh <-chan entity.SampleEntity, gophers int) <-chan error {
    errCh := make(chan error)

    // singleton DB instanceを取得する
    db := Connect()

    // stmtは並行安全なのでgoroutineで共用する
    stmt, err := db.Prepare("insert into sample (id, label) values ($1, $2)")
    if err != nil {
        log.Fatal(err)
    }

    var wg sync.WaitGroup
    wg.Add(gophers)

    // fan-out. goroutineを複数生成する
    for i := 0; i < gophers; i++ {
        go func() {
            defer wg.Done()

            // 並行処理でInsertしていく...
            for ent := range entityCh {
                _, err := stmt.Exec(ent.Id, ent.Label)
                if err != nil {
                    errCh <- err
                }
            }
        }()
    }

    go func() {
        defer stmt.Close()
        wg.Wait()
        close(errCh)
    }()

    return errCh
}

性能

以下の環境で試験しています。

software version
os macOS Catalina
go 1.15
postgres postgres:13-alpine


50,000件Insertしてみた結果は以下

並行数 時間
1 101 sec
3 52 sec

goroutine の数を増やしたらちゃんと早くなりました 😌

参考

golang.org

ポンコツ宣言

産業医面談を経て、自分のストレスがかなり高まっていることを認識したので、上司に、心がポンコツになったので今後の仕事内容はポンコツになります、とポンコツ宣言をしました。上司の心配そうな顔の影に「何いってんだこいつ?」感がほんの少しでているように思いました。自分が同じ立場だったらそうなると思うので残当です。もしくは、他人の表情を悪く捉える自分の認知に問題があるのかもしれません。なんにせよ、こういう宣言をすることができる会社にいる、というのはかなりの救いです。仕事の進め方ややり方を一緒に考えていけるようになりました。


人と話すことが嫌いなのでプライベートでは一切人と関わらないのですが、テレワーク主体となって仕事でも人と基本関わらなくなった結果、人間に対する耐性が極限まで低下してしまったのだと思います。そのため、新しいプロジェクトに参加した際の、よく知らない人間たちとのコミュニケーションの嵐に耐えられなくなってしまったのだと自己分析しています。今の自分の人間耐性は、アパレル店員と少し話しただけで翌日1日は寝込む、くらいに低下しています。仕事したくない。。

雑記

仕事の話

社会人である以上、話したくない人間とコミュニケーションを取らないといけない場面というものは往々にしてある。ある程度は仕方ないと諦めているが、できるだけそういう会話の機会は減らしたいと思っている。特に、自分と比較して、頭の回りが極めて悪い人間とはできるだけ距離を取りたい。 しかし、最近は技術力の高い同僚たちが次々と会社を去っていったため、地頭の良い人達と会話する機会が減り、相対的にその話したくない人間たちとのコミュニケーションの機会が増えており、働くのがどんどん辛くなってきている。自分より頭の良い人達とだけ会話したい 😑

登山の話

先日奥多摩で何年かぶりに道迷いをしてしまい、ガチで遭難か!?と結構あせった。
右の尾根に降りるつもりがなぜか左の尾根に入ってしまい、正規の登山道から外れてしまった。しかし自分では右の尾根を歩いていると認識していたので、道を間違えたことに気づいた後、誤った認識のままで道を引き返してしまってどんどん泥沼にはまってしまい、気づいたら取り返しがつかないほど方向感覚が崩壊していた 😥
自分がわけのわからないところにいる、と認識した瞬間、気持ちの良かった山道がとたんに何やら恐ろしいものに感じ、視界が狭くなり、呼吸が浅くなり、、山はやっぱり怖いなあと思った。なんとか正しい道に戻れてよかったが、反省したい。

Multi Module ProjectのSpring Boot × Kotlin Applicationにdetektを導入する

仕事で使うことになったので勉強しました 😌
前回の記事 に適用します。

point

  • ktlintはdetekt-formattingを利用する。そのためにmaven kotlinx repositoryを追加する
  • multi moduleすべてを見るようにinput files を設定する
  • checkでdetektも走るようにする

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

// use kotlin 1.4.10 at dependecyManagement
// https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/235
extra["kotlin.version"] = "1.4.10"

// detekt version (pluginとversionをあわせる)
val detektVersion = "1.14.2"

plugins {
    id("org.springframework.boot") version "2.3.4.RELEASE" apply false
    id("io.spring.dependency-management") version "1.0.10.RELEASE"
    id("com.google.protobuf") version "0.8.13" apply false
    id("java")

    // pluginを追加
    id("io.gitlab.arturbosch.detekt") version "1.14.2"
    kotlin("jvm") version "1.4.10" apply false
    kotlin("plugin.spring") version "1.4.10" apply false
}

repositories {
    mavenCentral()
    jcenter()
    // for detekt org.jetbrains.kotlinx:kotlinx-html-jvm
    // これを入れないとdetekt pluginをinstallできない
    maven(url = "https://kotlin.bintray.com/kotlinx")
}

allprojects {
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
}

subprojects {
    // 省略します ;-)
}

// ここからdetektの設定
detekt {
    toolVersion = detektVersion

    // default settingをoverrideする
    config = files("detekt/detekt.yaml")
    buildUponDefaultConfig = true

    // subprojectsのsrc/main|test/kotlinをチェックする
    val sources = subprojects.map { listOf("${it.projectDir}/src/main/kotlin", "${it.projectDir}/src/test/kotlin") }.flatten()
    input = files(sources)
}

// ktlintはdetekt pluginで設定可能なのでこちらを利用する
project.dependencies {
    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
}

// detektをcheckで実行
tasks.named("check") {
    dependsOn(tasks.named("detekt"))
}

tasks.withType<io.gitlab.arturbosch.detekt.Detekt> {
    jvmTarget = "11"
}

kotlin

@SpringBootApplication
class CleanApplication


// detekt 検出を防ぐ
@Suppress("SpreadOperator")
fun main(args: Array<String>) {
    runApplication<CleanApplication>(*args)
}

detekt-formattingを使っている記事が見つからなかったのでちょっとハマった 😔

Multi Module ProjectのSpring Boot × Kotlin ApplicationにgRPCを導入する

Clean Architecture (Multi Module Project) のSpring BootでgRPCを使えるようにします。 仕事で使いそうなので勉強しました。本当はGoで作りたかったけど、誰も賛同してくれなかった。人望/Zero 😉

続きを読む