20191130のGoに関する記事は6件です。

Rails10年選手がGoの勉強会に参戦してきた

はじめに

静岡でRailsのWebエンジニアをしているkazuomatzです。2019年も残り1ヶ月を切りましたが、どうやら最近は業界的にRailsはオワコンらしいです。

僕は、2007年くらいからRailsを使ってきたので、かれこれ10年以上Railsを触ってきたことになるわけですが、オワコンなんて言われると、このままRailsを使い続けるか、それとも新しい言語に乗り換えるかの岐路に立たされているような気にもなってきます。

Railsが世の中に登場したのが2004年、2019年現在、Railsのバージョンは6.0です。2007年の12月にRailsのバージョン2が公開されていますから、かれこれ、5世代のRailsを使ってきたことになります。

Web2.0とRailsとの出会い

ちょっと昔話になりますが、2000年半ばに「Web」はバージョン1からバージョン2にバージョンバップされました。「Web 2.0」です。

これまでのWeb(=Web 1.0)は、ユーザーが情報を取得するだけのものでした。それがWeb 2.0になると、ユーザーがWebを通じて情報を発信することができるようになりました。そして、Webをプラットフォームとした様々なサービスやビジネスが生み出される時代に向かっていきました。

今となっては、Web 2.0とは結局何だったのかという議論もありますが、ともかく2007年11月に「Web 2.0 Expo」が盛大に開催されました。Web 2.0を提唱したティム・オライリーの講演をはじめ、当時の先進的なWeb関連のセッションが盛りだくさんでした。

当時、僕はJavaプログラマでマッシュアップ関連のWebサービスをいろいろ作っていましたが、そのWeb 2.0 Expoの会場で、Sun Microsystems(当時)のティム・ブレイの衝撃的な一言を聞くことになります。

Ruby on Railsに触れたJavaプログラマーは、もうJavaには戻ってこないだろう

Javaの開発元であるSun Microsystemsの中の人がこれ言っちゃったわけですから、大変なことです。少なくともWebが大きく変わるぞって時に、この発言によってRailsは脚光を浴びることになります。恐らく、これきっかけでRails始めた人はかなりいたはずです。

僕もその一人です。あの時からこの10年あまり、Railsに魅了され、RailsでいろいろなWebサービスを開発してきました。

Go勉強会へ

前置きが長くなりました。

ともあれ、Railsオワコン説を聞く中で、10年Railsを触ってきた身として、確かにいろいろ辛いことが多くなってきたなぁとも思います。そんな中見つけたのが、Shizuoka.goの勉強会でした。

勉強会の運営者も以前の仕事仲間(@secondarykey)だったので、Rails10年選手がGoを模索しているというネタで何かしゃべらせてとお願いして、参加することにしました。

go.png

ここからは、発表した内容の抜粋です。

Railsのここがよかった

  • 設定より規約 / DRY ( Don’t Repeat Yourself )の哲学
  • フルスタックなWebフレームワーク MVC / ORM / Asset
  • 便利なライブラリ(Ruby Gems)が豊富!!
  • お手軽な動的型付言語 Ruby
  • 開発効率が半端ない ような気がしてた・・・

とにかくRailsに魅了されていた過去の自分。

Railsがオワコンの原因について

  • フロントエンドのフレームワークがいろいろ出てきた
  • Webpackなどのモジュールバンドラーが便利
  • Railsでイケてたところのほころびが出てきた

僕もVue.jsは結構使うんですが、フロントエンドのフレームワークとRailsとがうまく調和しなくなってきてますね。Webpackについては、Rails向けのWebpackerがありますが、これもいまいち、むしろWebpackをそのまま使う人も多しね。Sprocketsもnode対応したり、ES6対応したりでこれでいけないこともないし・・・。

むしろ、僕が困ったと思っているのは、イケてたところのほころびが出てきたと書いたところ。

RailsってRubyGemsにものすごく多くの便利なライブラリーたちがあって、これをサクッと導入することで、いろいろなことが簡単にできます。もちろん、Ruby/Rails以外の言語やフレームワークでもそのような仕組みはあるわけだけど、Railsはちょっと探すと大抵のやりたいことは、Gem入れれば実現できちゃった。それが、当時のRailsって開発効率いいねという神話を作っていたのかもしれません。

rails.jpg

こんなの見ちゃうと、Railsのレールに乗っているイケてる感に拍車かかりますしね・・・。

