ソフトウェアエンジニアの雑記

日々思ったことをまとめます

レビュー「小さなチーム、大きな仕事 働き方の新スタンダード」

概要

著者は、アメリカのソフトウェア会社「ベースキャンプ (旧 37signals)」のエンジニアで、この会社はRubyOnRailsを開発しているので有名だと思います。この本が出版される頃でも社員は16人と小規模で、それを可能にするために 「常識」や「王道」の考え方を根本から見直し、事業から開発の仕方、働き方まで別の角度で異議を唱えている意欲的な書籍です。大きな組織に辟易している方には読んでいただきたい一冊です。

Amazon.co.jp: 小さなチーム、大きな仕事 働き方の新しいスタンダード (ハヤカワ文庫NF) eBook : ジェイソン フリード, デイヴィッド ハイネマイヤー ハンソン, 黒沢 健二, 松永 肇一, 美谷 広海, 祐佳 ヤング: Kindleストア

感想

章ごとに数行の感想を書いていきます。

現実の世界なんて無視しよう

「現実」の世界は、悲観と絶望と言い訳に満ちているから、引きずり込まれないように無視しようという話。「現実」にはステレオタイプな考え方がはびこっていて、規模や失敗確率の誤解、計画の害悪など私達が信じまくっている内容を1章から覆していく。

先に進む

自分たち(ベースキャンプ社)で必要な「自分が使いたいものを作る」や「時間がないは言い訳」、「信念を持つ」、「身軽でいること」など前進するために、後回しにしないこと、不要なものは作らないことなどが語られている。前進・行動することの大切さが語られている。この中では、「身軽でいること」というのは、大変共感していて、通常業務でもいつのまにか色々な制約や契約に縛られて、遅くなっていることが多いがビジネスはスピードが命なので、この部分はかなりの神経を使って注意を払うことがいいと思います。

進展

「制約」から知恵が生まれ、少ないことをうまくやり、必須機能から取り組み、決断して前進するという話です。また、世の中に目を向けると変化しているものと変化していないものがあり、それをどうキャッチしていくかなどが紹介されています。前章の「先に進む」に近いのですが、より具体的な内容になっています。

生産性

書類は幻想で、やることに対して意味をもたせ、集中できる環境づくり・時間づくりが大事で、会議はなるべく少なく、完璧を目指さないということが大事だということが書かれています。完璧を目指さず、小さい目標を設定し、どんどん仕事をするというのは本当に大事で、日本人はこの真逆をよくやっているとおもいます。立ち上げフェーズだとスピードがやはり大事なので、どんどん細かくリリースすることが大切です。

競合相手

すでに競合相手がいる場合は、表層を真似るだけではダメだし、競合相手より上をいかないと勝てないし、しかし競合相手が何をしているかなんて気にする必要がないと書かれている。言ってることは矛盾しているような気もしますけど、気にしすぎると判断を誤るから、自分のプロダクトに集中することを促している。

進化

「基本的にノーと言おう」、「熱意を優先順位と混同するな」、「舞台裏を見せる」などについて書かれているが、前半は進化するために、ついついやってしまいがちな『良い人』のようなものは、プロダクトやサービスを全く成長はさせないと書かれていrました。そして、後半の「舞台裏を見せる」は、ノウハウみたいなものは公開しても実際に大切なのはノウハウを作り出す過程だったり作り出す人なので、公開してもいいという話でした。ノウハウは常にブラッシュアップしていくもので、現時点のスナップショットを公開してもすぐに風化いくとのことでした。確かにそうだとは思いますけど、不変的な事項のフルオープンは躊躇しそうだなとおもいました。

人を雇う

「限界で人を雇う」、「履歴書はばかばかしい」、「全員が働く」、「文章力のある人を雇う」で、ここの章がこの本の言いたいことの大きな1つだと思います。如何にチームを小さくするか、どう採用の基準をどうみるか、そしてどう効率的に働くかが書かれています。個人的にはすべて同意なので、時間がない人はこの章だけでも読むと良いと思います。

ダメージコントロール

「対応の速度はすべてを変える」、「謝り方を知る」、「全員を最前線に」とあり、スピードを落とさないために、どうするかを考えるのはすごい大事だと思います。設計も、コードも、採用もスピードが落ちるなら一旦考え直した方がいいと個人的にはおもっています。「文句は放っておく」で大事なのは、自信があるなら突き進むということを書いてあります。人は変化に敏感で、反応するけどすぐに慣れるというものでした。

