Skip to content

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 informationMeaning
package com.example.catalog.compositionTechnical 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 provideRelative 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.

cmn
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:

cmn
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.

cmn
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 ReviewController boundary;
  • both sets still belong to CatalogBackend and 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 controller boundary.
  • Diagram cartridges can show which resource trees belong to a component.
  • An implementation facility can use componentNamespace, namespaceSuffix, and controller to 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:

cmn
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 read block corresponds to the read endpoint 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' ]]).

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:

cmn
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 use section 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.yaml for the resources in the API model;
  • org.joinedworkz.examples.backend_customerbackend.yaml for all endpoints provided by CustomerBackend.

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.