ところが、10年もいろいろWebサービスを作ってくると、サービス中止になるものもあれば(まぁこちらの方が多いわけですが)、セキュリティ対応やRailsのバージョンアップをしながら保守し続けるサービスもあります。この後者のサービスの延命が辛いんです。

Railsのバージョンアップは痛みを伴います。むしろ作り替えてしまった方が早い場合もあります。いや、作り替えられればまだいいかもしれません。データベースに貯まった様々なデータが特定のライブラリー依存の形で保存されているとかなり辛いです。

例えば、PaperClipという便利なGemがあります。画像ファイルをアップロードすると、Amazon S3などのクラウドストレージにアップロードしてくれる便利なライブラリです。これ本当によく使いました。でも、このPaperClipは、Rails 5.2以降では使えません。開発者の方が開発中止を宣言して、Rails5.2以降では、Railsに標準装備されたActiveStorageを使ってねということになっています。

これから新規に開発するサービスであれば、Rails 6でActiveStorageを使えばよいですが、すでにPaperClipを使ってデータも保存されているサービスをRails 6に移行することは簡単にはできません。

これは、Railsが悪いわけでも、開発を止めてしまったデベロッパーが悪いわけではありません。むしろ便利なGemを使うことでイケてる感を出してきたツケが回ってきたのかなと思います。

今のRuby / Railsの状況

  • 2〜3年前に開発したWebサービスがサポート外のRailsで動いている
  • とはいえ、Railsのバージョンアップする時間・費用がない
  • 慣れ親しんだライブラリーが更新されず、最新のRails環境で使えない

こんな状況をGoは救ってくれるのか

ここからやっとGoの話。こんな状況をGoは救ってくれるのかを検証してみた。

これからもWebで食べていく僕がやっていくことは、こんなこと。

  • Webサービスの開発(バックエンドもフロントエンドもやる)
  • アプリ開発におけるバックエンドの開発(iOS / Androidアプリも作る)
  • AWSを上手に使ってマイクロサービス指向で行く(Azure、GCPにも手を出す)

願うこと

  • Web開発に必要なよいフレームワーク
  • シンプルに開発ができること
  • リファクタリングしやすく保守性が高いこと

発表しながら気づいた。第一にフレームワークを求めちゃうのは、フルスタックな全部入りのRailsにずいぶん甘やかされてきたんだなぁと。Rubyの世界ではWebフレームワークと言ったらほぼRails一択だけど、Goの場合は、軽量級なものからフルスタックなものまで、いろいろなフレームワークが存在する。Routerはこれで、ORMはこれをといった選択することも可能。Railsやってるとこの感覚は確かにない。

デモアプリをGoで作ったよ

僕は、この発表をするにあたり、簡単なWebサイトのモックをGoで作ってみた。

静岡市が運営している市民協働のポータルサイト「ここからネット」。このサイトには市民活動団体の活動情報や開催する講座やイベント情報、ボランティア情報などが掲載されている。そしてこれらデータはオープンデータとして公開されており、Web APIでデータを取得できる。APIのリファレンスもGithubで公開されている。

ここからネットに登録されているデータの中から静岡市の保育施設の一覧を取得して表示するだけのシンプルなものを作ってみた。

できあがりはこんな感じ。

APIからの戻りはこんな感じのJSON。

search.json
{
  "status": 200,
  "page": 1,
  "per_page": 2,
  "all_page_num": 133,
  "count": 2,
  "all_count": 266,
  "pois": [
    {
      "id": 3450,
      "name": "静岡大学教育学部附属幼稚園",
      "prefecture_name": "静岡県",
      "city_name": "静岡市葵区",
      "address1": "大岩町1-10",
      "address2": "",
      "tel": "054-245-1191",
      "url": "http://fzk.ed.shizuoka.ac.jp/youchien/",
      "option_items": [
        {
          "display_name": "分類",
          "attribute_name": "分類",
          "value": "従来どおりの幼稚園"
        },
        {
          "display_name": "設立",
          "attribute_name": "設立",
          "value": "国立"
        },
        {
          "display_name": "受入",
          "attribute_name": "受入",
          "value": "3歳~就学前"
        },
        {
          "display_name": "情報登録日",
          "attribute_name": "情報登録日",
          "value": "H29.9.1"
        }
      ],
      "updated_at": "2018/05/09 22:01:39 +0900",
      "lat": 34.9913406,
      "lng": 138.3794399
    }
  ]
}

