ぺい

渋谷系アドテクエンジニアの落書き

GolangのgoaでAPIをデザインしよう(レスポンス編)

goaはいいぞ!

f:id:tikasan0804:20170505212036p:plain

Golangのgoaの勉強に役立つ情報まとめ - ぺい
goaの情報をもっと見たい方は、上のリンクから確認してください

レスポンス

いきなりですが、以下のエンドポイントを見てください。RESTを知ってる人は、レスポンスが違うことが分かると思います。

GET /users/1
GET /users

開発するチームにもよって変わる可能生はあると思いますが、GET /users/1は恐らくひとりのユーザー情報だけを返す。そして、GET /usersはひとり以上のユーザー情報を返す。
また、GET /usersはIDや名前だけで良かったり、GET /users/1は詳細な情報が欲しかったり、同じリソースに対しての操作でも、レスポンスは変える必要があります。(と思っています)

具体的にレスポンスの例を示すと以下のような感じになります。

GET /users

[
  {
    "id": 1,
    "name": "ユーザー1"
  },
  {
    "ID": 2,
    "name": "ユーザー2"
  }
]

GET /users/1

{
  "id": 1,
  "email": "satak47cpc@gmail.com",
  "name": "ユーザー1"
}

また、場合によってはIDの一覧だけ欲しい時もあったり

{"america":3,"japan":2,"korea":4}

キーに意味を持たせた連想配列が欲しかったり

[1,2,3]

上記のような需要全てに対応することが出来ます。実際に動かしてみましょう。

goaはコード生成する時に少し長めのコマンドが必要になるので、Makefileを作っておくと良いと思います。 ちなみに、コマンドやフォルダ構成は毎回同じものを使うので、テンプレートを作成しました。よろしければ利用してください。
もし、もっと便利なフォーマットがあればPRをください。

$ go get github.com/tikasan/goa-stater
または
$ ghq get github.com/tikasan/goa-stater

APIデザインをする

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

var _ = Resource("response", func() {
    BasePath("/response")
    Action("list", func() {
        Description("ユーザー(複数)")
        Routing(
            GET("/users"),
        )
        // 複数返す
        Response(OK, CollectionOf(UserMedia))
        Response(BadRequest, ErrorMedia)
    })
    Action("show", func() {
        Description("ユーザー(単数)")
        Routing(
            GET("/users/:id"),
        )
        // 単一
        Response(OK, UserMedia)
        Response(BadRequest, ErrorMedia)
    })
    Action("hash", func() {
        Description("ユーザー(ハッシュ)")
        Routing(
            GET("/users/hash"),
        )
        // 連想配列
        Response(OK, HashOf(String, Integer))
        Response(BadRequest, ErrorMedia)
    })
    Action("array", func() {
        Description("ユーザー(配列)")
        Routing(
            GET("/users/array"),
        )
        // 配列
        Response(OK, ArrayOf(Integer))
        Response(BadRequest, ErrorMedia)
    })
})

MediaTypeを定義する

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

var UserMedia = MediaType("application/vnd.user+json", func() {
    Description("example")
    Attributes(func() {
        Attribute("id", Integer, "id", func() {
            Example(1)
        })
        Attribute("name", String, "名前", func() {
            Example("hoge")
        })
        Attribute("email", String, "メールアドレス", func() {
            Example("satak47cpc@gmail.com")
        })
        Required("id", "name", "email")
    })
    // 特別な指定がない場合はdefaultのMediaType
    View("default", func() {
        Attribute("id")
        Attribute("name")
        Attribute("email")
    })
    // tinyという名前の場合は、簡潔なレスポンスフォーマットにすることが出来る
    View("tiny", func() {
        Attribute("id")
        Attribute("name")
    })
})

Controllerを定義する

goagen bootstrap -d github.com/tikasan/goa-stater/design

特にビジネスロジックなどはなしで、値を返すだけのコードを実装します。

package controller

import (
    "github.com/goadesign/goa"
    "github.com/tikasan/goa-stater/app"
)

// ResponseController implements the response resource.
type ResponseController struct {
    *goa.Controller
}

// NewResponseController creates a response controller.
func NewResponseController(service *goa.Service) *ResponseController {
    return &ResponseController{Controller: service.NewController("ResponseController")}
}

// Array runs the array action.
func (c *ResponseController) Array(ctx *app.ArrayResponseContext) error {
    // ResponseController_Array: start_implement

    // Put your logic here

    // ResponseController_Array: end_implement
    res := make([]int, 3)
    res[0] = 1
    res[1] = 2
    res[2] = 3
    return ctx.OK(res)
}

// Hash runs the hash action.
func (c *ResponseController) Hash(ctx *app.HashResponseContext) error {
    // ResponseController_Hash: start_implement

    // Put your logic here

    // ResponseController_Hash: end_implement
    res := make(map[string]int, 3)
    res["japan"] = 2
    res["america"] = 3
    res["korea"] = 4
    return ctx.OK(res)
}

// List runs the list action.
func (c *ResponseController) List(ctx *app.ListResponseContext) error {
    // ResponseController_List: start_implement

    // Put your logic here

    // ResponseController_List: end_implement
    res := make(app.UserTinyCollection, 2)
    u1 := &app.UserTiny{
        ID:   1,
        Name: "ユーザー1",
    }
    u2 := &app.UserTiny{
        ID:   2,
        Name: "ユーザー2",
    }
    res[0] = u1
    res[1] = u2
    return ctx.OKTiny(res)
}

// Show runs the show action.
func (c *ResponseController) Show(ctx *app.ShowResponseContext) error {
    // ResponseController_Show: start_implement

    // Put your logic here

    // ResponseController_Show: end_implement
    res := &app.User{}
    res.ID = 1
    res.Name = "ユーザー1"
    res.Email = "satak47cpc@gmail.com"
    return ctx.OK(res)
}

