tag:crieit.net,2005:https://crieit.net/tags/Go/feed 「Go」の記事 - Crieit Crieitでタグ「Go」に投稿された最近の記事 2019-12-20T17:17:45+09:00 https://crieit.net/tags/Go/feed tag:crieit.net,2005:PublicArticle/15627 2019-12-20T16:47:14+09:00 2019-12-20T17:17:45+09:00 https://crieit.net/posts/MinQ-6-Go MinQ開発日記 (6) ローカルのGoの開発環境の構築とスキーマ・マイグレーション <h1 id="Echoによるエコー・サーバー"><a href="#Echo%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A8%E3%82%B3%E3%83%BC%E3%83%BB%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC">Echoによるエコー・サーバー</a></h1> <p>とりあえずEchoサーバーを立てる. またローカルに開発環境を構築していなかったことを思い出す. やり方は<a href="https://crieit.net/posts/MinQ-3-Docker-Go">MinQ開発日記 (3) Docker上にGo開発環境を構築</a>と同じと思ったのですがGo Modulesというのがあるようです.</p> <h2 id="構成"><a href="#%E6%A7%8B%E6%88%90">構成</a></h2> <ul> <li>Echo (Webサーバー兼アプリケーション)</li> <li>MariaDB Docker Container</li> <li>React (認証画面とCRUDインターフェース)</li> </ul> <h2 id="goenvとGo Modules"><a href="#goenv%E3%81%A8Go+Modules">goenvとGo Modules</a></h2> <p>Goのバージョン1.13以降は何もしなくてもGo Modulesが使えるようです.</p> <p>その前にGoのバージョン管理ができるようにしましょう. pyenvやnodeenvのようにgoenvというのがあります. 公式の<a target="_blank" rel="nofollow noopener" href="https://github.com/syndbg/goenv/blob/master/INSTALL.md">Installation</a>ガイドを読むと大体わかります. GOENV_ROOTがgoenvバイナリのロケーションです.</p> <pre><code class="bash">export GOENV_ROOT="$HOME/.goenv" export PATH="$GOENV_ROOT/bin:$PATH" eval "$(goenv init -)" export GOPATH="$HOME/go" export PATH="$PATH:$GOPATH/bin" </code></pre> <p>goenvでインストール可能なバージョンを列挙するには以下のようにします.</p> <pre><code class="bash">goenv install - </code></pre> <p>バージョンがずらっと並ぶので, この中から一つ選んでインストールします. 最新版(2019/12/14時点)である1.13.4をインストールします.</p> <pre><code class="bash">goenv install 1.13.4 goenv global 1.13.4 </code></pre> <p>これでgoのバージョンは1.13.4です. Go Modulesは好きなところにフォルダを作れます.</p> <pre><code class="bash">cd ~/your_folder mkdir minq && cd minq go mod init </code></pre> <p>これでgo.modというファイルができました. 適当にserver.goというファイルを作ります(多分main.goの方が良い気もする).</p> <pre><code class="go">package main import ( "net/http" "github.com/labstack/echo" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323")) } </code></pre> <p>minqフォルダ内部で以下を実行すると必要なパッケージ(例えばecho)なんかを勝手にダウンロードしてきて, ビルドしてくれます.</p> <pre><code class="bash">go build </code></pre> <p>minqフォルダの生成物はフォルダ名と同じになるのでこの場合はminqです.</p> <pre><code class="bash">./minq </code></pre> <p>サーバーが起動したらオッケーです. 特別依存性を指定しなくてもimportから解決してくれるっぽいのは良いですが, 反面githubのレポジトリを直接指定する必要があるのは少し面倒でもあります.</p> <h2 id="MariaDB"><a href="#MariaDB">MariaDB</a></h2> <p>brewで入れます.</p> <pre><code class="bash">brew install mariadb </code></pre> <p>起動もbrewを使います.</p> <pre><code class="bash">brew services start mariadb </code></pre> <p>起動したデータベースへアクセスします.</p> <pre><code class="bash">sudo mariadb -uroot </code></pre> <h3 id="GORMで必要になる情報"><a href="#GORM%E3%81%A7%E5%BF%85%E8%A6%81%E3%81%AB%E3%81%AA%E3%82%8B%E6%83%85%E5%A0%B1">GORMで必要になる情報</a></h3> <p>GORMからMariaDBにアクセスするには以下の情報が必要になります.</p> <ul> <li>username</li> <li>password</li> <li>IPアドレス/ホスト名</li> <li>ポート番号</li> <li>データベース名</li> </ul> <h4 id="username &amp; password"><a href="#username+%26amp%3B+password">username & password</a></h4> <p>上の例でmysqlというデータベースがあると思います. このデータベースにuserというテーブルがあります.</p> <pre><code class="SQL">SELECT user FROM mysql.user; </code></pre> <p>名前が被らないように, ここに新しいユーザーを登録します. <a target="_blank" rel="nofollow noopener" href="https://mariadb.com/kb/en/library/create-user/">CREATE USER</a>を使います.</p> <pre><code class="SQL">CREATE USER 'new_name'@'localhost' IDENTIFIED BY 'your_password'; </code></pre> <p>new_nameというユーザーにyour_passwordでアクセスできます. ホストはlocalhostです.</p> <h4 id="ポート番号"><a href="#%E3%83%9D%E3%83%BC%E3%83%88%E7%95%AA%E5%8F%B7">ポート番号</a></h4> <p>システム変数を指定すると以下のように表示できるようです.</p> <pre><code class="bash">show variables like 'port'; </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://mariadb.com/kb/en/library/show-variables/">SHOW VARIABLES</a><br /> <a target="_blank" rel="nofollow noopener" href="https://mariadb.com/kb/en/library/server-system-variables/#port">port</a></p> <p>ちなみに私の環境では3306でした(多分誰でも同じ).</p> <h4 id="データベース名"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E5%90%8D">データベース名</a></h4> <p>MariaDBがアクセスしたユーザー用に管理してるデータベースを表示するのは簡単です.</p> <pre><code class="bash">SHOW DATABASES; </code></pre> <p>testというテスト用のデータベースが存在するはずです. とりあえずこれを使います.</p> <pre><code class="SQL">USE test; </code></pre> <p>これでデータベースがtestに切り替わります. 後でGOMRでちゃんとスキーマが設定できたかを確認しましょう.</p> <h2 id="GORM"><a href="#GORM">GORM</a></h2> <p>GORMでtestデータベースにアクセスしてみましょう. ハンドラを作っておきます. ステータス・コードなんかは現状適当です(DBサーバーでエラーが出たらどうしたら良いのかわからないので).</p> <pre><code class="go">func accessDB(context echo.Context) error { db, err := gorm.Open("mysql", "root:@(127.0.0.1:3306)/test?charset=utf8&parseTime=true") defer db.Close() if err != nil { return context.String(http.StatusOK, err.Error()) } return context.String(http.StatusOK, "Connect to DB") } </code></pre> <p>この時点では以下のようなエラーが出力されます. これはrootユーザーでアクセスする場合sudoer出ないと実行できないからのようです.</p> <pre><code class="bash">Error 1698: Access denied for user 'root'@'localhost' </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/37239970/connect-to-mysql-server-without-sudo">Connect to mysql server without sudo</a></p> <p>ここで上で作ったnew_nameユーザーを使いましょう(名前は適宜読み替えてください). testというデータベースがデフォルトで存在するはずなのでそれを指定しましょう.</p> <pre><code class="go">db, err := gorm.Open("mysql", "new_name:@(localhost:3306)/test?charset=utf8&parseTime=true") </code></pre> <p>該当箇所を変更してビルド&リスタートするとConnect to DBと表示されるはずです(末尾のクエリ文字列的なのは一旦無視します).</p> <pre><code class="go">e.GET("/v1/example/mariadb", accessDB) </code></pre> <h3 id="スキーマ・マイグレーション"><a href="#%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%83%BB%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3">スキーマ・マイグレーション</a></h3> <p>データベースのスキーマの更新を行う作業をスキーマ・マイグレーション, あるいは単にマイグレーションと言うようです. ORM(Object Relational Mapping/Mapper)ではオブジェクト(Goの構造体)のフィールドからスキーマを決めるようです. つまり個々のテーブル上のレコードと構造体のインスタンスが対応関係にあるわけです.</p> <pre><code class="go">type User struct { ID int Name string } </code></pre> <p>テーブル名は自動的に雛形となる構造体名の複数形になります. この場合だとusersというテーブルがtestデータベースに追加されます.</p> <pre><code class="go"><br />if db.HasTable("users") { return context.JSON(http.StatusCreated, "User table is already existed") } db.AutoMigrate(&User{}) return context.JSON(http.StatusCreated, "Create users table") </code></pre> <p>適当なハンドラ関数を作って, エンドポイントに紐付けます. MariaDBに戻って以下を実行します.</p> <pre><code class="SQL">SHOW COLUMNS FROM users; </code></pre> <p>これでusersの構造が表示されるはずです.</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>EchoからGORMを通じてMariaDBにアクセスすることができました. GORMのチュートリアルでは利用するデータベースの操作には慣れている前提なのか少しつまづきました.</p> <h2 id="今後"><a href="#%E4%BB%8A%E5%BE%8C">今後</a></h2> <h3 id="React"><a href="#React">React</a></h3> <p>データベースを管理するCRUD用のインターフェースを作ります. <a target="_blank" rel="nofollow noopener" href="https://jaredpalmer.com/formik/docs/overview">formik</a>や<a target="_blank" rel="nofollow noopener" href="https://draftjs.org/">Draft.js</a>を使います. これはできたらPWA(Chrome Desktop)にしてネイティブ・アプリのように実行できるようにしたいです. 詳細に踏み込みたくないのでcreate-react-appを使います. またテストにはCypress.jsを使ってみたいです.</p> <h3 id="認証"><a href="#%E8%AA%8D%E8%A8%BC">認証</a></h3> <p>現状他の人は使わない前提なので, 実験的にWebAuthenticationを使おうと思っています.</p> <h3 id="Gitの導入"><a href="#Git%E3%81%AE%E5%B0%8E%E5%85%A5">Gitの導入</a></h3> <p>プロジェクトはバージョン管理をするのですが, このままだとgom.Openに指定したデータベースのパスワードが丸見えです. 色々方法があるようです. 最初はコマンドライン引数で渡せばいいのかと思ったのですが, Stackoverflowで質問してみるとダメだそうです. コマンドラインにセンシティブなデータを渡すことがそもそもタブーのようです.</p> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/59390753/what-is-a-standard-way-to-specify-password-for-mariadb-in-an-api-server">What is a standard way to specify password for MariaDB in an API server?</a></p> <h4 id="じゃあどうするのか?"><a href="#%E3%81%98%E3%82%83%E3%81%82%E3%81%A9%E3%81%86%E3%81%99%E3%82%8B%E3%81%AE%E3%81%8B%3F">じゃあどうするのか?</a></h4> <p>まずそもそも構成ファイルをgitの管理下に置かないという手があります(.gitignore). あるいは構成ファイルに.exampleのような拡張子を付け適当なパスワードを入れて本番用とは切り分けるやり方です.</p> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/a/2397905/12036118">What is the best practice for dealing with passwords in git repositories?</a></p> <p>ただこの方法はどちらかというとプロジェクト・テンプレートを公開するような用途に使った方がいい気もします. どちらの方法も認証情報は別の方法で管理する必要があります.</p> <p>もう一つは<a target="_blank" rel="nofollow noopener" href="https://www.vaultproject.io/">Vault</a>というソフトウェアを使う方法ですが, こちらはかなり大げさなようです.</p> <p><a target="_blank" rel="nofollow noopener" href="https://opensource.com/article/19/2/secrets-management-tools-git">4 secrets management tools for Git encryption</a></p> <p>今回は<a target="_blank" rel="nofollow noopener" href="https://www.agwa.name/projects/git-crypt/">git-crypt</a>や<a target="_blank" rel="nofollow noopener" href="https://git-secret.io/">git-secret</a>というのがあるようでこの辺を調べてみようと思います.</p> <p><a target="_blank" rel="nofollow noopener" href="https://techblog.bozho.net/storing-encrypted-credentials-in-git/">STORING ENCRYPTED CREDENTIALS IN GIT</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/daisukeoda/items/c6b6c36009fa3409dc39">本番用の.envを外部に一切知られずに安全にgithubで保存する方法</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/jqtype/items/9b0524baa4b7fe6dbde0">Gitリポジトリ暗号化のススメ - git-secret -<br /> Git</a></p> <h2 id="Reference"><a href="#Reference">Reference</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/seicode/items/9ffce10086f0646379a1">【Go】goenvを使ってGo1.13.4の環境構築</a><br /> <a target="_blank" rel="nofollow noopener" href="https://tech.opst.co.jp/2019/07/09/go-modulesも触れてみるgo入門/">Go Modulesも触れてみるGo入門</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/gorilla0513/items/27cd34433a48fc8b65db">Go言語のGORMを使ってみた①</a><br /> <a target="_blank" rel="nofollow noopener" href="http://psychedelicnekopunch.com/archives/639">Golang + GORM + MySQL でデータをやりとりする</a></p> ブレイン tag:crieit.net,2005:PublicArticle/15580 2019-12-04T19:16:21+09:00 2019-12-04T19:16:21+09:00 https://crieit.net/posts/MinQ-3-Docker-Go MinQ開発日記 (3) Docker上にGo開発環境を構築 <h1 id="Goの導入"><a href="#Go%E3%81%AE%E5%B0%8E%E5%85%A5">Goの導入</a></h1> <p>前回の続きです. まずGoを入れてファイアウォールの設定をして静的サイトを配信できるかチェックしてみようと思います.</p> <p><a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/serialization/understood-cats-vps/">ネコでもわかる!さくらのVPS講座</a>の<a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/8541/">第三回「Apacheをインストールしよう」</a>と<a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/9006/">第四回「phpとMariaDBをインストールしよう」</a>の内容がベースになります.</p> <h2 id="Go言語を選んだ理由"><a href="#Go%E8%A8%80%E8%AA%9E%E3%82%92%E9%81%B8%E3%82%93%E3%81%A0%E7%90%86%E7%94%B1">Go言語を選んだ理由</a></h2> <p>本当はRustのRocketとかでやりたかった. のだがnightlyとか非同期とか色々と不安定な時期を完全に脱したとは言えない. 加えてRust自体が結構難しく(基本はそうでもないのだが)ベストプラクティスが不明である. それに比べるとGoは学習の困難が少ないように見える(最適化とかは知らん).</p> <ol> <li>メモリ管理不要</li> <li>静的な型</li> <li>ビルトインWebサーバーとその容易な拡張性</li> <li>クラスと継承がない</li> <li>文法が親しみやすい</li> <li>プログラミング全体を学ぶ教材が揃っている気がする</li> <li>クリーン・アーキテクチャとの相性が良さそう</li> </ol> <p>1は大抵の動的な言語でも満たすので意味はないですが, 静的な型があるのは良い場合があります. クリーン・アーキテクチャは各層を依存性逆転で依存の方向が内側に向かうようにしています. これがPythonでは不可能ではないですが不自然さは否めません. Goはインターフェースがあるので特に悩まず実装できそうです.</p> <p>ローカルなどの開発環境で動かせてもプロダクション環境では色々設定が必要になるのは面倒です. シンプルな三層アーキテクチャとしてもWebサーバー, APサーバー, DBサーバーが必要で, それ以外にアプリケーション・ロジックをWebフレームワークで作る必要があります. このうちWebサーバー, APサーバー, アプリケーション・ロジックがGoなら一纏めでできると思ったからです. 要はさっさとデプロイしたいということです.</p> <blockquote> <p>Composition over Inheritance</p> </blockquote> <p>らしく継承がないです. そもそもクラスないです. 構造体を組み合わせることになります. その他の文法もシンプルです. メソッド名の前にレシーバーを指定する記法は少し面食らいますが, selfを暗に陽に第一引数として取るよりは特別な引数感が出せます. なお同期処理なんかも簡単らしいです.</p> <p>学習用言語としてはPythonが動く擬似コードなんて呼ばれることがありますが, Pythonの利用用途は数値計算とかその応用である機械学習などに集中しているように思います. 一方Goはランタイムがあるものの『Goならわかるシステムプログラミング』とか『Real World HTTP』, 『Goプログラミング実践入門』などいろんなことが基礎から学べます. 他にも『Writing A Compiler In Go』や『Writing A Interpreter In Go (Go言語でつくるインタプリタ) 』なんかもあり学習用言語としても優秀かなと思いました.</p> <p>最後はMVCではなくクリーン・アーキテクチャを導入したいと思っているという話です. すでにこの話はしましたが, 型がある方が有利と思います.</p> <h3 id="PHPでない理由"><a href="#PHP%E3%81%A7%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1">PHPでない理由</a></h3> <p>PHPの良いところはHTMLとの親和性だと思う. テンプレートが必要な場合(動的レンダリングなど)では強みになると思う. Wordpressなんかが良い例だと思う. 一方Web APIのようにテンプレートが原則必要ない場合はこの優位点は消えると思う. MVCのようにサーバー側で色々やるからPHPの特色が生かせるのではないか. Vが切り離された状況なら他の言語でも良い気がしてならない. Laravelとか便利そうだがAPIサーバーを作るにはデカい. Lumenというのもあるが, そこまでしてPHPを使うべきなのか良くわからなくなってしまった.</p> <h3 id="Pythonでない理由"><a href="#Python%E3%81%A7%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1">Pythonでない理由</a></h3> <p>最終的にはクイズの成績をデータ分析とかしてみたいという野望がある. この点から言えばPythonを選択することは将来性があるとは思う. 文法的にもPythonの方が慣れているとは思う. Flaskのデコレータを使ったルートの定義は視認性が高いし, FastAPIというイベント駆動なWebフレームワークもある. ただデータ分析となるとバッチ処理になる気はする. 流行りのAIでクイズをレコメンドしたり最適な教材を生成したりとかなると意義はあるのかもしれないが, その元となるAPIがPythonである必要性はない気はする. よってクイズの成績とかを管理するWebアプリはPythonでやってみたい.</p> <h3 id="Rubyでない理由"><a href="#Ruby%E3%81%A7%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1">Rubyでない理由</a></h3> <p>Rubyの他のWebフレームワークは知らないのだが, RubyやるならやっぱりRoRがやりたいとも思うが, やはりデカすぎる気がする. 後昔gemが動かなかったトラウマからRubyに苦手意識がある.</p> <h3 id="Node.jsでない理由"><a href="#Node.js%E3%81%A7%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1">Node.jsでない理由</a></h3> <p>JavaScriptはフロントだけで十分です(しかもこいつだけ言語じゃねぇ). APIサーバーを立てるわけですから, APIができたらJAMStackな構成でサイトを作ってみたいです.</p> <h3 id="Elixirでない理由"><a href="#Elixir%E3%81%A7%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1">Elixirでない理由</a></h3> <p>楽しそうではあるんですが, 同時にだるさもあります. サーバー(当然関数)自体を再帰的に呼び出すことで無限ループを作るらしいです.</p> <h2 id="Goの導入"><a href="#Go%E3%81%AE%E5%B0%8E%E5%85%A5">Goの導入</a></h2> <h3 id="Goバイナリのインストール"><a href="#Go%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Goバイナリのインストール</a></h3> <p>Goには<a target="_blank" rel="nofollow noopener" href="https://hub.docker.com/_/golang/">公式のイメージ</a>がありますが, <a href="https://crieit.net/posts/MinQ-2-Docker-CentOS">前回</a>は構築したCentOSベースのイメージに追加します. /usr/local/srcは自分でビルドするプログラムのソースをダウンロードするフォルダです. といってもビルドはしないので<a target="_blank" rel="nofollow noopener" href="https://golang.org/doc/install">公式のダウンロード・ページ</a>からバイナリを取って来て/usr/local以下に展開するだけです.</p> <pre><code class="dockerfile">ARG GO_VERSION=1.13.4 WORKDIR /usr/local/src ADD https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz ./ RUN tar -C /usr/local/ -xzf go${GO_VERSION}.linux-amd64.tar.gz && \ rm /usr/local/src/*.tar.gz </code></pre> <p>Goのバージョンはbuild-argオプションで変更できるようにARG命令で定義しました. 2019/11/30の最新バージョンである1.13.4をデフォルト・バージョンとしています. また<a target="_blank" rel="nofollow noopener" href="https://tech.librastudio.co.jp/entry/index.php/2018/02/20/post-1792/">GolangのGOPATHやGOROOTについて</a>に従って自作パッケージと外部パッケージのインストール・フォルダを分けます. なおADDでgoバイナリを取得していますが, wgetでもcurlでも同じです.</p> <pre><code class="dockerfile">ENV PATH=$PATH:/usr/local/go/bin ENV GOPATH=$HOME/go/package:$HOME/go/workspace ENV PATH=$HOME/go/package/bin:$HOME/go/workspace/bin:$PATH </code></pre> <p>まずgo自体にパスを通す必要があります. GOPATHには二つのパスを指定しています. go getは最初に指定したパスにパッケージをインストールしてくれます. 上の例では以下のように分けました.</p> <div class="table-responsive"><table> <thead> <tr> <th align="center">フォルダ名</th> <th align="center">用途</th> </tr> </thead> <tbody> <tr> <td align="center">workspace</td> <td align="center">自作のGoプロジェクト</td> </tr> <tr> <td align="center">package</td> <td align="center">サード・パーティ製のパッケージのインストール</td> </tr> </tbody> </table></div> <p>それぞれのフォルダにはsrc, bin, そしてpkgというフォルダが必要です.</p> <div class="table-responsive"><table> <thead> <tr> <th align="center">フォルダ名</th> <th align="center">用途</th> </tr> </thead> <tbody> <tr> <td align="center">src</td> <td align="center">ソースコードの保存</td> </tr> <tr> <td align="center">pkg</td> <td align="center">パッケージをコンパイルした生成物</td> </tr> <tr> <td align="center">bin</td> <td align="center">ビルド後の生成物</td> </tr> </tbody> </table></div> <p>Goはパッケージという単位でソースをモジュール化します. それぞれがコンパイルされ, その後プロジェクトのmainパッケージを中心にリンクされ最終生成物として実行可能バイナリが出力されるようです. そのためbinフォルダにパスを設定しておきます. こうしておくとビルドされたコマンドを即呼び出せます.</p> <p>Goのサンプルを以下のようにインストールして, 実行してみましょう. 折角なのでバージョンをコマンドラインから指定してみましょう.</p> <pre><code class="bash">docker build -t minq:0.1 --build-arg=1.13.4 context/ docker run -it --rm minq:0.1 </code></pre> <p>後は対話モードで以下を実行します.</p> <pre><code class="bash">$ go get github.com/golang/example/hello $ hello Hello, Go examples! </code></pre> <p>リモート・パッケージも簡単にインストールでき, 実行できることがわかります. Dockerfileでは以下のようになります. workspaceとpackageフォルダそれぞれにsrc, bin, pkgを作成し, echoをインストールします.</p> <pre><code class="bash">RUN mkdir -p ${HOME}/go/workspace ${HOME}/go/workspace/src ${HOME}/go/workspace/pkg ${HOME}/go/workspace/bin && \ mkdir -p ${HOME}/go/package ${HOME}/go/package/src ${HOME}/go/package/pkg ${HOME}/go/package/bin && \ mkdir -p ${HOME}/go/workspace/src/minq && \ go get -u github.com/labstack/echo/... </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/Esfahan/items/cf0fd87d0e3bc3194bd8">Golang + Chrome on CentOS7(Docker)でスクレイピング</a><br /> <a target="_blank" rel="nofollow noopener" href="https://linuxize.com/post/how-to-install-go-on-centos-7/">How to Install Go on CentOS 7</a><br /> <a target="_blank" rel="nofollow noopener" href="https://golang.org/doc/install">Download the Go distribution</a></p> <h3 id="Goの環境変数のまとめ"><a href="#Go%E3%81%AE%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81">Goの環境変数のまとめ</a></h3> <div class="table-responsive"><table> <thead> <tr> <th align="center">環境変数</th> <th align="center">用途</th> </tr> </thead> <tbody> <tr> <td align="center">GOROOT</td> <td align="center">Goバイナリのパス</td> </tr> <tr> <td align="center">GOPATH</td> <td align="center">作業フォルダのパス</td> </tr> </tbody> </table></div> <h4 id="GOROOT"><a href="#GOROOT">GOROOT</a></h4> <p>GOROOTは指定する必要は特にないそうです. 例えば複数バージョンのGoをインストールした場合にGOROOTを使うとバイナリのパスを取得できます.</p> <pre><code class="bash">$ go env GOROOT /usr/local/go </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://golang.org/doc/install#extra_versions">Installing extra Go versions</a></p> <h4 id="GOPATH"><a href="#GOPATH">GOPATH</a></h4> <p>GOPATHはデフォルト値として$HOME/goが指定されています.</p> <pre><code class="bash">$ go env | grep GOPATH GOPATH="/home/vpsuser/go" </code></pre> <p>getサブコマンドで外部からパッケージを取得する場合のインストール先もGOPATH直下になります. つまり/home/vpsuser/go以下に展開されます.</p> <blockquote> <p>If the specified package is not present in a workspace, go get will place it inside the first workspace specified by GOPATH.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://golang.org/doc/code.html#remote">Remote packages</a><br /> <a target="_blank" rel="nofollow noopener" href="https://golang.org/doc/code.html">How to Write Go Code</a></p> <h3 id="Echo"><a href="#Echo">Echo</a></h3> <p>Webサーバーとして<a target="_blank" rel="nofollow noopener" href="https://echo.labstack.com/">Echo</a>というのを使います. <a target="_blank" rel="nofollow noopener" href="https://echo.labstack.com/guide">Quick Start</a>を参考にserver.goというファイルをcontextフォルダに作成します. プロジェクト名はminqとします.</p> <pre><code class="dockerfile">COPY server.go ${HOME}/go/workspace/src/minq </code></pre> <p>ビルドした後コンテナを起動してポートを転送しておきます.</p> <pre><code class="bash">docker run -it --rm -p 1323:1323 minq:0.1 </code></pre> <p>コンテナに入ってコマンドプロンプトに以下のコマンドを打ち込みましょう.</p> <pre><code class="bash">$ go install minq </code></pre> <p>これで${HOME}/go/workspace/bin以下にminqという実行可能バイナリが出力されます. すでにbinフォルダのパスは通っているので, コマンドラインから以下を実行するとサーバーが起動します.</p> <pre><code class="bash">$ minq </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>これまでの作業をまとめるとこんな感じでしょうか.</p> <pre><code class="dockerfile">FROM centos:7 AS builder RUN yum -y update && yum -y install \ useradd \ usermod \ chpasswd \ sudo \ wget \ which \ git \ yum clean all ARG GO_VERSION=1.13.4 WORKDIR /usr/local/src ADD https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz ./ RUN tar -C /usr/local/ -xzf go${GO_VERSION}.linux-amd64.tar.gz && \ rm /usr/local/src/*.tar.gz ENV USER_NAME vpsuser ENV HOME=/home/${USER_NAME} RUN useradd --create-home ${USER_NAME} && \ usermod -aG wheel ${USER_NAME} && \ echo "${USER_NAME}:tekitou" | chpasswd # ENV GOROOT=/usr/local/go ENV PATH=$PATH:/usr/local/go/bin # ENV GOPATH=$HOME/go # ENV PATH=$PATH:$GOPATH/bin ENV GOPATH=${HOME}/go/package:${HOME}/go/workspace ENV PATH=${HOME}/go/go/bin:${HOME}/go/workspace/bin:${PATH} USER ${USER_NAME} WORKDIR ${HOME} RUN mkdir -p ${HOME}/go/workspace ${HOME}/go/workspace/src ${HOME}/go/workspace/pkg ${HOME}/go/workspace/bin && \ mkdir -p ${HOME}/go/package ${HOME}/go/package/src ${HOME}/go/package/pkg ${HOME}/go/package/bin && \ mkdir -p ${HOME}/go/workspace/src/minq && \ go get -u github.com/labstack/echo/... COPY server.go ${HOME}/go/workspace/src/minq </code></pre> <h2 id="Reference"><a href="#Reference">Reference</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/serialization/understood-cats-vps/">ネコでもわかる!さくらのVPS講座</a><br /> <a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/serialization/linux-security/">Linuxセキュリティ入門</a><br /> <a target="_blank" rel="nofollow noopener" href="https://knowledge.sakura.ad.jp/serialization/getting-start-docker/">Docker入門</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/i35_267/items/10789865dd240e075eba">Docker : firewalldでssh, http, httpsのみ許可する。(CentOS 7)</a></p> <p><a target="_blank" rel="nofollow noopener" href="http://gihyo.jp/dev/serial/01/game_mysql">ゲームを題材に学ぶ 内部構造から理解するMySQL</a><br /> <a target="_blank" rel="nofollow noopener" href="http://gihyo.jp/dev/serial/01/mysql-road-construction-news">MySQL道普請便り</a></p> ブレイン tag:crieit.net,2005:PublicArticle/15548 2019-11-14T13:34:28+09:00 2019-11-25T17:52:46+09:00 https://crieit.net/posts/Go-Response-Body 手軽にGoのResponse.Bodyを再利用したいとき <p>こんなコードがあるとする。</p> <pre><code class="go">... response, _ := http.Get(requestURL) body, _ := ioutil.ReadAll(response.Body) body.Close() ... </code></pre> <p>GoのHTTP Responseを<code>ioutil.ReadAll</code>なんかで読み込むと、<code>response.Body</code>のコンテンツをどこまで読み込んだかという情報を持つポインタ(Seeker)が最後まで進んでしまうので、もう1度<code>response.Body</code>を読み込もうと思っても何も読み込めない。なので、<code>response.Body</code>を別の場所にコピーしておく必要があるんだけど、適当に<code>foo = response.Body</code>とかやってもダメ。</p> <p>そんなときは<code>io.TeeReader</code>を使って別の場所にコピーする(というかバッファを読み込むためのReaderをもう1つ用意する)。そうすれば新しいSeekerを使って読み込みを行えるので、<code>response.Body</code>の再利用が出来る。</p> <pre><code class="go">var b bytes.Buffer _ = io.TeeReader(response.Body, &b) // こんな感じでコンテンツを読み込んでも後続の処理では引き続き`response.Body`を利用することが出来る。 log.Printf("DEBUG: %s", b.String()) </code></pre> <p>おしまい。</p> shige tag:crieit.net,2005:PublicArticle/15477 2019-10-13T23:43:10+09:00 2019-10-13T23:43:10+09:00 https://crieit.net/posts/960b0aeb50068c42582d201c7dd14467 熨斗(のし)の王様を作ったときに気をつけたこと <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://noshi-king.matsubarase.com">のし王</a>というWebサービスを作った。<br /> <a href="https://crieit.now.sh/upload_images/fffb317da90f57ce80e2986ead5be3bd5da337e03f206.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fffb317da90f57ce80e2986ead5be3bd5da337e03f206.png?mw=700" alt="のし王サムネイル" /></a></li> <li>のし作成サービスが既に存在する中で、下記に気をつけたというお話。 <ul> <li>サービス作成のきっかけになった不満点・ニーズをもれなく仕様に落とし込む</li> <li>仕様策定時は技術的な楽さに甘えず、ユーザー体験を優先する</li> <li>上記を実現するため必要最低限の技術を選定する</li> </ul></li> </ul> <h1 id="不満点の解消、ニーズを仕様に落とし込む"><a href="#%E4%B8%8D%E6%BA%80%E7%82%B9%E3%81%AE%E8%A7%A3%E6%B6%88%E3%80%81%E3%83%8B%E3%83%BC%E3%82%BA%E3%82%92%E4%BB%95%E6%A7%98%E3%81%AB%E8%90%BD%E3%81%A8%E3%81%97%E8%BE%BC%E3%82%80">不満点の解消、ニーズを仕様に落とし込む</a></h1> <ul> <li>ちょっとした贈り物に「のし」をつけたかった。</li> <li>既存サービスは、以下の点で不満が残った。 <ul> <li>「のし紙」のテンプレート(背景画像)だけが提供されており、Wordなどに貼り付けた後、自分で文字を描かないとダメだった</li> <li>Web上で任意の文字まで入れられるサービスでは、最後になるまで仕上がり具合がが分からなかった</li> <li>最終画像にサービス名のロゴが入ってしまい、贈り物には不向きだった</li> <li>フォントがしょぼかった</li> <li>水引き(背景画像)や表書き(御礼などの上部の文字)をどう選んでよいか分からなかった</li> </ul></li> <li>不満点の解消をもれなく仕様に落とし込んだ。 <ul> <li>背景画像と任意の文字を組み合わせてPDFで出力できるようにする</li> <li>最終出力結果には広告を入れない</li> <li>有料でもカッコいいフォントを採用する</li> <li>用途に応じて自動で「水引き」と「表書き」が選択できるようにする</li> </ul></li> <li>マッスルの神様 = マ神 → 熨斗の王様 = のし王</li> </ul> <h1 id="ユーザー体験を最優先する"><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E4%BD%93%E9%A8%93%E3%82%92%E6%9C%80%E5%84%AA%E5%85%88%E3%81%99%E3%82%8B">ユーザー体験を最優先する</a></h1> <div class="table-responsive"><table> <thead> <tr> <th align="left"></th> <th align="left">技術的な楽さを優先</th> <th align="left">ユーザー体験を優先</th> </tr> </thead> <tbody> <tr> <td align="left">文字描画</td> <td align="left">ユーザー自身がWordなどで描画</td> <td align="left">Webサービス上で描画</td> </tr> <tr> <td align="left">仕上がり確認</td> <td align="left">プレビュー不可 or プレビューボタンクリックで表示</td> <td align="left">リアルタイムプレビュー表示</td> </tr> <tr> <td align="left">水引き、表書き</td> <td align="left">ユーザーの選択したものを表示</td> <td align="left">用途を選ぶと最適な水引き、表書きを自動で選択</td> </tr> <tr> <td align="left">描画フォント</td> <td align="left">無料の範囲で選択可能</td> <td align="left">有料で質の良いフォントが選択可能</td> </tr> <tr> <td align="left">ユーザーが泥酔状態</td> <td align="left">酔いが冷めてから使う</td> <td align="left">泥酔状態でも直感で使える</td> </tr> </tbody> </table></div> <h1 id="仕様の実現方法を考える"><a href="#%E4%BB%95%E6%A7%98%E3%81%AE%E5%AE%9F%E7%8F%BE%E6%96%B9%E6%B3%95%E3%82%92%E8%80%83%E3%81%88%E3%82%8B">仕様の実現方法を考える</a></h1> <ul> <li>使用した技術 <ul> <li>Webサービス上で文字描画しPDF出力 <ul> <li>水引きの種類や表書きなどの情報をブラウザ上で選択させる</li> <li>サーバーでPDFを作成してダウンロードさせる(Golang/Google App Engine)</li> </ul></li> <li>リアルタイムプレビュー表示 <ul> <li>サムネイル描画サーバー(Golang/Google App Engine)を準備</li> <li>水引きの種類や表書きなどの情報が変更されたらAjax(jQuery)でサーバーからサムネイル画像を取得し、ブラウザ上に即時反映</li> </ul></li> <li>用途を選ぶと最適な水引き、表書きを自動で選択 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://wikiki.github.io/components/quickview/">Bulma-ExtensionsのQuickView</a>で用途一覧を表示</li> <li>用途を入力させて絞り込み(jQuery)</li> <li>用途クリックで「水引き」と「表書き」を最適なものに変更(jQuery)</li> </ul></li> </ul></li> <li>選定理由 <ul> <li>仕様実現に際して最低限/シンプルなものを選ぶ</li> <li>最新の技術よりも枯れて安定した技術を選ぶ</li> <li>なるべくメンテナンスが不要なものを選ぶ</li> </ul></li> </ul> Matsubarase.com公式 tag:crieit.net,2005:PublicArticle/15246 2019-07-16T00:42:42+09:00 2019-07-16T00:43:48+09:00 https://crieit.net/posts/Go-time-LoadLocation Goのtime.LoadLocationには外部依存性がある <h1 id="Exective Summary"><a href="#Exective+Summary">Exective Summary</a></h1> <p>Goのtime.LoadLocationには外部依存性があるので、引数に空白(<code>""</code>)、<code>UTC</code>、<code>Local</code>(<code>time.Local</code>にタイムゾーンを設定している場合)のいずれかではない場合(例えば<code>Asia/Tokyo</code>)は <code>unknown time zone</code>というエラーがでる場合がある。対策としては<code>time.Local = time.FixedZone("Local", 9*60*60)</code>としてから<code>time.LoadLocation("Local")</code>とすれば良い。</p> <h1 id="本編"><a href="#%E6%9C%AC%E7%B7%A8">本編</a></h1> <p>Goでは<code>time.LoadLocation</code>を利用してタイムゾーンの情報を得ることができる。得たタイムゾーンの情報は、例えば<code>time.ParseInLocation</code>という関数で使うことができる。一連の使い方の例として、<a target="_blank" rel="nofollow noopener" href="https://golang.org/pkg/time/#ParseInLocation">公式ドキュメント</a>では以下のような例が示されている。</p> <pre><code class="golang">package main import ( "fmt" "time" ) func main() { loc, _ := time.LoadLocation("Europe/Berlin") const longForm = "Jan 2, 2006 at 3:04pm (MST)" t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc) fmt.Println(t) // Note: without explicit zone, returns time in given location. const shortForm = "2006-Jan-02T03" t, _ = time.ParseInLocation(shortForm, "2012-Jul-09T01", loc) fmt.Println(t) } </code></pre> <p>例では<code>Europe/Berlin</code>がタイムゾーンとして設定されているが、これは実行環境にある<a target="_blank" rel="nofollow noopener" href="https://github.com/golang/go/blob/eef0140137bae3bc059f598843b8777f9223fac8/src/time/zoneinfo_unix.go#L19-L26">タイムゾーンの設定ファイル</a>などを参照するために使われている。よって、Alpineのように何も環境下で<code>time.ParseInLocation</code>を実行して迂闊にタイムゾーンの名前を渡すと、 <code>unknown time zone</code>というエラーが発生してアプリケーションが実行できなくなる。そんな場合は<code>time.Local = time.FixedZone("Local", 9*60*60)</code>のように予め<code>time.Local</code>にタイムゾーンの設定情報を入れておいて<code>time.LoadLocation("Local")</code>とすることでエラーを防ぎ、かつアプリケーションから外部依存性を取り除くことが出来る。</p> <pre><code class="golang">// https://play.golang.org/p/lRiZxwT1u-U package main import ( "fmt" "time" ) func main() { time.Local = time.FixedZone("Local", 9*60*60) jst, _ := time.LoadLocation("Local") layout := "2006-01-02T15:04:05" t, _ := time.ParseInLocation(layout, "2019-07-15T20:21:22", jst) fmt.Println(t) // Print "2019-07-15 20:21:22 +0900 Local" } </code></pre> <p>もう少し踏み込んで確認したい場合は<a target="_blank" rel="nofollow noopener" href="https://github.com/golang/go/blob/9e277f7d554455e16ba3762541c53e9bfc1d8188/src/time/zoneinfo.go#L263-L308">ソースコード</a>を読んでみると良い。</p> <p>おしまい。</p> shige tag:crieit.net,2005:PublicArticle/14942 2019-04-21T23:35:17+09:00 2019-04-21T23:35:17+09:00 https://crieit.net/posts/tmrts-go-patterns-Builder-Pattern tmrts/go-patternsのBuilder Patternを日本語に勝手訳した <p>この記事は <a target="_blank" rel="nofollow noopener" href="https://github.com/tmrts/go-patterns/blob/master/creational/builder.md">https://github.com/tmrts/go-patterns/blob/master/creational/builder.md</a> を勝手に翻訳したものです。なので、内容の正確さは<strong>全く保証されてません</strong>。なので、気になる人はちゃんとオリジナルのコンテンツを読みましょう。(とはいえ、誤訳は指摘してもらえると嬉しい…</p> <h1 id="Builder Pattern"><a href="#Builder+Pattern">Builder Pattern</a></h1> <p>Builder patternとは、複雑なオブジェクトの表現から生成(コンストラクト)を分離させたものであるので、同じコンストラクトの処理で異なる表現を作成することができる。</p> <p>Goにおいては、通常、設定用のstructが同じふるまいを達成するために使われる。ただし、builder methodにstructを渡すということは<code>if cfg.Field != nil {...}</code>というチェックのボイラプレートのコードで満たすことができる。</p> <h2 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h2> <pre><code class="go">package car type Speed float64 const ( MPH Speed = 1 KPH = 1.60934 ) type Color string const ( BlueColor Color = "blue" GreenColor = "green" RedColor = "red" ) type Wheels string const ( SportsWheels Wheels = "sports" SteelWheels = "steel" ) type Builder interface { Color(Color) Builder Wheels(Wheels) Builder TopSpeed(Speed) Builder Build() Interface } type Interface interface { Drive() error Stop() error } </code></pre> <h2 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h2> <pre><code class="go">assembly := car.NewBuilder().Paint(car.RedColor) familyCar := assembly.Wheels(car.SportsWheels).TopSpeed(50 * car.MPH).Build() familyCar.Drive() sportsCar := assembly.Wheels(car.SteelWheels).TopSpeed(150 * car.MPH).Build() sportsCar.Drive() </code></pre> <h2 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h2> <p>(これは訳者、つまりこの記事を書いている人の勝手な意見)<br /> 同じコンストラクタを使って、パラメータに応じて異なる動作をする何かを生成するときに使えそう。例えば、異なるDBドライバを利用する何かだったり、コンテナのランタイムに応じて内部的な動作を切り替えるドライバ的な何かとか?サンプルコードの車の例はわかりやすくて理解に役立った。</p> shige tag:crieit.net,2005:PublicArticle/14234 2017-12-23T06:27:15+09:00 2018-09-20T13:44:09+09:00 https://crieit.net/posts/Go-glide-Wercker-CI Goのglide環境にてWerckerのCI導入 <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2017/go3">Go3 Advent Calendar 2017 - Qiita</a></p> <p>23日目</p> <p>GoのアプリケーションのリポジトリをBitBucketに作っているので、WerckerでのCIを試した。</p> <p>シンプルで綺麗なパスでアプリケーションを作っている場合、<br /> 多分Werckerにてアプリケーションを登録する際に表示されるサンプルそのままのwercker.ymlでそのまま動くのではないかと思う。</p> <p>ただ、今回のアプリケーションはパッケージ管理にglideを使っているのでそのままでは動かない。<br /> ちなみにフォルダ構成としては、下記に降りたところにアプリケーションが入ってる。</p> <pre><code>/src/myapp </code></pre> <p>Dockerコンテナ内では上記のルートをGOPATHとしている。ちょっと変な構成。</p> <p>とりあえず動くところまでを調整してみた。<br /> とりあえずなので変な感じになっている。</p> <pre><code class="yaml">box: golang build: steps: - setup-go-workspace - script: name: install glide code: | curl https://glide.sh/get | sh # Gets the dependencies - script: name: glide install cwd: src/crawler/ code: | glide install # Build the project - script: name: go build cwd: src/crawler/ code: | export GOPATH=/go/src/bitbucket.org/myaccount/myapp go build # Test the project - script: name: go test cwd: src/crawler/ code: | export GOPATH=/go/src/bitbucket.org/myaccount/myapp go test $(glide novendor) </code></pre> <p>細かいところを補足していくと、まずglideのドキュメント通りにインストール</p> <pre><code class="sh">curl https://glide.sh/get | sh </code></pre> <p>最初に書いたとおりパスがちょっと違うので合わせる。</p> <pre><code class="sh">cwd: src/crawler/ code: | glide install </code></pre> <p>パッケージの位置がずれて読み込めていなかったので、exportでGOPATHを指定している。<br /> (ymlの設定であるかも?)</p> <p>プロジェクトの環境変数を設定すると今度はglideのインストールとかがうまくいかなくなるのでここで指定している。</p> <pre><code>cwd: src/crawler/ code: | export GOPATH=/go/src/bitbucket.org/myaccount/myapp go build </code></pre> <p>こんな感じで通った。</p> <p>あとはパイプラインでデプロイの処理などと繋げれば楽だと思う。</p> だら@Crieit開発者