文化

この章は、「文化はつくるものではない」、「従業員ではガキではない」、「大げさに反応しない」、「なるたけ早くは毒」などに言及されている。文化は作るものではなく、自然発生的に醸成されていくものだし、従業員のために細かくルールを作らなくてもいいし、「なるたけ早く」は、本当の緊急時のみに使用することとしています。特に「なるたけ早く」は依頼側が勝手に期日を設定するわけで、そんなときほど仕様はボロボロなのでホントにやめてほしい。

最後に

この書籍は、海外での会社の中のことを書いていると思いますが、日本の事業会社の実態とそんなに変わらないなと感じています。僕がこの10年くらいで開発組織やチームをうまく蘇生するチェックポイントだったり教訓だったりが、そのまま書籍になっていました。

自分自身、大きな会社も小さな会社も経験をしましたが、むやみに人を増やすとコミュニケーションにかかる時間が増えていき、生産活動時間が減っていくことを目の当たりにしました。適切な文化醸成であったり、情報共有ルールなどの整備を行うことで、人を増やしても1人あたりの生産量が減らない、もしくは減りにくい状況にはなるかなと思います。

この書籍から学べることはあると思いますので、ぜひ読んでみてください。当たり前のことのように思いますけど、理由まで説明されているので、頭に残りやすいと思います。

Amazon.co.jp: 小さなチーム、大きな仕事 働き方の新しいスタンダード (ハヤカワ文庫NF) eBook : ジェイソン フリード, デイヴィッド ハイネマイヤー ハンソン, 黒沢 健二, 松永 肇一, 美谷 広海, 祐佳 ヤング: Kindleストア

タスクランナーTaskでモノレポでも快適な開発環境を構築する

Taskを使うとプロジェクトで使うタスクランナーをMakefileから脱却できます。モジュラーモノリス構成のモノレポでも各サブプロジェクトごとにTaskfile.ymlを設置でき便利です。簡単な使い方をご紹介します。

インストール

brew install go-task
scoop bucket add extras
scoop install task

基本的な使い方

go-taskのタスクはTaskfile.ymlに定義します。以下は簡単な例:

version: '3'

tasks:
  hello:
    cmds:
      - echo 'Hello, World!'
    silent: true

このファイルをプロジェクトのルートに置き、task helloを実行するとHello, World!が出力されます。silent: trueはコマンドの実行ログを非表示にし、結果のみを表示します

環境変数の利用

環境変数が使用できます。環境変数は、 {{ .環境変数名 }} で使用できます。 下記は、使用例になります。

version: '3'

env:
  ENV: task env

tasks:
  stdout:
    cmds:
      - echo "Hello,  {{ .ENV }}"
      - echo "Hello, {{ .SHELL_ENV }}"

実行してみます。

export SHELL_ENV="shell env"
task  stdout

Hello, task env
Hello, shell env

Taskfile.ymlからTaskfile.ymlを読み込む

Taskでは、Taskfile.ymlのネストが可能です。 include という機能になります。今回は、commonという共通用のサブプロジェクトがいるという想定にします。

project/
├── Taskfile.yml
├── common/
│   └── Taskfile.yml

common/Taskfile.ymlの内容

version: '3'

tasks:
  common-echo:
    cmds:
      - echo "Hello, Common Taskfile!"

上記をルートのTaskfile.ymlでcommonサブプロジェクトのTaskfile.ymlを読み込む

Taskfile.ymlの内容

version: '3'

includes:
  common:
    basedir: ./common
    desc: Common echo task
    taskfile: ./common/Taskfile.yml

tasks:
  echo:
    desc: Run the dev task
    deps:
      - common:common-echo
    cmds:
      - echo "Hello, Root Taskfile"

タスク一覧をみてみると、下記のようにincludeが成功しています。

task -l

* echo:                     Run the dev task
* common:common-echo:       Common echo task

これでタスクランナーに階層を持たせることができました。

サブプロジェクトからほかのサブプロジェクトのTaskfile.ymlを読み込む

下記のようなディレクトリ構成になっているとします。serviceAサブプロジェクトからcommonサブプロジェクトのタスクを使用したい場合の方法になります。

