必要なツールを必要だと思った時に、必要な分だけ用意する 〜 shell で小さく始めるscaffold 〜

CARTA fluct エンジニア の なっかー @konsent_nakka です。 この記事は CARTA アドベントカレンダー2025 11日目の記事です。

techblog.cartaholdings.co.jp

techblog.cartaholdings.co.jp

レイヤードアーキテクチャで構築されたアプリケーションでAPIを追加する際、毎回似たようなファイル構造を作る必要があり、実装に時間を取られていました。
ふと思いつき2時間くらいでshellとMakefileを利用してscaffoldingツールを作りました。

課題

アプリケーションが成長するにつれて、新しいAPIやリソースを追加する際に必要なファイル群は決まったパターンになっていました。
Entity層、UseCase層、Infrastructure層、Presentation層、各層間のMapperなど、毎回手動で作成してると、作る場所を間違えたり地味なミスでエラーが発生するということもありました。特に新しいメンバーにとってはどこにどのファイルが必要なのかを把握するのに時間がかかっていました。

解決方針

ふと思いつきで「そういえばscaffoldingツール導入すればいいじゃん」ということに思い至り、当時Golangで書かれたアプリケーションだったのでついでにGo言語で書かれたちょうど良いscaffoldingツールを探しました。
ですがその時は見つからなかったので、自作することにした。ただ、最初から完璧なツールを作ろうとはせず、サボりつつ素早く小さく始めることをしました。結論、Shellで作ることに。

ひとまずsedとかで何とかならんかな、というところからひとまずsedで特定の文字列を置き換えるところから始め、必要なだけの実装をshellで始めて見ることにしました。
何なら最初はMakefileだけで何とかならないかと思ったのですが、流石に特殊文字の扱いだったり、行数的にも逆に複雑になってしまうのでshellファイルで実装することにしました。

それぞれかかった時間は大体この様になっています。

  • DesignDoc作成:1時間
  • Makefileとshellスクリプト作成:30分
  • テンプレートファイル作成:30分

実装

その時実装したMakefileとshellスクリプトのほぼ全部になります。

.PHONY: run clean

RESOURCE :=

run:
    bash scaffold.sh $(RESOURCE)

clean:
    -@(cd ../ && git clean -dfn)
    -@echo '`cd ../ && git clean -df && cd -` を実行すれば上記のファイル群が削除されます。'
#!/bin/bash -eEu

