文芸モデルの実例:住所
本記事は文芸モデル (literate model)の実例を見て文芸モデルのイメージを掴んでもらうための導入記事です。文芸モデルの概念は 📄 文芸モデル を参照してください。ここでは、住所モデルの自然文と構造定義、さらに Cozy が出力する Scala ソースを並べて、文芸モデルの雰囲気を確認できるようにしています。
文芸モデル
-
自然文に、構造モデルや記述モデルを埋め込む形式です
-
仕様書として読めて、そのままプログラム生成にも使われます
自然文自体も重要な仕様であり、AI時代には直接開発作業に利用できる重要な情報です。
文芸モデルの特徴として、
-
仕様とモデル定義が同一箇所にあるため、仕様の乖離が起きにくい
-
仕様ファイルの散逸が起きない
-
仕様記述が定型化されるため、書き漏らしが起きにくい
-
自然文記述作成時のAIアシストを受けやすい
といった利点があります。
本記事では、住所をモデル化したAddressを例に、Addressに求められる機能や背景、ユースケースなどを自然文として記述しています。
文芸モデルの例
文芸モデルの例として住所をモデル化したバリュー・オブジェクトであるAddressのCML (Cozy Modeling Language)を以下に示します。
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`.
address.cmlの中には、モデル構造だけでなく、背景や設計意図も自然文として含まれており、そのまま仕様書として読むことができます。
冒頭の自然文には、このモデルの目的や設計方針、外部標準との関係が記述されています。 VALUE節ではモデル構造が定義され、その直下に SUMMARY や DESCRIPTION といった説明が付与されています。
さらに、Example や Background、Mapping などのセクションによって、
-
想定される利用例
-
技術的な背景
-
外部仕様との対応関係
が一つの文書の中にまとめられています。
モデル構造と説明が分離されていないため、通常の仕様書と同じ感覚で読み進めることができるわけです。
見るポイント
Address.cmlの内容からモデルとして利用される部分だけ抜き出したものを以下に示します。
ここでは、モデル構造として利用される部分を抜き出しています。
# 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
この抜き出されたモデル構造は、そのままプログラムの型定義やバリデーション仕様として利用される部分です。
ただし、この部分だけを見ると、情報は「構造」と「制約」に限定されており、 従来のプログラミングにおけるデータモデル仕様とほぼ同じものになります。
AIの観点では、この情報だけでは十分ではありません。
-
なぜこの構造になっているのか
-
どのようなユースケースを想定しているのか
-
外部標準とどのように関係しているのか
といった意味的な情報は、この上にある自然文の中に記述されています。
文芸モデルでは、構造モデルと自然文が一体となることで、AIが扱うための十分な情報量を持つ仕様になります。
例
Address.cmlで扱われるデータの形式は以下のようなものになります。
この形式は、Addressの各属性に対応する値をそのまま記述したものです。
構造モデルで定義されたフィールドが、そのままデータのキーとして現れ、 値は各 Value Object の制約に従った形で記述されます。
モデル構造が、そのままデータ形式として表れることが分かります。
addressCountry: JP
postalCode: "160-0022"
addressRegion: Tokyo
addressLocality: Shinjuku-ku
addressSubLocality: Shinjuku
streetAddress: "1-2-3"
extendedAddress: "Building A 101"
Scalaコード
ここから生成されるコードは、単なるデータクラスだけではなく、
-
Value Objectとしての型定義
-
Builder
-
Validation
-
Record変換
などを含む実装コードになります。
ここにある Scala は、文芸モデルから実際にこんなコードが出る、というイメージを見るためのものです。 細かい実装の読み込みは目的ではなく、 Address が型付きの Scala クラスとして展開される感覚が伝われば十分です。
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
}
}
上記は Address を中心とした生成コードの一部です。
実際には、このような Value Object ごとのクラスが複数生成され、 それぞれが独立した型として扱われます。
-
Address は複数の Value Object を組み合わせたルート構造
-
CountryCode や PostalCode などは単一値をラップした Value Object
-
Builder や Validation によって、構築と検証のロジックも含まれる
モデルで定義した構造と制約が、そのまま型とロジックとして展開されていることが分かります。
-
Address.scala
-
CountryCode.scala
-
ExtendedAddress.scala
-
Locality.scala
-
PostalCode.scala
-
Region.scala
-
StreetAddress.scala
-
SubLocality.scala
総ステップ数は1500行程度になります。
まとめ
CMLは、
-
モデル構造
-
モデル記述
-
自然文
を一つの文書に統合した形式です。 仕様書として読むことができ、実装にも利用でき、AIによる処理にも適した形式です。
また、仕様とモデル定義が同一箇所にあるため乖離が起きにくく、 仕様ファイルの散逸も防ぐことができます。 さらに、記述場所が定型化されることで書き漏らしが起きにくく、 自然文記述に対するAIアシストも受けやすくなります。
参照
用語集
- 文芸モデル (literate model)
-
文芸モデル(Literate Model)は、モデル構造と自然言語による語り(構造化文書)を統合した「読めるモデル」です。 文芸的プログラミング(Literate Programming)の思想をモデリング領域に拡張し、 構造(モデル)+語り(構造化文書) を一体化することで、人間とAIの双方が理解・操作できる知識表現を実現します。 「Literate Modeling(文芸的モデリング)」という発想自体は、 これまでにも一部の研究者や開発者によって試みられてきました。 しかし、それらは主にドキュメント生成やコード理解の支援にとどまっており、 モデルと言語・語り・AI支援を統合した体系的なモデリング手法として確立されたものではありません。 文芸モデル(Literate Model)は、SimpleModelingがAI時代に向けて新たに体系化・提唱したモデリング概念です。 文芸的モデリングの思想を継承しつつ、 AI協調型の知識循環とモデル生成を可能にする知的モデリング基盤として再構成されています。 文芸モデルは、単なるモデル記述技法ではなく、 人間の思考過程や設計意図を語りとしてモデルに埋め込み、 AIがそれを解析・再構成して設計や生成を支援するための枠組みです。
- バリュー・オブジェクト (value object)
-
バリュー・オブジェクトは、ドメイン・モデルにおいて属性や説明などの意味的まとまりを持った値の集合を表すオブジェクトです。 エンティティのような永続的識別子は持たず、不変であり、等価性は値によって判断されます。
- CML (Cozy Modeling Language)
-
CMLは、Cozyモデルを記述するための文芸モデル記述言語です。 SimpleModelingにおける分析モデルの中核を担うDSL(ドメイン固有言語)として設計されています。 モデル要素とその関係性を自然言語に近い文体で記述できるよう工夫されており、AIによる支援や自動生成との高い親和性を備えています。 CMLで記述された文芸モデルは、設計モデル、プログラムコード、技術文書などに変換可能な中間表現として機能します。
- 妥当性確認 (validation)
-
Validation(妥当性確認)とは、システムや機能が利用目的や要求仕様に対して妥当であるかを確認する行為である。