├── Taskfile.yml
├── common/
│   └── Taskfile.yml
├── serviceA/
│   └── Taskfile.yml

serviceA/Taskfile.ymlは下記のようになります。

version: '3'
includes:
  common:
    description: Common tasks
    basedir: ../common
    taskfile: ../common/Taskfile.yml

tasks:
  serviceA-echo:
    desc: Service A echo task
    deps:
      - common:common-echo
    cmds:
      - echo "Hello, ServiceA Taskfile!"

includesで、common/Taskfile.ymlを読み込めばOKです。こうすることで、プロジェクトで共通のタスクランナーの管理が簡単になります。

ホットリロード

Taskにはホットリロード機能も備わっています。これはかなり便利で、npmなどより軽いので、移行できるならしてもいいかなと思います。

Taskfile.ymlは下記のように書きます。

version: '3'

tasks:
  test:
    cmd: |
       ./gradlew test  // 実行するコマンドを指定
    ignore_error: true   // エラーが発生しても無視 
    sources:   // 対象のファイル群を指定
      - src/main/java/**/*.java
      - src/main/resources/**/*.*
    method: timestamp   // 更新要素を指定する(他にchecksumも使える)

このようにすると、sourcesのファイル群が変更された際にテストが別タスクで実行される

コマンドは、watch用のコマンドがあるので、それを実行する。

task test -w

これで、ファイル変更ごとに./gradlew testが実行される。

まとめ

MakefileだとShellで自前で実装しないといけないものが、Taskだと大体備わっています。GithubActionsでもすぐ使えるので、大変便利かなと。開発環境をよりよくするお供にぜひ使ってみてください。

Javaをスクリプト言語っぽくするJBangその5(CLIツールを作成する、テンプレート機能)

Javaスクリプト言語っぽくするJBangその5です。CLIツールの作成とテンプレート機能を説明します。CLIとかでJavaを使う選択肢はあんまりないかなと思いますが、結構簡単につくれてしまうのでJavaが動く環境であれば選択肢に入れていいかと思います。

k-sasaki.net

k-sasaki.net

k-sasaki.net

k-sasaki.net

テンプレートからCLIを作成

JBangではテンプレートからCLIがすぐに制作できるようにプリセットされています。下記のコマンド投入で

jbang init --template=cli cli/CliSample.java

下記のJavaコードが出力されます。

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3


import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

import java.util.concurrent.Callable;

@Command(name = "CliSample", mixinStandardHelpOptions = true, version = "CliSample 0.1",
        description = "CliSample made with jbang")
class CliSample implements Callable<Integer> {

    @Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
    private String greeting;

    public static void main(String... args) {
        int exitCode = new CommandLine(new CliSample()).execute(args);
        System.exit(exitCode);
    }

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        System.out.println("Hello " + greeting);
        return 0;
    }
}

中身を見てみると、//DEPSPicoCLIが設定されています。実行してみましょう。

$ jbang cli/CliSample.java CLI
Hello CLI

プロジェクト内で必要なコマンドとかは簡単に作れそうですね。

templateコマンド

JBangにはtemplateコマンドがあります。プリセットされているものも含めて一覧にするコマンドがあるので、実行してみます。

$ jbang template list
agent
   Agent template
cli
   CLI template
gpt
   Template using ChatGPT (requires --preview and OPENAI_API_KEY)
gpt.groovy
   Template using ChatGPT for groovy (requires --preview and OPENAI_API_KEY)
gpt.kt
   Template using ChatGPT for kotlin (requires --preview and OPENAI_API_KEY)
hello
   Basic Hello World template
hello.groovy
   Basic groovy Hello World template
hello.kt
   Basic kotlin Hello World template
jbang-catalog
   Template for creating a new JBang catalog hosted on github with automatic renovate updates
qcli
   Quarkus CLI template
qmetrics
   Quarkus Metrics template
qrest
   Quarkus REST template
readme.md
   Basic markdown readme template

プリセットされているものだけでも、魅力的なものがたくさんあります。QuarkusのRESTAPIやCLIだったり、ChatGPTのAPIクライアントを生成できるようです。

JBangのtemplateに追加する

その3で作成したJBangアプリケーションをテンプレート化してみましょう。

$ jbang template add --name SpringBootTemplate --description SpringBootSampleApp api/SpringBootSample.java api/application.yml

