ballerina langを触る hello world編
はじめに
同僚から聞いたballerinaという言語を触ってみる。インドのほうで流行っているらしい。
ソースコードをシーケンス図やフローチャートとしてグラフィカルに表示と編集できるのは面白そうなので触ってみることにした。
言語の特徴
- 型システムを採用している
- Visual Studio Codeの拡張機能によって、テキストコードと同期したシーケンス図やフローダイアグラムを生成・編集できる
- ソースコードから直接DockerやKubernetesのアーティファクトを生成できる
などといった特徴があります。
インストール方法
こちらを参照してください。
Ballerina downloads - The Ballerina programming language
私はhome brewを使っているので以下コマンドでインストール
$ brew install bal本記事は以下のバージョンを使ったものです。
$ bal version
Ballerina 2201.13.1 (Swan Lake Update 13)
Language specification 2024R1
Update Tool 1.5.1プロジェクト作成
先ずはGet startedの内容をやってみよう
https://ballerina.io/learn/get-started/
以下コマンドでプロジェクトを作成できるそうだ。
$ bal new hoge試しに、hello-worldをするためだけのプジェクトを作成するとこんな感じ。
はじめから.devcontainer.jsonが入っているのが今風だ。
$ bal new etude_bal_hello_world
Created new package 'etude_bal_hello_world' at etude_bal_hello_world.
$ cd etude_bal_hello_world/
$ ls -al
total 32
drwxr-xr-x@ 6 wagomu staff 192 Jan 3 10:38 ./
drwxr-xr-x@ 13 wagomu staff 416 Jan 3 10:38 ../
-rw-r--r--@ 1 wagomu staff 148 Jan 3 10:38 .devcontainer.json
-rw-r--r--@ 1 wagomu staff 483 Jan 3 10:38 .gitignore
-rw-r--r--@ 1 wagomu staff 147 Jan 3 10:38 Ballerina.toml
-rw-r--r--@ 1 wagomu staff 82 Jan 3 10:38 main.bal各種ファイルはこのような感じだ。
ドキュメントを見るとBallerina.tomlはパッケージのルートを識別するためにも使用されているようだ。
$ cat Ballerina.toml
[package]
org = "wagomu"
name = "etude_bal_hello_world"
version = "0.1.0"
distribution = "2201.13.1"
[build-options]
observabilityIncluded = true$ cat main.bal
import ballerina/io;
public function main() {
io:println("Hello, World!");
}$ cat .devcontainer.json
{
"image": "ballerina/ballerina-devcontainer:2201.10.3",
"customizations": {
"vscode": {
"extensions": ["WSO2.ballerina"]
}
}
}$ cat .gitignore
# Ballerina generates this directory during the compilation of a package.
# It contains compiler-generated artifacts and the final executable if this is an application package.
target/
# Ballerina maintains the compiler-generated source code here.
# Remove this if you want to commit generated sources.
generated/
# Contains configuration values used during development time.
# See https://ballerina.io/learn/provide-values-to-configurable-variables/ for more details.
Config.toml注意
プロジェクト名を-で作成したところ、_区切りにするほうが良さそうだった
$ bal new etude-bal-hello-world
Package name is derived as 'etude_bal_hello_world'. Edit the Ballerina.toml to change it.
Created new package 'etude_bal_hello_world' at etude-bal-hello-world.hello worldを実行してみる
bal runで実行できる。
$ bal run
Compiling source
wagomu/etude_bal_hello_world:0.1.0
ballerina/io:1.6.3 [central.ballerina.io ->/Users/wagomu/.ballerina/repositories/central.ballerina.io/bala/ballerina/io/1.6.3] 100% [======================================================================] 112/112 KB (0:00:00 / 0:00:00)
ballerina/io:1.6.3 pulled from central successfully
Running executable
Hello, World!実行後のファイル構成は以下のようになった。
新たにDependencies.tomlとtarget/が作成された。
$ ls -al
total 40
drwxr-xr-x@ 8 wagomu staff 256 Jan 3 10:55 ./
drwxr-xr-x@ 13 wagomu staff 416 Jan 3 10:38 ../
-rw-r--r--@ 1 wagomu staff 148 Jan 3 10:38 .devcontainer.json
-rw-r--r--@ 1 wagomu staff 483 Jan 3 10:38 .gitignore
-rw-r--r--@ 1 wagomu staff 147 Jan 3 10:38 Ballerina.toml
-rw-r--r--@ 1 wagomu staff 1357 Jan 3 10:56 Dependencies.toml
-rw-r--r--@ 1 wagomu staff 82 Jan 3 10:55 main.bal
drwxr-xr-x@ 6 wagomu staff 192 Jan 3 10:56 target/内容はこのような感じ。
依存関係を表すtomlファイルとjarファイルだ。
java21を使っているようだ。
$ cat Dependencies.toml | head -20
# AUTO-GENERATED FILE. DO NOT MODIFY.
# This file is auto-generated by Ballerina for managing dependency versions.
# It should not be modified by hand.
[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.13.1"
[[package]]
org = "ballerina"
name = "io"
version = "1.8.0"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "lang.value"}
]
modules = [
{org = "ballerina", packageName = "io", moduleName = "io"}
]$ ls -alR target/
total 8
drwxr-xr-x@ 7 wagomu staff 224 Jan 3 12:19 ./
drwxr-xr-x@ 8 wagomu staff 256 Jan 3 12:19 ../
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 backup/
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:20 bin/
-rw-r--r--@ 1 wagomu staff 1869 Jan 3 12:19 build
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 cache/
drwxr-xr-x@ 2 wagomu staff 64 Jan 3 12:19 resources/
target/backup:
total 0
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 7 wagomu staff 224 Jan 3 12:19 ../
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 bin/
target/backup/bin:
total 21344
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ../
-rw-r--r--@ 1 wagomu staff 10925925 Jan 3 12:19 etude_bal_hello_world.jar
target/bin:
total 21344
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:20 ./
drwxr-xr-x@ 7 wagomu staff 224 Jan 3 12:19 ../
-rw-r--r--@ 1 wagomu staff 10925925 Jan 3 12:20 etude_bal_hello_world.jar
target/cache:
total 0
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 7 wagomu staff 224 Jan 3 12:19 ../
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 wagomu/
target/cache/wagomu:
total 0
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ../
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 etude_bal_hello_world/
target/cache/wagomu/etude_bal_hello_world:
total 0
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ../
drwxr-xr-x@ 4 wagomu staff 128 Jan 3 12:19 0.1.0/
target/cache/wagomu/etude_bal_hello_world/0.1.0:
total 0
drwxr-xr-x@ 4 wagomu staff 128 Jan 3 12:19 ./
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ../
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 bir/
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 java21/
target/cache/wagomu/etude_bal_hello_world/0.1.0/bir:
total 8
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 4 wagomu staff 128 Jan 3 12:19 ../
-rw-r--r--@ 1 wagomu staff 3333 Jan 3 12:19 etude_bal_hello_world.bir
target/cache/wagomu/etude_bal_hello_world/0.1.0/java21:
total 56
drwxr-xr-x@ 3 wagomu staff 96 Jan 3 12:19 ./
drwxr-xr-x@ 4 wagomu staff 128 Jan 3 12:19 ../
-rw-r--r--@ 1 wagomu staff 25124 Jan 3 12:19 wagomu-etude_bal_hello_world-0.1.0.jar
target/resources:
total 0
drwxr-xr-x@ 2 wagomu staff 64 Jan 3 12:19 ./
drwxr-xr-x@ 7 wagomu staff 224 Jan 3 12:19 ../jarファイルの生成のみをしたい場合には、bal buildというコマンドがある。
$ bal build
Compiling source
wagomu/etude_bal_hello_world:0.1.0
Generating executable
target/bin/etude_bal_hello_world.jar生成されたjarファイルを実行するためにもbal runコマンドを利用するようだ。
$ bal run target/bin/etude_bal_hello_world.jar
Hello, World!実験1
試しにhoge.balとmain.balを作成してbal runを実行したところ、以下エラーとなった。
main.balのmain関数を実行していると思ったが、ファイル名は関係なくmain関数を探しているっぽい。
$ cat hoge.bal
import ballerina/io; ⏎
public function main() {
io:println("hoge");
}
$ cat main.bal
import ballerina/io;
public function main() {
io:println("Hello, World!");
}
> bal run
Compiling source
wagomu/etude_bal_hello_world:0.1.0
ERROR [main.bal:(3:17,3:21)] redeclared symbol 'main'
error: compilation contains errors実験2
実験1で結果は分かっているが念の為の実験。main.balを削除して、hoge.balのみの状態でbal runをしたところ、実行できた。やはりファイル名は関係なくmain関数を探して実行しているようだ。
$ ls -al
total 32
drwxr-xr-x@ 6 wagomu staff 192 Jan 3 10:53 ./
drwxr-xr-x@ 13 wagomu staff 416 Jan 3 10:38 ../
-rw-r--r--@ 1 wagomu staff 148 Jan 3 10:38 .devcontainer.json
-rw-r--r--@ 1 wagomu staff 483 Jan 3 10:38 .gitignore
-rw-r--r--@ 1 wagomu staff 147 Jan 3 10:38 Ballerina.toml
-rw-r--r--@ 1 wagomu staff 73 Jan 3 10:51 hoge.bal
$ cat hoge.bal
import ballerina/io;
public function main() {
io:println("hoge");
}
$ bal run
Compiling source
wagomu/etude_bal_hello_world:0.1.0
Running executable
hogeREST APIを作る
main.balを以下のように書き換えて実行し、別のターミナルからcurlで呼び出したところ以下のようになった。
service / on new http:Listener(8080) { ... }と、
Country[] countries = check countriesClient->/countries;という書き方が独特だなぁ。
$ cat main.bal
import ballerina/http;
public type Country record {
string name;
string continent;
int population;
decimal gdp;
decimal area;
};
final http:Client countriesClient = check new ("https://dev-tools.wso2.com/gs/helpers/v1.0/");
service / on new http:Listener(8080) {
resource function get countries() returns Country[]|error {
// Sending a GET request to the "/countries" endpoint and retrieving an array of `Country` records.
Country[] countries = check countriesClient->/countries;
return countries;
}
}
$ bal run
Compiling source
wagomu/etude_bal_hello_world:0.1.0
Running executable$ curl localhost:8080/countries
[{"name":"United States", "continent":"North America", "population":331002651, "gdp":2.736E+13, "area":9833517.0, "capital":"Washington, D.C.", "languages":"English", "currency":"USD"}, {"name":"Canada", "continent":"North America", "population":37742154, "gdp":2.14E+12, "area":9984670.0, "capital":"Ottawa", "languages":"English, French", "currency":"CAD"}, {"name":"Brazil", "continent":"South America", "population":212559417, "gdp":1.9444E+12, "area":8515767.0, "capital":"Brasília", "languages":"Portuguese", "currency":"BRL"}, {"name":"United Kingdom", "continent":"Europe", "population":67886011, "gdp":2.56E+12, "area":243610.0, "capital":"London", "languages":"English", "currency":"GBP"}, {"name":"Germany", "continent":"Europe", "population":83783942, "gdp":4.5E+12, "area":357022.0, "capital":"Berlin", "languages":"German", "currency":"EUR"}, {"name":"France", "continent":"Europe", "population":65273511, "gdp":2.93E+12, "area":551695.0, "capital":"Paris", "languages":"French", "currency":"EUR"}, {"name":"India", "continent":"Asia", "population":1380004385, "gdp":3.2E+12, "area":3287263.0, "capital":"New Delhi", "languages":"Hindi, English", "currency":"INR"}, {"name":"China", "continent":"Asia", "population":1439323776, "gdp":1.773E+13, "area":9596961.0, "capital":"Beijing", "languages":"Mandarin", "currency":"CNY"}, {"name":"Japan", "continent":"Asia", "population":126476461, "gdp":4.2E+12, "area":377975.0, "capital":"Tokyo", "languages":"Japanese", "currency":"JPY"}, {"name":"Australia", "continent":"Oceania", "population":25499884, "gdp":1.78E+12, "area":7692024.0, "capital":"Canberra", "languages":"English", "currency":"AUD"}, {"name":"South Africa", "continent":"Africa", "population":59308690, "gdp":1.275E+12, "area":1219090.0, "capital":"Pretoria", "languages":"11 official languages", "currency":"ZAR"}, {"name":"Russia", "continent":"Europe/Asia", "population":145934462, "gdp":2.196E+12, "area":17098242, "capital":"Moscow", "languages":"Russian", "currency":"RUB"}, {"name":"Mexico", "continent":"North America", "population":128932753, "gdp":1.79E+12, "area":1964375.0, "capital":"Mexico City", "languages":"Spanish", "currency":"MXN"}, {"name":"Italy", "continent":"Europe", "population":60461826, "gdp":2.0E+12, "area":301340.0, "capital":"Rome", "languages":"Italian", "currency":"EUR"}, {"name":"Argentina", "continent":"South America", "population":45195774, "gdp":4.07E+11, "area":2780400.0, "capital":"Buenos Aires", "languages":"Spanish", "currency":"ARS"}, {"name":"Spain", "continent":"Europe", "population":46754778, "gdp":1.4E+12, "area":505990.0, "capital":"Madrid", "languages":"Spanish", "currency":"EUR"}, {"name":"Indonesia", "continent":"Asia", "population":273523615, "gdp":1.119E+12, "area":1904569.0, "capital":"Jakarta", "languages":"Indonesian", "currency":"IDR"}, {"name":"Saudi Arabia", "continent":"Asia", "population":34813871, "gdp":7.93E+11, "area":2149690.0, "capital":"Riyadh", "languages":"Arabic", "currency":"SAR"}, {"name":"South Korea", "continent":"Asia", "population":51269185, "gdp":1.647E+12, "area":100210.0, "capital":"Seoul", "languages":"Korean", "currency":"KRW"}, {"name":"Turkey", "continent":"Europe/Asia", "population":84339067, "gdp":7.2E+11, "area":783562.0, "capital":"Ankara", "languages":"Turkish", "currency":"TRY"}, {"name":"Egypt", "continent":"Africa", "population":102334404, "gdp":3.63E+11, "area":1002450.0, "capital":"Cairo", "languages":"Arabic", "currency":"EGP"}, {"name":"Thailand", "continent":"Asia", "population":69799978, "gdp":5.43E+11, "area":513120.0, "capital":"Bangkok", "languages":"Thai", "currency":"THB"}, {"name":"Pakistan", "continent":"Asia", "population":220892340, "gdp":2.78E+11, "area":881913.0, "capital":"Islamabad", "languages":"Urdu, English", "currency":"PKR"}, {"name":"Nigeria", "continent":"Africa", "population":206139589, "gdp":4.32E+11, "area":923768.0, "capital":"Abuja", "languages":"English", "currency":"NGN"}, {"name":"Vietnam", "continent":"Asia", "population":97338579, "gdp":3.4E+11, "area":331212.0, "capital":"Hanoi", "languages":"Vietnamese", "currency":"VND"}, {"name":"Philippines", "continent":"Asia", "population":109581078, "gdp":4.02E+11, "area":300000.0, "capital":"Manila", "languages":"Filipino, English", "currency":"PHP"}, {"name":"Colombia", "continent":"South America", "population":50882891, "gdp":3.24E+11, "area":1141748.0, "capital":"Bogotá", "languages":"Spanish", "currency":"COP"}, {"name":"Sri Lanka", "continent":"Asia", "population":21803000, "gdp":8.41E+10, "area":65610.0, "capital":"Sri Jayawardenepura Kotte", "languages":"Sinhala, Tamil", "currency":"LKR"}]さらに改善して、レスポンスの内容を変更させるとこのような感じだ。
C#で見慣れたLINQのような構文が採用されている。
個人的に面白いなと感じたのが、LINQのような構文の途中で変数宣言を行ない、その変数をもとにorder byをしている点だ。
let decimal gdpPerCapita = (gdp / population).round(2) // Calculating and rounding GDP per capita to 2 decimal places.
order by gdpPerCapita descending // Sorting the results by GDP per capita in descending order.$ cat main.bal
import ballerina/http;
public type Country record {
string name;
string continent;
int population;
decimal gdp;
decimal area;
};
public type CountryResponse record {
string name;
string continent;
decimal gdpPerCapita;
};
final http:Client countriesClient = check new ("https://dev-tools.wso2.com/gs/helpers/v1.0/");
service / on new http:Listener(8080) {
resource function get countries() returns CountryResponse[]|http:InternalServerError {
do {
// Sending a GET request to the "/countries" endpoint and retrieving an array of `Country` records.
Country[] countries = check countriesClient->/countries;
// Using a query expression to process the list of countries and generate a summary.
CountryResponse[] topCountries =
from var {name, continent, population, area, gdp} in countries
where population >= 100000000 && area >= 1000000d // Filtering countries with a population >= 100M and area >= 1M sq km.
let decimal gdpPerCapita = (gdp / population).round(2) // Calculating and rounding GDP per capita to 2 decimal places.
order by gdpPerCapita descending // Sorting the results by GDP per capita in descending order.
limit 10 // Limiting the results to the top 10 countries.
select {name, continent, gdpPerCapita}; // Selecting the country name, continent, and GDP per capita.
return topCountries;
} on fail var err {
return <http:InternalServerError>{
body: {
"error": "Failed to retrieve countries",
"message": err.message()
}
};
}
}
}
$ bal run
Compiling source
wagomu/etude_bal_hello_world:0.1.0
Running executable$ curl localhost:8080/countries
[{"name":"United States", "continent":"North America", "gdpPerCapita":82657.95}, {"name":"Russia", "continent":"Europe/Asia", "gdpPerCapita":15047.85}, {"name":"Mexico", "continent":"North America", "gdpPerCapita":13883.21}, {"name":"China", "continent":"Asia", "gdpPerCapita":12318.28}, {"name":"Brazil", "continent":"South America", "gdpPerCapita":9147.56}, {"name":"Indonesia", "continent":"Asia", "gdpPerCapita":4091.05}, {"name":"Egypt", "continent":"Africa", "gdpPerCapita":3547.19}, {"name":"India", "continent":"Asia", "gdpPerCapita":2318.83}]
# 存在しないリクエストを実行した場合
$ curl localhost:8080/hoge
{"timestamp":"2026-01-03T02:26:14.159298Z", "status":404, "reason":"Not Found", "message":"no matching resource found for path : /hoge , method : GET", "path":"/hoge", "method":"GET"}⏎ おわりに
ソースコードのビジュアライズは次回に回そうと思います。