プラットフォーム・ランタイム・フレームワークの選択

発端

グレゴール・ホープ氏の投稿した記事を読んでたらいろいろ思うところがあった。

プラットフォーム・ランタイム・フレームワークの選択

基本的には、これらを判断できる情報が集まってればよさそう。

なんか投資信託みたいだな・・・・・・

それなりの期間継続して利用する前提のシステムなら、業務あるいは事業の予算計画や売上計画があるはず。

プラットフォーム・ランタイム・フレームワークという技術要素の選択は、それらの計画に正負両方の影響を与える可能性がある、というのを意識する(させる)必要がありますねと。

具体例を踏まえたよもやま

  • 対象:社内業務システム(Webシステム)のリプレース
  • 利用期間:今の業務が無くなるまで(つまり未定)
  • 開発期間:4ヶ月くらい(できるだけ早くして欲しい)
  • 環境:AWS(社内標準だから)
  • ランタイム:Java11(社内標準だから)
  • フレームワーク:指定なし
  • 運用:自社のメンバーでやっていくつもり

ノーコードじゃないんかい、とかいろいろ思うところはあるけど、 できるだけ早く とか Java11 に引っ張られて Wagby や Spring Boot を選んでしまいそう。

たぶん、その考えは落とし穴です。辛い話がいろいろ思いついてしまう。

Spring Boot は依存ライブラリ管理とコンポーネント構成を半自動化するフレームワークで、イニシャルコストはすごく下げられる。 しかし、EOLが15ヶ月なので、比較的短期に deprecated になってしまう。

依存ライブラリのセキュリティフィックスを単独で取り入れようとしても、旧バージョンの Spring Boot が対応していないバージョンなら、依存ライブラリ定義とコンポーネント構成を自前でやらないといけない。 そのためには、MavenやGradleに加えて Spring Frameworkの高度な知識が必要になってしまう。

Spring Boot を最新版にすればいいかというと、それもまた悩ましい判断になる。 全ての機能が正しく動作する保証はないから。

それをランニングコストと呼ぶのか、リスクヘッジのコストと呼ぶのかわかりませんが、安く付くはずが無い。

さらに、Java11 が保守されるのは一般的に 2024 年までとされているので、たとえ今の業務が続いているとしても、その次の LTS へ乗り換えないといけない。 もちろんフレームワークSpring Framework 6.x へ切り替えないといけないわけで。

Pull Requestによるコミュニケーションが成立する条件

  • システムに対する要求を理解できていること
  • 設計の前提条件を理解できていること
  • 開発者間に権力勾配がないこと
  • ソースコードをチームの共同所有物として認識していること
  • コーディング規約、ユニットテスト、ビルド、インテグレーションテストなどを自動化すること
  • 「分かってくれるだろう」と考えないこと

解釈の幅が広い条件もあるけど、多分これらが全部必要になる。

揃わないならPull Requestベースの開発をする土台が整ってないので、工程表を作って、フェーズゲート型のレビューを開催した方がいい。

 

pgdiff(Rubyスクリプト)による PostgreSQL データベースの比較

pgdiff を使うと、オンラインの PostgreSQL を比較して、データベース定義を揃えるための SQL を生成できます。

特徴とメリット・デメリット

総合すると、何も使えないときは頼りたくなる、くらいの評価……

  • 依存する gem は pg だけで、それ以外は pure Ruby
    • 👎Windowsで実行できるようにするのが難しい(WSLならいける)
  • 稼働中のデータベースプロセスを停止せずに比較できる
    • 👍 リードレプリカのない環境だと嬉しい
  • SQLを生成する
    • 👎 冪等性、排他制御、効率は考慮されてない

使い方

$ apt-get install -q -y libpq5 libpq-dev
$ gem install pgdiff
$ command pgdiff
/home/yujiorama/.rbenv/shims/pgdiff
$ pgdiff "postgresql://user:pass@host1:port1/database1" "postgresql://user:pass@host2:port2/database"

「イシューからはじめよ」読んだ

ウィッシュリスト経由でいただいたので早速読んだ。

