goaのdesignからもっといいJSコードを生成する¶
goa 便利ですね。designからサーバーコードが生成できるし、 Swagger でドキュメントも生成できるし。
でもちょっと待って下さい。サーバーのコードが生成できたとしても、結局フロントエンドのコードは書かないといけないですよね。うーむ。 と思っていたところ、 goのgoaでAPIをデザインしよう(クライアント編) という記事を拝見しました。
$ goagen js -d github.com/tikasan/goa-simple-sample/design
でJSコードが生成される、という話です。おお、素晴らしい、と思って試したのですが、残念ながらあれ…という印象でした。
引数の渡し方が、Path Paramとdataの差がなくて、必要なだけ渡したい場合にちょっと使いにくい
クライアントサイドValidationがない
という具合です。特にクライアントサイドでのValidationがないのがあかんですね。せっかくdesignでいろいろ制約書けるのに。
ということで作ってみました¶
goaにはコードを生成する部分をpluginとして自前のパッケージを指定できます。これを使ってJSコードを生成するパッケージを作成してみました。
https://github.com/shirou/goagen_js
goagen gen --pkg-path=github.com/shirou/goagen_js -d github.com/some/your/great/design
で、指定したdesignを js ディレクトリ以下に書き出します。
特徴としては、
ES 2015準拠でfetch APIを使ったPromiseを返す。名前もResource名 + Action名と、分かりやすく
クライアントサイドValidation
flowtypeやTypeScript形式での生成も指定可能
それぞれ順番に説明します。例として、以下のようなデザインを使うとします。
var UserCreatePayload = Type("UserCreatePayload", func() {
Attribute("name", String, "name", func() {
MinLength(4)
MaxLength(16)
})
Attribute("age", Integer, "age", func() {
Minimum(20)
Maximum(70)
})
Attribute("email", String, "email", func() {
Pattern(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
})
Attribute("sex", String, "sex", func() {
Enum("male", "female", "other")
})
Required("name")
})
var _ = Resource("user", func() {
BasePath("user")
Response(InternalServerError)
Action("create", func() {
Routing(POST("create/:Type"))
Params(func() {
Param("Type", String, "type of user", func() {
Enum("normal", "admin")
})
Required("UserID")
})
Payload(UserCreatePayload, func() {
Example(map[string]interface{}{
"name": "fooboo",
})
})
Response(OK)
})
使い方¶
goagen gen でコードを生成すると、以下のような関数を持つ、 api_request.js が生成されます。
// UserCreate
// type_(string): type of user
// payload(object): payload
export function UserCreate(type_, payload) {
const url = urlPrefix + `/user/create/${type_}`;
let e = undefined;
e = v.validate(v.UserCreate.Type, type_);
if (e) {
return Promise.reject(e);
}
e = v.validate(v.UserCreate.payload, payload);
if (e) {
return Promise.reject(e);
}
return post(url, payload); // 同時に生成されるヘルパー関数
}
ですので、以下のように使えます。
import * as api from "./api_request.js";
const payload = {age: 30, name: "shirou"}
api.UserCreate("admin", payload).then((response) => {
...
});
クライアントサイドValidation¶
先程の関数の中で、 v.validate(v.UserCreate.Type, type_) というのがありました。これがValidationです。 api_validator.js というファイルに以下のコードが生成されています。
export const UserCreate = {
"Type": {
"kind": "string",
"enum": [
"normal",
"admin"
]
},
"payload": {
"age": {
"kind": "number",
"minimum": 20,
"maximum": 70
},
"email": {
"kind": "string",
"pattern": "^[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,4}$"
},
"name": {
"kind": "string",
"min_length": 4,
"max_length": 16
},
"sex": {
"kind": "string",
"enum": [
"male",
"female",
"other"
]
}
}
};
これを、同時に生成されている validate というヘルパー関数に食わせてあげます。返り値が undefined であれば問題なし。問題があれば文字列が返ってきます。
リクエストとvalidationを分離しているのは、例えば入力のたびにon-the-flyでチェックする、という用途がフロントエンドではよくあるからです。
import * as v from "../api_validator.js";
// name だけチェックする
if v.validate(v.UserCreate.payload.name, value) !== undefined {
// validationエラー
}
という感じで使えます。エラー時にどういう文言が返ってくるかはapi_validator.jsの中に定義されてるので、適宜使ってください。
これにより、フロントエンドとサーバーとで同じvalidationを二回書く、ということがなくなります。
flowtypeやTypeScript形式¶
goaはgolangで書いていますので、型情報があるわけです。しかし、JavaScriptにした瞬間にせっかくの型情報が失われてしまいます。あまりにももったいない。 ということで、flowtypeやtypescriptで生成することもできるようにしました。
goagen gen --pkg-path=github.com/shirou/goagen_js -d github.com/shirou/goagen_js/example/design -- --target flow
or
goagen gen --pkg-path=github.com/shirou/goagen_js -d github.com/shirou/goagen_js/example/design -- --target type --genout ts
typescriptの場合、 ts というディレクトリ名が普通かな、と思いますので genout オプションで吐き出すディレクトリ名を指定しています。なお、拡張子もtsになります。
こうすると、
export function UserCreate(type_: string, payload: UserCreatePayload): Promise<UserCreateMedia> {
という感じで型情報がくっつきます。 UserCreatePayload は同時に生成される api.d.ts ファイルに以下のように定義されます。
interface UserCreatePayload {
name: string;
age: number;
email: string;
sex: ["male","female","other"];
}
interface UserCreateMedia {
email: string;
age: number;
sex: string;
name: string;
}
Responseは *Media として定義されます。
発表資料¶
ということを goa勉強会 で発表しました。
swaggerから生成したほうが良くね?¶
といったところで、ふと気が付きました。
goaはswaggerを吐き出せるんだから、swaggerからJSコード生成すればよくね?
調べてみると、 swagger-codegen でJSどころかTypeScriptのコードも生成できるではないですか。
おおう。orz...
ということで、ここまで作ったところで実はちょっと継続して作成するモチベーションが下がってしまいました。 codegenだとValidationがないので、Validationだけ別で生成するかもしれません。
まとめ¶
標準のJSコード生成は正直微妙
goagen_js作ったYO
クライアントValidationやTypeScript生成もできちゃうー
あれ、swagger-codegenのほうがよくね…?
goaはいいぞ!
Comments
comments powered by Disqus