GoでAPIをリクエストしてJSON Parseしてクライアントに返すプログラムはこんな感じ。
動的言語のRubyと違って静的型付言語のGoにおいては、APIで受け取るJSONの構造体をキチンと書く。

/controllers/kindergarten.go
// PoiData 受信データ
type PoiData struct {
    Status     int `json:"status"`
    Page       int `json:"page"`
    PerPage    int `json:"per_page"`
    AllPageNum int `json:"all_page_num"`
    Count      int `json:"count"`
    AllCount   int `json:"all_count"`
    Pois       []struct {
        ID             int    `json:"id"`
        Name           string `json:"name"`
        Kana           string `json:"kana"`
        Description    string `json:"description"`
        ZipCode        string `json:"zip_code"`
        PrefectureName string `json:"prefecture_name"`
        CityName       string `json:"city_name"`
        Address1       string `json:"address1"`
        Address2       string `json:"address2"`
        Tel            string `json:"tel"`
        URL            string `json:"url"`
        ImageURL       string `json:"image_url"`
        OptionItems    []struct {
            DisplayName   string `json:"display_name"`
            AttributeName string `json:"attribute_name"`
            Value         string `json:"value"`
        } `json:"option_items"`
        StartAt        string  `json:"start_at"`
        EndAt          string  `json:"end_at"`
        LocationID     string  `json:"location_id"`
        ActivityID     string  `json:"activity_id"`
        PostPhotoID    string  `json:"post_photo_id"`
        InformationID  string  `json:"information_id"`
        UpdatedAt      string  `json:"updated_at"`
        OrganizationID string  `json:"organization_id"`
        Lat            float64 `json:"lat"`
        Lng            float64 `json:"lng"`
        Marker2X       string  `json:"marker2x"`
        Marker         string  `json:"marker"`
        Type           string  `json:"type"`
    } `json:"pois"`
}

// GetKinderGarten 保育園情報を取得する
func (controller *KinderGartenController) GetKinderGarten() {

    url := beego.AppConfig.String("endPointURL") + "/map/search.json"

    page := controller.GetString("page")
    url += "?page=" + page

    dataSet := controller.GetString("type")
    if len(dataSet) == 0 {
        dataSet = "1,2,3,4,5"
    }
    url += "&data_set=" + dataSet

    response, err := http.Get(url)
    if err != nil {
        controller.CustomAbort(500, "Internal server error")
        return
    }

    defer response.Body.Close()

    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        controller.CustomAbort(500, "Internal server error")
        return
    }

    jsonBytes := ([]byte)(body)
    data := new(PoiData)

    if err := json.Unmarshal(jsonBytes, data); err != nil {
        controller.CustomAbort(500, "Internal server error")
        return
    }
    controller.Data["json"] = data
    controller.ServeJSON()
}

フロントエンドはVue.jsを使っている。モジュールバンドルはWebpackだ。ここはGoとは直接関係ない世界。

選択したWebフレームワーク

そして、肝心のWebフレームワークはいろいろ探した結果、最終的にBeego Frameworkを選んだ。

beego.png

BeegoはRubyのWebフレームワークSinatraを意識して開発されたとのこと。SinatraはRubyのWebフレームワークではRailsの次に使われているもので、フルスタックなRailsよりも軽量なもの。

Beegoは、ORM、Routerも使えて、MVCモデルもいける。CLIもあってコマンド叩いていろいろできる。

実は、自分の発表の前に、元仕事仲間(@secondarykey)がGoのWebフレームワークをいろいろ紹介してくれていた(資料)。彼の所感では、Beegoは「老舗っぽくフルスッタックで色々機能がありそう。重そう。」とのこと。やっぱり、Railsのフルスタック脳がBeegoを選んだ結果は納得できる。

まとめ

Go言語とRubyを比較すると、静的と動的の議論も確かに出てきます。やっぱり静的の方が安全だよねという風潮もありますし、動的の柔軟さが開発効率をあげているのも事実です。

このあたりはトレードオフなので、要件に合わせてどちらが最適かを見極めていくことも必要かなと思います(今まで動的万歳だった自戒も込めて)。

今、自分がサクッとWebサービスを作るとしたら、やはりRailsを選ぶと思います。Rails 6のActiveStorageはよくできていると思いますし、まだまだ使えるGemも多い。自前のライブラリーやTipsも豊富にあったりするので、ユーザビリティや品質の高いWebサービスはまだまだRailsで作れます。

