20200912のGoに関する記事は11件です。

echoでより高速なJSONを返す

はじめに

goのwebフレームワークであるEchoにてデフォルトのJSONよりも高速なJSONを返したいことがあると思います。その例としてeasyjsonで作ったJSONを返そうとしたときに少し詰まりどころがあったので書き留めておきたいと思います。

まずはデフォルトから

main.go
package main

import (
    "github.com/masibw/echo_json/model"
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    // Echo instance
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Routes
    e.GET("/", getUser)

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

// Handler
func getUser(c echo.Context) error {
    user := model.User{Name:"John",Age:18}
    return c.JSON(http.StatusOK, user)
}

という感じで

return c.JSON(http.StatusOK, user)

と返せば良いです。

easyjsonで作ったJSONを返す

詳しいパフォーマンス測定は行っていませんが こちらの記事によるとencoding/jsonよりも2倍くらいは速そうです。

echoにて既にencoding済みのJSONを返すには

func(c echo.Context) error {
  encodedJSON := []byte{} // Encoded JSON from external source
  return c.JSONBlob(http.StatusOK, encodedJSON)
}

という感じで渡してあげれば良いです。ということでencodedJSONのところにeasyjsonで作ったJSONを渡しましょう。

ディレクトリ構成はこんな感じで作業します。

.
├── go.mod
├── go.sum
├── main.go
└── model
    └── model.go
model.go
package model

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

easyjsonのインストール

go get -u github.com/mailru/easyjson/...

した後にmodelが記載されているディレクトリに移動して

easyjson -all <file>.go

する必要があります。
すると

<file>_easyjson.go

というファイルが作成されるはずです。
あとはプログラム内で

encodedJSON, err := easyjson.Marshal(user)

のようにJSONへencodeすることができます。
これをechoで返す様にすれば良いです。全体像がこちら

main.go
package main

import (
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/mailru/easyjson"
    "github.com/masibw/echo_json/model"
    "log"
    "net/http"
)

func main() {
    // Echo instance
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Routes
    e.GET("/", getUser)

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

// Handler
func getUser(c echo.Context) error {

    user := model.User{Name: "John", Age: 18}
    encodedJSON, err := easyjson.Marshal(user)

    if err != nil {
        log.Fatal("json marshal error")
    }
    return c.JSONBlob(http.StatusOK, encodedJSON)
}

つまりどころ

mainパッケージに対してコマンドが実行できない

easyjson -all <file>.go

該当Issue?
なのでmodelパッケージのように別パッケージを用意してあげましょう。
それに加えて厳密な条件は調べていませんが構造体定義以外が対象ファイルに記載されているとエラーが返ってきてしまうことがあるようです。
その場合は一旦構造体のみ別パッケージに移すなりしてeasyjsonのコマンドを実行し,自動生成されたファイルをコピーして同じパッケージにすれば動きます(構造体定義が変わるたびに実行する必要があるのでめんどくさいですが)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

A Tour of Go メモ【2】1日目

A Tour of Go
時折、コードを変更したり、関連記事を調べたりしながら、学習しました。

for

func main() {
    sum := 0
    for i :=0; i < 10; i++ {
        sum += i
        fmt.Println(i)
        fmt.Println(sum)
    }
}

#省略可能
for ; sum < 100; {
    sum += sum
}

#さらに省略可能
for sum < 100 {
    sum += 100
}

# 無限ループ
for {

}

if

func judge(x int) string {
    if x > 0 {
        return "マイナス"
    }
    return fmt.Println("プラス")
}
func main() {
    fmt.Println(judge(−100))
}
>> "マイナス"

#注意 returnどちらか一つ
if文内の returnに到達したら、
if文外の return の戻り値は返ってこない

条件の前に変数を定義できる

条件の前に定義した変数はifのスコープ内のみ使用可能
もちろん、elseブロッック内でも使用可能

func pow(x, n, lim float64) float64 {
    if v := math.pow(x, n); v < lim {
        return v
    } else {
        return v+10
    }
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10)
        pow(3, 3, 20)
    )
}
>> 9
>> 127


#スコープ外で使うとエラー

if v := 10: v < 100 {
  return "100以下"
}
fmt.Println(v)
>> undefined: v

Switch

breakはいらない
caseの条件が一致すれば、自動的にbreakする

runtime.GOOSで実行しているOSを判定できる

func main() {
    fmt.Printl("Go runs on")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS x.")
    case "linux":
        fmt.Println("Linux.")
    default:
       fmt.Printf("%s.\n", os)
    }
}

switch {
    t := time.now()
    case t.Hour() < 12:
        fmt.Println("Good morning")
    case t.Hour() < 17:
        fmt.Println("Good afternoon")
    default:
        fmt.Println("Good evening")
}

Defer

呼び出し元の関数が終わるまで、deferに渡した関数の実行を遅らせる
deferに渡した関数の引数はすぐに評価される
Go言語のdeferを正しく理解する | How defer in Golang works

func main() {
        world := "world"
        defer fmt.Println(world)
        world = "new world"
        fmt.Println("hello")
}

> hello
# deferにより、main()関数が終わった後に実行される
# しかし、worldはすぐに評価されるので、deferの後に"new world" と変更しても、反映されない
> world
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

A Tour of Go メモ【1】1日目

goのチュートリアル

A Tour of GO
時折、コードをいじって、どんなエラーが出るかなどを試しながら、学習しました。

出力

packages.go
package main

# パッケージをインポート
import (
  # 出力のためのパッケージ
    "fmt"
)

