20200518のGoに関する記事は4件です。

【Golang】defer(遅延処理)

【Golang】defer(遅延処理)

Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。

package main 
//defer 遅延実行
//goroutinやファイルの書き込み、読み込みでよく使われる
//Pythonのwithのよう

import (
    "fmt"
    "os"
)



func main(){
    //
    defer fmt.Println("最後に実行される")
    fmt.Println("最初に実行")

    //3,2,1で実行される
    fmt.Println("run")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("success")

    //主な使われ方
    //fileを開く
    //_はエラーハンドリング
    file, _ := os.Open("./if.go")
    //最後に閉じる
    defer file.Close()
    //バイト配列作成(文字数)
    data := make([]byte, 100)
    //バイト配列分読み込む
    file.Read(data)
    //文字列に変換して出力
    fmt.Println(string(data))
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SQLBoilerでhas both field and method named IDエラーになる場合の対処法

SQLBoilerの使い方

has both field and method named IDのエラーが出る場合、
あるテーブル同士のリレーションがhasOneになっているケースです。

                            Table "public.spans"
     Column     |           Type           | Collation | Nullable | Default 
----------------+--------------------------+-----------+----------+---------
 id             | character varying(40)    |           | not null | 
 highlight_text | text                     |           | not null | 
 category_id    | character varying(40)    |           | not null | 
 page_num       | integer                  |           | not null | 
 corrdinate     | character varying(255)   |           |          | 
 created_at     | timestamp with time zone |           | not null | now()
Indexes:
    "spans_pkey" PRIMARY KEY, btree (id)
    "idx_spans_category_id" btree (category_id)
Foreign-key constraints:
    "spans_annotation_id_fkey" FOREIGN KEY (id) REFERENCES annotations(id)
    "spans_category_id_fkey" FOREIGN KEY (category_id) REFERENCES categories(id)

これはspansというテーブルがannotationsのIDをprimary keyとして参照しています。
これだと、sqlboilerで生成した、モデルがIDがfieldとmethodがかぶってしまうというケースが起こります。
これはこれで修正されれば良いですが、対処法としては、

spansテーブルのidannotation_idというように、参照しているテーブルの名前に変更してやるのが一番簡単な解決策となります。

よって

                            Table "public.spans"
     Column     |           Type           | Collation | Nullable | Default 
----------------+--------------------------+-----------+----------+---------
 annotation_id  | character varying(40)    |           | not null | 
 highlight_text | text                     |           | not null | 
 category_id    | character varying(40)    |           | not null | 
 page_num       | integer                  |           | not null | 
 corrdinate     | character varying(255)   |           |          | 
 created_at     | timestamp with time zone |           | not null | now()
Indexes:
    "spans_pkey" PRIMARY KEY, btree (annotation_id)
    "idx_spans_category_id" btree (category_id)
Foreign-key constraints:
    "spans_annotation_id_fkey" FOREIGN KEY (annotation_id) REFERENCES annotations(id)
    "spans_category_id_fkey" FOREIGN KEY (category_id) REFERENCES categories(id)

このようなidだったprimary keyがannotation_idという名前に変更したテーブル設計になればエラーも起こらなくなります。

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

色んな言語でCOTOHA APIを叩いてみた

実装だけ見たい人向け

https://github.com/ofbear/cotoha_api

目的

ポストC言語(C並に早くて、Cよりメモリ管理や実装が楽な言語)がいくつか出てるけど、結局、どれが一番使いやすいのか。処理速度や実行ファイルのサイズなどではなく、PGとして書きやすい言語はどれだろう、ということで、ポストCとして有名な3つを試してみた。

  • golang
  • rust
  • nim

golang

私は元組込屋で、アセンブラやC、C++を触っていた。関係で、golangは一番書きやすい言語だった。書きやすいというのはとても感覚的な言葉だと思うが。例えば、悪名高いエラーハンドリング。

    func (c *Cotoha) post(url string, header map[string]string, param map[string]string) (result []byte, err error) {
        jsonData, err := json.Marshal(param)
        if err != nil {
            return nil, fmt.Errorf("post:%s", err.Error())
        }

        req, err := http.NewRequest(
            http.MethodPost,
            url,
            bytes.NewBuffer(jsonData),
        )
        if err != nil {
            return nil, fmt.Errorf("post:%s", err.Error())
        }
        for k, v := range header {
            req.Header.Set(k, v)
        }

        client := &http.Client{}
        res, err := client.Do(req)
        if err != nil {
            return nil, fmt.Errorf("post:%s", err.Error())
        }
        defer res.Body.Close()

        if res.StatusCode != 200 && res.StatusCode != 201 {
            return nil, fmt.Errorf("post:status is bad:%d", res.StatusCode)
        }

        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
            return nil, fmt.Errorf("post:%s", err.Error())
        }

        return body, nil
    }