# === エラーハンドリング ===
if [[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
    shopt -s inherit_errexit
fi
shopt -s inherit_errexit
on_error() {
    echo "[Err] ${BASH_SOURCE[1]}:${BASH_LINENO} - '${BASH_COMMAND}' failed">&2
}
trap on_error ERR

# 新しいAPIを作る時に必要なファイルを一括生成するスクリプト

# === 引数チェック ===
if [ "$#" -lt 1 ]; then
    echo "Error: Missing required argument(cnt=${#}): RESOURCE" >&2
    echo "Usage: $0 <RESOURCE>" >&2
    exit 1
fi

RESOURCE=$1

# === 命名規則の変換 ===
CONCAT_RESOURCE=$(echo ${RESOURCE} | sed 's/_//g')
CONCAT_REP=scaffoldresource__
SNAKE_RESOURCE=$(echo ${RESOURCE})
SNAKE_REP=scaffold_resource__
CAMEL_RESOURCE=$(echo ${RESOURCE} | awk -F '_' '{ printf $1; for(i=2; i<=NF; i++) {printf toupper(substr($i,1,1)) substr($i,2)}} END {print ""}')
CAMEL_REP=scaffoldResource__
PASCAL_RESOURCE=$(echo ${RESOURCE} | awk -F '_' '{ for(i=1; i<=NF; i++) {printf toupper(substr($i,1,1)) substr($i,2)}} END {print ""}')
PASCAL_REP=ScaffoldResource__

echo '==========='
echo $RESOURCE
echo $CONCAT_RESOURCE
echo $SNAKE_RESOURCE
echo $CAMEL_RESOURCE
echo $PASCAL_RESOURCE
echo '==========='

# === ファイルパス定義 ===
TRANSLATOR_FILE="../presentation/graphql/translator/${RESOURCE}/${RESOURCE}.go"
MODEL_TO_ENTITY_FILE="../presentation/graphql/translator/${RESOURCE}/mapper.go"
ENTITY_FILE="../entity/${RESOURCE}.go"
USECASE_FILE="../usecase/${RESOURCE}.go"
INTERACTOR_FILE="../interactor/repository/${RESOURCE}.go"
INFRA_FILE="../infrastructure/repository/${RESOURCE}.go"
ENT_TO_ENTITY_FILE="../infrastructure/repository/mapper/${RESOURCE}.go"
ENTITY_TO_MODEL_FILE="../presentation/graphql/mapper/${RESOURCE}.go"

# === ファイル生成関数 ===
function scaffolding_by_file() {
    echo "generate file: ${2}"
    mkdir -p $(dirname ${2})
    cat ${1} > ${2}
    sed -i '' "s/${CONCAT_REP}/${CONCAT_RESOURCE}/g" "${2}"
    sed -i '' "s/${SNAKE_REP}/${SNAKE_RESOURCE}/g" "${2}"
    sed -i '' "s/${CAMEL_REP}/${CAMEL_RESOURCE}/g" "${2}"
    sed -i '' "s/${PASCAL_REP}/${PASCAL_RESOURCE}/g" "${2}"
}

# === ファイル生成実行 ===
scaffolding_by_file translator.go.template ${TRANSLATOR_FILE}
scaffolding_by_file model_to_entity_mapper.go.template ${MODEL_TO_ENTITY_FILE}
scaffolding_by_file entity.go.template ${ENTITY_FILE}
scaffolding_by_file usecase.go.template ${USECASE_FILE}
scaffolding_by_file interactor_repository.go.template ${INTERACTOR_FILE}
scaffolding_by_file infrastructure_repository.go.template ${INFRA_FILE}
scaffolding_by_file ent_to_entity_mapper.go.template ${ENT_TO_ENTITY_FILE}
scaffolding_by_file entity_to_model_mapper.go.template ${ENTITY_TO_MODEL_FILE}

# === 追加コード生成 ===
sed "s/${CONCAT_REP}/${CONCAT_RESOURCE}/g" other_sample_code.go.template | sed "s/${SNAKE_REP}/${SNAKE_RESOURCE}/g" | sed "s/${CAMEL_REP}/${CAMEL_RESOURCE}/g" | sed "s/${PASCAL_REP}/${PASCAL_RESOURCE}/g"

この時、実は変数名に困って後々ケースごとの名称を調べたりしたことがあり、ニッチな知識ですが地味に役に立ったりするので面白いです。

https://zenn.dev/nakka_k/articles/c49e2413cc0782

効果

このツールを導入したことで、新しいリソース作成時の作業時間がかなり短縮されて、チーム全体の開発スピードが上がりました。

特に新しいメンバーにとって最も価値のある効果だったと思っており、「API全体の構成がどうなっているのか」「どんなファイルを作る必要があるのか」「どこにおく必要があるのか」を悩む必要がなくなったのは良い副産物でした。
APIを実装する時もまずはscaffoldingでファイルを生成してから実装を埋めていく形で進められるようになったので、明らかに既存メンバーもそうですし、新規のメンバーが軌道に乗るまでのスピード感も上がりました。

学び

やはり思うのが、shellスクリプトという簡易的な実装で始めたのが良かったですね。最小限の労力で最大限のリターンを得られたし、もし最初から完璧なツールを作り込んでたら、その期間使えないことによる損も割と大きかったのではないかと思います。

さらに、自分に対してもそうですが、特にチーム、またはそれ以上に対して生産性を向上する施策をやるとコスパが非常に良いのでやった方がいいと思ったことはドンドンやっていきましょう。

必要なツールを必要だと思った時に、必要な分だけ用意する」っていうアプローチの重要性を実感した。そのためには、思いついた時に適切な道具を選択できるようにしておくのが大事だなと思いました。