ただ、今回、Shizuoka.goに参加して、Goの魅力にも気づけたことも事実です。
自分が今後やっていきたいことの中で、「AWSを上手に使ってマイクロサービス指向で行く」をあげていますが、こちらはRubyよりもGoが向いているんだろうと思います。AWSでなくて、GCPなのかもしれませんが・・・。

ということで、今までやってきたアーキテクチャーとは違うものにチャレンジする際に、Goを選択することをここに誓います。

今回のShizuoka.goではWebフレームワークの話のほかに、@hogedigoさんのGoでのテスト手法についての話(資料)、@cupperさんのAWSとGoの話(資料)、@hrs_sano645
さんのGo製便利ツールの紹介(資料)など、興味深い話がたくさん聞けました。

最後に、Shizuoka.goのみなさんありがとうございました。また参加します。Shizuoka.go最高!

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

大文字小文字を区別しないという不幸な世界のワイルドカード(macOS)

この記事について

macOS のファイルシステムは、デフォルトでは殆どのディレクトリで大文字小文字を区別しない。
そのような世界で、ワイルドカードを用いると何がマッチするか、という話。

大文字小文字を区別しないということ

例えばこんなスクリプトを実行すると

bash
#!/bin/bash

set -eu

# ASCII の英数字
touch foo.txt FOO.txt
ls *.txt && rm *.txt

# 所謂全角文字
touch zen.txt ZEN.txt
ls *.txt && rm *.txt

# ギリシャ文字・キリル文字・ローマ数字
touch ωяⅶ.txt ΩЯⅦ.txt
ls *.txt && rm *.txt

# DZ, NJ
touch dz.txt # U+01F3   Latin Small Letter DZ
touch Dz.txt # U+01F2   Latin Capital Letter D with Small Letter z
touch DZ.txt # U+01F1   Latin Capital Letter DZ
touch nj.txt # U+01CC   Latin Small Letter NJ
touch Nj.txt # U+01CB   Latin Capital Letter N with Small Letter J
touch NJ.txt # U+01CA   Latin Capital Letter NJ
ls *.txt && rm *.txt

# i witout dot, etc.
touch ı.txt # U+0131 Small I without dot
touch İ.txt # U+0130 Capital I with dot
touch i.txt # U+0069 Small I
touch I.txt # U+0049 Capital I
ls *.txt && rm *.txt

# 囲み文字
touch ⓐ.txt # U+24D0
touch Ⓐ.txt # U+24B6
ls *.txt && rm *.txt

こんな結果になる

foo.txt
zen.txt
ωяⅶ.txt
nj.txt  dz.txt
i.txt   İ.txt  ı.txt
ⓐ.txt

FOOfoo は簡単。大文字小文字を区別しないのでどちらかしか生き残れない。

zenωяⅶ のあたりを見て分かる通り、7bit-ASCII の範囲外の文字にも大文字小文字があり、それらは区別されない。

Dz は「Titlecase Letter」というカテゴリの文字で、大文字でも小文字でもない。
対応する小文字は dz で、対応する大文字は DZ になる。
この文字も大文字小文字を区別しないので、 touch dz.txt Dz.txt DZ.txt としても、ひとつしか生き残れない。

ı, İ, i, I の件は下表のとおり。

言語 Iの小文字 iの大文字
英語 i I
トルコ語 ı İ

ı.txt, İ.txt, i.txt, I.txt を touch すると、
i.txt, İ.txt, ı.txt が生き残る。

unicode.org を見ると、dot のない小文字ıは、大文字にすると普通のI になる。
にも関わらず、APFS 上では ı.txtI.txt は別の名前だとみなされる。

様々な環境での対応

いくつかの環境で何が起こるのか試してみた。

shell script(bash) 他

以下の環境でこの結果になる:

  • shell script(bash)
  • C(POSIX glob)
  • Go(filepath.Glob)
  • Python3(glob.glob)
  • PHP7(glob)

POSIX glob や bash と同じなのでこれが基本っぽいんだけど、わりと気持ち悪い動作になっている。

基本的には、

  • ワイルドカードがない場合は大文字小文字を区別しない。
  • ワイルドカードがある場合は大文字小文字を区別する。

となっている。Foo.txt だと1件マッチして F*.txt だと 0件マッチするというのはかなり意外だった。

