HelloWorldで理解するCNCFの実行モデル

浅海 智晴 Created: 2026-01-26

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)を使って記述されています。 一見すると単に文字列を返している手続きを記述しているだけのように見えますが、内部では以下の処理が行われています。

  • スクリプト全体が 1つのコンポーネント として生成される

  • そのコンポーネント1サービス・1オペレーション を持つ

  • スクリプトの処理本体は、そのオペレーションの実装として登録される

  • CNCFランタイム上で通常のコンポーネントと同様に実行される

つまり、このスクリプトは簡易記法で書かれているだけで、実際には 正式なコンポーネント生成と実行 が行われています。

run ブロックに渡される call は org.goldenport.cncf.action.ActionCall 型のオブジェクトです。 この ActionCall を通じて、以下の機能を利用できます。

  • 引数の取得

  • レスポンスの生成

  • CNCFの実行コンテキストへのアクセス

ごく簡単な処理であればスクリプト機能で十分ですが、構造を明示したい場合や、複数のサービス・オペレーションを持つ場合には、本記事のテーマである正式なコンポーネント定義を行います。

コンポーネント

以下は、スクリプトと同等のHelloWorld機能を、正式な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 が作成されます。

配備

今回はDockerで配布しているCNCFに、作成したコンポーネントを配備して使用します。

CNCFは、起動したディレクトリ配下に component.d ディレクトリが存在すると、そこをコンポーネントのリポジトリとして認識し、コンポーネントの配備と起動を行います。

ここでは /tmp/cncf-demo ディレクトリをデモ用ディレクトリとします。

この場合、作成されたJARファイルをデモ用ディレクトリの component.d にコピーします。 これで配備は完了です。

$ cp target/scala-3.3.7/cncf-helloworld-sbt_3-0.1.0.jar /tmp/cncf-demo/component.d

説明

先ほどはコンポーネントを実現するソースコード全体を表示しました。 ここから、その中身について説明していきます。

コンポーネントの宣言

コンポーネントはクラス org.goldenport.cncf.Component を継承したクラスです。 これにより、Componentとしての責務が自動的に付加されます。

以下では HelloWorldComponent を定義しました。

class HelloWorldComponent extends Component {
}

コンポーネントの初期化

コンポーネントは org.goldenport.cncf.Component.Factory を継承したファクトリクラスによって生成、初期化されます。

ファクトリクラスは、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}

CNCF Dockerイメージでは /app/component.d に配備されている Component をコンテナ内に自動ローディングして起動します。 ここでは実行ディレクトリの ./component.d を /app/component.d にマウントします。

Crousier

CrousierはScalaエコシステムのパッケージマネージャです。 launchコマンドを使ってScalaプログラムを起動することができます。 今回はこの launch コマンドを使用します。

CrousierでCNCFを使う場合には、CNCFが登録されているリポジトリを設定する必要があります。 今回は環境変数で登録することにします。

$ 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はコマンドとして起動して指定されたオペレーションを実行します。

Docker

helloworldコンポーネントの greeting サービスの hello オペレーションを、引数 world で呼び出しています。 その結果、「hello world」の文字列が出力されます。

$ docker run --rm \
  -v ./component.d:/app/component.d \
  goldenport-cncf:0.3.4 \
  command helloworld.greeting.hello world
Hello world

Crousier

crousierを使う場合も同様の動きになります。

$ cs launch org.goldenport:goldenport-cncf_3:0.3.4 -- \
  command helloworld.greeting.hello world
Hello world

サーバー実行

CNCFをサーバーとして実行します。

Docker

DockerでCNCFのコンテナを起動する時に server コマンドを指定すると、CNCFはサーバーとして立ち上がります。

$ 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はコマンドとして起動して指定されたオペレーションをサーバーに対して送信し、サーバー上での実行結果を受け取り出力します。

Docker

以下ではhelloworldコンポーネントの greeting サービスの hello オペレーションを、引数 world で呼んでいます。 その結果、「hello world」の文字列が出力されます。

$ docker run --rm \
  -v ./component.d:/app/component.d \
  goldenport-cncf:0.3.4 \
  command helloworld.greeting.hello world
Hello world

Crousier

crousierを使う場合も同様の動きになります。

$ cs launch org.goldenport:goldenport-cncf_3:0.3.4 -- \
  client helloworld.greeting.hello world
Hello world

まとめ

HelloWorldという非常に小さな例であっても、CNCF上では常に

という構造を持ったコンポーネントとして実行されます。

スクリプト、コマンド、サーバー、クライアントといった実行形態の違いは、処理の本質的な違いではなく、同一の操作をどの入口から呼び出すかの違いにすぎません。

この構造により、OpenAPI生成、CLI実行、HTTP API提供といった機能が、一貫したモデルから提供されます。

HelloWorldはその最小例ですが、ここで確認した構造はそのまま実運用レベルのコンポーネント設計へと拡張されていきます。

参照

用語集

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は特定ドメインの問題解決や自動生成に適した高い抽象度を持ちます。