func main() {
    # 出力
    fmt.Println("Hello World"))
}

パッケージのインポート

packages.go
package main

#複数行で書くこともできる
import "fmt"
import "math"

#こっちの方がスマート
import (
 "fmt"
 "math"
)

関数と引数

function.go
package main

import "fmt"

#変数の後ろに型名を指定する必要がある
func add(x int, y int) int {
    return x + y
}

func main() {
    fmt.Pringln(add(20, 30))
}

// 50


#関数の引数が同じ型なら省略可能

func add(x, y int) int {
  return x + y

}

複数の戻り値

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
  a, b := swap("hello", "world")
  fmt.Println(a,b)
}

// "world" "hello"




# Namec return values

* 以下は戻り値の変数が定義されないので、エラーになる
func split(sum int) (int, int) {
    a = sum / 2
    b = sum / 3
    return a, b
}


* 戻り値の変数に名前をつける必要がある
func split2(sum int) (a, b int) {
    a = sum / 2
    b = sum / 3
    return a, b
}

変数

var n int
var c


func main () {
  fmt.Println(n)
  fmt.Println(c)
}

# int を指定し、何も値を指定しないと初期値は0
// 0
# 型を指定しなかった c はエラーになる
// syntax error: unexpected newline, expecting type

 変数の初期値

var i, j int = 1, 2
# 初期値を同時に指定すると、型を宣言しなくて良い
var a, b = 10, 11
var c, python, java = true, false, "no"

func main() {
    fmt.Println(i, j)
    fmt.Println(a, b)
    fmt.Println(c, python, java)
}

// 1 2
// 10 11
// true false no

変数の定義と代入

func main() {
    a := "hello"
    k := 3
    fmt.Println(a)
    fmt.Println(k)
}

// hello
// 3



# func main()の外で := を使って、代入するとエラー

# ok
var a strint = "hello"
# ng
b := "hello"

func main() {
    fmt.Println(a)
    fmt.Println(b)
}
# a は OK
// hello
# b はエラーになる
// syntax error: non-declaration statement outside function body

各変数の初期値

func main() {
    var i int
    // 0
    var f float64
    // 0
    var b bool
    // false
    var s string
    // ""
    ...
}

型の変換

var i int = 32
var f float64 = float64(i)
var u uint = uint(f)

i := 32
f := float64(i)
u := uint(f)

型の確認

func main() {
    v := 100
    fmt.Printf("%T\n", v)
    s := "hello"
    fmt.Printf("%T\n", s)
}
// v
> int
// s
> string

定数の宣言

func main() {
    const Say = "hello"
    fmt.Println(Say)

   #再代入できない
    Say = "Hey"
    // cannot assign to Say
}
> "hello"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

レイヤードアーキテクチャ+DDDでAPI開発[入門]

前書き

  • 著者の理解が甘く間違っている箇所もあるかもしれません。その際は優しく教えていただけると幸いですmm
  • レイヤードアーキテクチャは具体的なディレクトリ構成等を示すものではなく、あくまで概念的な指標です。正解はありません。

なるべく初学者にもわかりやすい様に書きました。
始めは実装コード等理解しづらいと思いますが、概念的な理解の足掛かりにしていただけると嬉しいです☺️

レイヤードアーキテクチャって何?

概要

ソフトウェアの関心事をレイヤー毎に分離するための設計方法の一つです。
他にもクリーンアーキテクチャ・オニオンアーキテクチャなどが有名ですが、どれも責務・依存関係を明確化するというゴールは変わりません。

メリット

アーキテクチャを導入する主なメリットは以下の二つです。

  • DBやUI・ビジネスロジックなど、仕様変更の際にコードの修正箇所が明確・軽微
  • 各レイヤーにおいてテストする目的が明確・処理をモックしやすいことからテストしやすい

仕様変更に強くテストもしやすいことから、保守運用しやすいということで人気なんですね?

レイヤー解説

※矢印は依存方向です。依存とは、関数や変数を参照することを指します。
スクリーンショット 2020-09-12 23.08.29.png
レイヤードアーキテクチャ+DDDの基本はこの図です。
純正レイヤードアーキテクチャではdomain->infrastructure層という依存関係でしたが、「ビジネスの中核をなすdomainが、使用DB(infra)などに依存する・影響されるっておかしくない?」と言う事で、DDDではdomainが依存の最高位に君臨しています。これを依存関係逆転の原則(DIP)っていうみたいです?

domain層: ビジネスルールの中核の定義を担当
infrastructure層: DB通信・DBデータ更新を担当
usecase層: アプリケーション固有のビジネスルールを担う処理を担当
interface層: ユーザーからのリクエスト受け取りや、表示に関することを担当

大まかにはこんな役割です。このままではわかりづらいので以下で詳しく説明します!

domain層

ここでは「ドメインモデル」と「リポジトリ」を定義します。

ドメインモデル

一言でいうとビジネスルールの中核・対象の存在です。
ex)「物を売り買いする」というビジネスルールでは、「ユーザー」「商品」などがドメインモデルとして定義される。

SQLのテーブルとよく似ていますが、そのビジネスルールで使用しないデータの場合はdomainとして扱わないという点で異なります
ex) SQLには解析用にcreated_atが保存されているが、サービス上は使用しないためドメインモデルには持たせないなど。

以下、ユーザーのドメインモデルの例です。

path/to/domain/model/user/model.go
package user

type User struct {
    ID             int32
    Name           string
}

