🍒

値オブジェクトへの誤解が生まれる一つのストーリー - 文脈と定義を大事にする

2022/08/18に公開
2

先日、

https://zenn.dev/339/articles/e3c174fdcc083e

という記事を書いたところ、思ったよりも反響がありました。その影響があったかは不明ですが、また値オブジェクトについての話題がちょびちょびと発生していました。
そのやり取りの中で、私は未読だった論文が紹介されていて、その論文を読んだことで「このようにすると値オブジェクトに誤解が生じる」という一つのストーリーを認知できたため、どのようにこの論文を読むと誤解が発生するか、という事について説明します。

なお、前回書いた記事も、この記事も、誤りを糾弾したいとか、誤ったから著者が悪であるといった事を主張しているわけではありませんので、改めて記しておきます。この記事では、単純に事実の指摘と修正の提案、およびなぜ文脈や定義を大事にする必要があるのかという事について述べます。
いい加減、値オブジェクトの話題はしつこすぎるのでは?非生産的なのでは?そんな事よりもっと生産的な事をしたら?というご意見もあると思います。
そのような意見を決して否定するものではなく、私自身もそう感じる部分がありながらも、しかしなぜ敢えて「値オブジェクトへの誤解が生まれる一つのストーリー」という形でまとめたのか、一読して考えて頂けると大変ありがたいです。紹介された論文を読んでかなりまじめに考えた結果、「こうすると確かに誤解が発生し得る」というメカニズムの一つを理解したので、そのメカニズムを説明しておきたいという気持ちで書いています。
なお、私が誤解だと思っている思考がすべてここで説明するストーリーによって生まれたかはわかりません。あくまでも、

  • ここで説明するストーリーに従えば、たしかに誤解が発生する
  • しかも誤解している人からするとそれを誤解と認知する事も難しい
  • 文脈と定義を大事にすればそのような誤解は減る

というのが伝えたいことです。私自身、論文を読み進める上で、しばらく混乱した後で「これはかなり難しい・偶発的な論理構造に基づく誤解なのだ」と理解して、そのことについて素直に感動したので、可能であればその論理構造の難しさがうまく伝わってほしい、というような気持ちです。

※2022/8/19追記 Domain Primitiveとの関係性の話だったのでは、というツッコミを見かけたので、Domain Primitiveについて少しだけ補足しました。
追記:domain-primitiveとvalue-objectの関係性について

Value Objectの定義を考える

ミノ駆動本には、

値オブジェクト(Value Object)とは、値をクラス(型)として表現する設計パターンです。

と書かれています。私は前回の記事で、これを否定して以下のように書きました。

同一性がそれを構成する値によってのみもたらされるようなもののことを意味します。いわゆるメモリの位置と説明されるオブジェクトとして等価かどうかとか、各オブジェクトに割り当てられたその他のidが等しいかどうかとか、そういう事によって同一性が与えられる訳ではないもの の事です。

実際、私が目にした限りでは、ほとんどの文献に同一性に関する指摘がありました。
なお、Value ObjectのValueと対比される概念は必ずしも一つではなく、文脈や議論の対象のレイヤーによって、

Entity ... 主にDDDの文脈で、identityを持つモデル(モデリング対象)がEntity、そうでないモデルがValue Object。
Reference ... 主にやや実装に近い文脈で、プログラミング言語で参照と扱われるものReferenceと対比される概念としてのValue。
Object ... (後述します)

という3種類が定義としては 代表的と思います。
(もし他にあればぜひコメントください。ちなみに、↓のツイートのPrimitive Obsession避けについては、確かに対策としてValue Objectを使える・Value Objectの一つのご利益であるというような主張は日本以外でも存在していますが、それを直ちに定義としているものはあまり無いかなと思っています。)

※なお、Objectについてはこの後で詳しく説明しますが、私は元々このObjectとReferenceは同じようなものと考えていました。
https://twitter.com/kawasima/status/1524303151754780672
このツイートの参照オブジェクトというやつです。実際にそう思ってObjectという言葉を使っている人も日本に限らず存在するのではと思いますが、これから述べる1998年の論文では、Objectはモデリングにおける概念のことでした。

ここまで、今回の話の背景事情です。

ValueとObjectに関する1998年に書かれた論文

私はValue Objectの定義に興味があったのですが、1998年、つまりPoEAAやDDDが出版されるよりも前に書かれた論文が紹介されているのを見かけて、読んでみました。

https://www.riehle.org/computer-science/research/1998/ubilab-tr-1998-10-1.pdf

