Components & applications
Components group provided API boundaries and declare which types, services, or components they use. A provide declaration says that an API element belongs to the component's provided boundary; it does not by itself assert that executable implementation code already exists. Applications compose components into a logical system boundary.
Complete example
package com.example.customer
import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.common.base.api
platform Base
type Customer {
id**: Id
}
service CustomerService {
find(id*: Id): Customer
}
resource /customers as Customer[] by id {
query()
read()
}
component CustomerComponent uses CustomerService {
provide /customers
}
component ReportingComponent uses CustomerComponent {
}
application CustomerApp {
consists of {
CustomerComponent
}
use {
ReportingComponent
}
}CustomerComponent uses the service and provides the imported resource. What a cartridge generates from those declarations depends on the selected facility.
Component dependencies
Dependencies are written in the component header with uses; there is no depends keyword:
component CustomerComponent uses CustomerService, Customer {
}A component can use types, services, and other components.
Component declaration structure
The component declaration can have a stereotype, label, header dependencies, and profile-defined properties:
[description] component [<stereotype>] Name ["Label"]
[uses TypeOrServiceOrComponent, ...]
[property ...] {
...
}Within the body, the grammar orders typed component properties, data, routes, fragment includes, operations, component members, and nested components. This order is not freely interchangeable. The specialized families and their facility-support boundaries are indexed in Specialized model elements.
Provided API boundaries
The public declaration for a resource is:
component CustomerComponent {
provide /customers
}provide /customers references a resource that is visible in the model. It assigns the resource to the component's provided boundary. Properties and an optional body can add metadata or behavioral descriptions for cartridges that support them. For example, Base can use a controller property as an OpenAPI tag, while SpringBoot can also use it when generating a controller.
Do not read provide as a language-level implementation statement. Whether controllers, handlers, diagrams, or other artifacts are generated is a facility-/cartridge-specific decision.
Component artifact namespaces
The public component metadata separates the target namespace from the CMN model namespace:
package com.example.inventory.composition
component InventoryBackend
componentNamespace='com.example.inventory.runtime' {
provide /inventory/items
namespaceSuffix='items.v1'
controller='ItemController'
}packagedefines the technical CMN namespace used for model identity and imports.componentNamespacedefines a target namespace for artifacts derived from the component.namespaceSuffixis a relative suffix for artifacts derived from this particularprovideboundary.controlleris a logical, platform-neutral grouping name. A cartridge can use it for tags, diagrams, or a platform-native controller artifact.
These properties do not alter the HTTP resource path. Their concrete artifact mapping is part of the selected facility's contract.
On these scopes, the previous names remain compatibility aliases:
- component
basePackageis deprecated in favor ofcomponentNamespace; providepropertysubPackageis deprecated in favor ofnamespaceSuffix.
Do not use the deprecated names in new models.
Nested resource selection and the deepest boundary
A provide reference selects model elements along a resource tree. It is not a literal HTTP path expression. Identifiers declared with by affect the HTTP path when an item-relative resource or instance operation is reached, but they are not written into the provide selector:
type Item {
itemId**: Id
}
type Inspection {
inspectionId**: Id
}
resource /inventory {
/items as Item[] by itemId {
query()
./inspections as Inspection[] by inspectionId {
query()
}
}
}
component InventoryBackend
componentNamespace='com.example.inventory.runtime' {
provide /inventory/items
namespaceSuffix='items.v1'
controller='ItemController'
provide /inventory/items/inspections
namespaceSuffix='inspections.v1'
controller='InspectionController'
}The second selector names the nested inspections resource even though its HTTP collection path includes the parent identifier: /inventory/items/{itemId}/inspections. Where provided boundaries overlap, the most specific, deepest matching boundary supplies the logical controller metadata for operations in that subtree. The parent boundary continues to cover the remainder of its tree.
All of these boundaries still belong to the same component. They therefore contribute to one component-scoped OpenAPI aggregate. A repeated final controller identity is not a declaration that separately provided boundaries should be merged into one generated controller.
Resources, services, and implement
Both resources and services can be assigned to a component:
package com.example.customer
import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.common.base.api
platform Base
type Customer {
id**: Id
}
service CustomerService {
find(id*: Id): Customer
}
resource /customers as Customer[] by id {
read()
}
component CustomerComponent {
provide CustomerService
provide /customers
}The grammar also accepts implement in the same position. For a service, JoinedWorkz retains implement as an internal-feature marker in the canonical Core Model:
package com.example.internal
service InternalCustomerService {
}
component CustomerComponent {
implement InternalCustomerService
}For a resource path, the transformation does not retain an equivalent separate internal marker. Do not use implement /resource to infer a public versus internal resource contract. provide remains the documented form for the component API boundary.
Properties and command bodies can follow a provided feature. Their names and behavior are facility-defined; the core declaration alone does not promise an executable handler.
Base component-scoped OpenAPI
With the Base platform, every component with provided resources produces an additional component-scoped OpenAPI YAML and matching HTML viewer. The document aggregates all resource paths in that component's provide declarations, including resources imported from different CMN models.
The effective controller name of a provided boundary can contribute an OpenAPI operation tag. For overlapping parent and nested boundaries, the deepest matching boundary supplies that metadata for its subtree. Neither componentNamespace nor namespaceSuffix changes an OpenAPI path.
The same cartridge also creates a model-scoped document from the non-abstract resources declared directly in each CMN model. Applications do not create a third, application-wide aggregate.
Application composition
consists of { ... }lists components that belong to the application.use { ... }oruses { ... }lists referenced components outside that composition.
The component lists are blocks; use CustomerComponent is not valid application syntax. Application declarations do not by themselves create a runnable application; that requires a facility and, where applicable, a project application shell.
An application can also have a description, stereotype, and profile-defined properties. Its three optional body sections are consists of, use/uses, and supports; they may appear in any order, but each at most once. Component and scenario references inside these blocks are separated by whitespace, not commas.
supports { ... } references scenarios. Scenario processing is a specialized facility capability rather than a universal application output contract; see Specialized model elements.
The component and application structure of the stable facility is shown in the buildable Spring Boot example.
