Literate Model Example: Address
This article is an introduction to help you grasp the concept of literate models by looking at a concrete example. Please refer to 📄 Literate Model for the concept of literate models. Here, we present the natural language description and structural definition of the address model, along with the Scala source code output by Cozy, so you can get a feel for literate modeling.
Literate Model
As explained in 📄 Literate Model, a literate model has the following structure.
-
A format in which structural or descriptive models are embedded in natural language text.
-
It can be read as a specification and directly used for code generation.
Natural language itself is an essential part of the specification and, in the AI era, becomes a critical form of information that can be directly used in development activities.
Features of literate models include:
-
Because the specification and model definition are in the same place, discrepancies between them are less likely to occur.
-
Specification files are less likely to become scattered or lost.
-
Standardized specification descriptions help prevent omissions.
-
It is easier to receive AI assistance when writing natural language descriptions.
These are some of the advantages.
In this article, we use Address, a value object modeling an address, as an example and describe its required features, background, and use cases in natural language.
Example of Literate Model
Below is the CML of Address, a value object modeling an address, as an example of a literate model.
Address
=======
This document defines the Address value-object family as a literate model.
It preserves executable structure, machine-consumable metadata, and human
explanation in one source.
The canonical model is `Address`.
It is the maintained internal model for postal-address semantics in this
project.
The model is organized around a root value object, `Address`, and reusable
value objects referenced from its attributes.
Types required for Japanese operation remain part of the maintained core even
when the corresponding attributes are optional in individual `Address`
instances.
Important help-oriented text is kept in reserved descriptive sections under
`# VALUE`. Longer rationale, examples, and integration guidance are written as
narrative before and after structural definitions.
External standards such as `schema.org PostalAddress` and `vCard ADR` are
treated as important projection and interchange targets, not as the primary
definition of the model.
The design follows these principles:
- alignment with widely adopted standards
- international neutrality across different address systems
- separation of structure from formatting and validation
- support for multilingual and country-specific interpretation
Address is the root value object for a portable postal-address model.
It composes smaller reusable value objects while keeping country-specific
formatting and delivery validation outside the core structural definition.
The model is designed to preserve address semantics first and rendering rules
second.
`addressCountry` drives country-specific interpretation.
Formatting, projection-specific recovery, and country-dependent delivery rule
checks are downstream concerns rather than core model guarantees.
# VALUE
## Address
### SUMMARY
Structured postal destination value.
### ATTRIBUTE
- name: addressCountry
type: CountryCode
multiplicity: "1"
- name: postalCode
type: PostalCode
multiplicity: "?"
- name: addressRegion
type: Region
multiplicity: "?"
- name: addressLocality
type: Locality
multiplicity: "?"
- name: addressSubLocality
type: SubLocality
multiplicity: "?"
- name: streetAddress
type: StreetAddress
multiplicity: "1"
- name: extendedAddress
type: ExtendedAddress
multiplicity: "?"
### DESCRIPTION
Address is a structured postal destination value.
It represents address semantics as composable typed fields, not a single
formatted string.
## CountryCode
### SUMMARY
Two-letter country code.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 2
max: 2
pattern: "^[A-Z]{2}$"
### DESCRIPTION
Two-letter country identifier.
## PostalCode
### SUMMARY
Postal or ZIP code value.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
Postal or ZIP code. Format is country-dependent.
## Region
### SUMMARY
Top-level administrative division.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
Top-level administrative division (state, prefecture, or province).
## Locality
### SUMMARY
City or equivalent locality.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
City or equivalent locality.
## SubLocality
### SUMMARY
Subdivision within a locality.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
Subdivision within locality (ward, district, or neighborhood).
## StreetAddress
### SUMMARY
Primary street-level address value.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
Primary street-level address expression.
## ExtendedAddress
### SUMMARY
Additional address-detail value.
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
### DESCRIPTION
Additional address details (building, floor, apartment, and similar details).
# Example
## Address
addressCountry: JP
postalCode: "160-0022"
addressRegion: Tokyo
addressLocality: Shinjuku-ku
addressSubLocality: Shinjuku
streetAddress: "1-2-3"
extendedAddress: "Building A 101"
# Background
## International Standards
This model uses external standards as interoperability anchors, but it does
not reduce the Address family to any single external representation.
The canonical model is `Address`.
External standards are treated as important projection and interchange
targets.
The main anchors are:
- ISO 3166-1 (country codes)
- schema.org PostalAddress (web structured data)
- vCard ADR (RFC 6350)
## schema.org
`schema.org PostalAddress` is the primary web-facing projection target.
It is important for search-facing JSON-LD and general web interoperability,
but it does not define the full internal model.
Fields such as `addressSubLocality` and `extendedAddress` may require
projection policy beyond direct standard slots.
## vCard
vCard ADR positional form:
ADR: POBOX;EXT;STREET;LOCALITY;REGION;POSTALCODE;COUNTRY
`vCard ADR` is the primary contact/interchange projection target.
It provides a strong positional mapping for the core address fields and a
useful slot for `extendedAddress` via `EXT`.
It does not provide a stable dedicated slot for `addressSubLocality`, so
complete reversibility still depends on integration policy.
## ISO
Country identity is anchored by ISO 3166-1 alpha-2.
This standard is treated as part of the core validation contract for
`CountryCode`, not merely as descriptive background.
# Internationalization
Address structure differs by country.
Country-specific interpretation is delegated using `addressCountry`.
The generic internationalization context should not know `Address` directly.
Address-specific handling is expected to obtain an Address-specific class
context from the generic i18n context and resolve country-specific
interpretation through that class context.
## Japan
- addressRegion -> prefecture
- addressLocality -> city
- addressSubLocality -> ward
- streetAddress -> block/number
## UnitedStates
- addressRegion -> state
- addressLocality -> city
- streetAddress -> street + number
# Mapping
`Address` is the canonical internal model.
`schema.org PostalAddress` and `vCard ADR` are treated as projection targets,
not as the primary model definition.
Directly corresponding fields are mapped one-to-one where possible.
Fields without a stable standard slot are mapped by integration policy, and
some projections may be only partially reversible.
## schema.org
- addressCountry -> addressCountry
- postalCode -> postalCode
- addressRegion -> addressRegion
- addressLocality -> addressLocality
- streetAddress -> streetAddress
`addressSubLocality` does not have a stable standard slot in
`schema.org PostalAddress`.
It is therefore preserved in the canonical model and projected by integration
policy, typically by supplemental application metadata or by controlled
absorption into `streetAddress`.
`extendedAddress` also does not have a stable dedicated standard slot in
`schema.org PostalAddress`.
It is therefore projected by integration policy, typically by supplemental
metadata or by controlled absorption into `streetAddress`.
Round-trip implications:
- `Address -> schema.org` may lose the independent distinction of
`addressSubLocality`
- `Address -> schema.org` may lose the independent distinction of
`extendedAddress`
- `schema.org -> Address` cannot usually reconstruct `addressSubLocality`
unless an integration-specific extension is used
- `schema.org -> Address` cannot usually reconstruct `extendedAddress`
unless an integration-specific extension is used
## vCard
- extendedAddress -> EXT
- streetAddress -> STREET
- addressLocality -> LOCALITY
- addressRegion -> REGION
- postalCode -> POSTALCODE
- addressCountry -> COUNTRY
`addressSubLocality` does not have a stable dedicated slot in standard
`vCard ADR`.
It is therefore preserved in the canonical model and projected by integration
policy, typically by controlled absorption into `STREET`.
Round-trip implications:
- `Address -> vCard` can project `extendedAddress` directly to `EXT`
- `Address -> vCard` usually projects `addressSubLocality` by policy rather
than by one fixed standard slot
- `vCard -> Address` can usually reconstruct `extendedAddress` from `EXT`
- `vCard -> Address` cannot usually reconstruct `addressSubLocality`
independently unless an integration-specific rule is applied
# Validation
Validation is split between the core Address model and downstream policy.
The core model is responsible for:
- structural shape
- multiplicity
- shared reusable constraints
- stable cross-country semantics for the maintained fields
The downstream integration layer is responsible for:
- country-specific postal-code rules
- country-specific formatting rules
- country-specific completeness rules
- application-specific projection and recovery rules during interchange
Those downstream rules are expected to be resolved through an
Address-specific class internationalization context derived from a generic
i18n context, rather than by making the generic i18n context itself aware of
`Address`.
Current core validation contract:
- `addressCountry` is required
- `streetAddress` is required
- `CountryCode` is constrained to ISO 3166-1 alpha-2 style uppercase
two-letter codes
- other maintained fields may be optional even when they are part of the
maintained core model
This means `SubLocality` and `ExtendedAddress` remain part of the maintained
Address family even though `Address` may omit them in individual instances.
The model intentionally does not claim that a postal address is valid for
delivery in a specific country.
That judgment belongs to downstream validation policy driven by
`addressCountry`.
In address.cml, not only the model structure but also background and design intent are included as natural language, so it can be read directly as a specification.
The opening natural language section describes the purpose, design policy, and relationships to external standards for this model. In the VALUE section, the model structure is defined, and explanations such as SUMMARY and DESCRIPTION are provided directly beneath it.
Furthermore, sections such as Example, Background, and Mapping
-
Expected usage examples
-
Technical background
-
Correspondence with external specifications
are all brought together in a single document.
Because the model structure and explanations are not separated, you can read it just like an ordinary specification document.
What to Look At
Below is an excerpt from Address.cml showing only the parts used as a model.
Here, we have extracted the sections that are used as the model structure.
# VALUE
## Address
### ATTRIBUTE
- name: addressCountry
type: CountryCode
multiplicity: "1"
- name: postalCode
type: PostalCode
multiplicity: "?"
- name: addressRegion
type: Region
multiplicity: "?"
- name: addressLocality
type: Locality
multiplicity: "?"
- name: addressSubLocality
type: SubLocality
multiplicity: "?"
- name: streetAddress
type: StreetAddress
multiplicity: "1"
- name: extendedAddress
type: ExtendedAddress
multiplicity: "?"
## CountryCode
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 2
max: 2
pattern: "^[A-Z]{2}$"
## PostalCode
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
## Region
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
## Locality
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
## SubLocality
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
## StreetAddress
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
## ExtendedAddress
### ATTRIBUTE
- name: value
type: String
multiplicity: "1"
min: 1
This extracted model structure can be used directly as type definitions and validation specifications in programs.
However, if you look only at this part, the information is limited to "structure" and "constraints", and it is almost the same as conventional data model specifications in programming.
From an AI perspective, this information alone is not sufficient.
-
Why is this structure designed this way?
-
What use cases are assumed?
-
How does it relate to external standards?
Such semantic information is described in the natural language sections above.
In a literate model, the combination of structural models and natural language provides specifications with enough information for AI to handle.
Example
The format of data handled in Address.cml is as follows.
This format directly describes the values corresponding to each attribute of Address.
The fields defined in the structural model appear as data keys, and the values are written according to the constraints of each Value Object.
You can see that the model structure is directly reflected in the data format.
addressCountry: JP
postalCode: "160-0022"
addressRegion: Tokyo
addressLocality: Shinjuku-ku
addressSubLocality: Shinjuku
streetAddress: "1-2-3"
extendedAddress: "Building A 101"
Scala
The code generated from here is not just a simple data class,
-
Type definitions as Value Objects
-
Builder
-
Validation
-
Record conversion
but also includes implementation code such as the above.
The Scala code here is to give you an idea of what kind of code is generated from a literate model. The goal is not to read the detailed implementation, but to convey the sense that Address is expanded as a typed Scala class.
package domain.value
import scala.language.strictEquality
import cats.*
import cats.implicits.*
import cats.syntax.all.*
import io.circe.Codec
import io.circe.generic.semiauto.*
import org.goldenport.Consequence
import org.goldenport.ConsequenceT
import org.goldenport.datatype.*
import org.goldenport.schema.Schema
import org.goldenport.record.Record
import org.goldenport.protocol.*
import org.goldenport.protocol.spec.*
import org.goldenport.protocol.operation.*
import org.simplemodeling.model.datatype.*
import org.simplemodeling.model.value.{given, *}
import org.simplemodeling.model.directive.*
import domain.value.CountryCode
import domain.value.ExtendedAddress
import domain.value.Locality
import domain.value.PostalCode
import domain.value.Region
import domain.value.StreetAddress
import domain.value.SubLocality
case class Address(addressCountry: CountryCode, postalCode: PostalCode, addressRegion: Region, addressLocality: Locality, addressSubLocality: SubLocality, streetAddress: StreetAddress, extendedAddress: ExtendedAddress) extends org.goldenport.record.Recordable derives Codec.AsObject {
import Address.*
def withAddressCountry(addressCountry: CountryCode): Address = {
copy(addressCountry = addressCountry)
}
def withPostalCode(postalCode: PostalCode): Address = {
copy(postalCode = postalCode)
}
def withAddressRegion(addressRegion: Region): Address = {
copy(addressRegion = addressRegion)
}
def withAddressLocality(addressLocality: Locality): Address = {
copy(addressLocality = addressLocality)
}
def withAddressSubLocality(addressSubLocality: SubLocality): Address = {
copy(addressSubLocality = addressSubLocality)
}
def withStreetAddress(streetAddress: StreetAddress): Address = {
copy(streetAddress = streetAddress)
}
def withExtendedAddress(extendedAddress: ExtendedAddress): Address = {
copy(extendedAddress = extendedAddress)
}
// lenslikeupdate_methods
// validate_method
// iri_method
// properties_method
def toRecord(): Record = {
Record.dataAuto(
"address_country" -> _to_external_value(addressCountry),
"postal_code" -> _to_external_value(postalCode),
"address_region" -> _to_external_value(addressRegion),
"address_locality" -> _to_external_value(addressLocality),
"address_sub_locality" -> _to_external_value(addressSubLocality),
"street_address" -> _to_external_value(streetAddress),
"extended_address" -> _to_external_value(extendedAddress)
)
}
private def _to_external_value(v: Any): Any = v match {
case m if java.util.Objects.isNull(m) => null
case m: String => m
case m: java.lang.Number => m
case m: java.lang.Boolean => m
case m: java.lang.Character => m.toString
case m: org.goldenport.datatype.I18nLabel => m.toI18nString.displayMessage
case m: org.goldenport.datatype.I18nTitle => m.value.displayMessage
case m: org.goldenport.datatype.I18nBrief => m.toI18nString.displayMessage
case m: org.goldenport.datatype.I18nSummary => m.toI18nString.displayMessage
case m: org.goldenport.datatype.I18nDescription => m.toI18nString.displayMessage
case m: org.goldenport.datatype.I18nText => m.toI18nString.displayMessage
case m: Record => m
case m: org.goldenport.value.NameAttributes => Record.dataAuto("name" -> _to_external_value(m.name), "label" -> _to_external_value(m.label), "title" -> _to_external_value(m.title))
case m: org.goldenport.value.DescriptiveAttributes => Record.dataAuto("headline" -> _to_external_value(m.headline), "summary" -> _to_external_value(m.summary), "description" -> _to_external_value(m.description))
case m: org.goldenport.record.Recordable => m.toRecord()
case m: Option[?] => m.map(_to_external_value)
case m: Seq[?] => m.map(_to_external_value)
case m: Set[?] => m.toVector.map(_to_external_value)
case m: Array[?] => m.toVector.map(_to_external_value)
case m: Map[?, ?] => m.iterator.map { case (k, value) => k.toString -> _to_external_value(value) }.toMap
case m: org.goldenport.text.Presentable => m.print
case other => other.toString
}
private def _to_data_store_value(v: Any): Any = v match {
case m: org.simplemodeling.model.directive.Update[?] => m
case m: org.goldenport.datatype.I18nLabel => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.goldenport.datatype.I18nTitle => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.goldenport.datatype.I18nBrief => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.goldenport.datatype.I18nSummary => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.goldenport.datatype.I18nDescription => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.goldenport.datatype.I18nText => org.goldenport.convert.StringEncoder.encodeForStorage(m)
case m: org.simplemodeling.model.statemachine.StateMachine => m.dbValue
case m: org.simplemodeling.model.powertype.Powertype => m.dbValue.getOrElse(m.value)
case m: org.goldenport.value.NameAttributes => Record.dataAuto("name" -> _to_data_store_value(m.name), "label" -> _to_data_store_value(m.label), "title" -> _to_data_store_value(m.title))
case m: org.goldenport.value.DescriptiveAttributes => Record.dataAuto("headline" -> _to_data_store_value(m.headline), "summary" -> _to_data_store_value(m.summary), "description" -> _to_data_store_value(m.description))
case other => _to_external_value(other)
}
}
object Address {
final val PROP_ADDRESS_COUNTRY = "addressCountry"
final val INPUT_KEYS_ADDRESS_COUNTRY: List[String] = List("addressCountry", "address_country").distinct
final val PROP_POSTAL_CODE = "postalCode"
final val INPUT_KEYS_POSTAL_CODE: List[String] = List("postalCode", "postal_code").distinct
final val PROP_ADDRESS_REGION = "addressRegion"
final val INPUT_KEYS_ADDRESS_REGION: List[String] = List("addressRegion", "address_region").distinct
final val PROP_ADDRESS_LOCALITY = "addressLocality"
final val INPUT_KEYS_ADDRESS_LOCALITY: List[String] = List("addressLocality", "address_locality").distinct
final val PROP_ADDRESS_SUB_LOCALITY = "addressSubLocality"
final val INPUT_KEYS_ADDRESS_SUB_LOCALITY: List[String] = List("addressSubLocality", "address_sub_locality").distinct
final val PROP_STREET_ADDRESS = "streetAddress"
final val INPUT_KEYS_STREET_ADDRESS: List[String] = List("streetAddress", "street_address").distinct
final val PROP_EXTENDED_ADDRESS = "extendedAddress"
final val INPUT_KEYS_EXTENDED_ADDRESS: List[String] = List("extendedAddress", "extended_address").distinct
// Schema
given CanEqual[Address,Address] = CanEqual.derived
// Domain semantic equality = equality of identity
given Eq[Address] = Eq.fromUniversalEquals
given org.goldenport.convert.ValueReader[Address] with
def readC(v: Any): Consequence[Address] = v match
case m: Record => createC(m)
case _ => Consequence.failValueInvalid(v, org.goldenport.schema.XString)
private def _record_get_as_c[A](
record: Record,
keys: List[String]
)(using vr: org.goldenport.convert.ValueReader[A]): Consequence[Option[A]] = {
keys.foldLeft(Consequence.success(Option.empty[A])) { (z, key) =>
z.flatMap {
case s @ Some(_) => Consequence.success(s)
case None => record.getAsC[A](key)
}
}
}
private def _record_get_vector_of_record_c[A](
record: Record,
keys: List[String]
)(decode: Record => Consequence[A]): Consequence[Option[Vector[A]]] = {
def decode_all(xs: Seq[?]): Consequence[Vector[A]] =
xs.foldLeft(Consequence.success(Vector.empty[A])) { (z, x) =>
z.flatMap { zs =>
x match {
case m: Record => decode(m).map(a => zs :+ a)
case other => Consequence.failValueInvalid(other, org.goldenport.schema.XString)
}
}
}
keys.foldLeft(Consequence.success(Option.empty[Vector[A]])) { (z, key) =>
z.flatMap {
case s @ Some(_) => Consequence.success(s)
case None =>
record.getAny(key) match {
case Some(xs: Seq[?]) => decode_all(xs).map(Some(_))
case Some(xs: Array[?]) => decode_all(xs.toVector).map(Some(_))
case Some(other) => Consequence.failValueInvalid(other, org.goldenport.schema.XString)
case None => Consequence.success(None)
}
}
}
}
case class Builder(addressCountry: Option[CountryCode] = None, postalCode: Option[PostalCode] = None, addressRegion: Option[Region] = None, addressLocality: Option[Locality] = None, addressSubLocality: Option[SubLocality] = None, streetAddress: Option[StreetAddress] = None, extendedAddress: Option[ExtendedAddress] = None, _failures: Vector[Consequence.Failure[_]] = Vector.empty) {
def withAddressCountry(addressCountry: CountryCode): Address.Builder = {
copy(addressCountry = Some(addressCountry), _failures = _failures)
}
def withAddressCountry(addressCountry: Option[CountryCode]): Address.Builder = {
copy(addressCountry = addressCountry, _failures = _failures)
}
def withPostalCode(postalCode: PostalCode): Address.Builder = {
copy(postalCode = Some(postalCode), _failures = _failures)
}
def withPostalCode(postalCode: Option[PostalCode]): Address.Builder = {
copy(postalCode = postalCode, _failures = _failures)
}
def withAddressRegion(addressRegion: Region): Address.Builder = {
copy(addressRegion = Some(addressRegion), _failures = _failures)
}
def withAddressRegion(addressRegion: Option[Region]): Address.Builder = {
copy(addressRegion = addressRegion, _failures = _failures)
}
def withAddressLocality(addressLocality: Locality): Address.Builder = {
copy(addressLocality = Some(addressLocality), _failures = _failures)
}
def withAddressLocality(addressLocality: Option[Locality]): Address.Builder = {
copy(addressLocality = addressLocality, _failures = _failures)
}
def withAddressSubLocality(addressSubLocality: SubLocality): Address.Builder = {
copy(addressSubLocality = Some(addressSubLocality), _failures = _failures)
}
def withAddressSubLocality(addressSubLocality: Option[SubLocality]): Address.Builder = {
copy(addressSubLocality = addressSubLocality, _failures = _failures)
}
def withStreetAddress(streetAddress: StreetAddress): Address.Builder = {
copy(streetAddress = Some(streetAddress), _failures = _failures)
}
def withStreetAddress(streetAddress: Option[StreetAddress]): Address.Builder = {
copy(streetAddress = streetAddress, _failures = _failures)
}
def withExtendedAddress(extendedAddress: ExtendedAddress): Address.Builder = {
copy(extendedAddress = Some(extendedAddress), _failures = _failures)
}
def withExtendedAddress(extendedAddress: Option[ExtendedAddress]): Address.Builder = {
copy(extendedAddress = extendedAddress, _failures = _failures)
}
),
Consequence.successOrPropertyNotFound(PROP_EXTENDED_ADDRESS, extendedAddress)
).mapN(Address.apply)
}
def build(): Address = {
buildC().TAKE
}
def buildC(record: Record): Consequence[Address] = {
(
_record_get_as_c[CountryCode](record, INPUT_KEYS_ADDRESS_COUNTRY).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_ADDRESS_COUNTRY, addressCountry)
},
_record_get_as_c[PostalCode](record, INPUT_KEYS_POSTAL_CODE).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_POSTAL_CODE, postalCode)
},
_record_get_as_c[Region](record, INPUT_KEYS_ADDRESS_REGION).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_ADDRESS_REGION, addressRegion)
},
_record_get_as_c[Locality](record, INPUT_KEYS_ADDRESS_LOCALITY).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_ADDRESS_LOCALITY, addressLocality)
},
_record_get_as_c[SubLocality](record, INPUT_KEYS_ADDRESS_SUB_LOCALITY).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_ADDRESS_SUB_LOCALITY, addressSubLocality)
},
_record_get_as_c[StreetAddress](record, INPUT_KEYS_STREET_ADDRESS).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_STREET_ADDRESS, streetAddress)
},
_record_get_as_c[ExtendedAddress](record, INPUT_KEYS_EXTENDED_ADDRESS).flatMap {
case Some(s) => Consequence.success(s)
case None => Consequence.successOrPropertyNotFound(PROP_EXTENDED_ADDRESS, extendedAddress)
}
).mapN(Address.apply)
}
def build(record: Record): Address = {
buildC(record).TAKE
}
}
object Builder {
}
def createC(addressCountry: CountryCode, postalCode: PostalCode, addressRegion: Region, addressLocality: Locality, addressSubLocality: SubLocality, streetAddress: StreetAddress, extendedAddress: ExtendedAddress): Consequence[Address] = {
val builder = Builder()
val builder2 = builder.withAddressCountry(addressCountry).withPostalCode(postalCode).withAddressRegion(addressRegion).withAddressLocality(addressLocality).withAddressSubLocality(addressSubLocality).withStreetAddress(streetAddress).withExtendedAddress(extendedAddress)
builder2.buildC()
}
def create(addressCountry: CountryCode, postalCode: PostalCode, addressRegion: Region, addressLocality: Locality, addressSubLocality: SubLocality, streetAddress: StreetAddress, extendedAddress: ExtendedAddress): Address = {
createC(addressCountry, postalCode, addressRegion, addressLocality, addressSubLocality, streetAddress, extendedAddress).TAKE
}
def createC(record: Record): Consequence[Address] = {
val builder = Builder()
builder.buildC(record)
}
def create(record: Record): Address = {
createC(record).TAKE
}
}
The above is a part of the generated code focused on Address .
In practice, multiple classes for each Value Object are generated like this, and each is treated as an independent type.
-
Address is a root structure combining multiple Value Objects.
-
CountryCode and PostalCode are Value Objects that wrap single values.
-
Logic for building and validation is also included via Builder and Validation.
You can see that the structure and constraints defined in the model are directly expanded as types and logic.
-
Address.scala
-
CountryCode.scala
-
ExtendedAddress.scala
-
Locality.scala
-
PostalCode.scala
-
Region.scala
-
StreetAddress.scala
-
SubLocality.scala
The total number of steps is about 1,500 lines.
Summary
CML is
-
model structure,
-
model description,
-
natural language,
all integrated into a single document. This format can be read as a specification, used for implementation, and is also suitable for AI processing.
Also, because the specification and model definition are in the same place, discrepancies are less likely to occur, and you can prevent the scattering of specification files. Furthermore, standardizing the location for descriptions helps prevent omissions, and it becomes easier to receive AI assistance for natural language descriptions.
References
Glossary
- literate model
-
A Literate Model is a “readable model” that integrates model structure with natural-language narrative (structured documentation). It extends the idea of literate programming into the modeling domain, unifying structure (model) and narrative (structured text) into a single intelligible artifact interpretable by both humans and AI. The concept of “Literate Modeling” has been explored previously by some researchers and developers, mostly as an approach to improve documentation or code comprehension. However, those attempts did not establish a systematic modeling methodology that integrates models, narrative, and AI assistance as a unified framework. The Literate Model is a modeling concept newly systematized and proposed by SimpleModeling for the AI era. Building upon the ideas of literate modeling, it redefines them as an intelligent modeling foundation that enables AI-collaborative knowledge circulation and model generation. It is not merely a modeling technique but a framework that embeds human reasoning and design intent as narrative within the model, enabling AI to analyze and reconstruct them to assist in design and generation.
- value object
-
A Value Object is an object in a domain model that represents a semantically meaningful group of values, such as attributes or descriptions. It does not have a persistent identity like an entity, is immutable, and equality is based on its values.
- 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.
- validation
-
Validation is the activity of confirming that a system or product fulfills its intended use and stakeholder requirements.