[jbang] No explicit target pattern was set, using first file: {basename}.java=api/SpringBootSample.java
[jbang] Template 'SpringBootTemplate' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

テンプレートに名前をつけて、テンプレート化したいファイルをスペース区切りで入力します。説明文もつけてみました。作成した情報は、jbang-catalog.jsonの中に入ります。

{
  ...
  ,"templates": {
    "SpringBootTemplate": {
      "file-refs": {
        "{basename}.java": "../git/jbang-sample/api/SpringBootSample.java",
        "application.yml": "../git/jbang-sample/api/application.yml"
      },
      "description": "SpringBootSampleApp",
      "properties": {}
    }
}

登録されていますね。テンプレート一覧をみて、確認してみます。

$ jbang template list
SpringBootTemplate
   SpringBootSampleApp

追加されていました。テンプレートに追加したものでJBang initをしてみます。

jbang init --tempalte SpringBootTemplate template/SpringBootSample.java

tree template
template
├── SpringBootSample.java
└── application.yml

無事作成されています。これで、チーム内ルールがコード化されているので、ちょっとしたツールとかもカジュアルに生産性高く作れそうです。

まとめ

JBang templateの使用方法を説明してみました。生成されるコードはJavaコードがほとんどですが、コマンド、実行などスクリプト言語っぽく使え、チーム開発でも活用できそうです。ぜひ使ってみてください。

Javaをスクリプト言語っぽくするJBang その4(JBangアプリをインストールする、カタログにする)

Javaスクリプト言語っぽくするJBangその4です。今回は、JBangで作成したアプリのインストールとエイリアスとカタログについてです。

k-sasaki.net

k-sasaki.net

k-sasaki.net

JBangアプリのインストール

JBangで作成したアプリは、jbang app コマンドでインストールが可能です。

前回作成したSpringBootSample.javaをインストールしてみましょう。

$ jbang app install api/SpringBootSample.java

これでインストールが可能です。jbang app listでインストールアプリ一覧が表示されます。

$ jbang app list
SpringBootSample

となります。

$ SpringBootSample

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-15T07:27:45.723+09:00  INFO 1388 --- [           main] api.SpringBootSample

無事起動できています。

仕組みは簡単で、$HOME/.jbang/bin/配下に呼び出し用のshellscriptが作成されているだけになります。

JBangのエイリアス

JBangにはエイリアスがあります。これはjbang appとは異なり、jbangのショートカットみたいなものになります。下記のコマンドで追加が可能です。

$ jbang alias add api/SpringBootSample.java
[jbang] Alias 'SpringBootSample' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

登録ができているかを確認します。

$ jbang alias list

SpringBootSample
   /Users/kohei.sasaki/.jbang/../git/jbang-sample/api/SpringBootSample.java

登録できているようです。では、実行してみましょう。

jbang SpringBootSample

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-15T07:27:45.723+09:00  INFO 1388 --- [           main] api.SpringBootSample

無事実行できます。

さらにJavaコードに説明文をを書くことが可能です。

//DESCRIPTION SpringBoot sample application.

説明文は追加時のみ読み取られるので、更新をかけます。

$ jbang alias add --force api/SpringBootSample.java
[jbang] Alias 'SpringBootSample' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

その後、説明文が追加されたかみてみましょう。

$ jbang alias list

SpringBootSample
   SpringBoot sample application.
   /Users/kohei.sasaki/.jbang/../git/jbang-sample/api/SpringBootSample.java

説明文が追加されています。

カタログ

簡単に誤解を怖れずにいうと、前述のエイリアスをひとまとめにしたものになります。これを用いることで、チーム内のスクリプトの共有やテストツールの共有などが簡単にできてしまいます。

JBangのドキュメント内にあるカタログをみてみましょう。 github.com

下記は抜粋ですが、jbang alias がひとまとめになっています。

{
  "catalogs": {},
  "aliases": {
    "hello": {
      "script-ref": "hello.java",
      "description": "Script that says hello back for each argument"
    },
    ....
    ....
    ....
  }
}

こちらのカタログを追加してみましょう。

$ jbang catalog add https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json
[jbang] Catalog 'jbang-catalog' added to '/Users/kohei.sasaki/.jbang/jbang-catalog.json'

標準出力されたjbang-catalog.jsonファイルをみてみましょう。

{
  "catalogs": {
    "jbang-catalog": {
      "catalog-ref": "https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json"
    },
    ...
    ...
    ...
}

追加されていました。カタログのリストをみてみます。JBangではデフォルトで様々なカタログが標準で入っています。

$ jbang catalog list

alibaba
   https://github.com/alibaba/jbang-catalog/blob/HEAD/jbang-catalog.json
apache/camel
   Run Apache Camel routes easily
   https://github.com/apache/camel/blob/HEAD/jbang-catalog.json
...
...

jbangdev
   JBang's own catalog of small utilities
   https://github.com/jbangdev/jbang-catalog/blob/HEAD/jbang-catalog.json
...
...


`jbangdev`で追加されています。カタログの中に何が登録されているかを見てみます。


$ jbang catalog list jbangdev

...
hello@jbangdev
   Script that says hello back for each argument
   https://github.com/jbangdev/jbang-catalog/blob/HEAD/hello.java
...

先ほど登録したカタログには約20くらいのエイリアスが登録されています。helloを実行してみます。カタログ内のjbangを実行する際は下記のように@の後ろにカタログ名をつけます。

$ jbang hello@jbangdev jbang

Hello jbang

実行できました。これで自分で作ったJBangアプリが共有できるのは簡単ですね。

まとめ

JBangのアプリのインストール、エイリアスエイリアスをまとめたカタログをご紹介させていただきました。GistなどやGithubリポジトリ内にカタログファイルをおいておくと、簡単にツールなどを共有できますし、プロジェクト固有のツールなどの共有もスムーズにいくとおもいます。実際のプロジェクトですぐ使えるものばかりなので、ぜひ使っていただければと思います。

Javaをスクリプト言語っぽくするJBang その3(SpringBootでAPIサーバを作る,実用プロジェクトに変換する)

Javaスクリプト言語っぽくするJBangその3です。今回はSpringBootを起動してみようと思います。その1その2の内容は説明しませんので、下記からみてください。

k-sasaki.net

k-sasaki.net

想定シナリオ

ちょっとSpringBootとかで試したいことがあるときやAPIサーバの挙動とかを軽くテストしたいときに使う感じです。モノリスなどで大きい場合はちょっとした検証をするにもDBやRedisなどが必要になったり、ユニットテストを書かないといけなかったりちょっと面倒です。それをJBangが解決してくれます。

成果物

成果物は下記になります。一つずつ説明します。(追加説明を希望の場合はコメントにお願いします。)

Gistにもあげておきます。

https://gist.github.com/ko-sasaki/f38dfe9fc7146268520cf72f333565e5

///usr/bin/env jbang "$0" "$@" ; exit $?

package api;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

//JAVA 21+
//DEPS org.springframework.boot:spring-boot-starter-web:3.5.0
//FILES application.yml
//REPOS central,jitpack,google


@SpringBootApplication
@RestController
public class SpringBootSample {

    @Value("${sample.message}")
    private String message;

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

    /**
     * 固定メッセージを返却する.
     */
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }

    /**
     * URLパスで指定された文字列でメッセージを置換して返却する.
     */
    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name) {
        return "Hello, {name}!".replace("{name}", name);
    }
    
    /**
     * application.ymlのメッセージを表示する
     */
    @GetMapping("/hello/message")
    public String message() {
        return message;
    }
}