Value in Object Systemsというタイトルの論文です。

論文におけるvalue objectの定義

はじめ、私が特に注目したのは次の記述です。

(前略)

Values in computing systems are instances of value types,
for example Integer, Float, String, Date, SocialSecurityNumber, Currency, MonetaryAmount, and AccountNumber.

(中略)

Given no specific programming language concept, value types have to be implemented as classes, for example as classes Date, SocialSecurityNumber, or MonetaryAmount. From a conceptual point of view, we are still dealing with value types, be they implemented as classes or not. We call an instance of a value type a “value object”.

つまり、

  • 計算システムにおける値はvalue typeのインスタンスである
    • 具体例:Integer, Float, String, ..., AccountNumber
  • 特定の言語を仮定しない場合には、value typeはクラスで実装される
    • ただし、実装がクラスでないものでもvalue typesとして扱う
  • value typeのインスタンスを"value object"と呼ぶ

なるほど、この論文では

値オブジェクト(Value Object)とは、値をクラス(型)として表現する設計パターンです。

とかなり近いことが書かれているように見えます。少なくとも、私が上記で抜き出した部分は、「値オブジェクトは設計パターンではなくてインスタンスである」というような言葉の帳尻合わせみたいな部分を調整して

値オブジェクト(Value Object)とは、値をクラス(型)として表現した概念である

と言いなおせば、嘘でないように見えます。
そう、確かにそうなんですが、では、ここでいう値、つまりvalueとは...?

実は、これが核心なのです。

モデリングの概念としてのvalueとobject

この論文の3章のタイトルは「Objects and Values」です。3章のはじめには、

We now review the properties of objects and values as complementary modeling concepts.

と書いてあり、objectsとvaluesはともにモデリングの概念であって、相補的なものであるという事がわかります。この論文のvalueはモデリングの概念であるという前提の上で、

3.2 Values and value types
において、valueの定義がV1〜V4として示されています。いわく、

• Values are abstractions (universals or concepts) which model abstractions from a problem domain.
• Values have no lifecycle (i.e., they do not exist in time, are not created nor changed nor deleted).
• Values have no alterable state; representations can only be interpreted, not changed.
• Values are referentially transparent (i.e., there are no side-effects of using a value on other parts of the system).

(V1)問題領域をモデル化・抽象化して得られる概念で、(V2)ライフサイクルがなく、(V3)ステートレスで、(V4)参照透過的であるもの
となっています。これがvalueなのですね。そう、さきほど私が特に注目した記述はこの定義の後に出てくるのですが、

Values in computing systems are instances of value types,

というのはvaluesの定義というより、value typesが何かという説明なのでした。
このvalue typesですが、(当時の)一部のオブジェクト指向言語では厳密にvalueとして定義をする方法が提供されておらず(例えばJavaで定義できるクラスは全てObjectクラスを継承しており実装上は参照になる)、objectを使ってvalueを実装するという手法が必要になるわけです。この実装方法で得られるクラスがvalue type、そのインスタンスがvalue objectという事でした。
つまり、上記のV1〜V4を満たすようにvalueを(クラスで)実装したものがvalue objectである ということです。この論文の文脈では、値というのはV1〜V4の性質を満たすと定義されていて、そのような性質を持つクラスをvalue objectと呼んでいたのです。(ちなみに、Objectについても同様にV1〜V4と対となるO1〜O4による定義が与えられています。)
このV1〜V4によるvalueの定義を通じたvalue objectの定義は、冒頭で述べた「object」とvalueを対比した文脈での定義で、ここにおけるvalueやobjectという概念はモデリングについての概念です。DDDや言語/実装の文脈ではなくて、モデリングの文脈において、抽象化されたValueとObjectの概念の対比で生じているものです。
ただ、DDDや言語/実装と文脈・レイヤーが異なるとはいえ、本質的な主張は大きく逸脱しているものではありません。私の感覚では、これらの定義は厳密には勿論異なるけれども、ざっくり同じ概念を違うレイヤーで捉えたものと理解しても良いのではないかと思います。もちろん、厳密な話をするときは区別が必要かもしれませんが、これらについては私は致命的な差はないと考えています。(これは個人の考えですので、もっと厳密な考え方もあると思います。)

このとき、重要なのは、V1〜V4を満たすようにvalue type/value objectを実装すると、自然とvalueはidentityによって同一性が与えられるタイプの概念ではないと確定する ということです。valueという言葉が重要な意味を持っていて、一言で「valueをクラスによって実装する」と言っても、この論文では裏にこのような意味付けが伴うという事になります。