準備が整ったので、実行してリクエストを投げてみましょう!

$ go run main.go
$ curl -v 'http://localhost:8080/api/v1/response/users/1'
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/v1/response/users/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/vnd.user+json
< Date: Sat, 06 May 2017 04:33:08 GMT
< Content-Length: 63
< 
{"ID":1,"email":"satak47cpc@gmail.com","name":"ユーザー1"}
* Connection #0 to host localhost left intact
$ curl -v 'http://localhost:8080/api/v1/response/users'
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/v1/response/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/vnd.user+json; type=collection
< Date: Sat, 06 May 2017 04:35:02 GMT
< Content-Length: 66
< 
[{"ID":1,"name":"ユーザー1"},{"ID":2,"name":"ユーザー2"}]
* Connection #0 to host localhost left intact

いい感じのデータが返ってきました。goaはいいぞ!

GolangのgoaでAPIをデザインしよう(バリデーション編)

goaはいいぞ!

f:id:tikasan0804:20170505212036p:plain

Golangのgoaの勉強に役立つ情報まとめ - ぺい
goaの情報をもっと見たい方は、上のリンクから確認してください

わかっちゃいるけど、さぼりたいバリデーション

APIは単純なJSONやText返したり、用途は色々あると思います。ですけど、実際返す処理の前にクライアントから飛んでくるリクエストが間違っていないかバリデーションが必要になりますよね。これがだるい!
しかも、プログラム側で実装するだけでも大変なのに、それをドキュメントにどういう条件でOKとするかとか書き出すと、結構細かく記述する必要があり、正直面倒でしかないと思います。そして、仕様変更があれば楽しい修正が始まります。無駄無駄無駄無駄無駄無駄無駄!!!!!!

goaでやったら秒で終わるよ

そこで、今回はgoaでバリデーションすると、幸せになれる一例を示したいと思います。サンプルコードと説明を貼っていくので、この楽さを実感してほしいです。

goaはコード生成する時に少し長めのコマンドが必要になるので、Makefileを作っておくと良いと思います。 ちなみに、コマンドやフォルダ構成は毎回同じものを使うので、テンプレートを作成しました。よろしければ利用してください。
もし、もっと便利なフォーマットがあればPRをください。

$ go get github.com/tikasan/goa-stater
または
$ ghq get github.com/tikasan/goa-stater

デザイン定義

以下のような感じで、用意されているメソッドを使えば大体なんとかなります。
ちなみにFormay関数には、email以外にもいくつかあります。

  • “date-time”: RFC3339 date time
  • “email”: RFC5322 email address
  • “hostname”: RFC1035 internet host name
  • ipv4”, “ipv6”, “ip”: RFC2373 IPv4, IPv6 address or either
  • uri”: RFC3986 URI
  • mac”: IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address
  • “cidr”: RFC4632 or RFC4291 CIDR notation IP address
  • regexp”: RE2 regular expression
var _ = Resource("validation", func() {
    BasePath("/validation")
    Action("validation", func() {
        Description("Validation")
        Routing(
            GET("/"),
        )
        Params(func() {
            // Integer型
            Param("id", Integer, "id", func() {
                Example(1)
            })
            // Integer型かつ1〜10以下
            Param("integerType", Integer, "数字(1〜10)", func() {
                Minimum(0)
                Maximum(10)
                Example(2)
            })
            // String型かつ1〜10文字以下
            Param("stringType", String, "文字(1~10文字)", func() {
                MinLength(1)
                MaxLength(10)
                Example("あいうえお")
            })
            // String型かつemailフォーマット
            Param("email", String, "メールアドレス", func() {
                Format("email")
                Example("example@gmail.com")
            })
            // String型でEnumで指定されたいずれかの文字列
            Param("enumType", String, "列挙型", func() {
                Enum("A", "B", "C")
                Example("A")
            })
            // String型で何も指定が無ければ”でふぉ”という文字列が自動でセットされる
            Param("defaultType", String, "デフォルト値", func() {
                Default("でふぉ")
                Example("でふぉ")
            })
            // String型で正規表現で指定したパターンの文字列
            Param("reg", String, "正規表現", func() {
                Pattern("^[a-z0-9]{5}$")
                Example("12abc")
            })

            // 全て必須パラメーター
            Required("id", "integerType", "stringType", "email", "enumType", "defaultType", "reg")
        })
        Response(OK, ValidationMedia)
        Response(BadRequest, ErrorMedia)
    })
})

MediaTypeの定義

とりあえず、返すだけ。

var ValidationMedia = MediaType("application/vnd.validation+json", func() {
    Description("example")
    Attributes(func() {
        Attribute("id", Integer, "id", func() {
            Example(1)
        })
        Attribute("integerType", Integer, "数字(1〜10)", func() {
            Example(5)
        })
        Attribute("stringType", String, "文字(1~10文字)", func() {
            Example("あいうえお")
        })
        Attribute("email", String, "メールアドレス", func() {
            Example("example@gmail.com")
        })
        Attribute("enumType", String, "列挙型", func() {
            Example("A")
        })
        Attribute("defaultType", String, "デフォルト値", func() {
            Example("でふぉ")
        })
        Attribute("reg", String, "デフォルト値", func() {
            Example("12abc")
        })
    })
    Required("id", "integerType", "stringType", "email", "enumType", "defaultType", "reg")
    View("default", func() {
        Attribute("id")
        Attribute("integerType")
        Attribute("stringType")
        Attribute("email")
        Attribute("enumType")
        Attribute("defaultType")
        Attribute("reg")
    })
})