application.ymlの中身は下記

server:
  port: 8081

sample:
  message: Hello, Application Yaml!

ファイル作成

jbang init を使用して新規にファイルを作成します。

jbang init api/SpringBootSample.java

上記でファイルが作成されます。

パッケージ設定

SpringBootはルートパッケージですとDB設定などが動きだしてしまうので、テスト用のパッケージを指定します。

package api;

Javaバージョン

Java21以上で動作するように設定します。

//JAVA 21+

Javaがインストールされていない場合は下記のコマンドを入力してみてください。

// JBangを使用する場合
jbang jdk install 21

or 

// SDKMANを使用する場合
sdk install java 21.0.7-tem

依存関係

SpringBootをAPIサーバとして動かすのはこの1行だけで十分になります。

//DEPS org.springframework.boot:spring-boot-starter-web:3.5.0

なんかすごいですよね。この1行だけでSpringBootが起動できるなんて。

ファイル

//FILESで起動時に読み込むファイルを指定する。

//FILES application.yml

Javaコード

Javaコードの部分は、SpringBootのRestControllerのときに書くものを1ファイルにまとめたものです。いくつかのパターンで書いてあります。

@SpringBootApplication
@RestController
public class SpringBootSample {

    @Value("${sample.message}")
    private String message;

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

    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }

    @GetMapping("/hello/{name}")
    public String hello(@PathVariable String name) {
        return "Hello, {name}!".replace("{name}", name);
    }
    
    @GetMapping("/hello/message")
    public String message() {
        return message;
    }
}

