Resources & method types
Resources describe externally visible APIs. Resource methods reference reusable method types; HTTP verbs occur when a method type is declared, not as standalone methods inside a resource. The resource shape, method-type reference, optional local operation name, parameters, and results are CMN syntax. Names and properties supplied by Base, SpringBoot, or another facility are separate from that core syntax.
Complete Base-backed example
package com.example.customer.api
import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.common.base.api
platform Base
type Customer {
id**: Id
name*: Name
}
type Order {
orderId**: Id
}
type ErrorView {
message*: String
}
abstract resource /orders as Order[] by orderId {
query()
read()
}
resource /customers as Customer[] by id {
query(name: String)
create() consumes=Customer
read()
404: ErrorView
update()
deleteInstance()
./orders
}The by id clause identifies collection items by the id field. ./orders adds the abstract /orders resource below each customer item. deleteInstance() uses Base's opinionated item-deletion contract; raw delete() remains available for explicitly modeled DELETE behavior. The method types in this example are available because the model imports org.joinedworkz.facilities.common.base.api; they are not built-in CMN keywords.
Resource shapes and identifiers
as Type declares a singleton representation. A cardinality after the type declares a collection representation; [] is the common unbounded form:
type SystemSettings {
maintenanceMode*: Boolean
}
type Customer {
id**: Id
name*: Name
}
type AuditRecord {
message*: String
}
resource /settings as SystemSettings {
}
resource /customers as Customer[] by id {
}
resource /audit-records as AuditRecord[] by recordId: Id {
}The grammar also permits the resource collection marker before as:
type Request {
id**: Id
}
resource /requests[] as Request by id {
}Both /customers as Customer[] and /requests[] as Request transform to an unbounded collection resource. The cardinality after the type also supports the general bounded and dictionary forms. The bracket before as is the resource-cardinality form and can contain identifier parameters. Prefer one spelling consistently within a project.
The two by forms carry different information:
by idreferences theidfield of the complex representation. The transformed Core Model retains that field reference.by recordId: Iddeclares an identifier parameter by name and type. It does not reference a same-named representation field.
Several identifiers can be separated with commas. For an unbounded Type[] resource with a complex representation and no explicit by clause, the transformer derives an identifier from the representation's key field when one exists. An explicit clause is preferable when the public path should not depend on that inference.
Resource-method parameters use the same explicit typed and field-based forms as service operations. See Services and operations.
Nested and reusable resources
A nested resource beginning with / belongs to the collection-level path. A resource beginning with ./ belongs to the item-level path and therefore follows the parent identifiers. Reusable subresources can be declared abstract and referenced from another resource:
type Customer {
id**: Id
}
type CustomerSummary {
count*: Integer
}
type Order {
orderId**: Id
}
abstract resource /orders as Order[] by orderId {
}
resource /customers as Customer[] by id {
/summary as CustomerSummary {
}
./orders
}This models /customers/summary at collection level and /customers/{id}/orders at item level. Nested resources can also be declared inline, as /summary is here.
Method types and HTTP verbs
CMN declares a reusable method type with a name and an HTTP verb:
methodtype exportCustomers GET
produces='text/csv'
success=200
operationName='exportCustomers'
404: ErrorViewThe available verbs are GET, POST, PUT, PATCH, and DELETE. The Base facility supplies two groups of method-type names:
- raw:
get,post,put,patch,delete; - opinionated:
create,start,execute,read,downloadText,update,deleteInstance,query, andlist.
Base keeps the two deletion contracts unambiguous: delete is the raw HTTP method type, while deleteInstance supplies item-level defaults.
The SpringBoot facility additionally supplies createEntity, readEntity, updateEntity, queryEntities, and deleteEntity. These names are facility-defined model elements, not CMN keywords.
Method scope and representations
Resource-method properties are not fixed keywords of the CMN grammar. The selected Profile defines supported property names and value types; the model transformation and consuming Facility or Cartridge define their effect. Base contributes properties such as success and operationName, while instance, consumes, and produces are established transformation conventions. In the Base transformation, instance=false is the default and places a method on the resource itself; instance=true makes it an item-level operation and uses the identifiers of the resource path.
Properties on a concrete resource method take precedence over defaults from its method type. consumes and produces can name explicit CMN types, including collection and dictionary cardinalities:
methodtype getAttributes GET
produces=String[String]
success=200String[String] denotes a dictionary with String values and String keys. The same notation can define a request and response body:
methodtype echoDictionary PUT
consumes=String[String]
produces=String[String]
success=200
resource /dictionary-values {
echoDictionary()
}CMN records the dictionary shape for both contents. Whether they become JSON bodies, target-language maps, or another representation is defined by the selected facility. A dictionary operation parameter such as lookup(criteria: String[String]) is a different construct: it does not implicitly become a request body.
The Base-oriented generator stack also interprets these quoted method-property conventions:
'*'letsconsumesorproducesderive the resource representation;'*[]'letsconsumesorproducesderive a collection of that representation;produces='**'uses the key type of a complex resource representation.
These values are generator conventions, not separate CMN grammar keywords.
Resource-method properties and method-type inheritance
CMN accepts properties on both a resource method type and the concrete resource method that references it. A property can flow from the method type to the concrete method through the methodtype propagation relationship when the selected Profile permits that relationship. A value written directly on the concrete method has priority:
methodtype partialRead GET
success=200
resource /customers {
partialRead() success=206
}The effective success value of this resource method is 206. For a property with several values, a local declaration replaces the inherited property as a whole; the local and inherited lists are not combined.
Propagation selects the effective model value only. It does not make a facility-specific property part of the CMN language or give it the same effect on another target platform. The general rules are documented under Profile-controlled property propagation. The concrete request-header effects are defined separately by the Base OpenAPI contract and the SpringBoot runtime binding.
Use the canonical Base flags pagination and sort in new resource operations. sorting is a deprecated compatibility alias for sort. paging is also deprecated, but deliberately remains distinct from pagination. A context-free paginated response uses content, filteredElements, and totalElements; the legacy paging shape uses data. An explicit responseContext takes precedence over either flag and wraps the collection as data alongside context. Check that structural contract when migrating. See the property migration guide.
Resource methods
A resource method starts with a reference to a method type. It may then have an explicit local operation name, parameters, properties, and additional results:
resource /customers as Customer[] by id {
query(active: Boolean)
read readCustomer()
404: ErrorView
create() consumes=Customer
}In read readCustomer():
readreferences the imported method type and therefore supplies its HTTP and response defaults;readCustomeris the explicit local name of this one resource operation;- the declaration does not create or rename a method type.
The other two operations omit a local name. A parameter without * has no explicit minimum or maximum in the Core Model and is normally treated by the public facilities as an optional single value; parameter*: Type explicitly means exactly one. Properties on a concrete resource method take precedence over values inherited from its method type.
The general CMN grammar also accepts a dictionary cardinality on a resource method parameter. Target facilities need not support that shape as a direct path or query parameter. Consult the facility contract instead of assuming that ValueType[KeyType] is transported as one HTTP parameter.
Representations and responses
Immediately after the parameter list, as can declare the primary representation or representations:
resource /customers as Customer[] by id {
read() as Customer 'Customer response'
}Additional results begin with a mandatory numeric status code:
resource /customers as Customer[] by id {
read()
200: Customer 'Customer response'
422: ErrorView[String] 'Validation errors by field'
404: ErrorView 'Customer not found'
204: -
503: 'Temporarily unavailable'
}The result body can be:
- a representation with an optional cardinality and description;
- a description without a representation; or
-for no response body.
The colon after the status code is accepted but optional in the grammar; use it consistently to make result declarations visually distinct. A description comment may also precede a result, and facility-defined properties may follow it. There is no response(...) construct and responses are not enclosed in braces.
Documentation immediately before a resource method belongs to that operation. Within this block, @request documents its request body and @response documents its primary CMN result. Documentation immediately before an explicitly modeled status response belongs only to that response. The multiline object example shows the usual @request form for a nested complex type without an unnecessary value: boundary.
package com.example.customer.api
import org.joinedworkz.facilities.common.base
platform Base
type Customer {
id: Id
displayName: String
}
type CustomerError {
code: String
}
resource /customers as Customer[] by id {
'''
Creates one customer.
@request
Customer representation supplied by the caller.
@response
Identifier assigned to the created customer.
'''
create()
read()
'''
The requested customer does not exist.
'''
404: CustomerError 'Customer not found'
}The primary CMN result normally maps to a concrete success status such as 200 or 201; it is unrelated to OpenAPI's separate default: response key. The documentation block and the short quoted result description are distinct model inputs. A target facility defines how they are combined. For the Base OpenAPI cartridge, status-specific documentation takes precedence over @response, which takes precedence over the short quoted description and finally the standard HTTP-status text. See OpenAPI documentation and examples.
The 422 declaration demonstrates dictionary cardinality on an additional result: ErrorView is the value type and String is the key type. This is a CMN result shape; the selected facility defines its generated response contract.
Generated method names
The Base profile contributes the quoted, non-blank operationName property to resource operations. It can be declared by a method type or overridden on a concrete resource operation. A value can be a literal such as 'exportCustomers' or a template such as 'get${produces}ById'.
operationName is therefore a Base facility property, not the method-type name and not the optional local CMN name shown above. It is used to derive a generator-facing method name when the resource operation has no explicit local name.
Supported template variables are entity, resource, consumes, and produces. Each can be followed by []. For a value resolved from the model, that form pluralizes entity, consumes, or produces; resource is not pluralized. A non-blank fallback after : is used when the model supplies no value, for example ${produces:Result}. Surrounding whitespace is trimmed; the resulting fallback value is inserted without pluralization.
The Base profile also contributes the separate, quoted and non-blank operationId property. It defines the identifier written to OpenAPI and can use the same four template variables, [] suffix and non-blank fallback syntax. For example, operationId='customers.get${produces}ById' can resolve to customers.getCustomerById. An OpenAPI operation ID is not a Java or TypeScript identifier: dots, dashes and other literal characters are retained.
One OpenAPI path-and-verb entry represents all operation parts merged into that endpoint. A variable in operationId must therefore have either no model value or one unambiguous value across those parts. Repeating the same type is valid; different consumes, produces, or represented entity names are an error instead of selecting the first value. A fallback supplies a missing value, but it does not hide ambiguity or an unknown variable. The operations merged into the endpoint must also declare the same operationId literal or template; different declarations are an error. The resolved group ID is also used by SpringBoot's OpenAPI annotation, diagrams and the method-name fallback below.
The generated method name is selected in this order:
- an explicit name on the CMN resource operation;
- the resolved
operationNameproperty; - the last segment of the generated
operationId; and executeOperation.
An operationName value is validated even when an explicit operation name takes precedence. Every consuming cartridge can additionally validate the resolved name for its target language. Java-generating cartridges require a valid Java identifier that is not a Java keyword.
Active validation boundaries
The CMN linker reports an unresolved method-type reference. The core CMN validator additionally rejects two method-type declarations with the same name in the same model block. The root model and every nested package or subpackage block are validated independently for this rule.
The Base OpenAPI cartridge validates the static shape of operationName and operationId values on the current model. Resolution additionally rejects a missing or ambiguous operationId variable and conflicting operationId declarations once the endpoint's operations and parts are known. A cartridge that emits source code can reject a resolved method name that is not a valid identifier in its target language. These are facility-/cartridge-specific validations; the core CMN validator does not promise general resource-path uniqueness or HTTP-contract compatibility checks.
The buildable Spring Boot example contains the canonical customer resource used by the stable facility.
