20190708のGoに関する記事は9件です。

GoofysでS3をマウントする。(Linux環境)

はじめに

私はS3をマウントすることになりました。ググると主にStorageGateway、goofys、s3fsの3つのうちのどれかを使用するようです。試行錯誤の末、goofysを使用することにしました。
ちなみに私の場合最初、StorageGatewayを使おうとしましたが、ファイルゲートウェイ設定の際VMwareをインストールする必要があり、なぜか詰まりました。VMwareの環境構築の苦労についてはまた別の機会で、、

go,fuseのインストール

goofysの使用にあたってgo,fuseが必要になるため、先にインストールします。

sudo apt-get update
sudo apt-get install golang

PATHを通す

export GOPATH =$HOME/go

この時色々ググって試した結果、いつのまにかGOPATHとGOROOTが同じパスになってエラーになったため注意。

echo $GOPATH
echo $GOROOT
echo $PATH

上記のようにパスを確認しながら進めるとよかったです。

goofysのインストール

go get github.com/kahing/goofys
go install github.com/kahing/goofys

1行目を実行しても反応がなく、2行目を先に試したりすると、”permissiondenied”や”許可がありません”というエラーが出た。rootユーザーで試したり、awsconfigureの設定をし直したり、PATHの確認したり試行錯誤した。さらによく調べると、この部分の処理には時間がかかることがあると分かりました。そこで、しばらく待ってみると正常に実行されました。2行目の実行に関しては私の場合、2,3秒で終わりました。

(goofysのインストールver2)

wget https://github.com/kahing/goofys/releases/download/v0.0.5/goofys -P /usr/local/bin/

上記の方法も試してみたりしました。ですが、これは必要なかったと思います。

マウントの確認

$aws s3 mb s3://shangben-goofys  //S3バケット作成
$mkdir ~/mount-goofys  //ローカルディレクトリ作成

上のようにS3バケットとマウント先のローカルディレクトリを作ります。

ちょっとファイルを作ってみます。

$touch ~/mount-goofys/test

S3やボリュームを確認してみます。

$aws s3 ls s3://マウントしたバケット名
$ps auxf|grep goofys //プロセス確認
$df -h //ボリュームの確認

おわり

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

AWSのS3をマウントしたい人に。(Goofys)

はじめに

私はS3をマウントすることになりました。ググると主にStorageGateway、goofys、s3fsの3つのうちのどれかを使用するようです。試行錯誤の末、goofysを使用することにしました。
ちなみに私の場合最初、StorageGatewayを使おうとしましたが、ファイルゲートウェイ設定の際VMwareEsxiをインストールする必要があり、なぜか詰まりました。VMwareの環境構築の苦労についてはまた別の機会で、、
環境はLinuxです。

go,fuseのインストール

goofysの使用にあたってgo,fuseが必要になるため、先にインストールします。

sudo apt-get update
sudo apt-get install golang

PATHを通す

export GOPATH =$HOME/go

この時色々ググって試した結果、いつのまにかGOPATHとGOROOTが同じパスになってエラーになったため注意。

echo $GOPATH
echo $GOROOT
echo $PATH

上記のようにパスを確認しながら進めるとよかったです。

goofysのインストール

go get github.com/kahing/goofys
go install github.com/kahing/goofys

1行目を実行しても反応がなく、2行目を先に試したりすると、”permissiondenied”や”許可がありません”というエラーが出た。rootユーザーで試したり、awsconfigureの設定をし直したり、PATHの確認したり試行錯誤した。さらによく調べると、この部分の処理には時間がかかることがあると分かりました。そこで、しばらく待ってみると正常に実行されました。2行目の実行に関しては私の場合、2,3秒で終わりました。

(goofysのインストールver2)

wget https://github.com/kahing/goofys/releases/download/v0.0.5/goofys -P /usr/local/bin/

上記の方法も試してみたりしました。ですが、これは必要なかったと思います。

マウントの確認