コントローラー定義

コントローラーは、バリデーションが完了した値が来た時だけを想定した処理をすればおkです。デザイン定義が完了したら、デザインを元にコードを生成しましょう。

$ goagen bootstrap -d github.com/tikasan/goa-stater/design
package main

import (
    "github.com/goadesign/goa"
    "github.com/tikasan/goa-stater/app"
)

// ValidationController implements the validation resource.
type ValidationController struct {
    *goa.Controller
}

// NewValidationController creates a validation controller.
func NewValidationController(service *goa.Service) *ValidationController {
    return &ValidationController{Controller: service.NewController("ValidationController")}
}

// Validation runs the validation action.
func (c *ValidationController) Validation(ctx *app.ValidationValidationContext) error {
    // ValidationController_Validation: start_implement

    // Put your logic here

    // ValidationController_Validation: end_implement
    res := &app.Validation{}
    res.ID = ctx.ID
    res.IntegerType = ctx.IntegerType
    res.StringType = ctx.StringType
    res.Email = ctx.Email
    res.EnumType = ctx.EnumType
    res.DefaultType = ctx.DefaultType
    res.Reg = ctx.Reg
    return ctx.OK(res)
}

実際に動かしてみよう

$ go run main.go
$ curl -v 'http://localhost:8080/api/v1/validation?id=1&defaultType=&email=satak%40gmail.com&enumType=A&integerType=10&stringType=foo&reg=12abc'
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/v1/validation?id=1&defaultType=&email=satak%40gmail.com&enumType=A&integerType=10&stringType=foo&reg=12abc HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/vnd.validation+json
< Date: Sat, 06 May 2017 01:47:26 GMT
< Content-Length: 117
< 
{"id":1,"defaultType":"","email":"satak@gmail.com","enumType":"A","integerType":10,"reg":"12abc","stringType":"foo"}
* Connection #0 to host localhost left intact

goa紹介で使うソース達は、以下のリポジトリに随時更新していきます。
github.com

GolangのgoaでAPIをデザインしよう(基本編)

goaはいいぞ!

f:id:tikasan0804:20170505212036p:plain

Golangのgoaの勉強に役立つ情報まとめ - ぺい
goaの情報をもっと見たい方は、上のリンクから確認してください

goでAPIを作成する場合は、必ずといっていいくらいgoaでやっている私ですが、日本ではあまり使っている人が居ません。恐らくこの理由は日本語の情報があまり無いことやシンプルなサンプル集がないからかもしれない・・・と思いまして、軽量なサンプルを定期的に紹介していくことにしました。

API設計フェーズ

goaは最初に設計を行ってから、その設計書を元に実装を行っていきます。設計はDSLと呼ばれるもので、最初はこの定義に慣れないですが、読み方さえ分かれば容易にAPIを設計出来ます。

何が嬉しいの?

例えば、GET /users/:IDというエンドポイントがあるとします。どのようなロジックを組む必要があるでしょう?

curl http://localhost/users/1でリクエストする

  1. /users/1の1の部分を取得する
  2. :IDから取得したものが文字なのか数字なのかチェックする
  3. 取得した:IDを使って、データベースなりにSELECT句などで検索する
  4. 該当するものがなかったら、404ステータスを返す
  5. 正しく取得できたら、200ステータスとレコードを返す。

goaだとどうなるか?

  1. /users/1の1の部分を取得する
  2. :IDから取得したものが文字なのか数字なのかチェックする
  3. 取得した:IDを使って、データベースなりにSELECT句などで検索する
  4. 該当するものがなかったら、404ステータスを返す
  5. 正しく取得できたら、200ステータスとレコードを返す。

1,2に当たる値チェックなどが一切必要なくなります。上記のように少ないパラメータならそこまでの労力ではありませんが、正規表現が必要だったり、複数だったりすると・・・面倒ですよね?
つまり、goaはビズネスロジックオンリーの開発が出来るようになるので、人が組むコードの部分が非常にシンプルになります。
しかも、swaggerのドキュメントが自動生成されるので、ドキュメント更新地獄から解放されます。

フォルダ構成

以下のようなファイル構成を構築してください。
※designフォルダ内のファイルは実際は一つでもokですが、分ける方が私は好きです。

.
├── LICENSE
├── Makefile
├── README.md
├── design
│   ├── api_definition.go
│   ├── media_types.go
│   └── resources.go
└── public
    └── swagger

goaはコード生成する時に少し長めのコマンドが必要になるので、Makefileを作っておくと良いと思います。 ちなみに、コマンドやフォルダ構成は毎回同じものを使うので、テンプレートを作成しました。よろしければ利用してください。
もし、もっと便利なフォーマットがあればPRをください。

go get github.com/tikasan/goa-stater
または
ghq get github.com/tikasan/goa-stater

デザインする

準備が整ったので、さっそくデザインをしていきたいと思います。
今回はとりあえず動かそうということで、サンプルソースをひたすら貼ります。
細かい説明は順を追って記事を作成して、紹介致します。

APIの基本情報(api_definition.go)

APIの実際の動作ではなく、どういったAPIか?やグローバルな設定などを行います。ここに紹介しているもの以外にも関数は存在します。

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