実際、たとえばくまぎさんが
https://kumagi.hatenablog.com/entry/value-object
の記事において指摘している

Value Objectの出発点はそもそもcompound(合成物)であって単一の値を包めとは一言も言ってない
プリミティブ型を包んでもID(e.g. ポインタ)で比較していたらValue Objectと呼べない
包まれる値はプログラム言語のプリミティブ型とは限らなくて別のValue Objectであるケースも有り得る
ポインタやIDで比較しようが型システムの恩恵は得られるのでValue Objectに固有のアイデアではない
クラスを定義する際に固有の振る舞いを定義するのはOOPの基本でありValue Objectに固有のアイデアではないし、Value Objectを表すクラスに常に便利なメソッドを足せとはマーチン・ファウラーも言っていない
immutableは共有と複製の違いをぼかしたまま使うための工夫に過ぎなくて、常に複製しても良いとマーチン・ファウラーも言っている

これらは、「マーチン・ファウラー」などの語について適宜言い換えをすれば、すべてこの論文における主張と整合的です。
つまり、Value Objectが扱うものの"中身"が単一の値であるとは書いてないし、identityを利用していればそれはObjectであってValueでない(したがってValue Objectでない)し、Value Objectのネストも否定していない(そもそもこの論文の場合はモデリングの話なのでプリミティブとかいう概念から離れたレイヤーの話である)し、型という意味では通常のObjectにも当たり前に存在しているし、Value Objectに常に便利なメソッドを足すべきという主張もないし、Value Objectの実現パターンとしては複製もあるということがこの論文の中に書かれています。
上記各論点はこの論文と整合的で、レイヤーや元になる概念の差はあれど、本質的には同じような事を主張していると捉えられるものと思います。(もし違うとすれば、ぜひコメントください。)

ミノ駆動本に戻るとどうなっているか、値オブジェクトについての誤解

さて、ここでミノ駆動本に戻ると、値オブジェクトについては

値オブジェクト(Value Object)とは、値をクラス(型)として表現する設計パターンです。

と書かれていたわけですが、ここで重要になるのは「値とはなにか」という事です。
つまり、ここでいう"値"が上記論文のようにして定義されているものであれば、(設計パターンではないとしても)本質的に間違っているとは言えません。
しかし、値についての定義はぼかされたようなものしかなく、値オブジェクトの他の表現にしても「値オブジェクトとして設計する対象は、アプリケーションで取り扱う値、概念です。」といった記述になっていて、通常のオブジェクトとして扱うべきものとvalueとして扱うべきものの区別がありません。
値(値オブジェクト)についての具体例はあるものの、V1〜V4の各概念を満たす例なのだという主張として読み解くことはできません。
そのため、値について論文のようなモデリングの文脈を持っていない人からすると、ミノ駆動本における定義は「値をクラスによって設計すれば、それは値オブジェクトという設計パターンになる」という誤解を受ける記述となっています。
実際には、ある実装が値オブジェクトであるということは、それは概念としてライフサイクルがなく、ステートレスで、参照透過でなければならないし、そもそも値は「問題領域をモデル化・抽象化して得られる概念」でなければならないのですが、ミノ駆動本の記述をそのように読解するのは無理があります。
そうすると、ミノ駆動本で述べられた定義は、ざっくり私が同じようなもののレイヤー違いの考察と思って良いと考えるDDD(Entity)文脈、Reference文脈、モデリング(Object)文脈、のそれぞれと大きく異なるものであって、値オブジェクトの定義としては誤解であると言えます。より丁寧に言えば、筆者がどのように理解されているかはわかりませんが、少なくとも書かれた内容としては誤解を生じるものである ということです。
このことにどのような問題があるのかというと、この本に書いてあることを初学者が実践しているつもりで、実際に日本語における定義には適合しているにも関わらず、しかし多くの本で考えられている値オブジェクトと異なるものを実装してしまい、それがアンチパターンであるというような事が自然に発生するという問題があります。例えばmutableな値をもつクラスのインスタンスを値オブジェクトと呼んで、(値オブジェクトなのに)mutableかつ複数の変数として共有できる実装を許容 してしまったり、それをidentityによって比較したり、などということが、筆者の期待と反して起こる可能性があり、そうすると悪いコードを生むことを手助けしてしまいます。実際、ミノ駆動本に「良いコード」として紹介されているコードはしばしばmutableなクラスの例を含んでおり、それがvalueではなくてobjectであることの理由が与えられることもありません。また、言葉が指し示す概念が他の本で勉強した人と一致しない事によって、(DDD/Reference/モデリングの文脈の差よりもずっと)話が噛み合わなくなるという問題もあります。