これは指定のurlにpostするだけの関数なのだが、めちゃくちゃ長い。その理由の一つがエラーハンドリングである。golangでは複数の返り値を持てるが、2個目はエラーが暗黙のルールらしい。そのエラーが何かにつけて返ってくるため、逐一チェックする。これがgolangのお作法らしい。

これをして「golangはピーだ」という人もいるし、その気持ちも分からなくはない。スマートではない。全く。この泥臭い感じは実にCっぽい。Cの頃は返り値が1つしか持てないため、引数にアドレスを入れて受け取る。返り値はエラーコードを返してハンドリングをしていた。やっていることが全く同じである。

一事が万事、この調子で、golangを触っていると令和の時代に来たとは思えない懐かしさを感じる。それが嫌だ、という人もいるだろうし。それはよくわかる。しかし、あまりにも代わり映えがしないため、私のようなロートルでも違和感なく習得できる。

またlintが病的で、ちょっとした表記ゆれを許さない。ctrl+sを押すだけで決まりきった形に修正してくれる。上記のエラーハンドリングもそうだが。おそらく5人いても、ほとんど同じ形にしか実装できない。

人間の大半は馬鹿なんだ。馬鹿な人間しか現場にいなくてもコードの品質を変えずに、だいたいみんな似たようなものを作れる。交代しても素直に読める。golangにはそういう思想を感じる。ブラッシュアップされたCとしか言いようのない言語だが、これはこれで正解だと個人的に思う。

でも、jsonくらいは構造体を用意しなくてもパースできるようにして欲しい。

rust

触ってすぐにわかる。この言語はものすごく頭の良い人間が作って、頭の良い人間が使うことが想定された言語である。「馬鹿がCを触るとメモリ管理が破綻する」という事実があった際に、だいたいの言語は「わかりやすい書き方ができるようにして、セグフォを防ごう」と考える。rustは違う。「ただでさえややこしいcにルールを上乗せして、最高のメモリ管理を実現させよう」と考えている。コンパイラが強固な門番として君臨するため、破綻することはない。

String が存在するとき、 &str を末尾に連結することができます:

    let hello = "Hello ".to_string();
    let world = "world!";
    let hello_world = hello + world;

しかし、2つの String を連結するには、 & が必要になります:

    let hello = "Hello ".to_string();
    let world = "world!".to_string();
    let hello_world = hello + &world;

これは、 &String が自動的に &str に型強制されるためです。

ところで、これはrustで文字列を連結させる際の、公式ドキュメントの説明である。rustには可変文字列と固定文字列の定義があり、連結させるには左側に可変文字列を置かなくてはならない。どうも可変文字列の領域に固定文字列を追加させ、そのアドレスを可変文字列として引き渡すから、であるらしい。ただ2つの文字列変数を連結させたい場合、その変数が可変か固定かを認識した上で、それぞれに適切な指示を出さなくてはならない。

ポストCに期待することはメモリ管理を容易にすることであり、mallocだのfreeだのと考えなくても簡単に文字列を受け取ったり、処理することである。これで容易になったと言えるんだろうか。もちろん、破綻はしない。破綻しなくなるまでコンパイラが止めてくれる。

    fn __concat( &self, t1: impl Into<String>, t2: impl Into<String> ) -> String {
        t1.into() + &t2.into()
    }

