Textus Samples 02:Component Packaging
Textus Samples is a collection of small samples for learning Textus component development step by step.
This article focuses on the 02-component family from that collection.
In 01-minimal , we loaded the sample directory as the development project with --project-dev . and checked the minimal Operation.
This article covers the next development step: packaging a Component as a CAR and explicitly loading it from component.d/*.car .
Prerequisite
This article uses the environment prepared in 📄 Textus Samples: Launchers and Installation.
-
textus-tutorial-0.1.3has been extracted -
The
cozy,cncf, andtextuslaunchers have been installed -
The runtime version for each launcher has been checked
-
The minimal Operation in
01-minimalhas been checked
The download steps are covered in the prerequisite article, so they are not repeated here.
What You Learn
-
How to treat a Component in a source project as a packaged component artifact
-
What a CAR contains and what it hands to the runtime
-
That
component.d/*.caris the standard input form for packaged components -
That the Operation selector is unchanged whether the component is loaded from source or from a CAR
-
That
car.dand class discovery are auxiliary inspection forms, while the baseline iscomponent.d/*.car
Purpose / Why This Sample Matters
This sample shows how to hand a Component that works on the development classpath to the cncf launcher / CNCF engine as a distributable packaged Textus component.
The key point is to keep the Operation meaning unchanged while changing only the handoff boundary from source project to CAR.
Therefore, the subject of this article is not the contents of run.sh . The learning target is the relationship among Component source, packaged artifact, and runtime surface.
Concept Focus
-
Component packaging is the boundary for handing implementation to the runtime. It changes distribution shape, not operation meaning.
-
CAR directory loading and class discovery aim at the same result, but they validate different responsibilities.
Sample Content
The center of 02-component is a Component named testcomp . The Component has a main Service, and that Service has a hello Operation.
Therefore, the selector visible from the runtime has this shape.
testcomp.main.hello
This selector means: call the hello Operation in the main Service of the testcomp Component.
Component Source
The Component source defines the Component name in TestcompComponent and defines the Service / Operation surface in MainService .
package testcomp
import org.goldenport.Consequence
import org.goldenport.protocol.Request
import org.goldenport.protocol.operation.OperationResponse
import org.goldenport.protocol.spec.*
import org.goldenport.cncf.action.{ActionCall, QueryAction}
import org.goldenport.cncf.component.{Component, ComponentCreate, ComponentId}
import org.goldenport.schema.XString
final class TestcompComponent extends Component
object TestcompComponent extends Component.Factory {
val name = "testcomp"
val componentId = ComponentId(name)
protected def create_Component(params: ComponentCreate): Component =
TestcompComponent()
protected def create_Core(
params: ComponentCreate,
comp: Component
): Component.Core =
spec_create(
name,
componentId,
MainService
)
}
object MainService extends ServiceDefinition {
val specification = ServiceDefinition.Specification.Builder("main")
.operation(HelloOperation)
.build()
object HelloOperation extends OperationDefinition {
val specification = OperationDefinition.Specification.Builder("hello").copy(
response = ResponseDefinition(result = List(XString))
).build()
override def createOperationRequest(
req: Request
): Consequence[HelloQuery] =
Consequence.success(HelloQuery(req))
}
}
final case class HelloQuery(
request: Request
) extends QueryAction() {
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] =
response_string("Hello from testcomp")
}
The point to read from this code is not the complexity of the behavior. The Component name, Service name, and Operation name form the runtime surface, which becomes the selector testcomp.main.hello .
Packaging Model
Packaging builds a jar from the source project and puts that jar together with a descriptor into a CAR. A CAR is a Component artifact that can be handed to the Textus runtime.
src/main/scala/testcomp/TestcompComponent.scala
|
v
target/scala-3.3.7/textus-samples-02-component_3-*.jar
|
v
component.d/testcomp.car
|
v
Textus runtime surface: testcomp.main.hello
The important point in this flow is that the source structure corresponds to the surface visible from the runtime. Packaging does not change the invocation shape testcomp.main.hello .
CAR Shape
The CAR in 02-component has the following minimal shape.
testcomp.car
component-descriptor.yaml
component/
main.jar
component-descriptor.yaml tells the runtime the artifact name and Component name. component/main.jar is the actual Component implementation.
name: testcomp
version: 0.1.0
component: testcomp
With this descriptor, the runtime can treat component.d/testcomp.car not as a plain zip file, but as the testcomp Component artifact.
Loading Forms
The 02 family checks the same Component through multiple loading forms.
-
02-component: The baseline. It loads the packaged CAR fromcomponent.d/*.car. -
02.a-car-dir-lab: Inspects expandedcar.dto understand the archive contents and the loader/debug perspective. -
02.b-discover-classes-lab: Checks the form that discovers the Component from development project classes instead of from a packaged CAR.
These three samples are not for learning different Components. They compare which boundary, source, package, or inspection, is used to hand the same Component to the runtime.
Confirmation
For confirmation, the important point is not reading a long packaging script, but verifying that the packaged CAR is visible as a runtime surface.
$ cd samples/02-component
$ bash run.sh
run.sh is a batch form that builds the jar, creates the CAR, and confirms loading from component.d/testcomp.car . The learning target of the article is not each script line, but that Component / Service / Operation appear as a runtime surface in the output.
Reading the Result
If the help output shows the testcomp Component, main Service, and hello Operation, the packaged artifact has been recognized as a runtime surface.
When the operation is executed, testcomp.main.hello returns Hello from testcomp . However, the central point of this sample is not the return value itself, but that the same selector is provided from the packaged CAR.
Common Pitfalls
-
If an old jar remains in the component directory, the runtime may load the previous artifact instead of the edited source.
-
Check which script uses development classpath and which uses packaged artifacts so the two paths are not confused.
CNCF Engine Meaning
A Component is the runtime registration unit, and it exposes the same selector whether loaded from source or archive.
This separation supports fast edit cycles during development and stable artifacts for distribution.
References
Glossary
- Component
-
A software construct that encapsulates well-defined responsibilities, contracts, and dependencies as a reusable and replaceable unit. In the logical model, it serves as an abstract structural unit; in the physical model, it corresponds to an implementation or deployment unit.
- Cloud Native Component Framework (CNCF)
-
Cloud Native Component Framework (CNCF) is a framework for executing cloud application components using a single, consistent execution model. Centered on the structure of Component, Service, and Operation, it enables the same Operation to be reused across different execution forms such as command, server (REST / OpenAPI), client, and script. By centralizing quality attributes required for cloud applications—such as logging, error handling, configuration, and deployment—within the framework, components can focus on implementing domain logic. CNCF is designed as an execution foundation for literate model-driven development and AI-assisted development, separating what is executed from how it is invoked.
- CML (Cozy Modeling Language)
-
CML is a literate modeling language for describing Cozy models. It is designed as a domain-specific language (DSL) that forms the core of analysis modeling in SimpleModeling. CML allows model elements and their relationships to be described in a narrative style close to natural language, ensuring strong compatibility with AI support and automated generation. Literate models written in CML function as intermediate representations that can be transformed into design models, program code, or technical documentation.