コンテンツにスキップするには Enter キーを押してください

Goaを使ったデザインファーストなAPIサーバ構築

こんにちは、Fusicの岡嵜です。

ここ最近、フロントエンドにVue.jsを、バックエンドはRuby on RailsでAPIサーバを構築するという開発が続いています。

Ruby on Railsはシステムを構築するには申し分無いWAFですが、個人的にはAPIサーバを構築するには機能過多だと感じています。勿論、Ruby on Rails5でAPIモードが追加され、APIサーバの構築も想定されたユースケースの1つではあるのですが、MVCに縛られずにもっと簡単にAPIサーバを作りたいな、と。

そんな時に、Go言語でAPIサーバを構築できるGoaというフレームワークを知ったので、試しに使ってみました。

Goaとは

Goa Logo

公式サイト github

デザインファーストなmicroserviceフレームワークです。

どういった点がデザインファーストかと言うと、最初にDSLでAPIサーバの仕様や扱うリソース、対応する機能を定義し、これを元にソースコードの大半が自動生成される点です。自動生成されたソースコード中に、リクエストを受け付けた時の処理を記述するだけでAPIサーバが出来上がるので、処理ロジックに集中して開発することができます。

JWT認証等のセキュリティ機能を追加したり、O/Rマッパーを使用することもできます。Go言語なのでコンパイル時の型チェックがあったり、golangのエコシステム(数多くのツールやライブラリ)を活用出来る点も嬉しいところです。

以下コマンドを実行することでGoaをインストールできます。

$ go get -u github.com/goadesign/goa/...

APIサーバを作ってみる

まずは、Workspace(今回は app というディレクトリ)を作成し、 my_app/design/design.go を作成・編集します。このファイルが、後に元にソースコードを自動生成する際のいわば設計図となります。

VSCode

今回はシンプルに User を取得するAPIサーバを作ることにしました。

package design

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

var _ = API("MyApp", func() {
	Title("My App")
	Description("This is api server for my application.")
	Scheme("http")
	Host("localhost:8080")
	BasePath("/api/v1")
})

var _ = Resource("user", func() {
	BasePath("/users")
	DefaultMedia(UserMedia)

	Action("list", func() {
		Description("Get all users")
		Routing(GET("/"))
		Response(OK, func() {
			Media(CollectionOf(UserMedia, func() {
				View("default")
			}))
		})
		Response(NotFound)
	})

	Action("show", func() {
		Description("Get user by id")
		Routing(GET("/:userID"))
		Params(func() {
			Param("userID", Integer, "User ID")
		})
		Response(OK)
		Response(NotFound)
	})
})

// UserMedia defines the media type used to render bottles.
var UserMedia = MediaType("application/vnd.goa.example.user+json", func() {
	Description("A user of my application")
	Attributes(func() {
		Attribute("id", Integer, "Unique user ID")
		Attribute("email", String, "User's email")
		Attribute("last_name", String, "User's last name")
		Attribute("first_name", String, "User's first name")
		Required("id", "email", "last_name", "first_name")
	})
	View("default", func() {
		Attribute("id")
		Attribute("email")
		Attribute("last_name")
		Attribute("first_name")
	})
})

コマンドを実行して、ソースコードを自動生成します。

$ goagen bootstrap -d github.com/yuuu/my_app/design

VSCode

自動生成された user.go にリクエスト時の処理を記述します。

package main

import (
	"github.com/goadesign/goa"
	"github.com/yuuu/my_app/app"
)

// UserController implements the user resource.
type UserController struct {
	*goa.Controller
}

// NewUserController creates a user controller.
func NewUserController(service *goa.Service) *UserController {
	return &UserController{Controller: service.NewController("UserController")}
}

var users = []*app.GoaExampleUser{
	&app.GoaExampleUser{ID: 1, Email: "user1@example.com", LastName: "Fusic", FirstName: "Taro"},
	&app.GoaExampleUser{ID: 2, Email: "user2@example.com", LastName: "Fusic", FirstName: "Jiro"},
}

// List runs the list action.
func (c *UserController) List(ctx *app.ListUserContext) error {
	// UserController_List: start_implement

	// Put your logic here
	// 実際にはデータベース等から読み出す
	res := users
	//res := app.GoaExampleUserCollection{}
	return ctx.OK(res)
	// UserController_List: end_implement
}

// Show runs the show action.
func (c *UserController) Show(ctx *app.ShowUserContext) error {
	// UserController_Show: start_implement

	// Put your logic here
	// 実際にはデータベース等から読み出す
	res := users[ctx.UserID]
	//res := &app.GoaExampleUser{}
	return ctx.OK(res)
	// UserController_Show: end_implement
}

APIサーバへリクエストしてみる

go run してAPIサーバを起動します。

$ go run *.go

curl でリクエストすると、レスポンスに User の情報がセットされていることがわかります。

$ curl localhost:8080/api/v1/users/
[{"email":"user1@example.com","first_name":"Taro","id":1,"last_name":"Fusic"},{"email":"user2@example.com","first_name":"Jiro","id":2,"last_name":"Fusic"}]

$ curl localhost:8080/api/v1/users/0
{"email":"user1@example.com","first_name":"Taro","id":1,"last_name":"Fusic"}

$ curl localhost:8080/api/v1/users/1
{"email":"user2@example.com","first_name":"Jiro","id":2,"last_name":"Fusic"}

今後の展望

今回はデータベース等を使用せずにAPIサーバの動作確認をしていますが、実際にはデータベースに対してCRUD操作する場合がほとんどなのでO/Rマッパーを使うよう設定する必要があります。また、認証機能(JWT等)を使えるようにする必要があります。

Vue.jsと組み合わせて使うことを想定すると、クロスオリジン要求に対応するよう設定することも必要です。

このあたりは、次回解説したいと思います。

2018年の年明けに組込み畑からやってきた、2児の父 兼 Webエンジニアです。
業務ではRuby on Rails、最近ではフロントエンドにVue.jsを使っています。趣味でGo言語を触ることも。
Lab.Consoleのプロダクトオーナーをしており、AWSと仲良くなれるよう日々勉強中です。

コメントする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です