var _ = API("goa simple sample", func() {
    // APIのタイトル
    Title("tikasan/goa-simple-sample")
    // APIの説明
    Description("goaのサンプルです")
    // 作成者へのコンタクト情報
    Contact(func() {
        Name("pei")
        Email("satak47cpc@gmail.com")
        URL("https://github.com/tikasan/goa-simple-sample/issues")
    })
    // APIのライセンス
    License(func() {
        Name("MIT")
        URL("https://github.com/tikasan/eventory/blob/master/LICENSE")
    })
    // APIのドキュメント
    Docs(func() {
        Description("wiki")
        URL("https://github.com/tikasan/goa-simple-sample/wiki")
    })
    // ホストの設定
    Host("localhost:8080")
    // 対応しているプロトコル定義、httpかhttpsまたはその両方
    Scheme("http", "https")
    // 全てのエンドポイントのベースパス
    // /usersというエンドポイントがあったら、/api/v1/usersとなる
    BasePath("/api/v1")

    // CORSポリシーの定義
    Origin("http://localhost:8080/swagger", func() {
        //クライアントに公開された1つ以上のヘッダー
        Expose("X-Time")
        // 1つまたは複数の許可されたHTTPメソッド
        Methods("GET", "POST", "PUT", "DELETE")
        //プリフライト要求応答をキャッシュする時間
        MaxAge(600)
        // Access-Control-Allow-Credentialsヘッダーを設定する
        Credentials()
    })
})

MediaType(media_types.go)

レスポンスデータの形式を定義します。(詳しいことは別記事を紹介予定)

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

// レスポンスデータの定義
// MediaTypeに名前をつけます
var IntegerMedia = MediaType("application/vnd.integer+json", func() {
    // 説明
    Description("example")
    // どのような値があるか(複数定義出来る)
    Attributes(func() {
        // idはInteger型
        Attribute("id", Integer, "id", func() {
            // 返すレスポンスの例
            Example(1)
        })
        // レスポンスに必須な要素(基本は全て必須にした方が楽)
        Required("id")
    })
    // 返すレスポンスのフォーマット(別記事で紹介予定)
    View("default", func() {
        Attribute("id")
    })
})

リソースへの操作(resources.go)

リソースへの操作を定義します。(詳しいことは別記事を紹介予定)

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

// /actionsの定義をする
var _ = Resource("actions", func() {
    // actionsリソースのベースパス
    BasePath("/actions")
    /*
       リソースに対してどういった操作を行うか定義する
       add リソースを追加する
       list    リソースをリストで取得する
       delete  リソースを削除する
       上記のような感じで定義すればおkです。
   */
    Action("ping", func() {
        // アクションの説明
        Description("サーバーとの導通確認")
        Routing(
            // エンドポイント -> GET http://localhost/api/v1/actions/pingになる
            GET("/ping"),
        )
        // 返したいレスポンス
        // 200 OK + MessageTypeで定義しているMediaType
        Response(OK, MessageType)
        // 400 BadRequest + ErrorMediaというデフォルトで容易されているMediaType
        // 足りないパラメーターなどがあれば自動的に返される
        Response(BadRequest, ErrorMedia)
    })
    Action("hello", func() {
        Description("挨拶する")
        Routing(
            GET("/hello"),
        )
        // リクエストで付加出来るパラメーター
        Params(func() {
            // nameという名前でパラメーターをStringでなげれる
            Param("name", String, "名前", func() {
                // もし、空だった場合は空文字を格納する
                Default("")
            })
            // 必ず設定されるべきパラメーター(デフォルト値があるので、存在しなければ空になる)
            Required("name")
        })
        Response(OK, MessageType)
        Response(BadRequest, ErrorMedia)
    })
    Action("ID", func() {
        Description("複数アクション(:id)")
        Routing(
            // エンドポイントにリソースを指定出来る
            // GET http://localhost:8080/api/v1/actions/1になる
            GET("/:id"),
        )
        Params(func() {
            // :IDはIntegert型でなければならない。
            Param("id", Integer, "id")
            // Requiredはリソースを含めたエンドポイントになるので、定義しなくても良い
            //Required("id")
        })
        Response(OK, IntegerType)
        // 指定したリソースが無ければNotFoundを返す可能生がある
        Response(NotFound)
        Response(BadRequest, ErrorMedia)
    })
})

// Swaggerをローカルで実行するめの定義
var _ = Resource("swagger", func() {
    Origin("*", func() {
        Methods("GET")
    })
    Files("/swagger.json", "swagger/swagger.json")
    Files("/swagger/*filepath", "public/swagger/")
})

コードを生成する

インストール方法やコマンドの説明はikawahaさんの記事がわかりやすいので、紹介しておきます。
ikawaha.hateblo.jp

今回はgithub.com/tikasan/goa-staterのrepoをgo getした想定で説明します。

goagen bootstrap -d github.com/tikasan/goa-stater/design

コマンドを実行すると以下のようなログが流れます。 実行出来ない場合は、designパッケージ(フォルダ)の指定が間違っています。

app/contexts.go
app/controllers.go
.....
.....
swagger
swagger/swagger.json
swagger/swagger.yaml

出来上がる構成は以下のような感じです。
実際に作業をするファイルは一部です。 app,client,tool,swagger周りは自動で生成されたもので、変更することはないです。

.
├── LICENSE
├── Makefile
├── README.md
├── actions.go <--------- actionsリソース
├── app
├── client
├── design
│   ├── api_definition.go
│   ├── media_types.go
│   ├── resources.go
│   └── security.go
├── main.go <------------ Middlewareとかそこらへんやる
├── public
│   └── swagger
├── security.go
├── swagger
├── swagger.go
└── tool

このままだと少しごちゃごちゃしているので、私はcontrollerというパッケージにまとめたりしています。

.
├── LICENSE
├── Makefile
├── README.md
├── actions.go <--------- controllerフォルダへ移動させる
├── app
├── client
├── controller
├── design
│   ├── api_definition.go
│   ├── media_types.go
│   ├── resources.go
│   └── security.go
├── main.go
├── public
│   └── swagger
├── security.go
├── swagger
├── swagger.go <------------ controllerフォルダへ移動させる
└── tool

