Skip to main content

第34章:Published Language(公開する言葉)📢

ねらい🎯

この章を終えると、こんなことができるようになります😊✨

  • 境界(BC)をまたぐときの「契約の言葉(Published Language)」を説明できる📣
  • API / イベント / DTO を「契約」として設計できる📦
  • 変更しても壊れにくい “後方互換” の感覚がつかめる🔁✅

1) Published Languageってなに?📚✨

公開された言葉

**Published Language = 境界を越えてやり取りするために“公開する言葉”**です📢

たとえば、受注BC→配送BCに「注文が確定したよ!」を伝えるとき、**共通で理解できる用語・形式(データ構造)**が必要になりますよね🙂

DDDの定義でも、「既存のドメインモデルをそのままデータ交換言語にすると複雑すぎたり未ドキュメントで、しかも凍結して進化できなくなる。だから“よくドキュメント化された共有言語”を使おう」って話になります。(Domain Language)


2) ミニECでの例🛒📦🚚

受注管理BC(OrderManagement)と配送BC(Shipping)があるとします👇

  • 受注管理BCの中:

    • Order 集約(内部ルールがぎっしり)🧠
    • 割引在庫引当 の都合でモデルが頻繁に変わる🔁
  • 境界を越えるとき:

    • 配送BCが欲しいのは **「配送に必要な最低限」**だけ📦✨
    • だから “公開用の言葉”=Published Language を作る💡

例:OrderPlaced(注文確定)イベント

  • 配送BCが必要:OrderId / ShippingAddress / Items くらい
  • いらない:受注側の内部事情(割引計算の途中経過とか…)🙅‍♀️

3) ユビキタス言語・ACLとの関係🧩🔗

ここ、混ざりやすいので整理します😊

  • ユビキタス言語🗣️:BCの“内側”で一貫する言葉
  • ACL(Anti-Corruption Layer)🛡️:外のクセを内側に入れない「翻訳・防波堤」
  • Published Language📢:境界越えのために“公開する”共通言語(契約)

イメージとしてはこう👇

  • 相手がバラバラ(外部サービス/他部署)なら:ACLで守る🛡️
  • 自分が提供側で、複数の利用者がいるなら:Published Languageを整備して公開する📣 (Open-host Service と組み合わせが多い、って整理もあります)(Domain Language)

4) Published Languageを作ると何が嬉しい?😍✨

✅ うれしいこと

  • 境界の外に “内部モデルのゴチャゴチャ” を漏らさない🔒
  • 連携の変更に強くなる(壊れる範囲が小さい)🛡️
  • 仕様(契約)がドキュメント化されて、会話が速くなる🚀

⚠️ ありがちな事故

  • 「ドメインクラスをそのままDTOにして外へ出す」 → その瞬間、外部互換のために内部モデルが凍る🧊💥(進化できない)

5) “公開する言葉” の作り方:3つの型📦📨📣

Published Language は、だいたい次のどれか(または複合)です👇

  1. HTTP APIの契約(JSONレスポンス・URL・パラメータ)🌐
  2. イベントの契約(イベント名・ペイロード)📮
  3. 共有DTO/スキーマ(社内標準のJSON Schema / Protobuf 等)📑

この教材ではまず わかりやすいJSON契約で進めます🙂✨


6) 後方互換(Backward Compatibility)超入門☘️🔁

“後方互換”って?

昔のクライアント(利用者)が、何も変えなくても動く状態のこと😊 Published Languageではこれがめっちゃ大事です🧡

実務でよくある「互換ルール」例🧾

たとえば Microsoft の Microsoft Graph の方針だと、こんな感じで「何が破壊的変更で、何が互換変更か」を具体例で示しています👇

  • 破壊的(NG):プロパティ削除/リネーム/型変更、URL変更、必須ヘッダ追加…💥
  • 互換的(OK):nullable/既定値付きのプロパティ追加、enumメンバー追加…✅ (「未知のプロパティが来ても耐えるクライアントにしてね」という注意もあります)(Microsoft Learn)

また、Microsoft の Azure系では、安定版は後方互換で長く使える前提で、api-version を明示して進めるやり方が説明されています。(Microsoft Learn)


7) 互換を壊しにくい「変更のコツ」🍀✨