なお、ここでさらに物事を難しくしている事があります。それは、ミノ駆動本では、不変性を重視しているということです。つまり、不変になるようにクラスを設計すると、そのような対象は結果的に値オブジェクトの定義に該当する場合も出てくるので、「日本語の定義としては正しくないが、その後に補足された実装テクニックを合わせれば結果的に値オブジェクトといえるものができて、理屈・理解としては正しくないが、設計としてはとりあえず正しく動いてしまう」 という場合が生じます。これによって、定義が間違っているのに何が間違っているのか見方によってはわからない、という事が発生してしまうのです。
このことによって、「厳密な定義とは異なるかもしれないが、結果的に正しいのだから良いではないか」という意見も生じるようになります。これはうまくいく場合だけを切り取ればたしかにそうですが、上述のように日本語の定義として正しくない事に起因する弊害が発生するので、良い事ばかりとは言えません。別の言い方をすれば、「日本語の定義を誤解が生じない程度に正しい記述にすれば、よりミノ駆動本の価値を損なう事なく伝えられるようになり、関係する誰もがハッピー」という事になります。

文脈や定義を省略して、単語だけを拾って読むと誤解が生じる

さて、ここまで、

  • いくつかの本や論文における値オブジェクトの定義は、ざっくりとは同じようなものとして捉えられる(レイヤーの違いであったり、細かい定義や拡張の差はあるので、厳密には同じものではないが、"概ね"同じような概念について述べている)ということ
  • 一方で、ミノ駆動本における記述のように、一見論文の定義と同じように見えるにもかかわらず本質的には異なる対象を値オブジェクトと誤解してしまう(誤解させてしまう)ケースがある ということ

の2つのことを示しました。

ここからは、そのような誤解が生じる過程について、一つのパターンを説明します。
そのパターンとは、ずばり 「文脈や定義を省略して、単語だけを拾って自分が持っている知識と紐付けて論文を読む」 というものです。
つまり、

We call an instance of a value type a “value object”.

これだけを切り取れば、ミノ駆動本に書いてあるような定義が正しいように感じてしまいます。しかし、論文ではvalueやvalue typeというものが満たすべき性質を規定しているため、それによってPoEAAやDDDなどにおけるvalue objectに関する定義と大幅に乖離したものにはならない状態なのでした。そのような文脈を意識せずに、単語や部分的な定義だけを拾って自分の理解としてしまうと、そこで誤解が生じます。
しかも、この種の誤解は、それが誤解であるという事に気づくのも難しいです。上で見たように、偶然定義に当てはまってしまうような場合があったり、文字面だけ見れば間違っていないように見えたり、といった事象が発生してしまうので、仮に自分が誤解してしまったとしたら、それに気づくのはかなり困難であると思います。これは、私が第三者的な視点で考えていても混乱してしまうような事だったので、決して他人の誤解をバカにするような事はできません。

ただ、それはそれとして、ここで述べたように誤解は誤解だと思うので、直した方がよいと思います。
悪意やマウントを取るというような事ではなくて、直したほうが私のような人間が混乱することがなくなるからです。

誤解をなるべく避けるためにはどうするか

さて、では誤解をなるべく避けるには、どうすればよいのでしょうか。上で紹介した以外にも様々な誤解のパターンはあり得ると思いますが、少なくとも紹介したパターンを避けるためには、
「文脈や言葉の定義を勝手に省略したり、勝手に想像したりせずに、記述されていることを正確に追いかけるようにする」
という手法が有効です。文脈と定義を大事にしよう、ということです。

DDDという設計手法においては、文字通りドメインが重視されますが、ドメインを記述/描写するときには 開発者と非開発者の両方が正確に扱えるようなユビキタス言語 を用いることが強く推奨されています。これは、文脈や言葉の定義を大事にする、という事でもあります。DDDにおいてユビキタス言語が重要であるのと同程度に、技術用語の意味や定義を正確に扱うことも重要と思います。

もちろん、丁寧に読んで誤解を避けているつもりでもやっぱり誤解をしてしまう、という事はあります。人間は誰でも間違えます。ただ、だからこそ、間違えた時にさくっと修正できると、多くの人のためにもなるし、何よりカッコイイのかなと思いました。