ちなみに、私は自分で文字列の連結関数を作った。可変でも固定でも受け取って、可変として取り出して、連結させて、返す。おそらくrustを使う人はだいたいの人が自作するだろう。問題なのは、万人が作るだろうレベルの関数が実装されていないということである。作らなくてもわかるだろ、このくらいはできるだろ、ということだと思うのである。

    fn __access( &self ) -> Result<String, String> {
        let res = ureq::post("https://api.ce-cotoha.com/v1/oauth/accesstokens")
            .set("Content-Type", "application/json")
            .send_json(
                json!({
                    "grantType": "client_credentials",
                    "clientId": self.client_id,
                    "clientSecret": self.client_secret,
                })
            );

        if ! res.ok() {
            return Err(self.__concat("access:status is bad", res.status_line()));
        }

        let result = res.into_json();
        if ! result.is_ok() {
            return Err("access:Failed to parse response of cotoha api".to_string());
        }
        let json_data = result.ok().unwrap();

        let mut access_token = json_data["access_token"].to_string();
        if access_token == "" {
            return Err("access:Authentication failed for cotoha api".to_string());
        }
        access_token.retain(|c| c != '"');

        Ok(access_token)
    }

なんだかんだ文句を言っても、rustの実装はスマートではある。上のgolangとはじゃっかん違うが、post処理をしているという意味では同じである。というか、golangはごちゃごちゃしすぎてpost処理をくくりだすしかなかったが、rustはスマートなためくくりだす必要がなかった。

携わる人間がみな賢ければ、rustは有力な選択肢なのだろうと思う。問題はCが難しくて投げ出したくなるような、私のような人間が、それよりも難しい言語に取り組む気になれるのか。ということである。

nim

nimはpythonぽいよ、と誰かが書いていた。実際、書いてみて、おおむね正しい意見だとは思うのだが。そのぽさがあるばかりに、いちいち引数に型を書くのかよ、とか、戻り値の宣言いるのかよ、と無駄にストレスがたまる。今回、先にpythonで書いてから、それをnimに置き換えるなんてことをしたせいなのだが。似ていると逆にいらっとするということがある。

全体の印象で言うと、pythonの皮をかぶったCという感じである。golangほど冗長ではなく、rustのように難しくもしていない。Cをより柔らかく触れるようにしてくれており、非常に好印象である。

    proc post(url: string, header: openArray[tuple[key: string, val: string]], param: JsonNode): JsonNode =
        var client = newHttpClient()
        client.headers = newHttpHeaders(header)
        var res: Response = client.request(
            url = url,
            httpMethod = HttpPost,
            body = $param
        )

        if not res.status.contains("200") and not res.status.contains("201"):
            return %*{ "err": fmt"post:status is bad:{res.status}" }

        try:
            return parseJson(res.body)

        except:
            return %*{ "err": fmt"post:parseJson is failed" }

見てわかる通り、tryも使える。別にgolangにtryがないからだめだ、と言いたいわけではない。ただ、私のように何も考えたくない怠惰なPGからすると、tryは非常にありがたい存在である。リクエストが破綻しててもパース段階でひっかかるので、このようなめちゃくちゃ手を抜いたようなエラーハンドリングでも成り立つ。もっと細かく制御することも当然できる。

はっきり言って、書きやすさだけで言えば、3つの中で一番書きやすいのはnimである。とにかくPGに優しく、怠惰であることを許してくれる。golangのように冗長で読む気がなくなるようなこともなく、rustのように記号が多すぎて何やってんだか、だんだんわからなくなることもない。非常に優秀な言語である。

しかし、自分で書いていて思うが。あまりにも自由度が高いため、一番バグが出やすいのはおそらくnimだろういう予感もある。golangは嫌でも細かいエラーハンドリングを要求してくるし、誰が書いても同じものになるよう病的に神経質だ。rustは門番が厳しいので、手を抜くことが許されない。nimは手を抜くと、簡単にバグが作れる。

まとめ

先進的なベンチャー開発でテスト的に作るならrustでもありな気がする。おそらく1人や2人くらいで書いている間はrustも破綻せずくみ上げられるだろう。数十人が入れ代わり立ち代わり書く可能性があるなら、golangだ。誰が書いても同じになる。自分が趣味で触る分にはnimも悪くない気はする。

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

GoでDogStatsDを利用してcustom metricsを送る方法