$aws s3 mb s3://shangben-goofys  //S3バケット作成
$mkdir ~/mount-goofys  //ローカルディレクトリ作成

上のようにS3バケットとマウント先のローカルディレクトリを作ります。

ちょっとファイルを作ってみます。

$touch ~/mount-goofys/test

S3やボリュームを確認してみます。

$aws s3 ls s3://マウントしたバケット名
$ps auxf|grep goofys //プロセス確認
$df -h //ボリュームの確認

おわり

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

GoofysでAWSのS3をマウントする。

はじめに

私はS3をマウントすることになりました。ググると主にStorageGateway、goofys、s3fsの3つのうちのどれかを使用するようです。試行錯誤の末、goofysを使用することにしました。
ちなみに私の場合最初、StorageGatewayを使おうとしましたが、ファイルゲートウェイ設定の際VMwareEsxiをインストールする必要があり、なぜか詰まりました。VMwareの環境構築の苦労についてはまた別の機会で、、
環境はLinuxです。

go,fuseのインストール

goofysの使用にあたってgo,fuseが必要になるため、先にインストールします。

sudo apt-get update
sudo apt-get install golang

PATHを通す

export GOPATH =$HOME/go

この時色々ググって試した結果、いつのまにかGOPATHとGOROOTが同じパスになってエラーになったため注意。

echo $GOPATH
echo $GOROOT
echo $PATH
go env GOPATH //これでもできる。

上記のようにパスを確認しながら進めるとよかったです。

goofysのインストール

go get github.com/kahing/goofys
go install github.com/kahing/goofys

1行目を実行しても反応がなく、2行目を先に試したりすると、”permissiondenied”や”許可がありません”というエラーが出た。rootユーザーで試したり、awsconfigureの設定をし直したり、PATHの確認したり試行錯誤した。さらによく調べると、この部分の処理には時間がかかることがあると分かりました。そこで、しばらく待ってみると正常に実行されました。2行目の実行に関しては私の場合、2,3秒で終わりました。

(goofysのインストールver2)

wget https://github.com/kahing/goofys/releases/download/v0.0.5/goofys -P /usr/local/bin/

上記の方法も試してみたりしました。ですが、これは必要なかったと思います。

マウントの確認

$aws s3 mb s3://shangben-goofys  //S3バケット作成
$mkdir ~/mount-goofys  //ローカルディレクトリ作成

上のようにS3バケットとマウント先のローカルディレクトリを作ります。

ちょっとファイルを作ってみます。

$touch ~/mount-goofys/test

S3やボリュームを確認してみます。

$aws s3 ls s3://マウントしたバケット名
$ps auxf|grep goofys //プロセス確認
$df -h //ボリュームの確認

おわり

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

golang でgo modを使う場合のDockerfile

golangでgo mod(Go Modules) を使う場合のDockerfile情報があまりなかったのと、マルチステージビルドを試したかったのでやってみた。

マルチステージビルドを使いたかったのは、普通にDockerでビルドするとえらく大容量になってしまうので、容量を削減したかったのが主な理由。ちなみに、マルチステージを使う前のイメージサイズは283.6 MB。

go自体は main.go の1ファイルだけ。

以下がDockerfile

#Step 1 ビルド処理のみ
FROM golang:alpine as builder

RUN apk update \
  && apk add git

RUN mkdir /app
WORKDIR /app
COPY go.mod .
COPY go.sum .

RUN go mod download
COPY . .

RUN go build -o /main

#Step2 ビルドしたファイルを実際に使うために移動
FROM alpine:3.9

COPY --from=builder /main .

ENTRYPOINT ["/main"]

こちらでビルドすると、8.6 MBぐらいになってかなり軽量化できた。

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

golang でgo modを使う場合のDcokerfile

golangでgo mod(Go Modules) を使う場合のDockerfile情報があまりなかったのと、マルチステージビルドを試したかったのでやってみた。