ずいぶん前の忘れ物を見つけたような感覚。

相手に伝わる提案を組み立てるための戦略と戦術を解説してる本だった(この本の構成がまさにそうなっている!)。

 

本当に解決すべき(白黒はっきりさせるべき)問題をイシューと呼んでいる。

イシューを特定し、解決策のストーリーを構成し、仮説を裏付ける情報を収集し、プレゼンテーションを作成する、という流れ。

抽象度を少しずつ低くして、具体化していくということだ。

「ストーリーを意識する」という点が繰り返し登場するので、方向性を間違えないようにすることが大事なのがよく分かる。

目的に応じて、適切な調査や分析の手法を活用できるよう、訓練・習熟しておくのも重要。

 

いかにして問題をとくかや、

ライト、ついてますか―問題発見の人間学を読んでおくと良さそう。これらの本では、問題を解決するには問題を正しく理解しなければならないことを教えてくれるため、イシューを特定するときの考え方の参考になる。

第5回 The Software Architect Elevator 読書会@リモート

javaee-study.connpass.com


参加者トピック

ディスカッション

第16章 ITの世界はフラット

  • 顧客として、ベンダーさんには、もうちょっとこっちの事情も汲んでくれよ、という思いが強い
  • ベンダーとして、顧客と一緒にソリューションの構築をやっていくようにしている
  • 地図やランドスケープ、という抽象度で話をしているのは納得できるけど、「XXXマッピングで認識合わせをしましょう」みたいなツール先行の進め方は腑に落ちない
  • 自分で地図を作れるイメージが湧かない。コアについて2つ質問するだけでどうにかなるもの?ならないよね
    • 自分たちの知ってることなら地図に描けるはずなので描く。そこに何を追加したいのか考えるのが大事なのでは。
    • 分散トレーシングが必要なら、分散トレーシングを売りにしてる製品を検証していけばいい

17. あなたのコーヒーショップは2フェーズコミットを使用していない

  • 前に聞いたことのある話だった
  • 著者はしばらく日本に住んでいたこともあるそうで、その辺の話のようだ
  • 再試行のトピックから派生するトピック:復旧後のサービスに多量のリクエストが殺到して、本来の性能が発揮できなくなってしまうのを避けるにはどうしたらいいだろう?

III. Communication

  • どういうドキュメント書いてる?
    • 画面設計書(どういう項目を、どこに、どこから取得した値を表示する、みたいな)
    • 画面遷移図
  • メンテナンスできてる?
    • リリースできるまで維持できてればいいんじゃないかと思ってる(作る人使う人が身近な人だけの場合)

18. 物事の説明

  • キーワードが多い
  • 👍自分が理解したことを書くんじゃなくて、相手の理解を促す文章を書くのが重要
  • 「結論から言え」について
    • 経験的に、結論より先にコンテキストの説明が必要だった
    • 不要ならスキップすればいいし、そうでない場合もあったけど
  • 👍ここまで読み進めて、初めて「具体的に活用できそうな考え方」だと思った
  • 基本的に経営陣は事業成長のための判断材料を欲しているのであって、技術的に正確な理解をしたいわけではない
    • 経営陣には、技術的な理解を深めることで、効率的に判断材料を収集できることを伝えてあげないといけない
    • 技術者には、技術的判断が、事業成長にどのような影響を与えるのか理解してもらわないといけない

19. Show the Kids the Pirate Ship!

  • 後で参照しそうなプレゼンテーションの場合は目次付けるよねぇ

参考情報

DNSキャッシュ時間の問題

Cloud Native Build Pack で Spring Boot アプリのコンテナイメージを作れるから簡単だと思っていると、いい感じにDNSキャッシュ時間の設定値を構成する方法が無くて困る。

blog.astj.space

1. $JAVA_HOME/lib/security/java.security に記述

初期設定では、Cloud Natie Buildpack でイメージを作成すると、JREpaketo-buildpack/bellsoft-libericaを使う。