実行

Webアプリケーションでも実行方法は変わりません。下記のコマンドで実行します。

$jbang api/SpringBootSample.java

[jbang] Building jar for SpringBootSample.java...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-14T10:11:49.229+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : Starting SpringBootSample using Java 21.0.7 with PID 60388 (/Users/kohei.sasaki/.jbang/cache/jars/SpringBootSample.java.be468baacb1ab8033bb744d7e1c4d7073e1736db3075b9bb1af7e20d0c22a440/SpringBootSample.jar started by kohei.sasaki in /Users/kohei.sasaki/git/jbang-sample)
2025-06-14T10:11:49.230+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : No active profile set, falling back to 1 default profile: "default"
2025-06-14T10:11:49.664+09:00  INFO 60388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8081 (http)
2025-06-14T10:11:49.674+09:00  INFO 60388 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-14T10:11:49.674+09:00  INFO 60388 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-14T10:11:49.706+09:00  INFO 60388 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-14T10:11:49.706+09:00  INFO 60388 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 449 ms
2025-06-14T10:11:49.911+09:00  INFO 60388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8081 (http) with context path '/'
2025-06-14T10:11:49.917+09:00  INFO 60388 --- [           main] api.SpringBootSample                     : Started SpringBootSample in 0.938 seconds (process running for 1.114)
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-06-14T10:12:09.050+09:00  INFO 60388 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms

これで起動完了です。

curlコマンドでテストしてみます。

$ curl localhost:8081/hello
Hello, World!

$ curl localhost:8081/hello/hogehoge
Hello, hogehoge!

$ curl localhost:8081/hello/message
Hello, Application Yaml!

ちゃんと期待したものが応答されました。

Gistを経由してのコード共有

上記のコードをGistにあげておきます。jbangコマンドにGistのURLを指定すると自動でダウンロードして、依存解決をして実行してくれます。とても便利です。

$ jbang https://gist.github.com/ko-sasaki/f38dfe9fc7146268520cf72f333565e5

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.0)

2025-06-14T14:50:06.953+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : Starting SpringBootSample using Java 21.0.7 with PID 6158 (/Users/kohei.sasaki/.jbang/cache/jars/SpringBootSample.java.6ed9516f48ab6c188d73ce5f1a848c811e4640775b1e8eea6cc1305d8c9a6c69/SpringBootSample.jar started by kohei.sasaki in /Users/kohei.sasaki/git/jbang-sample)
2025-06-14T14:50:06.954+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : No active profile set, falling back to 1 default profile: "default"
2025-06-14T14:50:07.358+09:00  INFO 6158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8081 (http)
2025-06-14T14:50:07.368+09:00  INFO 6158 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-14T14:50:07.368+09:00  INFO 6158 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-14T14:50:07.395+09:00  INFO 6158 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-06-14T14:50:07.395+09:00  INFO 6158 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 415 ms
2025-06-14T14:50:07.574+09:00  INFO 6158 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8081 (http) with context path '/'
2025-06-14T14:50:07.579+09:00  INFO 6158 --- [           main] api.SpringBootSample                     : Started SpringBootSample in 0.846 seconds (process running for 1.019)

とても簡単ですね。

プロジェクトに変換する

ここまでプロジェクトができたら、jbangではなく通常のプロジェクトに変換したくなってきます。JBangではexportコマンドがあり、それを実現できます。exportのコマンドはExecutableJar形式、Gradle形式、Maven形式など色々な形式で出力できます。今回はGradle形式で出力してみます。

$ jbang export gradle --output export_dir api/SpringBootSample.java

export_dirディレクトリの中身をみてみます。

$ tree export_dir
export_dir/
├── build.gradle
└── src
    └── main
        ├── java
        │   └── api
        │       └── SpringBootSample.java
        └── resources
            └── application.yml

jbangで書いた構成がそのまま出力されています。build.gradleも生成されており、実際にビルドや実行などができます。

