HelloWorldで理解するCNCFの実行モデル
SimpleModelingはコンポーネント指向をベースとした開発方法論です。コンポーネント指向を成立させるためには、概念的なモデルの定義に加えてコンポーネント (Component)の実行系が必要です。この目的でクラウド・プラットフォーム上で動作するクラウド・アプリケーション用のコンポーネント・フレームワークとして開発しているのがCloud Native Component Frameworkです。
本記事ではHelloWorldを通して、Cloud Native Component Frameworkの実行モデルについて見ていきます。
HelloWorld
Cloud Native Component Frameworkを理解する最短ルートは、まず実際に動かしてみることです。
CNCFでは、command・server・client・script といった実行形態の違いは、異なるアプリケーションを意味するものではありません。 いずれも同一の Component / Service / Operation を、異なる呼び出しプロトコルで実行しているだけなのです。
今回のHelloWorldデモでは、このCNCFの基本思想を確認するために、以下の点を順に見ていきます。
-
HelloWorldコンポーネントを定義
-
HelloWorldコンポーネントのビルドと配備
-
同一の機能が command / server / client から呼び出される様子
スクリプト
📄 Cloud Native Component Framework:HelloWorldではCNCF上でプログラムを動作させるために、以下のスクリプトを用意しました。
#!/usr/bin/env -S scala-cli shebang
//> using repository "https://www.simplemodeling.org/maven"
//> using dep "org.goldenport:goldenport-cncf_3:0.3.2"
import org.goldenport.cncf.dsl.script.*
@main def main(args: String*): Unit = run(args) { call =>
"hello world"
}
このスクリプトは、スクリプト用途向けに用意されたDSL (Domain Specific Language)を使って記述されています。 一見すると単に文字列を返している手続きを記述しているだけのように見えますが、内部では以下の処理が行われています。
つまり、このスクリプトは簡易記法で書かれているだけで、実際には 正式なコンポーネント生成と実行 が行われています。
run ブロックに渡される call は org.goldenport.cncf.action.ActionCall 型のオブジェクトです。 この ActionCall を通じて、以下の機能を利用できます。
-
引数の取得
-
レスポンスの生成
-
CNCFの実行コンテキストへのアクセス
ごく簡単な処理であればスクリプト機能で十分ですが、構造を明示したい場合や、複数のサービス・オペレーションを持つ場合には、本記事のテーマである正式なコンポーネント定義を行います。
コンポーネント
このソースファイルと次に示すbuild.sbtを使って、コンポーネントのビルド環境を構築することができます。
package example
import org.goldenport.Consequence
import org.goldenport.protocol.*
import org.goldenport.protocol.operation.*
import org.goldenport.protocol.spec.*
import org.goldenport.cncf.component.*
import org.goldenport.cncf.action.*
class HelloWorldComponent extends Component {
}
object HelloWorldComponent {
val name = "helloworld"
val componentId = ComponentId(name)
class Factory extends Component.Factory {
protected def create_Components(params: ComponentCreate): Vector[Component] =
Vector(HelloWorldComponent())
protected def create_Core(
params: ComponentCreate,
comp: Component
): Component.Core = spec_create(
name,
componentId,
GreetingService
)
}
}
object GreetingService extends ServiceDefinition {
val specification = ServiceDefinition.Specification.Builder("greeting").
operation(
HelloOperation
).build()
object HelloOperation extends OperationDefinition {
val specification = OperationDefinition.Specification.Builder("hello").
build()
override def createOperationRequest(
req: Request
): Consequence[HelloQuery] =
Consequence.success(HelloQuery(req))
}
}
final case class HelloQuery(
request: Request
) extends Query() {
override def createCall(core: ActionCall.Core): ActionCall =
HelloActionCall(core, this)
}
final case class HelloActionCall(
core: ActionCall.Core,
query: HelloQuery,
) extends ActionCall {
override def execute(): Consequence[OperationResponse] = {
val greeting = args(0)
response_string(s"Hello $greeting")
}
}
build.sbt
Scalaプロジェクトのbuild.sbtは以下の通りです。
直接依存するライブラリはCNCFのものが1つだけです。
ThisBuild / scalaVersion := "3.3.7"
ThisBuild / organization := "org.example"
ThisBuild / version := "0.1.0"
lazy val root = (project in file("."))
.settings(
name := "cncf-helloworld-sbt",
libraryDependencies ++= Seq(
// CNCF core runtime
"org.goldenport" %% "goldenport-cncf" % "0.3.4"
)
)
ビルド
Scalaの通常のプロジェクト構成を作成します。 HelloWorldComponent.scalaとbuild.sbtを以下のように配置します。
.
├── build.sbt
└── src
└── main
└── scala
└── example
└── HelloWorldComponent.scala
sbtのpackageコマンドでビルド完了です。
$ sbt package
コンポーネントの物理ファイルとなるJARファイル target/scala-3.3.7/cncf-helloworld-sbt_3-0.1.0.jar が作成されます。
説明
先ほどはコンポーネントを実現するソースコード全体を表示しました。 ここから、その中身について説明していきます。
コンポーネントの初期化
ファクトリクラスは、JARファイルに同梱されているクラスの中から自動的に検出されます。
ここでは、HelloWorldComponentのファクトリをコンパニオン・オブジェクト内で定義しています。
object HelloWorldComponent {
val name = "helloworld"
val componentId = ComponentId(name)
object Factory extends Component.Factory {
protected def create_Components(params: ComponentCreate): Vector[Component] =
Vector(HelloWorldComponent())
protected def create_Core(
params: ComponentCreate,
comp: Component
): Component.Core = spec_create(
name,
componentId,
GreetingService
)
}
}
Factoryでは、以下の処理を行っています。
-
コンポーネントの生成
-
コンポーネントIDの割り当て
-
提供するサービス定義の登録(GreetingService)
ファクトリの動作により、helloworldというコンポーネントが生成されます。
サービス定義
helloworldコンポーネントの greeting サービスの定義を GreetingService として定義します。
GreetingServiceオブジェクトでは greeting サービスを定義し、その中で HelloOperation により hello オペレーションを定義しています。
object GreetingService extends ServiceDefinition {
val specification = ServiceDefinition.Specification.Builder("greeting").
operation(
HelloOperation
).build()
object HelloOperation extends OperationDefinition {
val specification = OperationDefinition.Specification.Builder("hello").
build()
override def createOperationRequest(
req: Request
): Consequence[HelloQuery] =
Consequence.success(HelloQuery(req))
}
}
HelloOperationでは createOperationRequest メソッドを提供しています。 createOperationRequestメソッドでは、Requestから HelloWorldComponent のアクションである HelloQuery を構築する処理を行います。
この定義により、以下の階層構造が確定します。
-
Component: helloworld
-
Service: greeting
-
Operation: hello
CNCFでは、Command名やHTTPパス、OpenAPIのoperationIdなどは、この構造から一貫して導出されます。
CommandとQuery
CNCFでは org.goldenport.cncf.action.Action が、コンポーネントに対する実行指示を表すオブジェクトです。 Actionには Command と Query の2種類があり、CQRSアーキテクチャにおける役割に応じた振る舞いをします。
ここでは HelloQuery という Query を定義しています。
final case class HelloQuery(
request: Request
) extends Query() {
override def createCall(core: ActionCall.Core): ActionCall =
HelloActionCall(core, this)
}
Operationが呼び出されると、RequestからQueryが生成されます。 Queryは、実際に処理を実行する ActionCall を生成する責務を持ちます。
ActionCallは処理の実行を行うオブジェクトで、生成時に実行コンテキストなど処理に必要な各種オブジェクトがバインドされます。
処理の実行
helloworldコンポーネントの greeting サービスの hello オペレーションの処理を行う ActionCall として、HelloActionCall を定義しました。
final case class HelloActionCall(
core: ActionCall.Core,
query: HelloQuery,
) extends ActionCall {
override def execute(): Consequence[OperationResponse] = {
val greeting = args(0)
response_string(s"Hello $greeting")
}
}
ActionCallの execute メソッドが、実際の処理本体です。 ここでは引数を取得し、文字列レスポンスを返しています。
コンポーネントの実行環境
コンポーネントの実行環境として、今回は Docker と Coursier を使います。
Docker
Dockerを用いると、Scalaの開発環境を持たない環境でもCNCFを実行することができます。 通常の運用環境ではDockerを使用することを想定しています。
Dockerを用いてCNCFのDockerイメージを起動します。
$ docker run --rm \
-v ./component.d:/app/component.d \
goldenport-cncf:0.3.4 \
{CNCF Parameters}
Crousier
CrousierはScalaエコシステムのパッケージマネージャです。 launchコマンドを使ってScalaプログラムを起動することができます。 今回はこの launch コマンドを使用します。
$ export COURSIER_REPOSITORIES="ivy2local|central|https://www.simplemodeling.org/maven"
CrousierでCNCFを実行する場合は、csコマンドを使って以下のようになります。
$ cs launch org.goldenport:goldenport-cncf_3:0.3.4 -- \
{CNCF Parameters}
コマンド実行
commandコマンドを指定すると、CNCFはコマンドとして起動して指定されたオペレーションを実行します。
サーバー実行
CNCFをサーバーとして実行します。
Docker
$ docker run --rm \
-p 8080:8080 \
goldenport-cncf:0.3.4 \
server
event=info scope=Subsystem name=cncf started [traceId=test-runtime-trace-2026012303353640+0000-4Lvl93R0LMlX7niKgXfbup correlationId=test-runtime-correlation-2026012303353640+0000-6RzUJdp4Xsz1giTcu39f57]
[io-compute-4] INFO org.http4s.ember.server.EmberServerBuilderCompanionPlatform - Ember-Server service bound to address: [::]:8080
Crousier
crousierを使う場合も同様の動きになります。
csを使った場合も server コマンドを指定すると、CNCFはサーバーとして立ち上がります。
$ cs launch org.goldenport:goldenport-cncf_3:0.3.4 -- server
event=info scope=Subsystem name=Bootstrap [component-dir] initialized component=helloworld class=example.HelloWorldComponent [traceId=Bootstrap-Subsystem-trace-2026012303361455+0000-Ax1FoLKpMqlMIl5pVoXzc]
event=info scope=Subsystem name=cncf started [traceId=test-runtime-trace-2026012303361456+0000-5SlbZLKLXywlYUOdSGoye0 correlationId=test-runtime-correlation-2026012303361456+0000-4f4UKRnWCGdB1ZbrGPNwG9]
[io-compute-3] INFO org.http4s.ember.server.EmberServerBuilderCompanionPlatform - Ember-Server service bound to address: [::]:8080
動作確認
いずれの場合も curl コマンドで REST の動きを確かめることができます。
$ curl http://localhost:8080/helloworld/greeting/hello?arg1=world
Hello world
OpenAPI
コンポーネント定義から、OpenAPI仕様も自動生成されます。
$ curl http://localhost:8080/openapi.json | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 5200 100 5200 0 0 551k 0 --:--:-- --:--:-- --:--:-- 564k
{
"openapi": "3.0.0",
"info": {
"title": "CNCF API",
"version": "0.1.0"
},
"paths": {
"/admin/component/list": {
"GET": {
"tags": [
"experimental:admin.component"
],
"summary": "admin.component.list",
"description": "Component: admin\nService: component\nOperation: list\n(experimental; subject to change in Phase 2.8)",
"operationId": "admin.component.list",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/admin/config/show": {
"GET": {
"tags": [
"experimental:admin.config"
],
"summary": "admin.config.show",
"description": "Component: admin\nService: config\nOperation: show\n(experimental; subject to change in Phase 2.8)",
"operationId": "admin.config.show",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/admin/extension/list": {
"GET": {
"tags": [
"experimental:admin.extension"
],
"summary": "admin.extension.list",
"description": "Component: admin\nService: extension\nOperation: list\n(experimental; subject to change in Phase 2.8)",
"operationId": "admin.extension.list",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/admin/system/ping": {
"GET": {
"tags": [
"experimental:admin.system"
],
"summary": "admin.system.ping",
"description": "Component: admin\nService: system\nOperation: ping\n(experimental; subject to change in Phase 2.8)",
"operationId": "admin.system.ping",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/admin/variation/list": {
"GET": {
"tags": [
"experimental:admin.variation"
],
"summary": "admin.variation.list",
"description": "Component: admin\nService: variation\nOperation: list\n(experimental; subject to change in Phase 2.8)",
"operationId": "admin.variation.list",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/client/http/get": {
"GET": {
"tags": [
"experimental:client.http"
],
"summary": "client.http.get",
"description": "Component: client\nService: http\nOperation: get\n(experimental; subject to change in Phase 2.8)",
"operationId": "client.http.get",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/client/http/post": {
"POST": {
"tags": [
"experimental:client.http"
],
"summary": "client.http.post",
"description": "Component: client\nService: http\nOperation: post\n(experimental; subject to change in Phase 2.8)",
"operationId": "client.http.post",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/debug/http/delete": {
"GET": {
"tags": [
"experimental:debug.http"
],
"summary": "debug.http.delete",
"description": "Component: debug\nService: http\nOperation: delete\n(experimental; subject to change in Phase 2.8)",
"operationId": "debug.http.delete",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/debug/http/echo": {
"GET": {
"tags": [
"experimental:debug.http"
],
"summary": "debug.http.echo",
"description": "Component: debug\nService: http\nOperation: echo\n(experimental; subject to change in Phase 2.8)",
"operationId": "debug.http.echo",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/debug/http/get": {
"GET": {
"tags": [
"experimental:debug.http"
],
"summary": "debug.http.get",
"description": "Component: debug\nService: http\nOperation: get\n(experimental; subject to change in Phase 2.8)",
"operationId": "debug.http.get",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/debug/http/post": {
"POST": {
"tags": [
"experimental:debug.http"
],
"summary": "debug.http.post",
"description": "Component: debug\nService: http\nOperation: post\n(experimental; subject to change in Phase 2.8)",
"operationId": "debug.http.post",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/debug/http/put": {
"PUT": {
"tags": [
"experimental:debug.http"
],
"summary": "debug.http.put",
"description": "Component: debug\nService: http\nOperation: put\n(experimental; subject to change in Phase 2.8)",
"operationId": "debug.http.put",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/helloworld/greeting/hello": {
"GET": {
"tags": [
"experimental:helloworld.greeting"
],
"summary": "helloworld.greeting.hello",
"description": "Component: helloworld\nService: greeting\nOperation: hello\n(experimental; subject to change in Phase 2.8)",
"operationId": "helloworld.greeting.hello",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/spec/export/openapi": {
"GET": {
"tags": [
"experimental:spec.export"
],
"summary": "spec.export.openapi",
"description": "Component: spec\nService: export\nOperation: openapi\n(experimental; subject to change in Phase 2.8)",
"operationId": "spec.export.openapi",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
}
}
}
ここには、HelloWorldのエンドポイントを含む、CNCFが提供するすべての操作が記述されています。
クライアント実行
clientコマンドを指定すると、CNCFはコマンドとして起動して指定されたオペレーションをサーバーに対して送信し、サーバー上での実行結果を受け取り出力します。
参照
用語集
- Cloud Native Component Framework (CNCF)
-
Cloud Native Component Framework(CNCF)は、クラウド・アプリケーションを構成するコンポーネントを、単一かつ一貫した実行モデルで実行するためのフレームワークです。 Component / Service / Operation という構造を中核とし、command、server(REST / OpenAPI)、client、script といった異なる実行形態から、同一の Operation を再利用できることを特徴とします。 ログ、エラー処理、設定、配備といったクラウド・アプリケーションに必要な品質属性をフレームワーク側に集約することで、コンポーネントはドメイン・ロジックの実装に集中できます。 CNCF は、文芸モデル駆動開発および AI 支援開発を前提に、「何を実行するか」と「どのように呼び出すか」を分離するための実行基盤として設計されています。
- コンポーネント (Component)
-
責務・契約・依存関係を明示的に定義し、再利用可能で交換可能な単位としてカプセル化されたソフトウェア構成要素。論理モデルでは抽象構造単位として、物理モデルでは実装・デプロイメント単位として扱われる。
- DSL (Domain Specific Language)
-
DSL(ドメイン固有言語)は、特定の領域(ドメイン)に特化して設計された言語であり、その分野の概念や構造を直接的かつ簡潔に表現することを目的とします。 一般的な汎用プログラミング言語(GPL)に比べ、DSLは特定ドメインの問題解決や自動生成に適した高い抽象度を持ちます。