背景

  • Datadogには様々なintegrationやmetricsが用意されているが、アプリケーションの中で自分達独自のeventやmetricsを送りたいときがある。
  • DatadogにはDogStatsD(https://docs.datadoghq.com/ja/developers/dogstatsd/)というDatadog agentに付属しているカスタムメトリクス(あとservice checkとevent)を送るための機能が付属されている。
  • 方法は公式Docには書かれているもののやや導入に苦戦したのでまとめた。

DogStatsD

  • 公式から説明を拝借
DogStatsD は、Datadog Agent に付属するメトリクス集計サービスです。カスタムアプリケーションメトリクスを最も簡単に Datadog に取り込むには、メトリクスを DogStatsD に送信します。DogStatsD は StatsD プロトコルを実装すると共に、Datadog 固有の以下の拡張機能を提供します。

ヒストグラムメトリクスタイプ
サービスチェック
イベント
タグ付け

設定方法

Datadog agentをセットアップ

  • この記事にたどり着く人は多分Datadog自体の設定はしていると思うのと公式やいろいろな記事もすでにあるので、Datadog agentセットアップ自体は省略します。

DogStatsDのセットアップ

  • DogStatsDはUDPとUDS(Unix Domain Socket)で通信することができるので各々の設定方法を解説します。
UDP
  • Agent v6以上のUDP ポート 8125 でデフォルトで有効になっているのでdatadog側の設定は特に必要ありません。ただしコンテナ側からhostの8125ポートアクセスできるようになっている必要があるので、ECSやKubernetesの場合、hostPortを開けておく必要があります。この記事ではkubernetesの設定方法を書いておきます。

  • kubernetesの場合は下記の設定をdatadog-agentのdaemonsetに追加してください。helmの場合はdatadog.dogstatsd.useHostPort(https://github.com/helm/charts/blob/de0939a800686fc8c28ac034aaabcfa2108931fa/stable/datadog/values.yaml#L167) という項目を有効にするとhostportを開放できます。

  • 他のコンテナからの DogStatsD パケットをリスニングするためにDD_DOGSTATSD_NON_LOCAL_TRAFFICの環境変数もtrueにします

    • ただUDPを使った方法はdaemonsetでhostportを開放してしまうので、node側が一つポートが使えなくなってしまうのが問題点です。Datadog公式では後述のUDSの方法のほうがおすすめと書かれています。
  ports:
    - containerPort: 8125
      hostPort: 8125
      name: dogstatsdport
      protocol: UDP
コード
package main

import (
    "log"
    "time"

    "github.com/DataDog/datadog-go/statsd"
)

func main() {
    statsd, err := statsd.New("127.0.0.1:8125")
    if err != nil {
        log.Fatal(err)
    }
    for {

        statsd.Incr("example_metric.increment", []string{"environment:dev"}, 1)
        statsd.Decr("example_metric.decrement", []string{"environment:dev"}, 1)
        statsd.Count("example_metric.count", 2, []string{"environment:dev"}, 1)
        time.Sleep(10 * time.Second)
    }
}
  • Goでのサンプルコードです。このコードでは example_metric.increment, example_metric.decrement, example_metric.count というmetricsを10秒毎に送信しています。
  • UDPを使った場合は statsd, err := statsd.New("127.0.0.1:8125") hostに対してrequestを送るので 127.0.0.1:8125 を指定してください。
UDS
volumeMounts:
    - name: dsdsocket
      mountPath: /var/run/datadog
    ##...
volumes:
    - hostPath:
          path: /var/run/datadog/
      name: dsdsocket
  • Custom metricsを送るアプリケーション側
volumeMounts:
    - name: dsdsocket
      mountPath: /var/run/datadog
      readOnly: true
    ## ...
volumes:
    - hostPath:
          path: /var/run/datadog/
      name: dsdsocket
コード
package main

import (
    "log"
    "time"

    "github.com/DataDog/datadog-go/statsd"
)

func main() {
    statsd, err := statsd.New("unix:///var/run/datadog/dsd.socket")
    if err != nil {
        log.Fatal(err)
    }
    for {

        statsd.Incr("example_metric.increment", []string{"environment:dev"}, 1)
        statsd.Decr("example_metric.decrement", []string{"environment:dev"}, 1)
        statsd.Count("example_metric.count", 2, []string{"environment:dev"}, 1)
        time.Sleep(10 * time.Second)
    }
}
  • UDPの場合とほぼ同じですが、statsd.Newにわたすアドレスが unix:///var/run/datadog/dsd.socket になります。volume mountしたパスと unix:のプレフィックスが必要です。

おわりに

  • DogStatsDを使うと、Daemonsetに立っているagentに直接metricsを送るだけなので、別途のapi管理なども要らず非常に簡単にcustom metrics,service check,eventを送る事ができます。Jobの成功失敗、アプリケーションの特殊なcountなど、サーバーの生死以外でも活用できる部分があると思うので是非使ってみてください!

参考資料

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