まとめ

JBangでSpringBootのWebアプリケーションサーバの起動がすぐできました。Gist経由でコードの共有や実行まで確認できたり、exportコマンドでスムーズにプロジェクトに昇格できるようになっています。プロダクトエンジニアのことがよくわかっている、かなり気が利いているツールになっています。エコシステムとしても既存のものを最大限フル活用しており、独自仕様のようなものがコンパクトにまとまっています。チーム内でも広めたいですし、Javaエンジニアのみならず、Java初学者の方がもっと使ってくれたらと思います。

次回はAI系またはデバッグについて書こうと思います。

Javaをスクリプト言語っぽくするJBang その2(JAVA,DEPS,SOURCES,FILES, etc...)

JBangのその2です。依存関係や複数ファイルでの実行などその1より少し大きいコードを書いていこうとおもいます。

インストールや基本的な設定はその1をみてください。 k-sasaki.net

成果物

本日のコードは下記のようになります。内容は、PlayWright for Javaのライブラリを使用して、引数で入力したURLのスクリーンショットを取得するコードになります。これもサクっとできちゃうので、JBangのいいところです。

1つ1つ解説していきます。

Sample.Java

///usr/bin/env jbang "$0" "$@" ; exit $?

//SOURCES ScreenShotService.java

//FILES application.yml

//REPOS central,jitpack,google

import java.util.Scanner;

public class Sample {
    public static void main(String... args) {
        System.out.println("starting playwright. please input url.");
        Scanner scanner = new Scanner(System.in); // 標準入力待ち状態にする
        String inputUrl = scanner.next();  // 標準入力を取り出す
        new ScreenShotService().takeScreenShot(inputUrl);
    }
}

今回は、説明のためにもう1ファイル追加します。

ScreenShotService.java

//DEPS com.microsoft.playwright:playwright:1.52.0

import java.nio.file.Paths;

import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.Response;

public class ScreenShotService {

    public void takeScreenShot(String url) {
        try (Playwright playwright = Playwright.create();) {
            Page page = playwright.chromium()
                    .launch(new com.microsoft.playwright.BrowserType.LaunchOptions().setHeadless(true))
                    .newPage();
            Response response = page.navigate(url);
            page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example_com_screenshot.png")));
            response.headers().forEach((key, value) -> {
                System.out.println(key + ": " + value);
            });
        }
    }
}

ディレクティブ

//JAVA//DEPSのようなものをディレクティブと呼びます。こちらはJBang特有のディレクティブでJBang Directiveとドキュメントにも書いてあります。この記法を使い、依存関係やJavaのバージョンなどを解決していきます。

JAVA

Javaのバージョン指定をするディレクティブになります。

下記のようにJava21固定なものとJava21以上のと範囲を宣言できます。

//JAVA21

or 

//JAVA21+

PATHから該当するJavaのバージョンが見つからなかったら、実行時エラーとなります。

その場合は、jbangでもJDKのインストールコマンドがあるので、実行してみてください。

jbang jdk install 21

SDKMANでもインストールは可能です。

sdk install java 21.0.7-tem

DEPS

依存関係を解決するディレクティブです。Gradleの依存解決の記法を採用しています。 Javaソースコード内に必要な依存関係を書くだけで実行時にライブラリを取得してきてくれます。

//DEPS com.microsoft.playwright:playwright:1.52.0

REPOS

Maven Repositoryを指定するディレクティブです。central, jitpack, google などが指定でき、個別で立てているMavenRepositoryは、key=valueの形で記述します。

//REPOS central,jitpack,google,MYREPO=https://www.example.com/

SOURCES

実行時に指定したJavaファイル以外のJavaファイルを指定するディレクティブです。いくつ書いてもOKです。今回の例ではPlayWrightの処理は、別ファイルに書いてますので、実行時に指定するJavaファイルに//SOURCESでファイルを指定しないとコンパイルエラーになります。

//SOURCES ScreenShotService.java

FILES

//SOURCESJavaファイルでしたが、それ以外のファイルはFILESディレクティブで指定します。指定方法は//SOUECESのときと同じで引数にファイル名を書く感じです。いくつ書いてもOKです。

//FILES application.yml

実行

$ jbang Sample.java