ワイルドカードがない部分については、(気付いた範囲では)ファイルシステムと同じ意見になっている。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

ruby(Dir.glob)

ruby の動作は POSIX glob とはだいぶ異なる。

基本的には「大文字と小文字を区別しない」という動作で一貫しているような感じ。
Foo.txt だと foo.txt のみがマッチして、 F*.txt だと foo.txtfred.txt がマッチする。わかりやすい。

しかし、

ruby
Dir.glob("dz-u*.txt") #=> []
Dir.glob("dz-uu.txt") #=> ["files/DZ-uu.txt"]

と、ワイルドカードを入れるとマッチする件数が減るというパターンもある。
バグ?

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

Java(PathMatcher)

java.nio.filePathMatcher という インターフェイスがあるので、それを使ってみた。
これも POSIX glob とはだいぶ異なる。
常に大文字小文字を区別するように見える。
ファイルシステムのファイル名とは異なる動作だが、一貫性はある。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

C#(.NET Core / Directory.GetFiles)

ruby の動きと似ている。
ruby と異なり、 DZ*.txtdz-ll.txt にちゃんと(?)マッチする。
しかし逆に、dz-uu.txt では DZ-uu.txt が取れない。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

C#(Mono / Directory.GetFiles)

意外にも、 .NET Core と Mono で動作が異なる。
大文字でも小文字でもない Dz という文字に負けている感じがする。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

Perl(glob)

POSIX glob とよく似た動作になっているが、ドットなしの小文字 i の扱いが異なる。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

まとめ

POSIX glob は、ワイルドカードがない部分についてはファイルシステムと同じ意見になっているものの、ワイルドカードが入ると大文字小文字を区別するようになるところがわかりにくい。

  • Go(filepath.Glob)
  • Python3(glob.glob)
  • PHP7(glob)

は、POSIX glob と同じ意見になっている。

一方

  • ruby(Dir.glob)
  • Java(PathMatcher)
  • C#(.NET Core GetFiles)
  • C#(Mono Directory.GetFiles)
  • Perl(glob)

は独自のアルゴリズムで処理しているらしく、POSIX glob とは異なる結果を返す。
「アルファベット2文字をセットにした文字で一文字目だけ大文字に出来るもの」や「小文字のiからドットを除いたもの」のあたりで不穏な動作になりやすい。

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

Docker - How to Get Private Git Repository Inner Golang Container?

This method didn't need to generate ssh key.

FROM golang:1.13.4-alpine
...
...
...
ARG GIT_URL
ARG GIT_GROUP
ARG GIT_ACCOUNT
ARG GIT_PASSWORD

RUN apk update -qq && apk --no-cache add git

RUN printf "machine ${GIT_URL}\n\
    login ${GIT_ACCOUNT}\n\
    password ${GIT_PASSWORD}\n"\
    >> /root/.netrc
RUN chmod 600 /root/.netrc

RUN go env -w GOPRIVATE=${GIT_URL}/${GIT_GROUP}
...
...
...

https://medium.com/@jwenz723/fetching-private-go-modules-during-docker-build-5b76aa690280

https://blog.csdn.net/du_chao_qun/article/details/53464454

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

Docker - How to Get Private Git Repository Inner Golang Container via HTTP?

You may be put your golang modules in a self-hosting git repository which likes gitlab. The most ways to tell us using ssh method and it is not easy enough (need to generate ssh key). I am just want to use https to get golang modules.

FROM golang:1.13.4-alpine
...
...
...
ARG GIT_URL
ARG GIT_GROUP
ARG GIT_ACCOUNT
ARG GIT_PASSWORD

RUN apk update -qq && apk --no-cache add git

RUN printf "machine ${GIT_URL}\n\
    login ${GIT_ACCOUNT}\n\
    password ${GIT_PASSWORD}\n"\
    >> /root/.netrc
RUN chmod 600 /root/.netrc

RUN go env -w GOPRIVATE=${GIT_URL}/${GIT_GROUP}
...
...
...

https://medium.com/@jwenz723/fetching-private-go-modules-during-docker-build-5b76aa690280

https://blog.csdn.net/du_chao_qun/article/details/53464454

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

Goでつくるdaemon処理レシピ集

本記事は、アドベントカレンダー Go4 の1日目の記事です。

これは何?

みなさんは、どんなdaemon処理を書いていますか?