むすび

いかがでしたか?

私としては、値オブジェクトに関する誤解をよく整理できたように思いました。つまり、丁寧に定義を追いかけないと初学者は間違える可能性があるものの、結果的に他の実装テクニックによって本来の定義を満たしてしまうような場合もあったりして、「一度誤解すると、誤解に気づくことが難しくなる」という構造があるのだなあと学ぶことができました。
これは、独学の難しさを示している事象でもあると思います。例えば、今回の記事で紹介したくまぎさんの記事の内容について、基本的には深く同意するものですが、

Value Objectとは何であるか?
マーチン・ファウラーのPatterns of Enterprise Application Architecture(PofEAA)やエヴァンス・エリックのDomain Driven Design: Tackling Complexity in the Heart of Software(DDD)が原典である

という箇所については、今回の記事で説明したように「Value Objectの概念自体は必ずしもDDDなどによるものではなく、1998年には既にValue Objectという概念を明示している論文がある」と"反論"することもできます。このような"反論"にこだわってしまうと、くまぎさんの記事の本旨が1998年の論文と非常に整合的であり、Value Objectの概念についての適切な指摘であるにも関わらず、その指摘を正しく受け止めることができなくなってしまうのです。
初学者の立場からすると、ミノ駆動本のような説明とくまぎさんの記事のような説明があったときに、どちらの方が内容を信頼できるかを容易に判断できない場合があります。職場などにくまぎさんやそれに類する人が居るような環境であれば、おそらくその判断に迷うことはなく、自然と筋の良い理解が進みますが、そのような環境に接することができなくて独学しなければならない場合には、「知識としては無料で広くアクセスできる状態になっているにも関わらず」それを理解できないという事になってしまいます。これは、少し見方を変えると、何かを学習するときには「その主張が信頼できる人・環境を探す」という事が大事で、それを間違えてしまうと誤解の沼にはまりこむ事になる、ということを示しているようにも見えます。すごく難しいですね。

今回の記事でも何度か言及したように、「こうした問題点があるからミノ駆動本は悪だ」というような事ではないです。ミノ駆動本自体は、純粋に筆者が苦労したことを一部解決するようなノウハウを提供したいという事で書かれていると思っていて、全く何も知らない人からすれば、知っておいた方がよい知識が出てくるのは確かなことです。
ただ、この記事で詳しく見たように、複雑でわかりにくい誤解を再生産する構造があるので、それを直せばより良い本になって、また状況が改善するのかなと思いました。

これは、値オブジェクトという具体的な話題に限らず、様々なことを学習する際に普遍的に言えることなのかなと思ったので、"いまさら"値オブジェクト警察をして整理しました。私の記述力や理解が及ばず、うまく伝わらない箇所もあるかもしれませんが、こうした事を頭の片隅に入れておくと、色々な物事を理解するときに役立つのではないかと思います。

補足:値の等しさ・同一性と参照透過性について

最後に、これは本筋と直接関係ない、細かーい補足ですが、Value Objectの説明では「identityによらない等しさの概念」として、しばしばequalsを明示的に実装しているか否かが焦点になる場合があるように感じています。しかし、equalsを実装すべきか否かは必ずしも重要なことではありません。実際、全てのValue Objectがequalsに相当する比較を必要とするとは限りません。
そのような場合であっても、「そのValue Objectが使用される全ての関数/メソッドにおいて、値が同じであればその他の同一性などによらず常に同じ値が返され、かつ副作用もない」という事が担保されているならば、関数/メソッドの戻り値によってValue Objectの値の等しさの概念が抽象的に定義されている と考えることができます。
比喩的に言えば、たとえば2という記号で表現される値と、twoという記号で表現される値を考えたとき、
5 + 2 = 5 + two (=7)
3 + 2 = 3 + two (=5)
というように、あらゆる足し算で2を足すこととtwoを足すことは意味的に同じになります。それがすなわち2とtwoの値の等しさの概念である、という考え方ができるという事です。仮にequalsが明示的に実装されていなくても、関数やメソッドの振る舞いによってその値が何かが本質的に概念として定義される、という考え方なのでした。

追記:Domain PrimitiveとValue Objectの関係性について

