セマンティックバージョニングってどうやって使うのさ！

# はじめに
この記事は [NTTコムウェア Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nttcomware) 2日目の記事です。

NTTコムウェアの薄井です！
普段はドコモグループのプロダクトのエンジニアリングマネージャを担っておりますが、第三子の生誕に伴い、この記事が公開される頃には育休取得中の予定です。

本日はプロダクト開発やAPI提供する際に必要となるバージョニングにおいて重要な
セマンティックバージョニングについて実例を交えつつ説明してゆきます！

# 想定読者
ソフトウェアプロダクトやAPI等を公開する皆様

# そもそもバージョニングって？
バージョン管理と表現される事もありますが、本質は例えば「**v1**」,「**20231202**」,「**1.0.0**」,「**令和最新版**」のように、プロダクトやソフトウェアに何か命名する（版を決める）事です。バージョン命名の方式をいくつか例に挙げます。

#### 適当な名前をつけるもの
例：イナダ　→　ワラサ　→　ブリ
[Android&trade;のコードネーム](https://ja.wikipedia.org/wiki/Android%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E5%B1%A5%E6%AD%B4)はお菓子の名前なのは有名な話ですね。

#### シーケンシャルにバージョニングをつけていく方法
例： 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でも使われる等、ソフトウェアプロダクトのバージョニングにおいてデファクト化してきているバージョニング手法です。


![「考え方は似てるけど、ちょっと違うね」という命名によって起きる認識齟齬、それがもたらす悲劇の例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302799/1ac149ef-1ad1-cffa-386e-529b17a609e6.png "「考え方は似てるけど、ちょっと違うね」という命名によって起きる認識齟齬、それがもたらす悲劇例")
**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を返す
<details><summary>サンプル(OpenAPI)</summary>

[SwaggerEditor](https://editor.swagger.io/)等で確認ください
```yaml:1.0.0.yaml
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
```
</details>

***

めでたく初期リリースができましたが、ユーザからの要望で、体重の情報を登録できる機能を追加することになりました。
この場合、機能追加となるので**マイナーバージョン**のアップとなり、セマンティックバージョニングのルールに従うと**バージョン1.1.0**となります。

:::note warn
もし**体重の情報を必須項目**とする場合は、ユーザからの登録情報がこれまで通り名前と性別のみの場合、受け付けられなくなる（=後方互換が失われる）ので、**メジャーバージョン**のアップが必要となってしまいます。
:::

***
#### 1.1.0仕様
- name, sex, weightをフィールドに持つリクエストを受けられる
    - nameは必須項目
    - nameは文字列
    - nameは上限20文字を許容。
    - sexは文字列
    - sexはfemale or maleのいずれか
    - **weightは数値**
    - **weightは1～1000の間**
- 登録成功すると200を返す（登録した内容をオウム返しする）
- フィールドに誤りがあると400を返す

<details><summary>サンプル(OpenAPI)</summary>

[SwaggerEditor](https://editor.swagger.io/)等で確認ください
```yaml:1.1.0.yaml
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
```
</details>

***

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を返す

<details><summary>サンプル(OpenAPI)</summary>

[SwaggerEditor](https://editor.swagger.io/)等で確認ください
```yaml:1.1.1.yaml
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
```
</details>

***

ここで、更なるユーザからの要望で、生年月日を入力できるようにしようと思います。
生年月日は必ずあるはずなので、必須項目として登録できるようにします。
必須項目とするが故に、これまでのユーザは、これまで200が返ってきていた利用方法（名前/性別/体重のみのリクエストでの利用）をした場合、フィールドに誤りがあると判定され、400が返ってしまいます。

これは、後方互換性が無くなり、これまでAPIを利用していた既存ユーザにとっては「ユーザ側で改修しない限り同等の使い方をできなくなる」という事と同義です。
この場合、**メジャーバージョン**をアップする必要があり、バージョン**2.0.0**となります。

既存ユーザが多数おり、変更の影響が大きい場合等においては、1.x系と2.x系の両方を並行してサポートする等の対応に迫られる可能性があります。

:::note info
メジャーバージョンを上げる事を避けるために、生年月日は必須項目では無くし、
「値が無い場合はデフォルトで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を返す

<details><summary>サンプル(OpenAPI)</summary>

[SwaggerEditor](https://editor.swagger.io/)等で確認ください
```yaml:2.0.0.yaml
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
```
</details>

***

と、このようにバージョンを命名してゆくことで、
ユーザから見ても後方互換性が保たれているかどうかを一目で判断でき、機能追加の有無も一目でわかるバージョニングとなります。利用者と開発者が共通の前提のもとバージョン情報を確認できるというのがセマンティックバージョニングの利点です。


# おわりに
昨今のプロダクト提供では、サードパーティによる拡張性を確保したり、ユーザの利便性向上のためにAPI仕様を公開するケースが多いかと思いますが、本記事を読み、セマンティックバージョニングを使いこなし、プロダクトのコミュニティの輪を広げて頂けると幸いです。

また、このように後方互換性をどの程度保つかを強く意識する事で、モノリシックとマイクロサービス化のバランスや、未来の見通しが付かない中で、設計時にどこまで思索を広げるか等、プロダクトのフェーズに依って変わる方針をチームで議論するきっかけになったりもするかもしれません。

昨日は<font color="#029f98">**弊社ロゴ(こんな色)**</font>で埋め尽くされた記事でしたが（注：執筆時点ではタイトルのみしか把握していないので、違ったらすみません）、
本日は<font color="#ff0000">**赤ちゃん**</font>を扱った記事が加わったことで、ますますクリスマス感が出てきましたね！（無理矢理）
明日以降の記事もお楽しみに！


※記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。


# 参考

https://semver.org/lang/ja/

https://en.wikipedia.org/wiki/Software_versioning

https://petstore3.swagger.io/