main.goを若干記述を変える必要があるので、調整だけしてください。

//go:generate goagen bootstrap -d github.com/tikasan/goa-stater/design

package main

import (
    "github.com/goadesign/goa"
    "github.com/goadesign/goa/middleware"
    "github.com/tikasan/goa-stater/app"
    "github.com/tikasan/goa-stater/controller" <-------importする
)

func main() {
    // Create service
    service := goa.New("goa simple sample")

    // Mount middleware
    service.Use(middleware.RequestID())
    service.Use(middleware.LogRequest(true))
    service.Use(middleware.ErrorHandler(service, true))
    service.Use(middleware.Recover())

    // Mount "actions" controller
    c := controller.NewActionsController(service) <-----controllerパッケージからメソッドを呼ぶ
    app.MountActionsController(service, c)

    // Start service
    if err := service.ListenAndServe(":8080"); err != nil {
        service.LogError("startup", "err", err)
    }
}

これで準備完了しました。ここまでの作業は正直テンプレートなので、慣れればほとんど時間がかかりません。

ビジネスロジックを組んでみよう

package controller

import (
    "github.com/goadesign/goa"
    "github.com/tikasan/goa-stater/app"
)

// ActionsController implements the actions resource.
type ActionsController struct {
    *goa.Controller
}

// NewActionsController creates a actions controller.
func NewActionsController(service *goa.Service) *ActionsController {
    return &ActionsController{Controller: service.NewController("ActionsController")}
}

// Action ID
func (c *ActionsController) ID(ctx *app.IDActionsContext) error {

    /*
       Paramsで定義したものはctx.hogeで受け取れる
       Params(func() {
           Param("ID", Integer, "ID")
       })
   */
    if ctx.ID == 0 {
        // Response(NotFound)
        // 0の場合は、404エラーを返す
        return ctx.NotFound()
    }

    // Response(OK, IntegerType)
    // MediaType IntegerTypeでレスポンス
    res := &app.Integer{}
    /*
       IDはInteger型で
       Attribute("ID", Integer, "ID", func() {
           Example(1)
       })
   */
    res.ID = ctx.ID
    return ctx.OK(res)
}

// Hello runs the hello action.
func (c *ActionsController) Hello(ctx *app.HelloActionsContext) error {
    // ActionsController_Ping: start_implement

    // Put your logic here
    name := ctx.Name

    // ActionsController_Ping: end_implement
    res := &app.Message{}
    res.Message = "Hello " + name
    return ctx.OK(res)
}

// Ping runs the ping action.
func (c *ActionsController) Ping(ctx *app.PingActionsContext) error {
    // ActionsController_Ping: start_implement

    // Put your logic here
    message := "pong"

    // ActionsController_Ping: end_implement
    res := &app.Message{}
    res.Message = message
    return ctx.OK(res)
}

以上でAPIビジネスロジックの実装完了です。簡単でしょう?
では、実行してみましょう。

go run main.go

エラーが起きた場合は、importミスやポートが空いてないや何かしらのミスが考えられます。

curl -v http://localhost:8080/api/v1/actions/1
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/v1/actions/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/vnd.integer+json
< Date: Fri, 05 May 2017 11:49:59 GMT
< Content-Length: 9
< 
{"id":1}
* Connection #0 to host localhost left intact

上記のような感じでレスポンスが返ってきました。
試しに間違ったリクエストを投げてみましょう。

curl -v http://localhost:8080/api/v1/actions/hoge
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/v1/actions/hoge HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 400 Bad Request
< Content-Type: application/vnd.goa.error
< Date: Fri, 05 May 2017 12:01:54 GMT
< Content-Length: 188
< 
{"id":"Iwzpk08B","code":"invalid_request","status":400,"detail":"invalid value \"hoge\" for parameter \"id\", must be a integer","meta":{"expected":"integer","param":"ID","value":"hoge"}}
* Connection #0 to host localhost left intact

エラーのフォーマットはカスタム出来ますが、いまはデフォルトで定義されているフォーマットで返ってきます。特にこだわりがなければこれで完了します。

SwaggerUIを使ってみよう

swaggerのドキュメントも出来上がっているので、github.com/tikasan/goa-staterを使っている場合は、以下のコマンドでAPIのドキュメントの参照とテストが出来ます!!!

open http://localhost:8080/swagger/index.html

f:id:tikasan0804:20170505214106p:plain

初めの方に説明した。ビズネスロジックだけを書けば実装が完了していることが実感出来たと思います。他にもヘッダーのチェックやmodel定義が簡単に出来るgormaなど色々あるので、どんどん紹介していきたいと思います。

goa紹介で使うソース達は、以下のリポジトリに随時更新していきます。
github.com

GithubのOAuth2.0の仕様について理解する(Githubログイン)

あの楽ちんなGithubログインボタン

結構前からですが、他のSNSサービスや比較的数多くのアカウント登録数を持っているサービスが採用しているOAuth2.0ですけど、自分が関わっているプロダクトで使う必要が出てきたので、仕様と使い方にGithubの公式ドキュメントを読みながらまとめてみた。
OAuth | GitHub Developer Guide

公式ドキュメントの大体合ってる日本語訳

※実際の翻訳と異なることが多少あります。大体そんな感じという日本語訳です。

OAuth2は外部アプリケーションがユーザーのGithubアカウントが持っているプライベートな情報にパスワードなしで情報にアクセスすることが出来るプロトコルです。トークンは特定のタイプのデータに限定でき、ユーザーはそのトークンをいつでも消せるということらしい。このトークン仕組みははBasic認証よりも良いですよ?みらいなことが書いてある。

つまり、トークンとやらを発行して、そのトークンを使うことで許可された範囲で操作出来るよってことらしい。Basic認証よりイケてるからみんな使ってこうぜ!的なことが言いたいのだと思う。