このBuildpackは、環境変数JAVA_TOOLS_OPTS-Djava.security.properties=<path>を埋め込むようになっており、java.securityの代わりに別の場所へ配置したプロパティファイルを使うようになる。

起動時のログでも確認できる。

Setting Active Processor Count to 8
Calculating JVM memory based on 2708640K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx2327887K -XX:MaxMetaspaceSize=73552K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 2708640K, Thread Count: 50, Loaded Class Count: 10572, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=8 -XX:MaxDirectMemorySize=10M -Xmx2327887K -XX:MaxMetaspaceSize=73552K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.5)

つまり、Buildpackの作成したレイヤーに作成されていることがわかる。

ビルド時に内容を設定できるのか考えたけど、ただ作ってるだけでそういう機能はないらしい。(paketo-buildpacks/libjvm/blob/main/java_security_properties.go#L47

まとめ

  • Cloud Native Buildpackで作成したコンテナイメージは、標準と違う場所に置いたプロパティファイルを使用する
  • コンテナイメージを作成するとき、設定だけでプロパティファイルをカスタマイズする方法は不明
  • コンテナイメージを実行するとき、設定だけでプロパティファイルをカスタマイズする方法は不明
  • コンテナイメージ実行時に、ボリュームマウントして任意のファイルを読ませることはできる

2. java.security.Security.setProperty() で指定

Spring Bootアプリはmainメソッドを作ることが多いと思うので、リンク先の記事のようにInetAddressCachePolicyの読み込み順を意識せずに対応できると思う。

毎回書かないで済むようBuildpackが対応して欲しい。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    static {
        try {
            var cacheTTL = System.getProperty("networkaddress.cache.ttl", "1");
            var cacheNegativeTTL = System.getProperty("networkaddress.cache.negative.ttl", "1");
            java.security.Security.setProperty("networkaddress.cache.ttl", cacheTTL);
            java.security.Security.setProperty("networkaddress.cache.negative.ttl", cacheNegativeTTL);
        } catch (Exception ignore) {
            // XXX
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

A5:SQL Mk-2のER図(.a5er)からDDLを生成する

TL;DR

コマンドラインユーティリティ自体の実行方法

A5:SQL Mk-2 コマンドラインユーティリティからzipファイルをダウンロードして適当な場所に展開します。

zipファイルには説明書も付属してます。
ここの説明はそれを元にしてるので、正確な詳しい内容は説明書を参照してください

次のように実行すると、c:\work\model.a5er に置いたER図から c:\work\model.ddlSQLDDL)が生成されます。

A5M2cmd.exe /ERDDL ^
/ERD=c:\work\model.a5er ^
/OutFileName=c:\work\model.sql

Maven で実行する方法

A5:SQL Mk-2 で ER 図を開いていると、他のプロセスからファイルを開けなくて失敗します

MavenDDLを生成できるようになっていると便利な気がします。

mvn generate-sources -Pgenerate-sources-ddl -Derd.path=c:/work/model.a5er

こういうふうに記述すると実現できます。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>a5m2cmd-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>a5m2cmd-demo</name>
    <packaging>jar</packaging>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
    </dependencies>

    <profiles>
        <profile>
            <id>generate-sources-ddl</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <erd.path>c:/work/model.a5er</erd.path>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>wagon-maven-plugin</artifactId>
                        <version>2.0.2</version>
                        <executions>
                            <!-- A5m2cmd.zipをダウンロード -->
                            <execution>
                                <id>fetch-a5m2command</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>download-single</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <skipIfExists>true</skipIfExists>
                            <url>https://ftp.vector.co.jp</url>
                            <fromFile>71/97/3301/A5M2cmd_2.14.3_x64.zip</fromFile>
                            <toFile>${project.build.directory}/A5M2cmd/A5M2cmd.zip</toFile>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-antrun-plugin</artifactId>
                        <version>1.8</version>
                        <executions>
                            <!-- A5m2cmd.zipをtargetに展開 -->
                            <execution>
                                <id>extract-zip</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <tasks>
                                        <unzip src="${project.build.directory}/A5M2cmd/A5M2cmd.zip" dest="${project.build.directory}/A5M2cmd" />
                                    </tasks>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>3.0.0</version>
                        <executions>
                            <!-- A5m2cmd.exeを実行してDDLを生成 -->
                            <execution>
                                <id>generate-sources-ddl</id>
                                <phase>generate-sources</phase>
                                <goals>
                                    <goal>exec</goal>
                                </goals>
                                <configuration>
                                    <executable>${project.build.directory}/A5M2cmd/A5M2cmd.exe</executable>
                                    <arguments>
                                        <argument>/ERDDL</argument>
                                        <argument>/ERD=${erd.path}</argument>
                                        <argument>/OutEncoding=UTF-8</argument>
                                        <argument>/OutFileName=${project.basedir}/src/main/resources/db/migration/V0_0_001__table.sql</argument>
                                        <argument>/RDBMSType=POSTGRESQL</argument>
                                        <argument>/CreateOrder=Dependent</argument>
                                        <argument>/GenerateComment=Y</argument>
                                        <argument>/GenerateDropTableStatement=Y</argument>
                                        <argument>/DropTableIfExists=Y</argument>
                                        <argument>/BackupRestoreTempTable=N</argument>
                                        <argument>/ForceQuoteIdentifier=N</argument>
                                        <argument>/CreatePkIndex=Y</argument>
                                        <argument>/CreateFk=Y</argument>
                                        <argument>/CreateFK_ParentCard1Only=Y</argument>
                                        <argument>/FKParentIndex=Y</argument>
                                    </arguments>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

Gradle で実行する方法

こういう感じに実行できると便利そうです。

gradle generateDDL -Perd.path=c:/work/model.a5er

こういう風に記述すると実現できます。

plugins {
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

def erdPath = findProperty('erd.path') ?: 'er.a5er'

repositories {
    mavenCentral()
}

task fetchA5M2cmd {
    def url = 'https://ftp.vector.co.jp/71/97/3301/A5M2cmd_2.14.3_x64.zip'
    doLast {
        file("${buildDir}").mkdirs()
        def a5M2cmdZip = file("${buildDir}/A5M2cmd.zip")
        a5M2cmdZip.withOutputStream { os -> new URL(url).withInputStream { is -> os << is }}
    }
}

task unzipA5M2cmd(type: Copy) {

  def zipPath = file("${buildDir}/A5M2cmd.zip")
  def zipFile = file(zipPath)
  def outputDir = file("${buildDir}/A5M2cmd")

  from zipTree(zipFile)
  into outputDir
}

task generateDDL(type:Exec) {

    executable file("${buildDir}/A5M2cmd/A5M2cmd.exe")
    args = [
        "/ERDDL",
        "/ERD=${erdPath}",
        "/OutEncoding=UTF-8",
        "/OutFileName=${projectDir}/src/main/resources/db/migration/V0_0_001__table.sql",
        "/RDBMSType=POSTGRESQL",
        "/CreateOrder=Dependent",
        "/GenerateComment=Y",
        "/GenerateDropTableStatement=Y",
        "/DropTableIfExists=Y",
        "/BackupRestoreTempTable=N",
        "/ForceQuoteIdentifier=N",
        "/CreatePkIndex=Y",
        "/CreateFk=Y",
        "/CreateFK_ParentCard1Only=Y",
        "/FKParentIndex=Y",
    ]
}

tasks.generateDDL.dependsOn 'fetchA5M2cmd', 'unzipA5M2cmd'

おまけ:GitリポジトリでER図(.a5er)を管理するときは .gitattributes を作ったほうがいい

ER図(.a5er)ファイルの改行コードは CRLF ですが、ソースコードや設定ファイルの改行コードは LF になっている場合がほとんどです。

そういう状態のままER図を編集していると、コミット時に改行コードが変換されることを示す警告メッセージが表示されます。

改行コードの変換を避ける方法はいろいろあるのですが、 .gitattributes を設定するのがいいと思います。

Git - Git の属性

具体的にはリポジトリに .gitattributes というファイルを作成して次のように記述します。

*.a5er text eol=crlf