[jbang] Resolving dependencies...
[jbang]    com.microsoft.playwright:playwright:1.52.0
[jbang] Dependencies resolved
[jbang] Building jar for Sample.java...

starting playwright. please input url.   // URLの入力の促し
https://www.example.com/

// ヘッダ情報の出力
accept-ranges: bytes  
alt-svc: h3=":443"; ma=93600,h3-29=":443"; ma=93600,quic=":443"; ma=93600; v="43"
cache-control: max-age=2328
content-encoding: gzip
content-length: 648
content-type: text/html
date: Fri, 13 Jun 2025 08:43:17 GMT
etag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134"
last-modified: Mon, 13 Jan 2025 20:11:20 GMT
vary: Accept-Encoding

スクリーンショットもとれています。

example_com_screenshot.png

まとめ

今回は、JBang Directiveの一部を紹介しました。このソースコードだけでPlayWrightのサンプルを作ることができます。GradleやMavenの設定とかは書く必要はなくて、コード上に依存関係を書くだけなので、モックやサンプル実行などに最適なんじゃないかと思います。次回は、実際のアプリケーションを書いてみます。SpringBootやAIのサンプル実装を書いてみます。

Javaをスクリプト言語っぽくするJBang その1(install, edit, run, build)

JBangはJavaでプロトタイピングやMVPやバグ検証などにサクッと試せるツールだということは知っていたのですが、JJUG CCC 2025のセッションでJBangの有効活用法の紹介があり、これは試してみないとということで、数回にわけてご紹介させていただきます。

感銘を受けたJJUGのセッション資料

https://github.com/tadayosi/jjug2025-jbang

インストール

公式のインストール手順はこちら

SDKMANでのインストール

ここでは、SDKMANでインストールしてみます。

sdk install jbang

JBangで処理開始

jbangを始めるにあたってjbang initを使うのが簡単です。

jbang init Sample.java

下記のファイルが生成されます。

///usr/bin/env jbang "$0" "$@" ; exit $?

import static java.lang.System.*;

public class Sample {
    public static void main(String... args) {
        out.println("Hello World");
    }
}

先頭行がハックらしくて、//Javaのインラインコメントで、そのあとにshebangが書いてありShellからでも実行ができるようになっています。

編集

jbangコマンド経由ですぐに編集ができます。

$ jbang edit Sample.java

[jbang] Assuming your editor have JBang support installed. See https://jbang.dev/ide
[jbang] If you prefer to open in a sandbox run with `jbang edit -b` instead.
[jbang] You requested to open default editor but no default editor configured.

jbang can download and configure a visual studio code (VSCodium) with Java support to use
See https://vscodium.com for details

Do you want to

(1) Download and run VSCodium
(2) Use 'code'
(3) Use 'idea'
(0) Cancel

使いたいIDE or エディタを選ぶのみになります。各環境で快適にコードを書くにはプラグインを入れる必要があるのでインストールをしてください。プラグイン一覧

実行

runコマンド

実行コマンドです。runがなくても第二引数にJavaファイルがあると動作します。Shellスクリプトとしても実行可能です。(Windowsの場合はWSL環境で実行できます)

$ jbang run Sample.java 
or
$ jbang Sample.java 
or
$ ./Sample.java
or
$ jbang https://gist.github.com/ko-sasaki/a655db96992709cd597908ae702aaad0  // Gistに上がっているものもローカルに落とさずに実行が可能です

Hello World

buildコマンド

buildコマンドもあり、すぐにexecutable jarにしてくれる。

$ jbang build --build-dir=jbang_build Sample.java
$ ls jbang_build
Sample.jar
$ java -jar jbang_build/Sample.jar
Hello World

GraalVMがインストールされていればネイティブコマンドにもビルドできます。

$ jbang build --build-dir=jbang_native_build --native Sample.java
... 時間がかかります ...

$ ls jbang_native_build/
Sample.bin      Sample.jar

$ ./jbang_native_build/Sample.bin 
Hello World

.jarファイルと.binファイルが生成されています。.binファイルの方は実行可能です。

まとめ

今回はJBangのインストールと実行とビルドについてでした。GistにアップしているソースもURL指定で実行できたり、Nativeビルドのコマンドが標準でついているあたり便利だと思います。Javaは実行するまでの設定が重い印象ですが随分ショートカットができていると思います。 次回は、依存関係や複数ファイルでの設定方法をまとめたいとおもいます。