マルチステージビルドを使いたかったのは、普通にDockerでビルドするとえらく大容量になってしまうので、容量を削減したかったのが主な理由。ちなみに、マルチステージを使う前のイメージサイズは283.6 MB。

go自体は main.go の1ファイルだけ。

以下がDockerfile

#Step 1 ビルド処理のみ
FROM golang:alpine as builder

RUN apk update \
  && apk add git

RUN mkdir /app
WORKDIR /app
COPY go.mod .
COPY go.sum .

RUN go mod download
COPY . .

RUN go build -o /main

#Step2 ビルドしたファイルを実際に使うために移動
FROM alpine:3.9

COPY --from=builder /main .

ENTRYPOINT ["/main"]

こちらでビルドすると、8.6 MBぐらいになってかなり軽量化できた。

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

XML 要素名が可変の Unmarshal

要素名が可変かつ複数だがその中の属性などはわかっている場合の Unmarshal の方法です。

具体的にはこんな感じ。

<NAMES>
  <!-- 画面ごとの表示名のリスト。プログラムとしては、ProgramX、CustName、お名前や顧客名が欲しい情報 -->
  <Program1 ID="CustName" Name="お名前" />
  <Program2 ID="CustName" Name="顧客名" />
</NAMES>

それを処理するのが以下のコード。

type ItemNames struct {
    Defs []Def `xml:",any"` // ★
}

type Def struct {
    XMLName xml.Name
    ID      string `xml:"ID,attr"`
    Name    string `xml:"Name,attr"`
}