✅ やっていい変更(壊れにくい)

  • フィールドを 追加(しかも optional / default で解釈できる形)➕✅
  • enum に値を 追加(受け側が未知値を許容できる設計なら)➕✅
  • 新しいエンドポイント(またはイベント v2)を 追加📌✅

❌ できれば避けたい変更(壊れやすい)

  • フィールド削除 / リネーム / 型変更 🧨
  • 意味をこっそり変える(同じ名前で別の意味にする)😇
  • “必須” を増やす(昔のクライアントが送れない)📛

8) C#で「公開用DTO」を作る例(API編)💻📨✨

ここからミニECの「受注管理BC」が外へ公開する想定でいきます🛒

8-1) 内部モデル(例)🧠

内部は自由に設計・進化できる(=頻繁に変わる)前提でOK👌 外に見せません🙅‍♀️

// 受注管理BCの内部(例:ドメイン層)
public sealed class Order
{
public OrderId Id { get; }
public DateTimeOffset PlacedAt { get; }
public IReadOnlyList<OrderLine> Lines { get; }

// 割引・クーポン・税計算など、内部事情が増えていく…
}

8-2) Published Language(公開用DTO)📢

外に出すのは **「契約」**なので、わかりやすく・薄く・安定させます🙂

// 公開用(契約)DTO:v1
public sealed record OrderPlacedV1Dto(
string OrderId,
string ShippingPostalCode,
string ShippingAddressLine1,
OrderItemV1Dto[] Items
);

public sealed record OrderItemV1Dto(
string Sku,
int Quantity
);

ポイント✨

  • OrderId は ValueObject をそのまま出さず string化(境界の外に依存を出しにくい)🔒
  • 配送に不要な情報は入れない🥗

8-3) Minimal APIで公開(v1 / v2)🌐

C#の最新機能は .NET 10 + C# 14 が前提になっています。(Microsoft Learn) (.NET 10 と Visual Studio 2026 は 2025/11 にリリース、LTSとして 2028/11 までサポート、という位置づけです)(Microsoft for Developers)

ここでは「URLに v を入れる」一番わかりやすい型でいきます😊

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// v1: 既存クライアント向け(壊さない)
app.MapGet("/api/v1/orders/{orderId}/placed",
([FromRoute] string orderId) =>
{
// 本当はドメインからデータ取得してマッピングする
var dto = new OrderPlacedV1Dto(
OrderId: orderId,
ShippingPostalCode: "100-0001",
ShippingAddressLine1: "東京都千代田区...",
Items: new[]
{
new OrderItemV1Dto("SKU-001", 1),
new OrderItemV1Dto("SKU-777", 2),
}
);

return Results.Ok(dto);
});

// v2: フィールド追加(互換のため、v1は残す)
app.MapGet("/api/v2/orders/{orderId}/placed",
([FromRoute] string orderId) =>
{
var dto = new OrderPlacedV2Dto(
OrderId: orderId,
ShippingPostalCode: "100-0001",
ShippingAddressLine1: "東京都千代田区...",
ShippingAddressLine2: "マンション名など",
Items: new[]
{
new OrderItemV2Dto("SKU-001", 1, "Tシャツ"),
new OrderItemV2Dto("SKU-777", 2, "本"),
}
);

return Results.Ok(dto);
});

app.Run();

// v2 DTO(追加=壊れにくい)
public sealed record OrderPlacedV2Dto(
string OrderId,
string ShippingPostalCode,
string ShippingAddressLine1,
string? ShippingAddressLine2, // v2で追加(optional)
OrderItemV2Dto[] Items
);

public sealed record OrderItemV2Dto(
string Sku,
int Quantity,
string? DisplayName // v2で追加(optional)
);

ここが超大事💡

  • v1は残す → 既存利用者が壊れない✅
  • v2は 追加で進化 → 新利用者が便利になる✅

9) C#で「公開する言葉」を作る例(イベント編)📮✨

イベントは **“過去形”**でしたね🌩️ ここでも Published Language を意識します😊

9-1) イベント名にバージョンを入れる📌

  • OrderPlaced.v1
  • OrderPlaced.v2

(イベントは「あとから取り返しがつかない」ことが多いので、バージョンを明示すると安全🔒)