ここでは、Go の daemon 処理サンプルを紹介します.
daemon とは、バックグラウンドで動作するプロセス実行し続けるアレで、typoに注意なやつです(いつもdeamonと書いてしまう).

Goではその言語特性から、様々な処理をシンプルに記述しやすいと感じており、daemonの実装例を通してGoらしさを学ぶことにも役立つと思います.
他にも役立つレシピがあれば、教えて下さい :smiley:

まずは基本から

単純に無限実行させる

The Go Playground で実行してみる

func main() {
    timeout := 5 * time.Second
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    f := func() error { // some task
        return nil
    }

  simple(ctx, f)
}

func simple(ctx context.Context, task func() error) {
    for {
        err := task()
        if err != nil {
            log.Printf("[ERROR] err: %v", err)
        }
        time.Sleep(500 * time.Millisecond)
    }
}

context を使ったタイムアウト

The Go Playgroundで実行してみる

func withTimeout(ctx context.Context, task func() error) {
    child, childCancel := context.WithCancel(ctx)
    defer childCancel()

    for {
        err := task()
        if err != nil {
            log.Printf("[ERROR] err: %v", err)
        }
        select {
        case <-child.Done():
            log.Printf("[DEBUG] timeout")
            return
        default:
            time.Sleep(500 * time.Millisecond)
        }
    }
}

応用編

特定時刻にだけ処理をする

The Go Playground で実行してみる

func runDaemon(ctx context.Context, f func(context.Context) error) {
    daemonHour := "7-10"
    waitDuration := 500 * time.Millisecond

    for {
        now := time.Now()
        if len(daemonHour) != 0 { //起動時刻の指定があったら
            isExec := isExecHour(now, daemonHour)
            log.Printf("[DEBUG] daemon起動時間かどうかの判定 now:%v, daemonHour:%s, isExec:%v", now, daemonHour, isExec)
            if !isExec {
                time.Sleep(1 * time.Minute)
                continue
            }
        }

        err := f(ctx)
        if err != nil {
            log.Printf("[ERRROR] err:%v", err)
        }
        time.Sleep(waitDuration)
    }
}

func isExecHour(now time.Time, dHour string) bool {
    delimitor := "-"
    dh := strings.Split(dHour, delimitor)
    if len(dh) <= 1 {
        return false
    }

    start, err := strconv.Atoi(dh[0])
    if err != nil {
    return false
    }
    end, err := strconv.Atoi(dh[1])
    if err != nil {
    return false
    }

    h := now.Hour()
    if start <= h && h <= end {
        return true
    }
    return false
}

一定周期ごとに(前の処理が終わっていなくても)処理を実行する

The Go Playground で実行してみる

func timeTicker(ctx context.Context, task func(context.Context) error) {
    counter := 0
    waitTime := 1 * time.Second
    ticker := time.NewTicker(waitTime)
    defer ticker.Stop()
    child, childCancel := context.WithCancel(ctx)
    defer childCancel()

    for { // deamon化するため無限実行
        select {
        case t := <-ticker.C:
            counter++
            requestID := counter
            log.Println("[DEBUG] START taskNo=", requestID, "t=", t)

            errCh := make(chan error, 1)
            go func() { // 登録したタスクをブロックせずに実行
                errCh <- task(ctx)
            }()

            go func() {
                // error channelにリクエストの結果が返ってくるのを待つ
                select {
                case err := <-errCh:
                    if err != nil {
                        // Deamonの強制終了
                        log.Println("[ERROR] ", err)

                    }
                    log.Println("[DEBUG] END requestNo=", requestID)
                }
            }()
        case <-child.Done():
            return
        }
    }
}

みなさんも、よき daemon Life をお過ごしください!

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

Goのstruct(構造体)はフィールドなしでも定義できる

概要

タイトルのままです。

フィールドなしでメソッドだけ定義したかったのですが、パッと調べてもわからなかったので、実際に試して知りました。

『そんなもん知ってるわ!!』という方はブラウザバックどうぞ

検証結果

main.go
package main

import (
    "log"
)

func main() {
    n := Network{}
    log.Print(n.CreateURL())
}

type Network struct {
}

func (n *Network) CreateURL() string {
    // TODO: 引数を受け取ってURLを返す
    return "https://github.com/"
}
実行結果
# go run main.go
2009/11/10 23:00:00 https://github.com/

実際に動かせる

こちら =>

https://play.golang.org/p/cuQNOBeEdK_R

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