func hoge() error {
    if _, err := os.Stat("itemNames.xml"); err == nil {
        fd, err := os.Open("itemNames.xml")
        if err != nil {
            return err
        }

        content, err := ioutil.ReadAll(fd)
        if err != nil {
            return err
        }
        fd.Close()

        var itemNames ItemNames
        err = xml.Unmarshal(content, &itemNames)
        if err != nil {
            return err
        }

        for _, d := range itemNames.Defs {
            // よしなに
            // d.XMLName.Local
            // d.ID
            // d.Name
        }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Monkey言語に機能を追加する!

Monkey言語って何?って思った方もいるでしょう。Monkey言語というのは、一般に使われている言語ではなく、「Go言語でつくるインタプリタ」という書籍の中で作成される独自の言語です。

この書籍では、Go言語の標準ライブラリのみを用いて、一からインタプリタ型言語を作成することができます。
字句解析器の作成から始めて、最終的には型(数値、文字列、真偽値、配列 etc)、四則演算、変数定義、関数定義などいろいろな構文を実装していく。さらに、各々を独立して実装するため、実装するたびに実際に動作させることができる!
作った機能が目に見える形で動くから、とても分かりやすい。

なにより、自分で一からプログラミング言語を作成するのはとても面白く楽しい!

ループ処理がない!

しかし!Monkey言語には、プログラミング言語といえば必ずと言ってもいいほど存在するループ処理の実装が無い!
ならば、追加実装するしかないな!と思い、自分でwhile文の機能を実装してみることに

目指す形は、以下のように実行できること。

>> let i = 0;
>> while (i < 5) {
..     puts("Hello");
..     let i = i + 1;
.. }
Hello
Hello
Hello
Hello
Hello
>> 

さぁ作ろう!

while文を認識させよう

まずは、なにをしなければならないのだろうか?
そうだね。while文が書いてあるのかを認識しなければならない。今のままだと「while」は変数と勘違いされてしまう...
というわけで、字句解析するために「while」のキーワードを登録しよう。

token.go
var keywords = map[string]TokenType{
    "fn":     Function,
    "let":    Let,
    "true":   True,
    "false":  False,
    "if":     If,
    "while":  While, // ここに追加
    "else":   Else,
    "return": Return,
}

上記のkeywordsは、字句解析時に最初に確認するキーワードたちを登録している。ここに登録してあるものは変数ではなく、最初から意味を持った字句であると認識される。見てみると、let文やif文などが登録されていることがわかる。
なのでここに「while」を登録すると、字句解析器が「while」を変数ではなくwhile文の開始を意味する特別なキーワードであると認識してくれる。

while文のASTのノードを登録しよう

字句解析でwhile文を認識するだけでは、もちろん何も起こらない。なぜなら、while文の構文をまだ定義していないからね。現在のインタプリタからすると「while文の始まりは見つけたけど、どう処理していいの...?」といった状態になっている。なので、while文に書いてある内容を解析しなければならない。

というわけで、解析しよう!と言いたいところだけど、まずは、解析後の結果を格納する構造体(ASTのノード:抽象構文木のノード)を定義しよう。

定義するのはいいけど、保持しておくべき情報は、何だろうか?
例として、とてもシンプルなwhile文を見てみよう。(無限ループしてるとかは気にしてはいけません...)

while (i < 5) {
    let msg = "Hello";
    puts(msg);
}

このとき、何を保持しようか。
結論としては、必要となるのは条件部と実行部の二つだね。

  • 条件部:i < 5
  • 実行部:let msg = "Hello"; puts(msg);

この二つを保持しておかないと、実際に実行する際に、ループしていいのか?何を実行すればいいのか?がわからなくなってしまう。

というわけで、この情報を保持する構造体を定義しよう。

type WhileExpression struct {
    Token       token.Token
    Condition   Expression
    Consequence *BlockStatement
}

この構造体は、以下のように解析後の情報を格納する。

  • Token    :「while」を意味するトークン
  • Condition   :「i < 5」を意味する式
  • Consequence :「{}」の中のすべての式を意味する式のカタマリ(let文やputs文の部分)

このように保持しておくことで、実際の実行時に条件部と実行部の情報を引っ張り出してきて、実行することができる。(Tokenフィールドは、自分が何のトークンなのかを認識する際に用いられている)

while文の構文解析を行おう

構文解析後の情報の格納先を定義できたので、今度こそwhile文の構文解析を行おうか。構文解析器にwhile文を解析する機能を追加しよう。

解析は具体的にはどのように進めていけばよいのだろうか?何を解析すればいいのだろう?
おそらくここが最も難しい部分となると思う。けれど心配はご無用で、難しいとは言っても簡単だ。簡単にできるようにMonkey言語は設計されているからね。

よし、while文の構文解析の流れを考えよう。前提として、「while」キーワードは発見されているとしよう。「while」の後には何が続いていけばよくて、何の文字が来たら終了なのだろう?
先ほどの例を用いて考えてみよう。

while (i < 5) {
    let msg = "Hello";
    puts(msg);
}

例を眺めてみればわかると思うが、空白と改行を取り除くと以下の形になっている。

while(条件部){実行部}

この中で解析したいのは、条件部と実行部のみなので、以下の流れなら解析できそうじゃないだろうか?

  1. 「while」の次の文字は「(」ではないなら構文エラー
  2. 条件部の式を解析
  3. 条件部の次は「)」で、その次は「{」ではないなら構文エラー
  4. 実行部の式を解析

実は、ほんとにこれだけで解析が完了してしまう。言葉にしてみると簡単だね。これをコードに起こしてみようか。

というわけで、以下は構文解析器が「while」を見つけた際に呼ばれる関数でwhile文の構文を解析する。

func (p *Parser) parseWhileExpression() ast.Expression {
    // while のASTノードを生成
    expression := &ast.WhileExpression{Token: p.curToken}

    // 「while」の次が「(」で始まっていない場合、構文エラーなので処理終了
    if !p.expectPeek(token.LParen) {
        return nil
    }

    p.nextToken()
    // 条件部にある式の解析を行って格納
    expression.Condition = p.parseExpression(Lowest)

    // 条件式の次が「)」で終わっていない場合、構文エラーなので処理終了
    if !p.expectPeek(token.RParen) {
        return nil
    }
    // 条件部の次が「{」で始まっていない場合、構文エラーなので処理終了
    if !p.expectPeek(token.LBrace) {
        return nil
    }

    // 実行部の式すべてを解析し、格納
    expression.Consequence = p.parseBlockStatemnt()

    return expression
}

ちょっと条件分岐が多いが、先ほどの処理の流れにあった構文エラーチェックを逐次行っているだけだ。これだけで、while文の解析は終了だ。とても簡単に解析できてしまったね!

while文を実行させよう

最後は、今まで解析してきたwhile文を実行する関数を追加しようか。

今まで通り、処理の流れから考えよう。while文は条件部の式の結果が真の場合、実行部の式たちを実行していく。1ループごとに条件部の式を再度評価することも忘れてはいけないね。じゃないと無限ループが発生してしまう。

以上のことをまとめると、以下の流れで処理すればいいのではないだろうか。

  1. 条件式を実行し、結果を得る
  2. 条件式の結果が真である限り、以下を実行し続ける
  3. 実行部の内容を実行
  4. 条件式を再度実行し、結果を再評価

またも、とてもシンプルだね。じゃあ実装してみよう。以下が、今まで解析してきたwhile文を実際に実行する関数だ。

func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object {
    // 条件式の実行
    condition := Eval(we.Condition, env)
    if isError(condition) {
        return condition
    }

    var res object.Object
    res = Null
    for isTruthry(condition) {
        // 条件式が真の間、実行部の式を実行する
        res = Eval(we.Consequence, env)
        // 条件式の結果を更新する
        condition = Eval(we.Condition, env)
    }
    return res
}

これだけで終了だ。先ほど考えた流れの通りに実行しているだけで、特筆すべきところもない、なんともシンプルな関数だね。

ループ処理を手に入れた!

以上の関数などの追加と、今回は省略しているが関数たちを呼ぶための数行を追加したら完成。晴れて、Monkey言語でループ処理が書けるようになる!

go-monkey.PNG

while文の追加した際に、行ったすべての変更箇所まで知りたい方は、以下のコミットを見てほしい。

https://github.com/x-color/monkey/commit/a37490fe982d8345bb0a8a403f9c74835f718948

ほんとに、たったこれだけの追加で、こんなにも簡単に構文を一つ実装することができる。
トークンや字句解析器、構文解析器、評価処理などがうまくインターフェイスを用いて、適度に抽象化されているおかげだ。互いの構文の定義が影響を及ぼすことの無いように、影響範囲がきれいに分けられている。

これだけうまく抽象化されていると一種の感動をおぼえる。同じように「すごい!」と思ってくれた人はいるかな?自分には文才が無いから全然伝わってないかもしれない...。
もしそうでも、そうでなくても、実際にMoneky言語のコードの全体像を見てみてほしい。ほんとに素晴らしく、なるべく無駄を省き簡単に理解できるように設計されていて、少し読んだら美しさがわかると思う。

以下のリンクは、自分が書籍を写経しながら作成したMonkey言語のリポジトリなので、実際に見てみてほしい。while文以外にも、ソースファイル実行機能や複数行入力機能なども追加している。これをクローンしてどんどんいじってみるのもいいと思う。

https://github.com/x-color/monkey

まだまだMoneky言語には、いろいろな機能を追加することができる。for文を追加するのもいいし、組み込み関数を追加するのもいいね。break文ももちろん無いから、実装できればさらに便利になるだろう。

最後に

プログラミング言語を自作したことがない方は、是非一度作ってみてほしい。今回紹介した「Go言語でつくるインタプリタ」を読んで実装するのもいいし、ソースコードを読んでみて、完全に自作することに挑戦するのもいいと思う。

最初は難しそうだなと思うかもしれないが、実際に作ってみると意外とシンプルであることがわかる。また、抽象化の大事さを実感することになると思う。

そして、プログラミング言語を自作し、自分の言語が動く楽しさを味わってみてほしい。

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

Go Modulesとマルチモジュール構成でGo Homeする方法

Go Modulesでマルチモジュールにする方法がわからなくて調べました。発端は単にgo.modがある別モジュールのパッケージをインポートしようとしても出来なかったことです。そこで、Go Modulesでマルチモジュールを実現するためのシナリオを説明してみたいと思います。

(本記事は自分のブログからの転載記事です。)

go

TL;DR

  • Go Modulesは便利なので使っていこう
  • Go Modulesでマルチモジュール構成にする場合はgo.modファイルでreplaceディレクティブを使おう
  • マルチモジュール構成の採用には慎重になろう

Go Modulesとは

とりあえず、Go Modules is 何?という方の為に簡単に説明します。ご存知の方はこの節を飛ばしてください。

Go ModulesはGo 1.11から試験的に導入され、Go 1.13からデフォルトで有効になる予定の新しいパッケージ依存関係の管理方法です。使ってみた実感としてはすでに充分実用的なので新規にプロジェクトを作成する場合はGo Modulesを使って作成することをオススメします。Go 1.12でGo Modulesを有効にするためには、GOPATH以外のパスで作業をするか以下で環境変数を設定します。

export GO111MODULE=on

この記事ではGo 1.12の前提で解説します。

Go Modulesで管理を始める

基本はディレクトリを作成してgo mod init <モジュール名>で始められます。

$ mkdir go-multi-modules
$ cd go-multi-modules
$ go mod init go-multi-modules

go.modというファイルが作成されています。これが依存関係を管理するファイルになります。

go.mod
module go-multi-modules

go 1.12

まずは基本のおはようの挨拶から

早速ですが、基本どおりHello Worldから初めて見ます。ただし、依存関係を入れるために go-figureを利用して挨拶をしてみます。ソースコードは以下の通りです。

main.go
package main

import(
    "github.com/common-nighthawk/go-figure"
)

func main() {
    myFigure := figure.NewFigure("Hello World", "", true)
    myFigure.Print()
}

go buildをすると依存関係があるパッケージがダウンロードされて、ビルドされます。事前にgo getする必要がないので、これだけでもGo Modulesの良さがわかります。./go-multi-modulesで実行して無事挨拶ができれば成功です。

$ go build
go: finding github.com/common-nighthawk/go-figure latest
$  ./go-multi-modules
  _   _          _   _            __        __                 _       _
 | | | |   ___  | | | |   ___     \ \      / /   ___    _ __  | |   __| |
 | |_| |  / _ \ | | | |  / _ \     \ \ /\ / /   / _ \  | '__| | |  / _` |
 |  _  | |  __/ | | | | | (_) |     \ V  V /   | (_) | | |    | | | (_| |
 |_| |_|  \___| |_| |_|  \___/       \_/\_/     \___/  |_|    |_|  \__,_|

go.modファイルを見てみるとrequireの行が追加されて依存関係が追跡されているのが分かります。

go.mod
module go-multi-modules

go 1.12

require github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a

また、go.sumというファイルも生成されます。依存関係の管理はgo.modだけでもできますが、go.sumは検査用に必要なようです。詳しくは ここを参照してください。

go.sum
github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a h1:kTv7wPomOuRf17BKQKO5Y6GrKsYC52XHrjf26H6FdQU=
github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=

おはようの挨拶をパッケージにしてみる

さて、挨拶は毎日するものです。せっかくなので再利用可能なようにパッケージとして分離してみます。pkgディレクトリを作成し1、その下にhello-worldディレクトリを作成して、その下にhello-world.goファイルを作成します。ディレクトリ構成は以下の通りです。今回はhelloworldというパッケージを作成します。

.
├── go-multi-modules // `go build`で生成された実行ファイル
├── go.mod // `go mod init` で生成されたモジュール管理ファイル
├── go.sum // `go build`で生成されたモジュール管理ファイル(検査用)
├── main.go // メインファイル
└── pkg
    └── hello-world
        └── hello-world.go // 新規追加

hello-world.goファイルの中身は以下の通りです。

hello-world.go
package helloworld

import(
    "github.com/common-nighthawk/go-figure"
)

func HelloWorld() {
    myFigure := figure.NewFigure("Hello World", "", true)
    myFigure.Print()
}

main.goファイルは以下のように書き換えます。

main.go
package main

import (
    "go-multi-modules/pkg/hello-world"
)

func main() {
    helloworld.HelloWorld()
}

go buildでビルドして./go-multi-modulesで実行して同じように挨拶ができたら成功です。

Go Homeしようとして失敗する

さて、挨拶も済んだのでもう用はありません。帰宅したくなってきたとします。ただし、帰宅時間まで細かく管理されたくないので別モジュールで管理することを考えます。この場合、pkgディレクトリ配下にgo-homeディレクトリを作成して、go-homeディレクトリに移動してからgo mod init gohomeを実行します。
ディレクトリ構成は以下のようになります。

.
├── go-multi-modules // `go build`で生成された実行ファイル
├── go.mod // `go mod init` で生成されたモジュール管理ファイル
├── go.sum // `go build`で生成されたモジュール管理ファイル(検査用)
├── main.go // メインファイル
└── pkg
    ├── go-home // このディレクトリ配下は別モジュールになる
    │   ├── go.mod // `go mod init`で生成される
    │   └── home.go // 新規追加
    └── hello-world
        └── hello-world.go // 挨拶パッケージ

go-home配下のgo.modは以下のようになります。初期化しただけなのでrequireはありません。

go.mod
module gohome

go 1.12

home.goは以下のようになります。

home.go
package gohome

import("github.com/common-nighthawk/go-figure")

func GoHome() {
    figure.NewFigure("Go Home!", "basic", true).Scroll(30000, 400, "right")
}

main.goは以下のように書き換えます。

main.go
package main

import (
    "go-multi-modules/pkg/hello-world"
    "go-multi-modules/pkg/go-home"
)

func main() {
    helloworld.HelloWorld()
    gohome.GoHome()
}

これをトップディレクトリ(go-multi-modulesディレクトリ)でgo buildでビルドしようとしたところ以下のようなエラーが出てうまくいきませんでした。どうやらモジュールの読み込みに失敗したようです。

$ go build
build go-multi-modules: cannot load go-multi-modules/pkg/go-home: cannot find module providing package go-multi-modules/pkg/go-home

replaceのおかげでGo Homeに成功する

解決方法は簡単で親のgo.modに以下のreplaceディレクティブを記述することでした。

replace go-multi-modules/pkg/go-home => ./pkg/go-home

replaceディレクティブを記述してgo buildをするとビルドが成功します。
以下はgo build後のgo.modです。依存関係(require)が追加されています。

go.mod
module go-multi-modules

go 1.12

require (
        github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a
        go-multi-modules/pkg/go-home v0.0.0-00010101000000-000000000000
)

replace go-multi-modules/pkg/go-home => ./pkg/go-home //追加

さて、ビルドできたら./go-multi-modulesで実行してみましょう。一瞬Hello Worldが表示されてその後Go Home!が実行されます。

我々はようやく成し遂げたのです(笑)。

なぜ、マルチモジュール化したかったのか?

さて、ここまででマルチモジュール化の方法が分かったわけですが、問題の発端のなぜ自分がマルチプロジェクトにしたかったのかをまだ説明していませんでした。

理由としてはC言語のライブラリをビルドしてcgoで呼び出すモジュールを書いたのですが、makeでビルドする必要があったのでgitのサブモジュールでローカルに取り込もうとして、必然的にマルチモジュール構成になりました。ただ本家のFAQでは一つのリポジトリに一つのモジュールをススメているので、一般的にはマルチモジュールの採用には慎重になったほうがいいと思われます。

まとめ

本記事ではGo Modulesにおける「マルチモジュール構成」に焦点を当てて、以下についてストーリ仕立てで解説しました。

  • Go Modulesを使って依存関係を管理する方法
  • パッケージに分割して呼び出す方法
  • マルチモジュール構成にする方法
    • ただし安易にマルチモジュール構成にしないほうがよい
  • Go Home! する方法2

またこの記事を書くために作成したコードは以下に置きました。

この記事がGo Modulesを使ってモジュール管理を始めようという方、マルチモジュールで躓いた方の参考になれば幸いです。

参考文献


  1. pkgディレクトリはGo Modulesを使う上で必須ではありませんが、ここではGoの標準レイアウトを採用しています。 

  2. Go Homeはネタなので優しくスルーして頂けると幸いです。 

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

go-ginでサクッとRESTAPIを構築する

ginとは

ginは、Go言語のフレームワークの中においてメジャーで歴史あるフレームワークで、軽量かつシンプルなインターフェイスが特徴です。

今回はそんなginを使って、簡単なRESTAPIを構築していきます。

ginを導入

go getで。

 go get -u github.com/gin-gonic/gin

公式githubサンプルを動かしてみましょう。

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}
go run main.go

http://localhost:8080/ping へアクセスしてみてください。
message":"pong"と表示されています。

r := gin.Default()

こちらの処理でデフォルトのミドルウェアで新しいルーターを作っています。

RESTAPIの構築

それでは新しく、RESTAPIを構築していきます。
サンプルとして作るのは、タイトルとディスクリプションだけ持ったArticleをPOST,GETできる機能になります。

またディレクトリ構造は下記になります。

src
┣article 
┃   ┗article.go
┃
┗httpd
 ┣handler
 ┃ ┗articleFunc.go   
 ┗main.go

まずはArticleを定義します。
書いてある通りですね。

article.go
package article

type Item struct {
    Title       string `json:"title"`
    Description string `json:"description"`
}

type Articles struct {
    Items []Item
}

func New() *Articles {
    return &Articles{}
}

func (r *Articles) Add(item Item) {
    r.Items = append(r.Items, item)
}

func (r *Articles) GetAll() []Item {
    return r.Items
}

続いて上記ファイルで定義したArticleのfunctionをまとめたファイルです。
こちらもそのままな処理ですね。

articleFunc.go

package handler

import (
    "net/http"

    "restAPI/article"

    "github.com/gin-gonic/gin"
)

func ArticlesGet(articles *article.Articles) gin.HandlerFunc {
    return func(c *gin.Context) {
        result := articles.GetAll()
        c.JSON(http.StatusOK, result)
    }
}

type ArticlePostRequest struct {
    Title       string `json:"title"`
    Description string `json:"description"`
}

func ArticlePost(post *article.Articles) gin.HandlerFunc {
    return func(c *gin.Context) {
        requestBody := ArticlePostRequest{}
        c.Bind(&requestBody)

        item := article.Item{
            Title:       requestBody.Title,
            Description: requestBody.Description,
        }
        post.Add(item)

        c.Status(http.StatusNoContent)
    }
}

最後はメインとなるmain.goです。

main.go
package main

import (
    "restAPI/httpd/handler"

    "restAPI/article"

    "github.com/gin-gonic/gin"
)

func main() {
    article := article.New()
    r := gin.Default()
    r.GET("/article", handler.ArticlesGet(article))
    r.POST("/article", handler.ArticlePost(article))

    r.Run() // listen and serve on 0.0.0.0:8080

}

ここまで構築をしたら下記で起動してみてください

go run httpd/main.go

http://localhost:8080/article
へのPOSTとGETが利用できるようになっているはずです。

試しにPostmanを用いて行ってみます。

POST(HeadersにContent-Type:application/jsonを設定)
Screen Shot 2019-07-08 at 12.35.47 AM.png

GET
Screen Shot 2019-07-08 at 12.35.57 AM.png

以上になります。

参考資料

https://github.com/gin-gonic/gin
https://gin-gonic.com/ja/docs/

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