- 投稿日:2019-07-08T17:18:07+09:00
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 golangPATHを通す
export GOPATH =$HOME/goこの時色々ググって試した結果、いつのまにかGOPATHとGOROOTが同じパスになってエラーになったため注意。
echo $GOPATH echo $GOROOT echo $PATH上記のようにパスを確認しながら進めるとよかったです。
goofysのインストール
go get github.com/kahing/goofys go install github.com/kahing/goofys1行目を実行しても反応がなく、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/testS3やボリュームを確認してみます。
$aws s3 ls s3://マウントしたバケット名 $ps auxf|grep goofys //プロセス確認 $df -h //ボリュームの確認おわり
- 投稿日:2019-07-08T17:18:07+09:00
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 golangPATHを通す
export GOPATH =$HOME/goこの時色々ググって試した結果、いつのまにかGOPATHとGOROOTが同じパスになってエラーになったため注意。
echo $GOPATH echo $GOROOT echo $PATH上記のようにパスを確認しながら進めるとよかったです。
goofysのインストール
go get github.com/kahing/goofys go install github.com/kahing/goofys1行目を実行しても反応がなく、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/testS3やボリュームを確認してみます。
$aws s3 ls s3://マウントしたバケット名 $ps auxf|grep goofys //プロセス確認 $df -h //ボリュームの確認おわり
- 投稿日:2019-07-08T17:18:07+09:00
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 golangPATHを通す
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/goofys1行目を実行しても反応がなく、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/testS3やボリュームを確認してみます。
$aws s3 ls s3://マウントしたバケット名 $ps auxf|grep goofys //プロセス確認 $df -h //ボリュームの確認おわり
- 投稿日:2019-07-08T15:30:01+09:00
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ぐらいになってかなり軽量化できた。
- 投稿日:2019-07-08T15:30:01+09:00
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ぐらいになってかなり軽量化できた。
- 投稿日:2019-07-08T11:23:05+09:00
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 } }
- 投稿日:2019-07-08T07:43:27+09:00
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.govar 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(条件部){実行部}
この中で解析したいのは、条件部と実行部のみなので、以下の流れなら解析できそうじゃないだろうか?
- 「while」の次の文字は「(」ではないなら構文エラー
- 条件部の式を解析
- 条件部の次は「)」で、その次は「{」ではないなら構文エラー
- 実行部の式を解析
実は、ほんとにこれだけで解析が完了してしまう。言葉にしてみると簡単だね。これをコードに起こしてみようか。
というわけで、以下は構文解析器が「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ループごとに条件部の式を再度評価することも忘れてはいけないね。じゃないと無限ループが発生してしまう。
以上のことをまとめると、以下の流れで処理すればいいのではないだろうか。
- 条件式を実行し、結果を得る
- 条件式の結果が真である限り、以下を実行し続ける
- 実行部の内容を実行
- 条件式を再度実行し、結果を再評価
またも、とてもシンプルだね。じゃあ実装してみよう。以下が、今まで解析してきた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言語でループ処理が書けるようになる!
while文の追加した際に、行ったすべての変更箇所まで知りたい方は、以下のコミットを見てほしい。
https://github.com/x-color/monkey/commit/a37490fe982d8345bb0a8a403f9c74835f718948
ほんとに、たったこれだけの追加で、こんなにも簡単に構文を一つ実装することができる。
トークンや字句解析器、構文解析器、評価処理などがうまくインターフェイスを用いて、適度に抽象化されているおかげだ。互いの構文の定義が影響を及ぼすことの無いように、影響範囲がきれいに分けられている。これだけうまく抽象化されていると一種の感動をおぼえる。同じように「すごい!」と思ってくれた人はいるかな?自分には文才が無いから全然伝わってないかもしれない...。
もしそうでも、そうでなくても、実際にMoneky言語のコードの全体像を見てみてほしい。ほんとに素晴らしく、なるべく無駄を省き簡単に理解できるように設計されていて、少し読んだら美しさがわかると思う。以下のリンクは、自分が書籍を写経しながら作成したMonkey言語のリポジトリなので、実際に見てみてほしい。while文以外にも、ソースファイル実行機能や複数行入力機能なども追加している。これをクローンしてどんどんいじってみるのもいいと思う。
https://github.com/x-color/monkey
まだまだMoneky言語には、いろいろな機能を追加することができる。for文を追加するのもいいし、組み込み関数を追加するのもいいね。break文ももちろん無いから、実装できればさらに便利になるだろう。
最後に
プログラミング言語を自作したことがない方は、是非一度作ってみてほしい。今回紹介した「Go言語でつくるインタプリタ」を読んで実装するのもいいし、ソースコードを読んでみて、完全に自作することに挑戦するのもいいと思う。
最初は難しそうだなと思うかもしれないが、実際に作ってみると意外とシンプルであることがわかる。また、抽象化の大事さを実感することになると思う。
そして、プログラミング言語を自作し、自分の言語が動く楽しさを味わってみてほしい。
- 投稿日:2019-07-08T07:09:52+09:00
Go Modulesとマルチモジュール構成でGo Homeする方法
Go Modulesでマルチモジュールにする方法がわからなくて調べました。発端は単に
go.mod
がある別モジュールのパッケージをインポートしようとしても出来なかったことです。そこで、Go Modulesでマルチモジュールを実現するためのシナリオを説明してみたいと思います。(本記事は自分のブログからの転載記事です。)
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.modmodule go-multi-modules go 1.12まずは基本のおはようの挨拶から
早速ですが、基本どおり
Hello World
から初めて見ます。ただし、依存関係を入れるために go-figureを利用して挨拶をしてみます。ソースコードは以下の通りです。main.gopackage 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.modmodule 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.sumgithub.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.gopackage helloworld import( "github.com/common-nighthawk/go-figure" ) func HelloWorld() { myFigure := figure.NewFigure("Hello World", "", true) myFigure.Print() }
main.go
ファイルは以下のように書き換えます。main.gopackage 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.modmodule gohome go 1.12
home.go
は以下のようになります。home.gopackage gohome import("github.com/common-nighthawk/go-figure") func GoHome() { figure.NewFigure("Go Home!", "basic", true).Scroll(30000, 400, "right") }
main.go
は以下のように書き換えます。main.gopackage 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.modmodule 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を使ってモジュール管理を始めようという方、マルチモジュールで躓いた方の参考になれば幸いです。
参考文献
- Using Go Modules - The Go Blog (和訳)
- Modules · golang/go Wiki
- Go Modulesの概要とGo1.12に含まれるModulesに関する変更 #golangjp #go112party - My External Storage
- Go Modules
pkg
ディレクトリはGo Modulesを使う上で必須ではありませんが、ここではGoの標準レイアウトを採用しています。 ↩Go Homeはネタなので優しくスルーして頂けると幸いです。 ↩
- 投稿日:2019-07-08T00:38:44+09:00
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.gohttp://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.gopackage 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.gopackage 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.gopackage 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.gohttp://localhost:8080/article
へのPOSTとGETが利用できるようになっているはずです。試しにPostmanを用いて行ってみます。
POST(HeadersにContent-Type:application/jsonを設定)
以上になります。
参考資料
https://github.com/gin-gonic/gin
https://gin-gonic.com/ja/docs/