全ての開発者は、これを利用する前にアプリケーションの登録をする必要があります。登録されたOAuthアプリケーションのには、固有のClient IDとClient Secretが割り当てられます。
※このClient Secretは絶対に共有しないでください! 開発者の自身で使用するための個人アクセストークンを作成したり、以下に紹介するフローを実装することで、他のユーザーがアプリケーションを許可することが出来ます。

ここで出たClient Secretは間違って、Githubとかに上げると悪用されるので、公開しないようにしてくださいという意味だと思われる。

GithubのOAuth実装は、authorization code grant typeをサポートしています。開発者は以下で説明するWebアプリケーションフローを実装して、認証コードを取得し、それをトークンに交換する必要があります。(implicit grant typeはサポートしていません)

Webアプリケーションでのフロー

f:id:tikasan0804:20170502231453p:plain 大体こんな感じだと思う。

1.ユーザーはリダイレクトでGithubへアクセスする

GET https://github.com/login/oauth/authorize

設定できるパラメーター

*は必須という意味です。

Name Type 説明
client_id * string 登録時にい発行されたクライアントID
redirect_uri string 承認後にユーザーのリダイレクト先。(アプリケーションURL)
scope string scopeスペースで区切られたスコープのリスト。scopeは空の場合は、アプリケーションのscopeを許可していないユーザーの空リストにデフォルトで設定されます。アプリケーションのscopeを承認したユーザーの場合は、scopeのリストを含むOAuth認証ページは表示されません。その代わり、このフローでは、ユーザーのアプリケーションに対しての承認を自動的に処理する。
state string サイト間のやり取りをする際のCSRF対策に使うランダムな文字列。これが漏れると攻撃されるので、取扱には気をつけたい。
allow_signup string 認証されていないユーザーに、OAuthフロー中にGithubにサインアップするオプションを提供するかどうかを設定する。つまり、認証フローに入る前からGithubにログイン済みのユーザーしか許可しないかどうかを決定するようです。もし、許可する場合はデフォルトで設定されているtrueで、許可したくない場合はfalseとするようです。

補足

redirect_uri
リダイレクトのURLには以下のようなルールがあります。

OAuth | GitHub Developer Guide

CALLBACK: http://example.com/path

GOOD: http://example.com/path
GOOD: http://example.com/path/subdir/other
BAD:  http://example.com/bar
BAD:  http://example.com/
BAD:  http://example.com:8080/path
BAD:  http://oauth.example.com:8080/path
BAD:  http://example.org

示されている例が少し分かりにくかったので、ありそうな例を書きました。

http://localhost/login

◯http://localhost/login/callback
×http://localhost/callback

scope

OAuth | GitHub Developer Guide

このscopeというのは、アプリケーションから取得または操作を許可する範囲のことを指している。
例えば、何も設定をしないno scopeの場合は、公開されている情報に読み取り専用でアクセスを許可されたり、user:emailなどにすると、ユーザーがGithubに登録しているメールアドレスへアクセスが出来るなど。様々なレベルのscopeがあるので、必要に応じて設定すると良いらしい。

2.Githubからアプリケーションにリダイレクトする

POST https://github.com/login/oauth/access_token

ユーザーがリクエストを受け入れると、Githubcodeパラメーターの一時コードと1の手順で指定したstateをパラメーターに指定したアプリケーションにリダイレクトされます。この時にstateが違った場合は攻撃の可能生が高いため、OAuthのフローを中止するべきです。

設定できるパラメーター

*は必須という意味です。

Name Type 説明
client_id* string アプリケーション登録時に発行されたclient_id
client_secret* string アプリケーション登録時に発行されたclient_secret
code* string 1の手順の応答で受け取る。tokenとの引き換え券のようなもの。
redirect_uri string 承認後にユーザーがリダイレクトされるアプリケーションのURL。リダイレクトのルールは先程紹介したものと同じです。

POST https://github.com/login/oauth/access_tokenへのレスポンス

access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer 