リポジトリ

ドメインモデルになんらかの変更をする処理を定義する所です。

注意したいのは、ここで定義されているモノは「ドメインモデルこうやって変更するよ!」と抽象的に宣言しているだけで、具体的な処理を記述したものではないという事です。「ドメイン層は、他の仕様変更(使用DBやUI)に影響されない(依存しない)」という思想の元、リポジトリには具体的な処理を記述せず、抽象的な宣言に止まります
具体的には、Interfaceを用いて抽象的なドメインの更新処理を表現しています。

ここら辺難しいですよねorz
イメージは、リポジトリという上司が「ドメインモデルに対してこんなことしたい!」と算段なく抽象的言っている感じで、DB更新などの具体的な処理はinfra層などの部下がリポジトリの意図(Interface)を汲み取って行います

以下、リポジトリの例です。ユーザーというドメインモデルに対して、「こんな引数・戻り値で名前を更新するぞ!」という抽象的な宣言がなされています。

path/to/domain/repository/user/repository.go
package user

import "path/to/domain/model/user"

type Repository interface {
    UpdateName(record *user.User, name string) error
}

infrastructure層

ここでは実際にDBと通信し、リポジトリに記載された「抽象的なドメインモデルの更新処理」を実現します

リポジトリで宣言されたInterface(抽象的な更新処理)を継承し、infra層での実際の更新処理をメソッド化しています。
そうすることによって、リポジトリで定義された抽象的な更新処理がinfra層の具体的な更新処理と紐づけられています。(go#interfaceの仕様上、整合性が取れないとエラーを吐くためです。もっと噛み砕けば、リポジトリで宣言された抽象的な更新処理が、infra層に実装されていなければエラーを吐くということです。)

ユーザーの名前変更を例にコードをみてみましょう。

  • server.goでinfra層に使用DBを渡す
  • リポジトリで定義したInterface(抽象的な更新処理)をinfra層(repositoryImpl)に継承
  • UpdateNameメソッドで実際のDB更新処理を記述

を行っています。(使用DBはinfra層にベタ書きでもいいのですが、sqlmockでテストしやすいためserver.goから渡しています。)

server.go
package server

import (
    ur "path/to/infrastructure/repositoryimpl/user"
)
...
    // mysql.Conn = sqlへのコネクション。sql.Open("mysql"...
    ur.NewRepositoryImpl(mysql.Conn)
...
path/to/infrastructure/repositoryimpl/user/repositoryimpl.go
package user

import (
    um "path/to/domain/model/user"
    ur "path/to/domain/repository/user"
    "database/sql"
)
type repositoryImpl struct {
    db *sql.DB
}

// NewRepositoryImpl Userリポジトリで定義したinterfaceを継承
func NewRepositoryImpl(db *sql.DB)ur.Repository {
    return &repositoryImpl{
        db,
    }
}

// UpdateName repositoryImplのメソッドとして、実際のDB更新。
func (impl repositoryImpl) UpdateName(record *um.User, newName string) error {
    stmt, err := impl.db.Prepare("UPDATE user SET name = ? WHERE id = ?")
    if err != nil {
        return err
    }
    _, err = stmt.Exec(newName, record.ID)
    return err
}

usecase層

ここではアプリケーション固有のビジネスルールを記述します。
ビジネスルールってなんだよ!って感じですよね笑

例として、「一定額以上の買い物をした人に対して、ある還元率でポイントを付与する」というケースで考えてみましょう。おさらいですが、実際にUserというドメインモデルの所持ポイントを更新するのは(抽象的に)リポジトリの役目ですよね?
しかし、いくら以上の支払いでポイントを付与するか・ポイント還元率は何%かなどは、実際の更新というよりも、更新に使うデータの選定・整形に近いと思います。これが「アプリケーション固有のビジネスルール」と言われている部分です。

それらを記述するのがusecase層です。interface層(handler)から渡されたデータを整形し、必要に応じてそのデータをリポジトリに渡します

コードをみてみましょう。例示が行き来して申し訳ないですが、名前更新処理です。

  • 先ほど作成した実際のデータ更新処理が記載されたinfra層(userRepoImpl)をユースケースに(リポジトリを介して間接的に)継承
  • interface層から渡されたnameから更新処理を目論む。データ更新の際は、継承されたrepositoryImplを(リポジトリを介して間接的に)叩く
server.go
package server

import (
    ur "path/to/infrastructure/repositoryimpl/user",
    uu "path/to/usecase/user"
)
...
    userRepoImpl := ur.NewRepositoryImpl(mysql.Conn)
    uu.NewUseCase(userRepoImpl)
...
path/to/usecase/user/usecase.go
package user

import (
    um "path/to/domain/model/user"
    ur "path/to/domain/repository/user"
)

// interface層で参照するためInterfaceで切り出し
type UseCase interface {
    UpdateName(name string) error
}


type useCase struct {
    repository ur.Repository
}

// NewUseCase Userリポジトリ(domain層)を継承したrepositoryImpl(infra層)を間接的に参照する
func NewUseCase(userRepo ur.Repository) UseCase {
    return &useCase{
        repository: userRepo,
    }
}


// UpdateName ユーザーの名前を更新するユースケース
func (ur useCase) UpdateName(name string) error {
    ...変数userにcontextからユーザーデータを代入する処理...

    err := ur.repository.UpdateName(user, name)

    return nil
}

interface層

ここではrequest/responseの整形・バリデーションを行います。

ユーザーへの表示をメインとしたこの層では、具体的なビジネスロジックは書かないことが重要です。APIではリクエストが不正値かどうかを確認した後ユースケースを呼び出し、返り値を整形してレスポンスすることだけが役目です。例えば名前更新処理では、長すぎる文字列列クエストはこの層でrejectします。

またinterfaceはGo言語の予約語なので、ディレクトリ作成時はinterfaces/とかにしましょう。

以下、名前更新の例です。

  • 先ほど作ったuserのusecaseを継承
  • リクエストのバリデーションを行い、そのデータをusecaseに渡す(呼び出す)。
  • 返り値を整形してレスポンス
server.go
import (
    ur "path/to/infrastructure/repositoryimpl/user",
    uu "path/to/usecase/user",
    uh "path/to/interfaces/api/handler/user"
    "net/http"
)

func Serve(addr string) {
    userRepoImpl := ur.NewRepositoryImpl(mysql.Conn)
    userUsecase := uu.NewUseCase(userRepoImpl)
    userHandler := uh.NewHandler(userUsecase)

    // contextにユーザー情報を入れるAuthenticateや、postメソッドは各々でお願いします。
    http.HandleFunc("/user/update", post(Authenticate(userHandler.HandleUpdate())))

    ...サーバー起動処理...
}
path/to/interfaces/api/handler/user.go
package user

import (
    "path/to/usecase/user"
    "encoding/json"
    "http/net"
)

// Handler UserにおけるHandlerのインターフェース
type Handler interface {
    HandleUpdate() http.HandlerFunc
}

type handler struct {
    useCase user.UseCase
}

// NewHandler : User データに関する Handler を生成
func NewHandler(userUseCase user.UseCase) Handler {
    return &handler{
        useCase: userUseCase,
    }

// HandleUpdate ユーザ情報更新処理
func (uh handler) HandleUpdate() http.HandlerFunc {
    return func(writer http.ResponseWriter, request *http.Request) {
        // リクエストBodyからNameを取得
        var requestBody UpdateRequest
        err := json.NewDecoder(request.Body).Decode(&requestBody)
        if err != nil {
            ...リクエストエラー...
            return
        }

        // リクエストのバリデーション(名前の長さは適切か)
        if len(requestBody.Name) < 名前の最短値 || 名前の最長値 < len(requestBody.Name) {
            ...バリデーションエラー...
            return
        }

        // 名前を更新するユースケースを呼び出す。
        err = uh.useCase.UpdateName(user, requestBody.Name)
        if err != nil {
            ...ユースケースのエラー処理...
            return
        }

        // いい感じのresponse
        response.Success(writer, nil)
    }
}

// UpdateRequest ユーザーの名前更新リクエスト
type UpdateRequest struct {
    Name string `json:"name"`
}

まとめ

以上がレイヤードアーキテクチャ+DDDの構成例解説です。
稚拙な文章を最後まで読んでくださりありがとうございました。
ご指摘・ご感想あればコメントか、twitterにまで連絡していただけると幸いですmm

参考にした記事は以下ですmm
今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。(golang)
【Golang + レイヤードアーキテクチャ】DDD を意識して Web API を実装してみる

それではみなさん、楽しい開発ライフを!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

production-ready なGo製2Dゲームライブラリ Ebiten の紹介

はじめに

Ebiten は、Goで書かれた非常にシンプルな2Dゲームライブラリです。(公式サイト: https://ebiten.org)

圧倒的な安定性を誇り、今2Dゲームを作るなら非常にオススメ! というわけで、改めて紹介記事を書いてみます。

Ebiten で作られた「くまのレストラン」などの名作を手掛ける odencat の最新作、ねずみバスターズが公開されました(もちろん Ebiten 製)。現在各種アプリストアにてダウンロードできます。(勝手に宣伝)

特徴

はじめる

(ここではGoのインストールおよび、Goの基礎知識に関しては割愛します。)

Windowsおよび、macOSで Ebiten を使い始めるのは非常に簡単です!

まずは、go mod init <モジュール名> で適当なGo Moduleを作り、以下のコードを書いてみましょう。
https://ebiten.org/tour/hello_world.html の転載)

main.go
package main

import (
    "log"

    "github.com/hajimehoshi/ebiten"
    "github.com/hajimehoshi/ebiten/ebitenutil"
)

type Game struct{}

func (g *Game) Update(screen *ebiten.Image) error {
    return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
    ebitenutil.DebugPrint(screen, "Hello, World!")
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
    return 320, 240
}

func main() {
    ebiten.SetWindowSize(640, 480)
    ebiten.SetWindowTitle("Hello, World!")
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

できたら、go run main.go を実行します。初回だけは各種ビルドに時間がかかるので待ちましょう。

画像のような、いい感じに味のあるHello Worldが表示されれば成功です!
スクリーンショット 2020-09-12 19.35.12.png

Windows, macOS以外のプラットフォームでのビルドについては、ドキュメント https://ebiten.org/documents/ を参照してください。

できること

ざっくりと Ebiten の機能を列挙してみます。詳しい解説は Ebitenの個人的まとめ に譲りますので、実際に扱う際はそちらを参照してください。

入力まわりを除けば全ての機能が、Webやモバイルを含む各種プラットフォームで同じように利用できます。(対応状況

できないこと

Ebiten は意図的にシンプルさを保っているため、フレームワーク的な部分は備えていないのでご留意ください。

より深く

最後に各種リソースへのリンクをご紹介するので、学習に役立ててください!

サンプル

Webサイト には、たくさんの今すぐ触れるサンプルが掲載されています。

ソースは、リポジトリの examples/ ディレクトリ以下にあります。Webよりもさらに数が多いです。(GitHubのリンク

ソースは以下のように、-tags=exampleをつけて実行します(インストール時に examples もバイナリとして入ってきてしまう問題への対策)。

# go run -tags=example ソースへのパス

go run -tags=example examples/rotate/main.go

ドキュメント

コミュニティ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集

はじめに

Ebiten は、Goで書かれた非常にシンプルな2Dゲームライブラリです。(公式サイト: https://ebiten.org

圧倒的な安定性を誇り、今2Dゲームを作るなら非常にオススメ! というわけで、改めて紹介記事を書いてみます。

Ebiten で作られた「くまのレストラン」などの名作を手掛ける odencat の最新作、ねずみバスターズが公開されました(もちろん Ebiten 製)。現在各種アプリストアにてダウンロードできます。(勝手に宣伝)

特徴

はじめる

(ここではGoのインストールおよび、Goの基礎知識に関しては割愛します。)

Windowsおよび、macOSで Ebiten を使い始めるのは非常に簡単です!

まずは、go mod init <モジュール名> で適当なGo Moduleを作り、以下のコードを書いてみましょう。
https://ebiten.org/tour/hello_world.html の転載)

main.go
package main

import (
    "log"

    "github.com/hajimehoshi/ebiten"
    "github.com/hajimehoshi/ebiten/ebitenutil"
)

type Game struct{}

func (g *Game) Update(screen *ebiten.Image) error {
    return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
    ebitenutil.DebugPrint(screen, "Hello, World!")
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
    return 320, 240
}

func main() {
    ebiten.SetWindowSize(640, 480)
    ebiten.SetWindowTitle("Hello, World!")
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}

できたら、go run main.go を実行します。初回だけは各種ビルドに時間がかかるので待ちましょう。

画像のような、いい感じに味のあるHello Worldが表示されれば成功です!
スクリーンショット 2020-09-12 19.35.12.png

Windows, macOS以外のプラットフォームでのビルドについては、ドキュメント https://ebiten.org/documents/ を参照してください。

できること

ざっくりと Ebiten の機能を列挙してみます。詳しい解説は Ebitenの個人的まとめ に譲りますので、実際に扱う際はそちらを参照してください。

入力まわりを除けば全ての機能が、Webやモバイルを含む各種プラットフォームで同じように利用できます。(対応状況

できないこと

Ebiten は意図的にシンプルさを保っているため、フレームワーク的な部分は備えていないのでご留意ください。

より深く

最後に各種リソースへのリンクをご紹介するので、学習に役立ててください!

サンプル

Webサイト には、たくさんの今すぐ触れるサンプルが掲載されています。

ソースは、リポジトリの examples/ ディレクトリ以下にあります。Webよりもさらに数が多いです。(GitHubのリンク

ソースは以下のように、-tags=exampleをつけて実行します(インストール時に examples もバイナリとして入ってきてしまう問題への対策)。

# go run -tags=example ソースへのパス

go run -tags=example examples/rotate/main.go

ドキュメント

コミュニティ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sqlfile : GoでSQLファイルを簡単に実行する

GoでSQLファイルを簡単に実行するためのライブラリを作ったので,その紹介をします.

https://github.com/tanimutomo/sqlfile

image.png

sqlfileの簡単な使い方

詳しい使い方については,本記事の下,もしくは,上記のGitHubをご参照ください.

main.go
import (
  "database/sql"
  "github.com/tanimutomo/sqlfile"
)

db, err := sql.Open("DBMS", "CONNECTION")

s := sqlfile.New()
err := s.File("example.sql")
res, err := s.Exec(db)

はじめに

Goで複数のクエリが書かれたSQLファイルを実行できるライブラリがなかったので,作りました. sqlfile

作成の背景
Goで,DAO (data access object) パッケージのユニットテストの際にseed data挿入が必要になりました.
RailsのFixtureのような,seed dataを作成するためのパッケージとして,testfixtures という便利なライブラリがあります.(RailsのFixturesほどの高価な機能はないです.)
しかし,以下のような点で使い勝手が悪かったです.
(もしかしたら,testfixturesの設定で解決できるのかもしれませんが,見つけられませんでした)

  • テーブルごとにファイルを作成する必要がある
  • テストごとにレコードが削除されるわけではないため,レコードを削除したいテーブルは,table_name.ymlというファイルを作成し,中身は[]を書いておく必要がある
  • 空のymlファイルがあるとエラーになる

これらのことから,
DBのデータに変更を加える関数をテストする際に,テストケースごとにseed dataを用意しようとすると,testdata/に大量のyamlファイルを保持しておく必要があり,管理が面倒でした(以下,例).

tree
.
└── testdata/
    ├── func1/
    │   ├── success_login_user/
    │   │   ├── users.yml
    │   │   ├── articles.yml
    │   │   ├── tag.yml
    │   │   └── article_tags.yml
    │   ├── fail_not_found_user/
    │   │   ├── users.yml
    │   │   ├── articles.yml
    │   │   ├── tag.yml
    │   │   └── article_tags.yml
    │   └── ...
    ├── func2/
    │   └── ...
    └── ...

特に,空のファイルが許容されてないので,

  • 不要になったテーブルは,ファイルごと削除する必要がある
  • seed data間の関係をみる際に,ファイルを跨ぐ必要がある.

以上の理由と,これらの問題に関して,testfixturesの挙動を調べるのが面倒だったので,SQLファイルを直接書いて実行したくなりました.

SQLファイルを簡単に実行できる方法がない

ということで,testfixturesを諦めて,SQLファイルを書こうと思ったわけですが,
ここで,GoにはSQLファイルを簡単に実行できる方法がないことに気づきました.

ここでやりたいのは,以下のような複数のクエリが書かれたSQLファイルを読み込んで,実行することです.

DELETE FROM users; -- delete all records from users table

INSERT INTO users ( -- new user
  id, name, created_at, updated_at
) VALUES (
  1, 'foo', NOW(), NOW()
);

単一のSQLのクエリであれば, database/sql を使えばできますが,一度に複数のクエリを一度に実行できません.
また,SQLファイルを1行ずつ読み込んで,database/sqlで,実行することも可能ですが,以下のような問題に対処する必要があります.

  • 一つのクエリが,複数行にまたがっている
  • コメントアウト部分を取り除く

そこで,これらの問題に対処して,適切にクエリを読み込み,実行するためのライブラリを作りました.

sqlfile

長くなってしまいましたが,ここからが作成した sqlfile というライブラリの紹介になります.

本記事を執筆した時点の最新版v1.0.0では,ざっくり以下の2機能しかありません.

  • SQLファイルの読み込み
  • 読み込んだクエリの実行

GitHubの方に,Usageを載せてありますが,ここでも簡単に説明をします.

Installation

go get github.com/tanimutomo/sqlfile

Usage

SQLファイルを準備
注意! : 各クエリの最後に,必ず;をつけてください.

example.sql
INSERT INTO users ( -- users table
  id, name, email, created_at, updated_at
) VALUES (
  1, 'user1', 'user1@example.com', now(), now() 
);

INSERT INTO articles ( -- articles table
  id, user_id, title, content, created_at, updated_at
) VALUES (
  1, 1, 'title1', "-- About -- \n I'm sqlfile.", now(), now() -- post1
), (
  2, 1, 'title2', '- About - \n I''m sqlfile.', now(), now() -- post2
);

SQLファイルの読み込みと,実行
File で読み込んで, Execで実行です.
Execの中では,内部でトランザクションを発行しているため,どこかのクエリでエラーが起こったら,全てがRollbackされます.

main.go
import (
  "database/sql"
  "github.com/tanimutomo/sqlfile"
)

// Get a database handler
db, err := sql.Open("DBMS", "CONNECTION")

// Initialize SqlFile
s := sqlfile.New()

// Load input file and store queries written in the file
err := s.File("example.sql")

// Load input files and store queries written in the files
err := s.Files("example.sql", "example2.sql")

// Load files in the input directory and store queries written in the files
err := s.Directory("./examples")

// Execute the stored queries
// transaction is used to execute queries in Exec()
res, err := s.Exec(db)

まとめ

Goでdaoのwriterオブジェクトをテストする際のベストプラクティスに関しては,模索中です.
開発中に,SQLファイルを実行したいタイミングがあれば,是非使ってみてください.
Goはまだ書き始めて4ヶ月とかなので,もし間違っている箇所や改善案などがあれば,ぜひコメントお願いします.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoのBenchmarkに出てくる関数名の後ろの数字

Goのベンチマークをするときに出てくる
関数名の後ろの数値ってなんだっけとど忘れしたのでメモしておきます。

image.png

関数名-4 <-この"-4"の事ですね。

これはベンチマークに使ったCPU数です。
なので

go test -bench . -cpu 2

のようにCPU数を指定すると

image.png

ちゃんと変わります。

普段からGo使ってる人にとっては当たり前の事かもしれませんが
使ってないと知らなかったり忘れたりするので。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語のチュートリアル A Tour of Go のチートシート その1 - Basics

Go言語入門の定番、チュートリアル A Tour of Go をやったので、復習を兼ねて内容をまとめておきます。

また、チュートリアル中、何度も前のページに戻って確認しなければならなかったので、これからやろうという方にはチートシートとしてお使いいただけます。

チュートリアルをやる代わりに、さらっと眺める用途にもどうぞ。

Packages, variables and functions

パッケージとインポート・エクスポート

  • プログラムはmainパッケージから実行される。
  • importでパッケージを読み込む。
  • 大文字で始まる名前はエクスポートされる。
// ファイルの冒頭にpackage名を宣言する
package main

// パッケージのインポートをグループ化できる
// factored import statementと呼ばれる
import (
    "fmt"
    "math"
)

func main() {
    // Printf / Sqrt はそれぞれのパッケージからエクスポートされている
    fmt.Printf("Sqrt(9) is %g\n", math.Sqrt(9))

    // 小文字で始まる名前は外部に公開されないので、エラーが発生する
    fmt.Println(math.pi)
}

関数

  • 関数はfuncで定義する。
  • 引数の型は変数の後ろに書く。
// 2つのintを受け取り、intを返す関数
func add(x int, y int) int {
    return x + y
}

// 型が同じ場合は、最後の型以外は省略できる
func addThree(x, y, z int) int {
    return x + y + z
}

// 複数の戻り値を返すことができる
func swap(x, y string) (string, string) {
    return y, x
}

// 戻り値に名前をつけることができる
func div(x, y int) (result int) {
    // result変数はこの時点で定義済み
    result = x / y
    // この場合、何も書かずにreturnできる(naked return)
    return
}

変数・定数

変数の宣言はパッケージまたは関数の中で利用できる

// 変数はvarで宣言する
var x, y, z int
var s string

// initializerで初期化可能、型を省略できる
var i, j, b = 1, 2, true

// 定数はconstで宣言する
const K int = 10000
const Truth = true

func main() {
    // 関数内限定でvarの代わりに:=を使うことができる
    foo := "bar"
    c := 2 + 0.5i // 複素数型
}

基本型(組み込み型)

  • bool
  • string
  • int int8 int16 int32 int64
  • uint uint8 uint16 uint32 uint64
  • byte(uint8の別名)
  • rune(int32の別名)
  • float32 float64
  • complex64 complex128 (複素数型)

ゼロ値

初期値なしで宣言すると、ゼロ値が入る

  • 数値型: 0
  • bool: false
  • string: ""(空文字列)

型変換

明示的な型変換が必要

var i int = 60
var f float64 = float64(i)
var u uint = uint(i)

Flow control statements

forループ

  • C言語などと同じで、初期化; 条件式; 後処理を記述する
  • 多言語のwhileループの代わりにGoではforを利用する
sum := 0

// カッコは不要
for i := 0; i < 10; i++ {
    sum += i
}

// 初期化と後処理は省略可能
for ;sum < 1000; {
    sum += sum
}

// セミコロンも省略可能 -> whileループ
for sum < 10000 {
    sum += sum
}

// 条件式も省略可能 -> 無限ループ
for {
    if sum > 100000 {
        break
    }
}

if

  • forループと同じでカッコは不要
  • 条件判定用の変数を初期化することができる
// カッコ不要
if i > 3 {
    return i
}

// xのスコープはif-elseブロック内のみ
if x := i * j + 3; x < 100 {
    return x
} else {
    return x - 100
}

// compile error
return x + 100

switch

  • ひとつのcaseが実行されたら、残りのcaseは実行されないので、caseごとのbreakは不要
  • ifと同じように条件判定用の変数を初期化することができる
  • caseは定数である必要はない
// os変数を初期化
switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("OS X.")
case "linux":
    fmt.Println("Linux.")
default:
    // freebsd, openbsd, plan9, windows...
    fmt.Printf("%s.\n", os)
}

// 条件のないswitchも可能(switch trueと同じ)
t := time.Now()
switch {
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

defer

  • deferに関数の実行を渡すと、関数の終わりまで評価が遅延される
  • 最後に渡したdeferから順番に実行される(last-in-first-out)
func hello() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

func countDown() {
    for i := 0; i < 10; i++ {
        defer fmt.Print(i)
    }
    // 9876543210と表示される
}

More types: structs, slices, and maps

ポインタ

  • ポインタは値のメモリアドレスを指す
  • ポインタのゼロ値はnil
// int型のポインタは *int型
var p *int

// &オペレータでポインタを引き出す
var i int = 50
p = &I

// *オペレータでポインタの指す変数を示す
j := *p // pからiの値を読みだす
*p = 40 // pを通してiに値を代入する

struct

  • struct(構造体)はフィールド(field)の集まり
type Vertex struct {
    X int
    Y int
}

func test() {
    // Vertexリテラル: field順に値を渡す
    v := Vertex{1, 2}
    v.X = 5

    // ポインタを使う
    p := &v
    // ポインタを通してフィールドにアクセスする
    (*p).X = 6
    // 省略してp.Xとも書ける
    p.X = 6 
}

// 初期化時にフィールドの名前を使うことができる
var (
    v1 = Vertex{Y: 2, X: 1} // フィールドの順番は関係なし
    v2 = Vertex{Y: 2}       // Xはゼロ値(0)をとる
    v3 = Vertex{}           // X: 0, Y: 0
    p = &Vertex{1, 2}       // *Vertex型
)

array

  • 配列は長さを指定して宣言する
  • 配列の長さは型の一部分なので、配列のサイズを変えることはできない
// int型の要素を持つ長さ10の配列の宣言
var a [10]int

// リテラル
primes := [6]int{2, 3, 5, 7, 11, 13}

var s [2]string
s[0] = "Hello"
s[1] = "World"

slices

  • 配列が柔軟になった可変長の型
  • a[1:4]などの記法で配列からスライスを作ることができる
  • スライスはデータを格納せず、元の配列の部分列を指し示す
primes := [6]int{2, 3, 5, 7, 11, 13}

// int型の要素を持つスライスの宣言
var s1 []int

// primesの要素のうち、1番目から3番目の要素を含むスライス
s1 = primes[1:4] // {3, 5, 7}

// 下限と上限の値を省略するとデフォルトの値が使用される
// デフォルト値は下限: 0, 上限: スライスの長さ
// 以下は等価
primes[0:6]
primes[:6]
primes[0:]
primes[:]

// 同じ元となる配列を共有している場合、変更が反映される
s2 := primes[1:3] // {3, 5}
s2[0] = 59 // -> primes[1]とs1[0]も59になる

// リテラル
// [3]{true, true, false}の配列を作成して、それを参照する
b := []{true, true, false}

長さ(length)と容量(capacity)

  • 長さ:スライスに含まれる要素数、len関数で取得可能
  • 容量:スライスの最初の要素から数えた元となる配列の要素数、cap関数で取得可能
a := [6]int{0, 1, 2, 3, 4, 5}

s1 := a[0:0]  // len(s1) -> 0, cap(s1) -> 6
s2 := a[0:4]  // len(s2) -> 4, cap(s2) -> 6
s3 := a[2:6]  // len(s3) -> 2, cap(s3) -> 4

// 容量が十分な場合、スライスを再度スライスすると長さが伸びる
s4 := s1[0:4] // len(s4) -> 4, cap(s4) -> 6

// スライスのゼロ値はnil、長さと容量は0になる
var s5 []int  // len(s5) -> 0, cap(s5) -> 0

make関数

組み込みのmake関数を使用してスライスを作成することができる

// 型と長さを引数にとる
a := make([]int, 2)    // len(a) -> 2, cap(a) -> 2, a: [0 0]
// 容量も引数に渡すことができる
b := make([]int, 0, 3) // len(b) -> 0, cap(b) -> 3, b: []

多次元スライス

スライスは任意の型を含むことができるので、スライスを含むスライスを作ることができる

board := [][]int{
    []int{1, 2, 3},
    []int{4, 5, 6},
    []int{7, 8, 9},
}

append関数

  • append関数: func append(s []T, vs ...T) []T
  • スライスへ新しい要素を追加することができる
  • appendしたときに、スライスの参照先の配列の容量を超えてしまう場合は、戻り値となるスライスはより大きいサイズの配列を作って参照する
var s[]int             // len(s) -> 0, cap(s) -> 0, s: []
s = append(s, 0)       // len(s) -> 1, cap(s) -> 1, s: [0]
s = append(s, 1)       // len(s) -> 2, cap(s) -> 2, s: [0 1]
s = append(s, 2, 3, 4) // len(s) -> 5, cap(s) -> 6, s: [0 1 2 3 4]

map

  • キーと値を関連づける
  • ゼロ値はnil
var m map[string]int
m = make(map[string]int)
m["likes"] = 10
m["stocks"] = 5

// リテラル
m = map[string]int{
    "likes": 10,
    "stocks": 5,
}

// 追加・更新
m["foo"] = 1

// 取得
likes := m["likes]

// 削除
delete(m, "likes")

// 存在確認、二つ目の戻り値は、値が存在すればtrue、存在しなければfalse
stocks, ok := m["stocks"]

range

  • slicesやmapをループするときに使うことができる
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

// ループごとにindexとvalueを返してくれる
for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}

// 不要な場合はアンダースコアで捨てることができる
for i, _ := range pow
for _, value := range pow

// indexだけが欲しい場合は2つ目を省略しても良い
for i := range pow

クロージャ

  • 関数はクロージャ
  • 関数は自身の外部から変数を参照できる
func adder() func(int) int {
    sum := 0
    // sumへの参照を保持した無名関数
    return func(x int) int {
        sum += x
        return sum
    }
}

続きは次回!

次回の内容は……

  • Methods and interfaces
  • Concurrency

参考

A Tour of Go

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go] Cobraをgo getでインストールしようとするとcommand not foundになる問題の解決

問題

cobraをインストールしてCLIを書いてみようと思って、cobra公式のインストールコマンドを実行した。

go get -u github.com/spf13/cobra/cobra

その後cobraがインストールされたか確認しようとしたらcommand not foundと表示された。

cobra --help
command not found

ちゃんと$GOROOT$GOPATH$GO111MODULEなどの環境変数の設定もしているので謎だった。

環境

  • MacOS Catalina 10.15.6
  • go version go1.15 darwin/amd64
  • goenv 2.0.0beta11

解決策

よく見るとインストールコマンドを実行した時のメッセージは以下のようになっていた。

go: github.com/spf13/cobra/cobra upgrade => v0.0.0-20200909172742-8a63648dd905
go get github.com/spf13/cobra/cobra: ambiguous import: found package github.com/spf13/cobra/cobra in multiple modules:
    github.com/spf13/cobra v1.0.0 (/Users/hoge/go/1.15.0/pkg/mod/github.com/spf13/cobra@v1.0.0/cobra)
    github.com/spf13/cobra/cobra v0.0.0-20200909172742-8a63648dd905 (/Users/hoge/go/1.15.0/pkg/mod/github.com/spf13/cobra/cobra@v0.0.0-20200909172742-8a63648dd905)

これについてググってみるとcobra公式のIssune#1215に解決策があった。

インストールするcobraのバージョンの指定が必要だったらしい。

A workaround I found was to "go get" a specific version:

go get -u github.com/spf13/cobra/cobra@v1.0.0

これでok.

cobra --help
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  help        Help about any command
  init        Initialize a Cobra Application

Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでスライスを逆順にする

ドキュメントをみたら、sort.Reverse()という関数があった。お、これはPythonの順番を逆転する例のやつかな、と思って使ってみた。

a: l = []string{"a", "b", "c"} // lはLess, Len, Swapが定義されたインタフェース
sort.Reverse(a)
log.Println(a)
// "a", "b", "c"
// 変わってない!

え、何も仕事してないじゃん、と思ってコードを見てみたら・・・

つまり、sort.Interfaceを実装してからソートしていたもうはるか思い出せないぐらい過去の時代のためのコードで、単にLess()メソッドのtrue/falseを逆転させた別のインタフェースを作っているだけで、ここからソートしてあげないとソートはされない

a: l = []string{"a", "b", "c"}
sort.Sort(sort.Reverse(a))
log.Println(a)
// "c", "b", "a"
// 変わった!

まあ、今時ならsort.Slice()使えばイチコロですね。添字だけみてソートすればOK。こちらの方が簡単ですね。

sort.Slice(a, func(i, j int) bool {
    return i > j
})
// "c", "b", "a"
// 変わった!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む