Components and applications
This page explains how components group provided resource trees, how applications compose components, and which parts of that model are independent of a target platform. It builds on APIs and the CMN resource reference.
SpringBoot's concrete Java mapping is documented separately under SpringBoot profile and modeling.
1. Components and provided resource trees
A component is a logical building block. It can provide resources and services, use other model elements, and attach behavioral descriptions to its boundary. A provide declaration assigns a visible resource or resource subtree to that component. It does not by itself assert that executable code already exists.
1.1 Model namespace and component target namespace
The CMN package header and the component properties serve different purposes:
| Model information | Meaning |
|---|---|
package com.example.catalog.composition | Technical namespace of the CMN model and its elements |
componentNamespace='com.example.catalog.runtime' | Target namespace for artifacts derived from the component |
namespaceSuffix='products.v1' on provide | Relative suffix for artifacts derived from this provided boundary |
Neither componentNamespace nor namespaceSuffix changes a resource's HTTP path or the namespace by which CMN imports resolve the component. Facilities decide which target artifacts use this metadata.
package com.example.catalog.composition
import com.example.catalog.api
import org.joinedworkz.facilities.common.base
platform Base
component CatalogBackend
componentNamespace='com.example.catalog.runtime' {
provide /catalog/products
namespaceSuffix='products.v1'
controller='ProductController'
}The Base platform does not generate a Java controller from this declaration. It can nevertheless use the component boundary and the logical controller metadata for OpenAPI and diagrams. A target platform can interpret the same metadata for its own artifacts.
For compatibility, basePackage on a component is a deprecated alias of componentNamespace, and subPackage on provide is a deprecated alias of namespaceSuffix. Existing models remain readable, but new and migrated models should use only the canonical names shown above.
1.2 One component can provide several imported resources
A component is an aggregation boundary, not a requirement that all resources be declared in the component's own CMN file. After importing their models, one component can provide resource trees from several files:
component OperationsBackend
componentNamespace='com.example.operations.runtime' {
provide /catalog/products
namespaceSuffix='catalog.v1'
controller='ProductController'
provide /fulfillment/shipments
namespaceSuffix='fulfillment.v1'
controller='ShipmentController'
}The logical controller names group operations within the component boundary. They are platform-neutral metadata: Base can derive OpenAPI tags from them, while an implementation platform can map them to a native controller concept.
1.3 Split a nested resource tree at provide boundaries
Several provide declarations can partition one nested resource tree. The most specific, deepest matching boundary owns the operations below it. This lets a parent resource and a selected nested subtree use different logical controllers without duplicating the resource model.
type Product {
productId**: Id
name*: String
}
type Review {
reviewId**: Id
text*: String
}
resource /catalog {
/products as Product[] by productId {
query()
read()
./reviews as Review[] by reviewId {
query()
read()
}
}
}
component CatalogBackend
componentNamespace='com.example.catalog.runtime' {
provide /catalog/products
namespaceSuffix='products.v1'
controller='ProductController'
provide /catalog/products/reviews
namespaceSuffix='reviews.v1'
controller='ReviewController'
}The result has two logical boundaries:
- product operations belong to
ProductController; - review operations belong to the deeper
ReviewControllerboundary; - both sets still belong to
CatalogBackendand therefore to the same component-scoped OpenAPI aggregate.
The path in provide /catalog/products/reviews is a model selector through the resource tree. It names the referenced resource elements; it is not a literal copy of the HTTP path. In the example, ./reviews is item-relative and by productId contributes the parent identifier to the HTTP path, so the review collection is exposed below /catalog/products/{productId}/reviews. The provide selector does not spell out {productId}.
Use one unambiguous provide boundary for each generated target. Reusing the same final controller identity for several boundaries is not a documented merge mechanism.
1.4 Cartridge interpretation
Different cartridges can interpret the same component metadata:
- Base aggregates the endpoints provided by the component and can derive operation tags from the effective logical
controllerboundary. - Diagram cartridges can show which resource trees belong to a component.
- An implementation facility can use
componentNamespace,namespaceSuffix, andcontrollerto organize target artifacts.
provide therefore establishes ownership in the canonical model. Whether a cartridge generates source code, links to handwritten code, or produces only documentation remains platform-specific.
2. Optional pseudo-code for behavior
Components can optionally contain pseudo-code that documents the behavior of individual endpoints. This pseudo-code is not executable business logic – it is used for diagrams and documentation.
Extending the previous example:
component GreetingBackend
componentNamespace='com.example.joinedworkz.quickstart.webapp' {
provide /hello
namespaceSuffix='greeting.v1'
controller="GreetingV1Controller" {
read {
if 'is morning' {
[[ return 'Good Morning' ]]
} else 'is evening' {
[[ return 'Good Evening' ]]
}
}
}
}Here:
- The
readblock corresponds to thereadendpoint on/hello. - Inside the block you describe the flow as pseudo-code:
- conditions (
if 'is morning',else 'is evening'), - actions (
[[ return 'Good Morning' ]],[[ return 'Good Evening' ]]).
- conditions (
The diagram cartridge of the Base platform can interpret this body as a sequence-like diagram that visualises the flow of the endpoint. This is useful for:
- documentation,
- discussions with other developers or stakeholders,
- understanding complex flows without reading implementation code.
Important:
- Pseudo-code is optional. Components are still useful without it (for assigning endpoints to components and driving generation).
- The exact pseudo-code syntax is intentionally lightweight and focused on readability. Details belong in the DSL reference and the documentation of the diagram cartridge.
3. Applications
An application is a top-level model element that represents a logical system boundary and composition of components.
Applications can be used to:
- get an overview of which components belong to a specific app,
- visualise dependencies between own and external components,
- generate application and component diagrams.
3.1 Basic application definition
Continuing the Greeting follow-on example:
application GreetingApp {
consists of {
GreetingBackend
}
use {
// referenced components
}
}Meaning:
- The application is called
GreetingApp. - It consists of the component
GreetingBackend. These are the components that are part of the application itself. - The optional
usesection can list external components that this application depends on (for example shared or third-party components).
The Base diagram cartridge can use this information to:
- draw a component diagram showing the application and its components,
- highlight dependencies between internal and external components.
3.2 OpenAPI scope
The Base OpenAPI cartridge generates two complementary kinds of documents:
- A model-scoped document contains the non-abstract resources declared in one CMN model.
- A component-scoped document contains all resource paths that one component provides. This is the important aggregation boundary: a component can provide resources imported from several resource models, and the cartridge combines their provided endpoints into one additional OpenAPI YAML and HTML viewer for that component.
Controller names on the provided resources can contribute operation tags to the component document. For example, the release Spring Boot example produces both:
org.joinedworkz.examples.customer.api.yamlfor the resources in the API model;org.joinedworkz.examples.backend_customerbackend.yamlfor all endpoints provided byCustomerBackend.
Applications do not define this aggregation boundary. They contribute to application/component diagrams, but they do not generate a separate application-wide OpenAPI YAML or viewer.
4. How components and applications fit in
Putting it all together:
- APIs (
resource/service) define what is exposed. - Components define who declares which part of the API as provided. The Base OpenAPI cartridge aggregates those endpoints into a component document; other cartridges can map the same boundary to implementation artifacts.
- Optional pseudo-code inside components documents how calls are handled and can be interpreted by the Base diagram cartridge.
- Applications assemble components into logical systems. The Base diagram cartridge uses them as the scope for application and component diagrams.
Because components and applications live in the canonical model, you can:
- reuse the same component/application structure across different platforms,
- generate diagrams and, with supporting cartridges, OpenAPI and implementation artifacts from a single source of truth,
- evolve your technical stack while keeping the logical architecture stable.
5. Next steps
From here you can:
- read about Profiles & platforms (.profile) to see how platforms configure cartridges (including diagram and API generators) and interpret component/application models;
- explore Modeling with JoinedWorkz Studio for details on how components and applications are visualised and edited in the Studio;
- consult the CMN reference for the exact syntax of component and application definitions and pseudo-code blocks.