9-2) v1 → v2 の進化(追加が基本)➕

// v1 イベント(契約)
public sealed record OrderPlacedV1(
string OrderId,
string ShippingPostalCode,
OrderItemV1[] Items
);

// v2 イベント(契約): 追加
public sealed record OrderPlacedV2(
string OrderId,
string ShippingPostalCode,
string? ShippingAddressLine1,
OrderItemV2[] Items
);

public sealed record OrderItemV1(string Sku, int Quantity);
public sealed record OrderItemV2(string Sku, int Quantity, string? DisplayName);

10) “契約を壊してない”をテストする🧪✅

10-1) 互換テストの超かんたん例✨

「昔のJSON(v1)を、新しいDTO(v2)で読めるか?」をチェックします。 (追加は壊れにくい、をテストで保証する感じ😊)

using System.Text.Json;
using Xunit;

public class ContractCompatibilityTests
{
[Fact]
public void V1_payload_can_be_deserialized_as_V2()
{
// v1の利用者が送ってくる/保存されているJSONのつもり
var v1Json = """
{
"orderId": "ORD-123",
"shippingPostalCode": "100-0001",
"items": [
{ "sku": "SKU-001", "quantity": 1 }
]
}
""";

var v2 = JsonSerializer.Deserialize<OrderPlacedV2Dto>(v1Json);

Assert.NotNull(v2);
Assert.Equal("ORD-123", v2!.OrderId);
Assert.Null(v2.ShippingAddressLine2); // v2追加分は無い=nullでOK
}
}

public sealed record OrderPlacedV2Dto(
string OrderId,
string ShippingPostalCode,
string? ShippingAddressLine2,
OrderItemV2Dto[] Items
);

public sealed record OrderItemV2Dto(string Sku, int Quantity, string? DisplayName);

11) 実務の定番:APIバージョニング支援ライブラリ🧰✨

「ルートだけでバージョンを管理する」でも十分スタートできます👌 でも、運用が大きくなってきたら APIバージョニングの仕組みが欲しくなります🙂

ASP.NET向けには ASP.NET API Versioning(Aspプロジェクト) があり、Minimal APIにも対応していて、Microsoft REST Guidelines のセマンティクスに沿うように作られています。(GitHub) また、昔の Microsoft.AspNetCore.Mvc.Versioning 系は非推奨になって移行が案内されています。(GitHub)

(この章では“Published Languageの考え方”が主役なので、導入は次の段階でもOKです😊)


12) ミニ演習🎮✅

演習A:公開DTOを「最小」に削る🥗✂️

受注管理BCから配送BCへ渡す情報として、次の候補から **“配送に必要な最小”**を選んで OrderPlacedV1Dto を作ってみよう✨

候補👇

  • OrderId
  • CustomerId
  • ShippingAddress
  • BillingAddress
  • DiscountDetail
  • Items (Sku, Quantity)
  • TaxBreakdown
  • PlacedAt

ゴール🎯

  • できたDTOを見て「配送BCの会話」になってるか確認👀✅

演習B:v2を「互換を壊さず」に追加する➕🔁

v1を残したまま、v2で次を追加してみよう✨

  • ShippingAddressLine2(任意)
  • Item.DisplayName(任意)

13) つまずきポイント集😵‍💫💡

  • DTOが太りすぎる: 「なんでも入れる」が始まったら黄色信号🚥(“必要最小”に戻す)

  • ドメイン型をそのまま出しちゃう: 便利だけど、外部互換で内部が凍りやすい🧊💥

  • “同じ単語で別の意味”が混ざる: Published Language は “境界を越える共通言語” なので、意味のズレは致命傷になりやすい🧨


14) お助けAIプロンプト🤖✨

  • 「このBC間連携に必要な“最小DTO”を提案して。配送側が必要な項目だけに絞って」🥗
  • 「v1 DTOから、後方互換を壊さずにv2へ進化させる追加案を3つ出して」➕🔁
  • 「このJSONを“破壊的変更か/互換変更か”で分類して理由も書いて」🧾✅
  • 「v1→v2互換テスト(xUnit)を作って。v1 JSON を v2 DTO で読めることを保証したい」🧪