こんにちは。サーバーサイドエンジニアをしている向井です。
今回は新規プロジェクトで導入を検討している、スプレッドシートによるAPIドキュメントの管理方法と、ドキュメントからクラスの自動生成する方法について紹介します。
Array型のMessagePackとクラス自動生成の必要性について
以前のブログで紹介した通り、新規プロジェクトではArray型によるMessagePackの導入を検討しています。
Array型のMessagePackについては以前のブログで説明していますが、要点だけ述べるとクライアント・サーバー間でクラスのプロパティやその順序が異なると、キー情報を送らないためにMessagePackのバイナリのデシリアライズに失敗します。
そこで、クライアント・サーバー間でプロパティとその順序をあわせるためにAPIドキュメントからクライアントとサーバーのクラスを作成し、タイミングを合わせて取り込むことで、プロパティの順序を合わせる工夫をしています。
APIドキュメントとクラスの自動生成
APIドキュメントはスプレッドシートにより管理し、AppScriptによりクラスの自動生成を行っています。
スクリプトによって自動生成されたクラスは、関係者のGoogleDriveへ共有されます。
スプレッドシートによるAPIドキュメントの管理
APIドキュメントは、以下の要素からなります。
- 概要
- APIの概要について記入します。
- URL
- APIのURLを記入します。
- 処理詳細
- APIの処理の詳細について記入します。
- リクエスト
- APIのリクエストパラメータについて記入します。
- レスポンス
- APIのレスポンスパラメータについて記入します。
- ステータスコード
- このAPIの返却するレスポンスコードの一覧について記入します。
クラスの自動生成では、特に上記のリクエストとレスポンスが重要になります。
リクエストとレスポンスの詳細について説明します。
リクエスト
リクエスト項目は、APIのリクエストパラメータについて記入します。 フォーマットは以下のとおりです。
- 項目名
- パラメータ名を記入します。
- 型
- パラメータの型名をJavaの型名で記入します。
- チェック項目
- バリデーションのルールを記入します。
- 必須や上限、下限などを指定することができます。
- 説明
- パラメータの説明を記入します。
例えば、前回のMessagePackの検証で利用したリクエストは以下のように記述しました。
| 項目名 | 型 | チェック項目 | 説明 |
|---|---|---|---|
| numRequest | Integer |
必須 | レスポンスとして返却するリストの要素数を指定します。 |
| data | List<Object4Sample> |
必須 | サーバーに送るデータを指定します。 |
また、上記のObject4Sampleについては以下のように定義します。
| 項目名 | 型 | 説明 |
|---|---|---|
| intValue | Integer |
|
| longValue | Long |
|
| shortValue | Short |
|
| boolValue | Boolean |
|
| floatValue | Float |
|
| doubleValue | Double |
|
| strValue | String |
|
| dateValue | Date |
レスポンス
リクエスト項目は、APIのリクエストパラメータについて記入します。フォーマットは以下のとおりです。
- 項目名
- パラメータ名を記入します。
- 型
- パラメータの型名をJavaの型名で記入します。
- 説明
- パラメータの説明を記入します。
リクエストと同様、前回のMessagePackの検証で利用したレスポンスは以下のように記述しました。
| 項目名 | 型 | 説明 |
|---|---|---|
| data | List | サーバーが返却するデータです。 |
自動生成されたクラス
このAPIドキュメントから、自動生成されたリクエスト・レスポンスクラスは以下のようになります。
まず、サーバー側のリクエスト・レスポンスになります。
/**
* SampleSample1Request
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Message
@EqualsAndHashCode(callSuper = false)
public class SampleSample1Request extends BaseFormRequest implements Serializable {
/** serialVersionUID */
@Ignore
@Getter(value = AccessLevel.NONE)
private static final long serialVersionUID = 1L;
/** */
@Index(1)
@NotEmpty
private Integer numRequest;
/** */
@Index(2)
@NotEmptyList
private List<Object4Sample> data;
}
/**
* SampleSample1Response
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Message
@EqualsAndHashCode(callSuper = false)
public class SampleSample1Response extends BaseFormResponse implements Serializable {
/** serialVersionUID */
@Ignore
@Getter(value = AccessLevel.NONE)
private static final long serialVersionUID = 1L;
/** */
@Index(1)
private List<Object4Sample> data;
}
import org.msgpack.annotation.Ignore;
import org.msgpack.annotation.Index;
import org.msgpack.annotation.Message;
/**
* Object4Sample
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Message
@EqualsAndHashCode(callSuper = false)
public class Object4Sample implements Serializable {
/** serialVersionUID */
@Ignore
@Getter(value = AccessLevel.NONE)
private static final long serialVersionUID = 1L;
/** */
@Index(0)
private Integer intValue;
/** */
@Index(1)
private Long longValue;
/** */
@Index(2)
private Short shortValue;
/** */
@Index(3)
private Boolean boolValue;
/** */
@Index(4)
private Float floatValue;
/** */
@Index(5)
private Double doubleValue;
/** */
@Index(6)
private String strValue;
/** */
@Index(7)
private Date dateValue;
}
上記のように、 リクエスト・レスポンス項目からプロパティの順番を決定し、@Index(N)アノテーションをつけることで順序を明記しています。
また、リクエストクラスについてはチェック項目から必要なバリデーションルールを決定し、@NotEmptyや@NotEmptyListといったアノテーションをつけています。
サーバーはこのアノテーションから、クライアントから受け取ったデータに適切なバリデーションを実施します。
また、クライアント側のクラスの一部は以下のようになります。
[System.Serializable]
public class APISampleSample1Request : RequestBase {
[MessagePackMemberAttribute(1)] // 0番目はRequestBaseに定義されています
public int numRequest ; //
[MessagePackMemberAttribute(2)]
public List<Object4Sample> data ; //
}
[System.Serializable]
public class Object4Sample {
[MessagePackMemberAttribute(0)]
public int intValue ; //
[MessagePackMemberAttribute(1)]
public long longValue ; //
[MessagePackMemberAttribute(2)]
public Short shortValue ; //
[MessagePackMemberAttribute(3)]
public bool boolValue ; //
[MessagePackMemberAttribute(4)]
public Float floatValue ; //
[MessagePackMemberAttribute(5)]
public Double doubleValue ; //
[MessagePackMemberAttribute(6)]
public string strValue ; //
[MessagePackMemberAttribute(7)]
public long dateValue ; //
}
[System.Serializable]
public class Object4Sample {
[MessagePackMemberAttribute(0)]
public int intValue ; //
[MessagePackMemberAttribute(1)]
public long longValue ; //
[MessagePackMemberAttribute(2)]
public Short shortValue ; //
[MessagePackMemberAttribute(3)]
public bool boolValue ; //
[MessagePackMemberAttribute(4)]
public Float floatValue ; //
[MessagePackMemberAttribute(5)]
public Double doubleValue ; //
[MessagePackMemberAttribute(6)]
public string strValue ; //
[MessagePackMemberAttribute(7)]
public long dateValue ; //
}
Javaと同様、APIドキュメントの項目からプロパティ順を決定し、[MessagePackMemberAttribute(N)]アトリビュートをつけてることで順序を定義しています。
また、APIドキュメントでは型名をJavaの型で定義していますが、C#ではそのまま利用する事ができないので、Javaの型に対応するC#の型を定義しておいて、クラス生成時にC#の型名を解決します。
自動生成したクラスを反映させる
あとは、自動生成したクラスを取り込みます。今のプロジェクトでは基本的に、クライアント・サーバーの両方のクラスの取り込みをサーバーエンジニアが行い、確認をクライアントエンジニアが行います。
その際のテストコードをサーバーエンジニアが実装しますが、APIテスト用のベースクラスを継承することで、十数行コードを記述するだけでテストができるような仕組みを用意しています。
API通信に関する実装をサーバーエンジニアが書くことで、APIの疎通をスムーズに行えるようになりました。
まとめ
APIドキュメントによる、クラス生成の自動化について紹介しました。
この件に限らず、手動ではバグの原因になりやすそうな箇所や、効率化が図れそうな作業は極力自動化を行うことで、開発の効率化をはかっています。