Accept ヘッダーに応じて、様々な形式で受け取ることが出来ます。

 Accept: application/json {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"} Accept: application/xml <OAuth> <token_type>bearer</token_type> <scope>repo,gist</scope> <access_token>e72e16c7e42f292c6912e7710c838347ae178b4a</access_token> </OAuth>
3. tokenを使ってAPIへリクエストする

ここまでのフローで受け取ったaccess_tokenでAPIへリクエストが出来ます。
tokenは以下のようなパラメーターで渡すパターン。

GET https://api.github.com/user?access_token=...

HeaderのAuthorizationを使って渡すことも出来ます。

Authorization: token OAUTH-TOKEN

curlコマンドでは以下のようになります。

curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/user

なんとなく分かった気がする。

GoでAPIから取得したJSONを5分でパースする

微妙に面倒なアレ

GoはAPIの用途で、私は結構使うのですが、そのAPIを構築する上で、外部のAPIを使ってデータを集めたりすることもよくあります。そして避けて通れないのが、JSON解析です。自力でやると地味に面倒です。
今回はその作業は5分で終わらせる方法を紹介します。

はよソース

github.com

便利なツール達

Let’s try!!!

目的のJSONを取得する

connpass.com 今回はConnpassのAPIから返ってくるJSONを解析します。

{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}

https://connpass.com/api/v1/event/?keyword=python&count=1
リクエストをブラウザで実行します。

JSON Pretty Linter - JSONの整形と構文チェック コピーして、JSON整形します。 整形したJSONをコピーして、goのソースに貼り付けます。

jsonStrという変数に以下のような感じで代入してください。

var jsonStr = `{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}`

JSONからstructを作成する

https://mholt.github.io/json-to-go/JSON-to-Go: Convert JSON to Go instantly
jsonを食わせて、strcutを生成する。

f:id:tikasan0804:20170426103310p:plain 右に出てるstrcutをコピーしてgoのソースに貼り付ける

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

UmarshalでJSONをパースする

jsonパッケージのUnmarshalでパースして終了。

jsonBytes := ([]byte)(jsonStr)
data := new(AutoGenerated)

if err := json.Unmarshal(jsonBytes, data); err != nil {
    fmt.Println("JSON Unmarshal error:", err)
    return
}
fmt.Println(data.Events[0])

$ go run main.go

以上で、完了です!!!簡単すぎる!!!!

全ソース

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

func main() {
    var jsonStr = `{
    "results_returned": 1,
    "events": [
        {
            "event_url": "https://kikaigakushuu.connpass.com/event/56040/",
            "event_type": "participation",
            "owner_nickname": "keiichirou_miyamoto",
            "series": {
                "url": "https://kikaigakushuu.connpass.com/",
                "id": 2589,
                "title": "人工知能勉強会"
            },
            "updated_at": "2017-04-26T02:47:01+09:00",
            "lat": "35.696575200000",
            "started_at": "2017-05-09T20:00:00+09:00",
            "hash_tag": "geek",
            "title": "強くなるロボティック・ゲームプレイヤーの作り方勉強会「4章 強化学習 前半」",
            "event_id": 56040,
            "lon": "139.771830100000",
            "waiting": 0,
            "limit": 30,
            "owner_id": 10866,
            "owner_display_name": "keiichirou_miyamoto",
            "description": "説明",
            "address": "東京都千代田区神田須田町2丁目19−23  (野村第3ビル4階)",
            "catch": "#人工知能,#機械学習,#深層学習,#ニューラルネット,#ディープラーニング,#初心者",
            "accepted": 17,
            "ended_at": "2017-05-09T22:00:00+09:00",
            "place": "コワーキングスペース秋葉原 Weeyble(ウィーブル)"
        }
    ],
    "results_start": 1,
    "results_available": 1744
}`

    jsonBytes := ([]byte)(jsonStr)
    data := new(AutoGenerated)

    if err := json.Unmarshal(jsonBytes, data); err != nil {
        fmt.Println("JSON Unmarshal error:", err)
        return
    }
    fmt.Println(data.Events[0])
}

実際にAPIにリクエストする

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

type AutoGenerated struct {
    ResultsReturned int `json:"results_returned"`
    Events          []struct {
        EventURL      string `json:"event_url"`
        EventType     string `json:"event_type"`
        OwnerNickname string `json:"owner_nickname"`
        Series        struct {
            URL   string `json:"url"`
            ID    int    `json:"id"`
            Title string `json:"title"`
        } `json:"series"`
        UpdatedAt        time.Time `json:"updated_at"`
        Lat              string    `json:"lat"`
        StartedAt        time.Time `json:"started_at"`
        HashTag          string    `json:"hash_tag"`
        Title            string    `json:"title"`
        EventID          int       `json:"event_id"`
        Lon              string    `json:"lon"`
        Waiting          int       `json:"waiting"`
        Limit            int       `json:"limit"`
        OwnerID          int       `json:"owner_id"`
        OwnerDisplayName string    `json:"owner_display_name"`
        Description      string    `json:"description"`
        Address          string    `json:"address"`
        Catch            string    `json:"catch"`
        Accepted         int       `json:"accepted"`
        EndedAt          time.Time `json:"ended_at"`
        Place            string    `json:"place"`
    } `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

func main() {
    url := "https://connpass.com/api/v1/event/?keyword=python&count=1"
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    jsonBytes := ([]byte)(byteArray)
    data := new(AutoGenerated)

    if err := json.Unmarshal(jsonBytes, data); err != nil {
        fmt.Println("JSON Unmarshal error:", err)
        return
    }
    fmt.Println(data.Events[0])
}

以上で終了です。Go最高っすね。

Go合宿でビール駆動開発してきた@土善旅館

乾杯!乾杯!乾杯!

connpass.com
Go言語の合宿参加してきました。
一言で言うと、アルコール+Gopherって感じでした。

会場について

土善旅館[弓道合宿・開発合宿]

開発向けのプランがあるというイケてる旅館でした。
以下に気になってる人向けに良かった点をまとめておきます。

  • 高機能な電源タップと延長コードが大量にある。
  • 大きなスクリーンがある。
  • WiFi完備
  • スイッチングハブがあったり。。。(本当によくわからないくらいある)
  • TCP/IPマスタリング本やけものフレンズなど、偏った本のチョイス。
  • コーヒーが飲み放題。
  • 温泉がある。(24H)
  • 宴会場が60人くらい入れる。
  • 人が駄目になるクッションがある。
  • ディスプレイがある。(デュアルディスプレイとか出来る。)
  • 飯がうまい。

誰でも参加できるイベント?

Go言語に興味がある人やGo言語が好きな人、Go知らないけど楽しいコトしたい!という方なら、誰でも参加出来るイベントです。
参加者の実力も様々で、一切やったことない人から、有名なコミッターの方まで、私のような学生でも問題なく楽しめます。少しでも興味があれば気軽に申し込みをすると良いと思います。

他の参加者の記事

qiita.com medium.com

本題

JR日暮里駅で集合して、そこから笹川駅へ!

f:id:tikasan0804:20170423220627j:plain 正直遠かったです。夜行バスからのこの移動は結構体力もってかれましたw
お金に余裕があるなら、新幹線か前日入りまたは飛行機で成田から合流とかした方が良いかもです。

f:id:tikasan0804:20170423224725j:plain 笹川のこのいい感じの田舎感。本当にIT系とはかけ離れてる場所でしたw

f:id:tikasan0804:20170423221819j:plain 合宿所到着!大きなスクリーン!この時点でかなりテンションがあがる。

f:id:tikasan0804:20170423221828j:plain 開発に適した環境が用意されていて、参加者びっくり。

f:id:tikasan0804:20170423221831j:plain Go合宿スタート!!!(初乾杯)

f:id:tikasan0804:20170423222641j:plain 開発部屋風景。(乾杯)

f:id:tikasan0804:20170423222803j:plain 開発にはビールは必須。当然ですが昼間からビールを飲みます。もちろん開発もしてました。(そして、乾杯)

合宿の形式

基本的にもくもく形式で、個々でテーマを決めて何かしらの成果を出します。そして、2日目にあるLT大会で発表する。参加者の中には初心者もいらっしゃったので、@tenntennさんよるレクチャータイムもありました。
LT大会にはGo華景品もあり、どうせなら頑張っちゃおうって感じで、みなさん頑張ってました。

f:id:tikasan0804:20170423223344j:plain そして、飯がうまい!!!(既に4回くらい乾杯してる)

飯を食ってからが本番。

晩御飯を食べている時に、参加者のみなさん口を揃えて、「開発?ってなんですか」「これは一度、オフトゥンの様子を確認した方が良さそうです!」とか、「風呂入って酔いを覚まして・・・そこから乾杯するか」など、全くやらないオーラすごかったですが・・・
部屋に戻ると、みなさん真面目に開発していました。そして、何人かの参加者が「ビール入れた方が捗るんだが」と目覚めてる人もちらほら居ましたw

f:id:tikasan0804:20170423224205j:plain いざ、LT大会!!!(乾杯)

f:id:tikasan0804:20170423224321j:plain 私もLTしました。goaの話に触れつつ、goを好きになる方法について話しました。
以下に資料を貼ったので、興味あればどうぞ。

www.slideshare.net

結果発表

Goを一番愛しているで賞?というものに受賞しました!!!
景品は、@tenntennさんがアメリカでGopherグッズを買ってきてくれるそうで、かなり嬉しいw
景品提供ありがとうございました!!!

合宿終了

運営の素晴らしい進行により、無事大成功で合宿は終了しました。
とても、楽しかったです!ありがとうございました!

f:id:tikasan0804:20170423224607j:plain どこかで見たことがある集合写真を撮影し、解散しました。

qiita.com

OSSにコントリビュートしたら楽しかった

OSS楽しい!

僕みたいな学生エンジニアに限らず、お世話になることが多い世界中のOSSですが、つい先日ようやくコントリビュート童貞卒業したぺいです。
結構ハードルを感じている人が居ますが、もっと気軽にやってもらいたいと考え、今回は魅力について書いてみたいと思います。

何にコントリビュートしたか?

趣味で作っているアプリのバックエンドに使っているGoのWAFのgoaです。主にバグの修正やGAE対応です。

github.com github.com github.com github.com

説明努力をすれば、周りも助けてくれるかも?

goaの関係者が優しいというのもありますが、どのPRにしても色んな人の助けられてマージに至っています。「直そうと思ったけど、こういう問題があって難しいです」と正直に相手に伝えてみるといいと思います。場合によってはヒントを頂けます。そういったコミュニケーションを繰り返し、改善をしていけばマージ出来るコードになります。なので、実力に自信がなくてコミット出来てない人は、完璧でないといけないと気負いすることをまずやめたら良いと思います。

メリットがたくさん

何かの問題を解決することは、全体の構成を知るためにコードをたくさん読む必要があったり、そもそも言語の問題などが絡んでることもあります。なので、強制的に勉強になることが多く、自分の知識の穴埋めに丁度良いです。また、テクニックなども盗み放題なので、自分のものにすることが出来るのも魅力です。
解決するものによってはかなり大変なものもあります。(バージョンの問題や下位互換など)ですが、そういった問題解決はとても良い経験になり、個人でやっている開発にも応用できます。また、単純に達成感もありすごく楽しいです。なので、興味が少しでもあればチャレンジしてみると良いと思います。

小さいことでも貢献になる

初心者からするとハードルを感じてしまうPRやISSUEなど、「こんなしょうもないこと書いたら馬鹿にされないかな」とか、「間違ってたらどうしよう」とか、色々思うことはあると思いますけど、正直あまり気にしなくてもいいと思います。以下のようなものでも十分な貢献になります。

  • READMEやコメントなどのtypo修正
  • closeしていないコネクションcloseするだけ
  • 処理結果変わらないけど、若干無駄が減る
  • バグか分からないけど、怪しい挙動の報告

人気なOSS程、製作者は暇でないことが多いので、気づかないような小さいミスは見過ごしがちなので、結構PRチャンスはあるらしい?また、相手が知らないかもしれないバグを報告することも有益な情報です。特に自分が使っているOSSなら、自分のためにもなるので積極的にコミットすることをおすすめします。

中身のないISSUEはやめよう

Githubは誰でもアカウントが取れるので、気軽にOSS活動が出来ますが、全く利益を生まないようなISSUEを投げるのはやめましょうw f:id:tikasan0804:20170409223134j:plain 製作者側からしたら読む気が失せるような内容の報告が結構散見されます。相手も人間なので一定のマナーを守ること大事だと思います。

PRによってはテストが必要

コードの修正によって起きる変更に合わせたテストも加えることも必要です。どういうテストが必要なのかどこに書いたらいいかなどは、他のテストを参考にしたり、どうしても分からない時は聞くなりすると良いと思います。

今後も出来る限りコミットを継続し、色んなOSSに携わりたい!