とりあえず、事実だけ記載します。そのうち詳細を追記するかもしれません。

  • Domain PrimitiveはValue Objectであって、さらに強い条件を満たすものである
    • 不変:具体的な実装として、代入時に毎回コピーするといったテクニックではなくて、不変なインスタンスを扱うというアプローチを取る
    • 不完全な状態での存在を認めない:意味的に不完全な状態でインスタンスを生成させない
  • (一部の人の主張としては)Domain Modelを記述する際は、プログラミング言語に標準として存在しているprimitiveではなくて、明示的に定義したDomain Primitiveを使わないといけない
    • Value Objectは一般にはprimitiveも含み得る概念なので、つまり一部のValue ObjectはDomain Modelの記述には不適であると言っていることになり、とても強い主張である
    • このDomain Primitiveの用法については私個人は否定的な見解で、場面によってはワークするだろうが、少なくとも常にそうあるべきという事ではないと考えている

ミノ駆動本では、定義と実際の利用シーンで乖離があって、定義は上述の通りゆるい状態になっているのですが、実際の利用シーン・推奨される使い方としてはむしろDomain Primitiveのようになっていました。
日本においては、単に値オブジェクトと言う場合にDomain Primitiveを指す場合も散見されるのですが、それはそれで意味が強すぎる(≒限定されすぎている)と思います。

Discussion

Naoki FujitaNaoki Fujita

関数型言語文脈においては「Record」と呼ばれているものですね。
これが特定の言語界隈で使われているニッチなタームなのかと言われるとそんなことはないようで、クラスベースオブジェクト指向言語の代表格であるJavaでもC#にも採用されている計算機科学の用語です。
https://en.wikipedia.org/wiki/Record_(computer_science)
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records
https://qiita.com/ReiTsukikazu/items/6dc3ec9ea9646c472db0
関数型言語においては暗黙的に「Record」はimmutableであることを期待し、Javaはその慣習に従い不変性を前提とした実装になっていますが、C#では不変性を得るためにはreadonlyを明示する必要があるようです。

言語仕様が実装を用意しているので、(新しめの言語バージョンが必要ですが)これを使えばいいのでは?と思います。(Pythonistaはnamedtupleか@dataclass(frozen=True)を使えば良いです。)
RecordとValueObjectの相違点について議論があると面白いですが、正直興味がないのでここまでで。

さざんかぬふさざんかぬふ

@naokifujita
コメントありがとうございます。私の考えとしては、RecordはValue Objectの実現にも使えますが、本質的には異なる概念であると考えています。
どこまでがRecordの本質かというのは難しいですが、Recordは一般的には(集合としての)Productのような考え方であると思っていて、例えばRGB形式で色を表現する場合には概念としてはrgb = (red,green,blue)という値の組で、rgb.redやrgb.greenといったアクセス方式が保証されているように思います。
wikipediaでいうと、以下のあたりの話です。

Construction of a record value from given field values and (sometimes) with given field names;
Selection of a field of a record with an explicit name;

しかし、抽象的な概念としてのValue Objectは、そうした「列名/フィールド名によるアクセサ」みたいなものを持つとは限らず、またValue Objectを生成するときに常にfieldの値を指定することによって得られるとも限りません。例えば、実装レベルでは、Value Objectはプライベートフィールドなどを持てるので、一般にはrgb.redみたいな事ができない可能性もあります。

他にも、rgbではなくcolorという概念について考える場合、「colorはRGBによる表現もできるがCMYKによる表現もできる」というケースを考えることができますが、これは RGBとCMYKを適当な計算式で1:1に対応させられると仮定すれば colorはred, green, blueとcyan, magenta, yellow, blackをすべて渡して生成するのではなく、RGBを決めたらCMYKが決まるとか、CMYKを決めたらRGBが決まるとか、そういうような生成の方法になるのかなと思います。
(実際にはRGB系の色とCMYK系の色は何の仮定もなく一意に対応するという事はないですが、話を簡単にするために一意に対応するものとして扱っています)
このような抽象概念としてのValue Objectについて、最終的なデータ保持はRecordやRecordの組み合わせで行われていてもよいと思いますが、概念的には違う話かなと思います。

また、Recordはしばしば特定の列を識別子/キーとする場合があって、そのようなデータ構造はむしろValue Objectではないものを表現するために使われると思います。
wikipediaにも

a record (also called a structure, struct, or compound data) is a basic data structure.

とあるとおり、Recordは基本的なデータ構造についての定義であって、それが概念的にどのような意味を持つものであるか、という事の定義ではなく、本質的にValue Objectとは異なる概念であると思います。
(Value Objectの場合は、データ構造としてはObject的であるが、概念的にはValueとして扱えるもの、というのがざっくりした定義と思います)