はじめに
この記事は NTTコムウェア Advent Calendar 2023 2日目の記事です。
NTTコムウェアの薄井です!
普段はドコモグループのプロダクトのエンジニアリングマネージャを担っておりますが、第三子の生誕に伴い、この記事が公開される頃には育休取得中の予定です。
本日はプロダクト開発やAPI提供する際に必要となるバージョニングにおいて重要な
セマンティックバージョニングについて実例を交えつつ説明してゆきます!
想定読者
ソフトウェアプロダクトやAPI等を公開する皆様
そもそもバージョニングって?
バージョン管理と表現される事もありますが、本質は例えば「v1」,「20231202」,「1.0.0」,「令和最新版」のように、プロダクトやソフトウェアに何か命名する(版を決める)事です。バージョン命名の方式をいくつか例に挙げます。
適当な名前をつけるもの
例:イナダ → ワラサ → ブリ
Android™のコードネームはお菓子の名前なのは有名な話ですね。
シーケンシャルにバージョニングをつけていく方法
例: v1 → v2 → v3 → v4
1 → 2 → 2.5 → 3
PlayStation(PS4→PS5)やWindows(Windows10→Windows11)等、
プロダクトが大きく変わった事をPRするために使われたりしています。
リリースした日付(時間)を設定する方法
例: 20231201 → 20231202 → 20231203
常に最新版を使う必要がある役所のドキュメントフォーマット等に見られます。
じゃあセマンティックバージョニングって何なのさ
というのが本題です。
バージョンの命名の仕方は前述の通り、多種多様にありますが、
大体は数字/アルファベットを組み合わせて、何となく順序性がわかるように決められる事が多いかと思います。
セマンティックバージョニングは、「考え方は似てるけど、ちょっと違うね」という命名によって起きる認識齟齬、それがもたらす悲劇を避けるべく、ルールを決めて、共通言語化すべくGitHubの共同創業者であるTom Preston-Werner氏が2010年に提唱したもので、GitHubやGoogleでも使われる等、ソフトウェアプロダクトのバージョニングにおいてデファクト化してきているバージョニング手法です。
Fig.1 「考え方は似てるけど、ちょっと違うね」という命名によって起きる認識齟齬、それがもたらす悲劇の例
セマンティックバージョニングの詳細はオフィシャルサイトの説明 https://semver.org/lang/ja/ に委ねますが、
以下に概要をかいつまんで記載します。
- バージョンナンバーは、メジャー.マイナー.パッチ(例: 1.0.0)とする。
- バージョンを上げる際には
- APIの変更に互換性のない場合はメジャーバージョンを、
- 後方互換性があり機能性を追加した場合はマイナーバージョンを、
- 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。
- 各バージョンは数値的にバージョンアップしなければなりません
- マイナーバージョンを上げた際にはパッチバージョンを0にリセットしなければなりません
わかりにくいので例
というわけで、わかりにくいので例を挙げて説明してゆきます。
例として、(育休にちなんで)赤ちゃんの情報を登録するプロダクト(WebAPI)を1.0.0としてリリースしてみることにしましょう。
1.0.0仕様
- name, sexをフィールドに持つリクエストを受けられる
- nameは必須項目
- nameは文字列
- nameは上限20文字を許容。
- sexは文字列
- sexはfemale or maleのいずれか
- 登録成功すると200を返す(登録した内容をオウム返しする)
- フィールドに誤りがあると400を返す
サンプル(OpenAPI)
SwaggerEditor等で確認ください
openapi: 3.0.3
info:
title: Baby API
version: 1.0.0
paths:
/baby:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"400":
description: Invalid input
components:
schemas:
Pet:
required:
- name
type: object
properties:
name:
type: string
example: a.k.a. chan
maxLength: 20
sex:
type: string
enum:
- female
- male
めでたく初期リリースができましたが、ユーザからの要望で、体重の情報を登録できる機能を追加することになりました。
この場合、機能追加となるのでマイナーバージョンのアップとなり、セマンティックバージョニングのルールに従うとバージョン1.1.0となります。
もし体重の情報を必須項目とする場合は、ユーザからの登録情報がこれまで通り名前と性別のみの場合、受け付けられなくなる(=後方互換が失われる)ので、メジャーバージョンのアップが必要となってしまいます。
1.1.0仕様
- name, sex, weightをフィールドに持つリクエストを受けられる
- nameは必須項目
- nameは文字列
- nameは上限20文字を許容。
- sexは文字列
- sexはfemale or maleのいずれか
- weightは数値
- weightは1~1000の間
- 登録成功すると200を返す(登録した内容をオウム返しする)
- フィールドに誤りがあると400を返す
サンプル(OpenAPI)
SwaggerEditor等で確認ください
openapi: 3.0.3
info:
title: Baby API
version: 1.1.0
paths:
/baby:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"400":
description: Invalid input
components:
schemas:
Pet:
required:
- name
type: object
properties:
name:
type: string
example: a.k.a. chan
maxLength: 20
sex:
type: string
enum:
- female
- male
weight:
type: integer
minimum: 1
maximum: 1000
1.1.0のリリース後、仕様と実装の乖離があり、体重にマイナスの値が入力しても、200を返してしまう事がわかりました(本来は入力フィールドの誤りとして、400を返すべき)。
これを修正して新たにリリースしますが、これはバグ修正となるので、パッチバージョンのアップとなり、バージョン1.1.1となります。
1.1.1仕様(変更無し)
- name, sex, weightをフィールドに持つリクエストを受けられる
- nameは必須項目
- nameは文字列
- nameは上限20文字を許容。
- sexは文字列
- sexはfemale or maleのいずれか
- weightは数値
- weightは1~1000の間
- 登録成功すると200を返す(登録した内容をオウム返しする)
- フィールドに誤りがあると400を返す
サンプル(OpenAPI)
SwaggerEditor等で確認ください
openapi: 3.0.3
info:
title: Baby API
version: 1.1.1
paths:
/baby:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"400":
description: Invalid input
components:
schemas:
Pet:
required:
- name
type: object
properties:
name:
type: string
example: a.k.a. chan
maxLength: 20
sex:
type: string
enum:
- female
- male
weight:
type: integer
minimum: 1
maximum: 1000
ここで、更なるユーザからの要望で、生年月日を入力できるようにしようと思います。
生年月日は必ずあるはずなので、必須項目として登録できるようにします。
必須項目とするが故に、これまでのユーザは、これまで200が返ってきていた利用方法(名前/性別/体重のみのリクエストでの利用)をした場合、フィールドに誤りがあると判定され、400が返ってしまいます。
これは、後方互換性が無くなり、これまでAPIを利用していた既存ユーザにとっては「ユーザ側で改修しない限り同等の使い方をできなくなる」という事と同義です。
この場合、メジャーバージョンをアップする必要があり、バージョン2.0.0となります。
既存ユーザが多数おり、変更の影響が大きい場合等においては、1.x系と2.x系の両方を並行してサポートする等の対応に迫られる可能性があります。
メジャーバージョンを上げる事を避けるために、生年月日は必須項目では無くし、
「値が無い場合はデフォルトでAPIをたたかれた日付を登録する」等の仕様変更で後方互換性を保つ事も、ユーザが既にいる場合は現実的な答えかもしれません。
2.0.0仕様
- name, sex, weight,birthdayをフィールドに持つリクエストを受けられる
- nameは必須項目
- nameは文字列
- nameは上限20文字を許容。
- sexは文字列
- sexはfemale or maleのいずれか
- weightは数値
- weightは1~1000の間
- birthdayは文字列
- birthdayはRFC3339準拠している事。例:2017-07-21
- 登録成功すると200を返す(登録した内容をオウム返しする)
- フィールドに誤りがあると400を返す
サンプル(OpenAPI)
SwaggerEditor等で確認ください
openapi: 3.0.3
info:
title: Baby API
version: 2.0.0
paths:
/baby:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
"400":
description: Invalid input
components:
schemas:
Pet:
required:
- name
- birthday
type: object
properties:
name:
type: string
example: a.k.a. chan
maxLength: 20
sex:
type: string
enum:
- female
- male
weight:
type: integer
minimum: 1
maximum: 1000
birthday:
type: string
format: date
と、このようにバージョンを命名してゆくことで、
ユーザから見ても後方互換性が保たれているかどうかを一目で判断でき、機能追加の有無も一目でわかるバージョニングとなります。利用者と開発者が共通の前提のもとバージョン情報を確認できるというのがセマンティックバージョニングの利点です。
おわりに
昨今のプロダクト提供では、サードパーティによる拡張性を確保したり、ユーザの利便性向上のためにAPI仕様を公開するケースが多いかと思いますが、本記事を読み、セマンティックバージョニングを使いこなし、プロダクトのコミュニティの輪を広げて頂けると幸いです。
また、このように後方互換性をどの程度保つかを強く意識する事で、モノリシックとマイクロサービス化のバランスや、未来の見通しが付かない中で、設計時にどこまで思索を広げるか等、プロダクトのフェーズに依って変わる方針をチームで議論するきっかけになったりもするかもしれません。
昨日は弊社ロゴ(こんな色)で埋め尽くされた記事でしたが(注:執筆時点ではタイトルのみしか把握していないので、違ったらすみません)、
本日は赤ちゃんを扱った記事が加わったことで、ますますクリスマス感が出てきましたね!(無理矢理)
明日以降の記事もお楽しみに!
※記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。
参考