20200601のGoに関する記事は5件です。

WSL&VScode&Go=permission denied

タイトルの通り、WSL上にGoの実行環境を用意した後VScodeのターミナルから色々やってた時に出くわした謎のエラーpermission deniedを解決したお話です。

経緯

WSL上でこれを参考にGoを勉強してたのだが、いよいよVimだとキツくなってきたため、VScodeからWSL、そしてGoを動かしたいと考えるようになった。
VSCodeにWSLの拡張機能を追加して、VScodeのターミナルからgo buildでWebアプリケーションを立ち上げようとしたときに問題が起こった。

permission denied

>>go build
....
....
main.go:4.12 ~~~/~~~/: rename ~~~/~~~/: permission denied

う~んと?
WSL上でやっていた時には問題なくビルドできていた筈なのに、なんぞこれ?
不思議に思いWSL上でビルドするも、同じエラー吐くように。

解決へ

とりあえず調べてみると、同じような症状で悩む人が居たようで。
私も色々試行錯誤した結果、原因は分からなかったものの一応解決はできた。
それはVScodeのターミナル機能(WSL)を使わないというものだ。
WSL上のみでgo buildするだけなら問題はなく、VScodeのターミナルでgo buildをした途端にpermission deniedが発生する。
うーんターミナルを使わないとなると、VScodeで開発する旨味が減る...Atom使えってことかぁ?

何か有益な情報があれば私めにお教え下さいませ。

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

Go xlsファイルを読み取る

Go xlsファイルを読み取る

今回は、Goで Excel の xlsファイル(Excel 97 ~ Excel 2003 ブック) を読み取る方法を考えてみます。

xlsxファイルの場合は、 excelize がオススメです。

ライブラリ

簡単に読み取りを行うなら、下記のライブラリを使うとよいです。

GitHub - extrame/xls: Pure Golang xls library
https://github.com/extrame/xls

↓ Example を参考に書いてみるとこんな感じ

package main

import (
    "fmt"
    "log"

    "github.com/extrame/xls"
)

func main() {
    xlFile, err := xls.Open("input.xls", "utf-8")
    if err != nil {
        log.Fatal(err)
    }

    sheet1 := xlFile.GetSheet(0)
    if sheet1 != nil {
        fmt.Printf("Total Lines : %d (%s)\n", sheet1.MaxRow, sheet1.Name)
        for row := 0; row < int(sheet1.MaxRow); row++ {
            // sheet から 行 を取り出す
            r := sheet1.Row(row)
            for col := r.FirstCol(); col < r.LastCol(); col++ {
                // 行(row) と 列(col) を指定すると 値 が返ってくる
                value := sheet1.Row(row).Col(col)
                // 下記でもよい
                // value := r.Col(col)

                fmt.Printf("(%d, %d) = %s\n", row, col, value)
            }
        }
    }
}

詳しくは、下記をご覧ください。

xls - GoDoc
https://godoc.org/github.com/extrame/xls

2020/06/01 時点では、うまく読み込めないものがあります。

うまく読み込めないもの

数式の含まれたファイル

例えば 参照セル =A2 というような数式となっていると、紹介したライブラリでは読めなさそう?
※数式の入ったセルの値が空文字となる

パスワードがかかったもの

パスワードかかったファイルの読み込みには対応していない

なんとかして読み込む

そんなファイルたちをOLEで読み込んでみましょう。

Object Linking and Embedding - Wikipedia
https://ja.wikipedia.org/wiki/Object_Linking_and_Embedding

ただOLEを使う場合、デメリットもあります。

  • 動作速度が遅い
    • 命令を送ってアプリケーションを操作してというような処理なのであまり早くはならない
  • サーバーサイドで使いにくい
    • Excelを直接動かすため、サーバーサイドでは使いにくいですね
  • メモリやパワーがいる
    • 裏でExcelが立ち上がって、なんやかんやするのでそれなりに負荷はあります

実装

ただ値を取りたいだけなのですが、かなりめんどくさい処理になってます。
※いい方法があったら教えてください

単にちょっとだけ情報取りたいだけなら
OLEで完結させてもいいかもしれません。
※OLE経由でセル情報を取得することができる

すべてのセルを見たい場合は、
別の扱いやすいファイルに直したほうが早いかなと思ったので
以下の方法を考えてみました。

  1. テンポラリファイルパスを作る (ioutil.TempFile)
  2. Excelを立ち上げる (OLE)
  3. xlsファイルを開く (OLE)
  4. csvファイル(テンポラリ)へ保存する (OLE)
  5. csvファイル(テンポラリ)を開く (encoding/csv)

別にcsvファイルでなくてもよいのですが、
公式packageにcsv読み込めるものがあるので利用しました。

出来上がったソースが、以下です。
※エラーとかあまり考慮してない

$ go build & xxx.exe xxx.xls な感じで読み込みできます。

実行には、Excelが必要です。

ソース
package main

import (
    "encoding/csv"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"

    "github.com/mattn/go-ole"
    "github.com/tanaton/go-ole-msoffice/excel"
    "golang.org/x/text/encoding/japanese"
    "golang.org/x/text/transform"
)

func main() {
    args := os.Args

    rows, err := getXlsData(args[1])
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%#v", rows)
}

func getXlsData(path string) ([][]string, error) {
    var rows [][]string

    // テンポラリファイルパス作成
    dist, err := ioutil.TempFile("", "*.csv")
    if err != nil {
        return rows, err
    }
    dist.Close()
    os.Remove(dist.Name())
    // 処理後削除
    defer os.Remove(dist.Name())

    // OLE
    ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE)
    defer ole.CoUninitialize()

    // Excelを取得する
    e := excel.ThisApplication()
    if e == nil {
        return rows, nil
    }

    // Excel設定: アラートの表示(true: する、false: しない)
    e.SetDisplayAlerts(false)
    // Excel設定: Excel自体の表示(true: する、false: しない)
    e.SetVisible(false)

    workbooks := e.GetWorkbooks()

    // 必ず絶対パスにする
    absPath := path
    if !filepath.IsAbs(absPath) {
        absPath, err = filepath.Abs(path)
        if err != nil {
            return rows, err
        }
    }

    // Excelファイルを開く
    book := workbooks.Open(absPath)

    // csvファイルとして保存する
    book.SaveAs(dist.Name(), excel.XlCSV)

    // ファイルとExcelを閉じる
    book.Close()
    e.Quit()
    e.Release()

    // csvファイルを開いて読み込む
    f, err := os.Open(dist.Name())
    if err != nil {
        return rows, err
    }
    defer f.Close()

    r := csv.NewReader(transform.NewReader(f, japanese.ShiftJIS.NewDecoder()))
    return r.ReadAll()
}

パスワード付きを読み込む場合

下記のようにすれば、パスワードがかかったものも読めます。

// Open(FileName, UpdateLinks, ReadOnly, Format, Password)
book := workbooks.Open(src, 0, true, 5, password)

Open メソッド (Excel) | Microsoft Docs
https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbooks.open

まとめ

OLEを使って xlsファイルの読み込みをやってみました。
OLEを使うことによって、いろんなことができると思うので試してみてくださいね。
xlsファイルだけじゃなくてExcelで扱えるファイルは、OLE経由で読めるはず。

あと いい方法・いいpackageあったら教えてください (o*。_。)o

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

Goで開発していたが、途中でPythonに切り替えた件を振り返る。

イントロダクション

目下、開発中のプロダクトなので詳しいことは書けないのですが、いろいろと気付きの多い出来事だったので、
少し自分自信の振り返りも兼ねて、投稿してみたいと思います。

これは、決してGoよりPythonのほうが優れているとかそういった話ではないです。
今回、自分は開発者というよりプロジェクトマネージャー(以降、PM)という立場になります。

Goの採用

パネイルのコア技術はPythonなのですが、今回、開発にあたってGoを採用していました。
主な採用理由としては、「プロトコルとしてgRPCを採用するにあたって、gRPCとの組み合わせ事例が多い」からでした。

gRPCの採用理由は、「同時に企画されていた別プロダクト(Python)との連携が想定されており、異なるプログラミング言語間でも型を維持したままデータ交換が可能」なことからでした。

当初は、プロダクトのリリース時期も未定でプロトタイプ的に実装されていたので、この時点では、社内的に実績のない新規技術を採用しても、特に問題がない状態でした。
問題がなかったかどうか検証するために、そもそも技術選定がどうあるべきか、考えたいと思います。

プロダクト開発にあたって技術選定はどうあるべきか

SIerでの開発経験を経て、自社開発の会社に入ったのでどうあるべきか、というのは分かっていませんでした。
まずは、考えるにあたって比較材料としてSIerでの例を挙げたいと思います。

SIerの場合

例として、SIerの開発の場合は、QCDの観点から実績のある技術を採用します。
これは基本的にSIerのビジネスとしては、

  • 発注先が検収してもらえる品質にした上で、要件通りに開発する。(Q)
  • 利益がちゃんと出る範囲内で開発コストをかける。(C)
  • 決められた納期内に開発を終える。(D)

という前提があるので、保守的な技術選定になりやすいというところでもあります。

極端な話、新規技術を採用した場合は、上記の裏返しとして、

  • 発注先が検収してもらえる品質にした上で、要件通りに開発できるか分からない。(Q)
  • 利益がちゃんと出る範囲内で開発コストが収まるか分からない。(C)
  • 決められた納期内に開発を終えられるか分からない。(D)

となるからです。

分からない=リスクがある、というところですが、このリスクをオフしていくには実績や経験が必要です。

  • 社外的に実績のある技術かどうか(=俗にいう枯れた技術)
  • 社内的に実績のある技術かどうか(=何かあったときにヘルプしてもらえるか)
  • PJ内に経験者はいるかどうか(=プロジェクトをリードするだけの実績や経験があるか)

あとは、社内・社外問わず開発者を集めやすいかどうか、というのも重要です。
これらの要素を考慮して想定されるリスクが許容範囲内だと判断された場合に、その技術は採用されます。

よっぽど発注元からの技術指定があったり、新規技術を採用して検証する場合は、その技術を使うこと自体がプロジェクトの目的になるので、これは別のケースです。
あとは、潤沢な人的・金銭的なリソースがある場合に限り、新規技術を採用できるかと思いますが、受託開発においては、稀だと思います。

自社開発の場合

プロダクト開発にあたっては、2通りあるのかなと思います。

  1. どんなものを作るのかから検討する段階で、どのぐらいの人数・メンバーでいつまでに開発するか分かっていない。
  2. QCDの前提条件がはっきりしており、どんなものをどのぐらいの人数・メンバーでいつまでに開発するか分かっている。

2については、SIer同様に判断できるかと思っていて、やはり「分からない=リスクがある」を避けるような技術選択をすることになると思います。
1については、SIerの受託開発だとあまりないパターンで自社開発特有なのかなと思います。

今回、Goを採用した段階ではまさに1の状態で「もやっとしたイメージはあるけど、プロダクトのコンセプトを固めるためにプロトタイプを作ろう」という段階でした。
なので、CostやDeadlineの観点はなく、要件を含んだQualityの観点からのみ判断すればよい状態でした。
かなりリスクをとって、社内的に実績のない新規技術を採用しても、要件に合致しているのであれば、許容される状況です。

そのため「Goの採用後、プロトタイプ的に実装されていた段階においては、問題がなかった」と考えられます。

プロトタイプ開発から本番プロダクト開発へ

Goで実装されていたプロトタイプですが、徐々に本番プロダクト開発が意識されるようになります。
とはいえ、この時点では、「N人ぐらいの体制で社内向けに徐々に開発していく」といった見通しでした。

社内向けに開発していって、社外のユーザーも使える段階に入ったら外部に公開しようという話でした。
しかも社内向けに開発していくが、代替手段はあるので無理のない範囲で開発していくという状態です。

この時点でQCDでいうと、Qがより明確になりつつあり、Cは月々の固定費が決まり、Dについては無理のない範囲でというところでした。
おそらく取りうるリスクのレベル感は、この時点でも下がっていたはずですが、開発メンバーも慣れない技術に手こずりつつも新規技術に触れられる事自体がモチベーションにもなっていたので、選択技術の見直し自体は話題に上がっていませんでした。

多少、gRPCでのWeb開発の先行事例の少なさから手間がかかりすぎている感じはしていたのですが、
そこまで懸念をしていませんでした。

プロダクト戦略の方針転換、リリース日が決まる

「社内向けに開発していって、社外のユーザーも使える段階に入ったら外部に公開」という想定で開発していたのですが、急遽、プロダクト戦略の方針転換が行われ、「○月に向けて開発をし、社外ユーザーを初期ターゲットにする」となります。

この時点でQCDのDが決まったことになります。ただし、Qについてはまだ詳細化されておらず、リリース日の決定が先行した段階だったので、

  • Qはおぼろげに見えつつあるが、正直分からない。(=詳細な要件が見えてないので工数が見積もれない)
  • Cについては、使えるだけ使う。(=社内・社外から集められるだけの人は集めた)
  • Dは○月。

という状態でした。

正直、技術選定のやり直しをすべきかどうかも判断がつかず、プロトタイプ的に作ってきたとはいえ、一部の機能も出来上がっているのでこのまま行くという判断を取りました。

ただ、振り返ってみれば、この時点でPythonに切り替えるという判断は出来たようにも思います。
まず、「プロトタイプ的に作ってきたとはいえ、一部の機能も出来上がっているので」というのは、まさにコンコルド効果でもあって判断の基準にしてはいけなかったと思います。

ここで0ベースで考えたとき、どういった判断や材料があったのか。

まず、当初のGoの採用理由でもあり、gRPCの採用理由は、「同時に企画されていた別プロダクト(Python)との連携が想定されており、異なるプログラミング言語間でも型を維持したままデータ交換が可能」なことからでした。

ここは、どうかというと当初の別プロダクト(Python)企画は見送りとなっており、別の別プロダクト(Python)が動き始めていました。
そうなると、同じなのではという気がしますが、当時と状況が違うのは、片方の企画はリリース日が決まっており、片方の企画は未定という状態です。

おそらくこの時点で技術選定の選定条件が崩れていたことを正しく認識し、0ベースで考えるべきだったと思いますが出来ていませんでした。
一番ポイントなのは、「プロジェクト全体のリスクを判断すべきポジションのメンバーがよく分かっていない技術を使って開発してはいけない」というところかもしれません。

ここは、プロジェクトや会社によってPMが技術を分かってなくてもいいというところもあるかもしれませんが、今回、自分自身がGoとGo周辺の状況をよく分かっていなかったというところが1つポイントのように思うのです。

当時、Goについての印象は、

  • Goは、Googleが開発した言語である。
    • なんのために開発した言語かは知らない。(=技術の目的を知らない)
  • Goは、JavaやC#と同様に型付け言語である。
    • JavaやC#の経験がある自分なら分かるだろう。(=過信)
  • Goは、採用理由にもあるようにgRPCと相性が良く、マイクロサービス的に考える上で良い。
    • マイクロサービスへの過信。(=過信)

でした。

Goを選択したメンバーが諸事情により離脱し、ORMを入れ替える。

この時点で自分も開発メンバーにも加わり、gRPCやGo、ORMについて見直しをしました。結果、以下のメリット・デメリットを判断してORMのGORMのみSQL BOILERに変更をしました。

メリット

SQLBoilerの方がGORMより速い。

  • 公式のBenchMarkを確認する限り、GORMを含むその他のORMよりSQLBoilerの方が優秀。

GORMよりSQLBoilerの方が書きやすい

他のORMからSQLBoilerに移行したという記事はあるが、逆がない。

デメリット

メリットでもデメリットでもないところ。

  • 自動生成
    • SQLBoilerはテーブル定義に基づいてmodelのstructを自動生成する。
    • GORMはmodelのstructに基づいてをテーブルを自動生成する。

ORMに関する私見

  • JavaやC#で過去にアプリケーションを実装してきた経験上から
    • 発行されるSQLが自動生成されるので、ORMを過度に信用するのは良くない。
      • 予期せぬ挙動をすることがあり、障害につながるケースがある。
      • パフォーマンス的に及第点のSQLになる可能性がある。
    • というところから、なるべく生SQLを書こうという流れを見てきました。
  • とはいえ、開発効率の面から
    • 複雑なクエリーは生SQL、それほど複雑ではないクエリーはORMの自動生成
  • というのが落とし所です。
  • sqlxからSQLBoilerへの移行もその一例かと思います。

gRPCやGo自体については、「リリース想定の時期までを考えるとそこまで見直している時間がない。すでに出来ているものもあるので活かしていく」というやはりコンコルド効果でもあったと思います。また、PMポジションにあるメンバーが実装に加わった時点でプロジェクトが破綻していたともいえます。

これは前職の先輩の教えでもあるのですが、「PMは開発メンバーよりも長期的な時間軸で物事を考えないといけない」という教えがありました。PJのメンバーにはそれぞれポジションによって見るべき時間軸があり、開発メンバーは比較的、短期から中期の時間軸、PMは中期から長期の時間軸で考えるべき、というものです。

PMが実装に加わるということは、短期から中期の時間軸で考える開発メンバーも兼ねるということであり、それはつまり中期から長期の時間軸に対する注意がおろそかになるということでもあります。

Goで開発していたが、途中でPythonに切り替えた。

リリース日が決まっているのでやれるところまでやってやろう、というまったく冷静さのかけた情熱の持ち具合で、自分自身も実装に加わってしばらく開発したところある事件が起きました。

「自分の知らない間にORMのバージョンが上がっていて、しかも動かなくなった箇所がある。

たまたま最新のバージョンで開発していた開発メンバーがいたことで気づいたのですが、特定のDB依存とはいえ、まったく動かなくなった処理がありました。

過去の自分の経験上、JavaやC#であれば、下位互換がされないようなアップデートがORMほどのWebアプリにとって根幹的な技術で起きたことはなく、またあったとしても対処法も合わせて提供されるような世界でした。さらに少数の数名のメンテナーの意思によって技術が左右される世界でもなかったと思います。

SQL BOILERというGORMに比べればマイナーなORMを選択した結果ともいえますが、それでもやはりGORMにしてもDjangoに比べれば、コミュニティのサイズが小さく感じられます。
合わせて、Goで実装していく際に感じるJavaやC#と比べたストレス具合によって、この時点で我に帰り、0ベースで技術選定をし直します。

0ベースで技術選定をし直した結果どうだったか。

結果的にPythonを選択することにしました。
理由は、

  1. もともとパネイルはPythonistaの集団であり、Pythonの経験やノウハウはあるが、Goはない。
  2. GoにもWebフレームワークはあるがまだ未成熟な段階であり、Pythonには十分に成熟したDjangoがある。そして、新規プロダクトはWebアプリケーションである。
  3. ORMを一例として、コミュニティの充実度がPythonの方が高い。我々がGolangでbuilt a strong ecosystem or communityしていくだけのスキルと覚悟があるなら、Goを選択すべきだが、そうではない。
  4. 新規プロダクトはパネイルとって重要なプロダクトである。Goの技術的優位性がない限り、ノウハウのあるPythonで作るべきである。サービス開始までの時間は限られている。
  5. 経験者の多いPythonで作った方がリリースに向けて、およびリリース以後も要員拡充がしやすく、事業優先度の高いプロダクトに対して手厚く開発体制を確保できる。
  6. 今後、Goで開発していくことのメリット(処理速度の速さ等)を受けられるようなプロダクトをパネイルが持っているかというと現状そうではない。
  7. Goをパネイルが組織としてキャッチアップしていくことのメリットが現状想定されない。Googleの課題解決(to solve problems which are at the scale of Google, that basically involves thousands of programmers working on large server software hosted on thousands of clusters.)のために開発された言語をパネイルが使用することの優位性が見当たらない。

でした。

参考にしたサイトとしては、

となります。

一番のトリガーは何だったのか。

もうこれは自分で実装していて気づいた点なのですが、ORMの充実具合がPythonのDjangoと比べたとき、Goだと十分ではなかったという点です。

以下、参考程度のイメージなのですが、Goで110行程度になる処理がPythonなら60行程度になるというところでした。単純なCRUDのマスターメンテナンスの時点では気づかなかったのですが、JOINするテーブルが2〜3に増えると途端にコードの行数が増えています。

GoのSQL BOILERの場合(GORMのstructを用意する点は変わらない)、

package query
import (
    "fmt"
    "time"
    "golang.org/x/net/context"
    "github.com/panair-jp/xxx/go/models"
    "github.com/panair-jp/xxx/go/pb"
    "github.com/panair-jp/xxx/go/query"
    "github.com/volatiletech/sqlboiler/queries/qm"
)

// xxxQuery struct{}

// xxxDataView is custom struct.
type xxxDataView struct {
    ID                       int     `boil:"xxx_id"`
    xxxID int                        `boil:"xxx_id"`
    Memo                     string  `boil:"memo"`
    xxx                      int     `boil:"xxx"`
    xxx                      float64 `boil:"xxx"`
    xxx                      float64 `boil:"xxx"`
    xxx                      float64 `boil:"xxx"`
    xxx                      string  `boil:"xxx"`
    xxx                      float64 `boil:"xxx"`
    xxx                      string  `boil:"xxx"`
}
// xxxDataViewliceModel is custom model.
type SxxxDataViewliceModel struct {
    xxxSlice []*xxxDataView
}
// Fetch xxxQuery is an method that select xxx
func (s *xxxQuery) Fetch(ctx context.Context, xxxID int, targetYear int) ([]*pb.xxxData, error) {
    xxxIds := make([]interface{}, 1)
    xxxIds[0] = xxxID
    var utilsQuery = query.UtilsQuery{}
    xxxIDs, err := utilsQuery.GetxxxIDs(ctx, xxxIds)
    if err != nil {
        fmt.Printf("%v", err)
        return nil, err
    }
    targetYearFrom, targetYearTo, err := utilsQuery.Getxxx(ctx, xxxID, targetYear)
    if err != nil {
        fmt.Printf("%v", err)
        return nil, err
    }
    var result = new(xxxDataViewliceModel)
    var queries = getxxxDataQueries(xxxIDs, targetYearFrom, targetYearTo)
    err = models.NewQuery(queries...).BindG(ctx, &result.xxxSlice)
    if err != nil {
        fmt.Printf("%v", err)
        return nil, err
    }
    pbxxxData := []*pb.xxxData{}
    for _, xxx := range result.xxxSlice {
        var xxx, xxx, xxx = Getxxx(xxx)
        pbxxxData = append(pbxxxData, &pb.xxxData{
            Id:                       uint64(xxx.ID),
            xxxId: uint64(xxx.xxxID),
            Memo:                     xxx.Memo,
            xxx:                      uint64(xxx.xxx),
            xxx:                      xxx,
            xxx:                      xxx,
            xxx:                      xxx.xxx,
            xxx:                      xxx,
            xxx:                      xxx.xxx,
            xxx:                      xxx.xxx,
        })
    }
    return pbxxxData, nil
}

func getxxxDataQueries(xxxIDs []interface{}, targetYearFrom time.Time, targetYearTo time.Time) []qm.QueryMod {
    var queries []qm.QueryMod
    {
        queries = append(queries, qm.Select(
            " xxx.id as xxx_id, "+
                " xxx.xxx_id as xxx_id, "+
                " xxx.memo as memo, "+
                " xxx.xxx as xxx, "+
                " xxx as xxx, "+
                " xxx.xxx as xxx, "+
                " xxx.xxx as xxx, "+
                " xxx.xxx as xxx, "+
                " xxx.xxx as xxx, "+
                " xxx.xxx as xxx "))
        queries = append(queries, qm.From("xxx xxx "))
        queries = append(queries, qm.InnerJoin("xxx xxx on xxx.id = xxx.xxx "))
        queries = append(queries, qm.InnerJoin("xxx xxx on xxx.id = spgcef.xxx "))
        queries = append(queries, qm.WhereIn("xxx.xxx_id in ? ", xxxIDs...))
        queries = append(queries, qm.And("xxx.\"timestamp\" between ? and ? ", targetYearFrom, targetYearTo))
        queries = append(queries, qm.OrderBy("xxx.memo, spg.\"timestamp\", xxx.xxx_id "))
    }
    return queries
}

// Getxxx provide xxx from xxx or xxx.
func Getxxx(xxx *xxxDataView) (float64, string, string) {
    var xxx float64
    var xxx string
    var xxx string
    var xxx = xxx.xxx
    if xxx == int(pb.xxx) {
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    }
    if xxx == int(pb.xxx) {
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    }
    if xxx == int(pb.xxx) {
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    }
    return xxx, xxx, xxx
}

Python&Djangoの場合、

from xxx.models import *
from xxx.pb import xxxData


def fetch(xxx_id, target_year):
    xxx_ids = [xxx_id]
    xxx_ids = get_xxx_ids(xxx_ids)
    target_year_from, target_year_to = get_target_year_from_to(xxx_id, target_year)
    xxx_list = Xxx.objects.filter(
        xxx_id__in=xxx_ids,
        timestamp__gte=target_year_from,
        timestamp__range=(target_year_from, target_year_to),
    ).order_by('memo', 'timestamp', 'xxx__xxx_id')

    pb_xxx_data_list = []
    for xxx in xxx_list:

        xxx = xxx
        xxx = xxx.xxx
        xxx = xxx.xxx

        xxx, xxx, xxx = get_xxx(xxx, xxx)

        pb_xxx_data = xxxData()
        pb_xxx_data.id = xxx.id
        pb_xxx_data.xxx_id = xxx.xxx_id
        pb_xxx_data.memo = xxx.memo
        pb_xxx_data.xxx = xxx.xxx
        pb_xxx_data.xxx = xxx
        pb_xxx_data.xxx = xxx
        pb_xxx_data.xxx = xxx.xxx
        pb_xxx_data.xxx = xxx.xxx
        pb_xxx_data.xxx = xxx.xxx
        pb_xxx_data.xxx = xxx.xxx
        pb_xxx_data.append(pb_xxx_data)

    return pb_xxx_data_list


def get_xxx(xxx):

    xxx = xxx.xxx.xxx

    if xxx == xxx:
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    elif xxx == xxx:
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    elif xxx == xxx:
        xxx = xxx.xxx
        xxx = xxx.xxx
        xxx = "xxx" + xxx.xxx
    else:
        raise ValueError("想定外の計算方式です")

    return xxx, xxx, xxx

となります。

ここでじゃあ、Djangoと同じように使えるORMを使えばいいのでは、というところでbeegoという選択肢をPJメンバーから出してもらったのですが、ここで0ベースで考えたとき、やはりSIerのときと同様にQCDを考慮した場合に実績のある技術を採用する、という判断を取りました。

最後に

プロダクト自体はまだリリースされておらず、目下、開発中です。

じゃあ、この判断自体の成否も判断できないのでは、というところですが、リリースに間に合ったからといって正解とも言えないように思うのです。
ただ、そうそう会社単位で頻繁に起こりうる判断でもないと思いますし、記憶の新しいうちに書き留めておくことで多少なりとも解像度を高く、今回のことを振り返ればと思って記載しました。

良くも悪くも世の中のエンジニアの参考になればと思います。

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

【Golang】ゴルーチン③バッファ、チャネル

【Golang】ゴルーチン③バッファ、チャネル

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

package main
//Buffered Channels
//同じチャネルに複数ゴルーチンから送信

import "fmt"

func main() {
    //1
    //channelを作成 2はバッファ 2つまで受け取る。長さ
    //3つあるとエラーになる
    ch := make(chan int, 2)
    ch <- 1000
    fmt.Println(len(ch))
    //>>1

    ch <- 2000
    fmt.Println(len(ch))
    //>>2

    //ch <- 300はバッファサイズ以上になる為、エラー(デッドロック)

    //2
    //一つ取り出す。channelの中は1つ減るため、追加できる
    /*
    x := <- ch
    fmt.Println(x)
    ch <- 3000
    */


    //3
    //forで取り出す
    //2つ以上取りに行くため、エラーになる。
    //チャネルに3つ目がないのに撮りに行こうとする為。
    //close(ch)にすることで、それ以上読み込まなくなり、エラーがなくなる
    //rangeで回す場合は必要
    //close()で全て入ったら閉じる
    close(ch)
    for c := range ch {
        fmt.Println(c)
    }
    /*
    1000
    2000
    */
}

/*
close
バッファ内が空になったりクローズされたりしても、チャネルが内包する型の初期値を受信する
ch := make(chan iont, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
i, ok<- ch
//>>1, true
i, ok<- ch
//>>2, true
i, ok<- ch
//>>3, true
i, ok<- ch
//>>0, false
i, ok<- ch
//>>0, false
*/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud FunctionsのHTTPトリガーをGolang + Echoでさばいてみた

GCPのCloud FunctionsのHTTPトリガーをGolangで使っているときにEcho使えるんじゃないかと思って試したメモです。

プロジェクト構成

Cloud Functionsで使えるように以下のようにファイルとディレクトリを作成します。

プロジェクトルート/
├── cmd
│   └── main.go
├── functions.go
├── go.mod
├── go.sum
└── shared
    └── echo.go

コード

go.mod

使った依存ライブラリはこんな感じです。

module github.com/sert-uw/go_echo_functions

go 1.13

require (
    github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
    github.com/labstack/echo v3.3.10+incompatible
    github.com/labstack/gommon v0.3.0 // indirect
    golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect
    golang.org/x/text v0.3.2 // indirect
)

shared/echo.go

Echoの初期化をInitEchoで行うよう実装します。

package shared

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "net/http"
)

var EchoServer *echo.Echo

func InitEcho() {
    EchoServer = echo.New()

    EchoServer.Use(middleware.CORSWithConfig(middleware.CORSConfig{
        AllowOrigins: []string{"*"},
    }))

    EchoServer.GET("/hello/:name", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello "+c.Param("name"))
    })
}

functions.go

initsharedパッケージのInitEchoを呼び出すことでCloud Functionsの関数インスタンス作成時にEchoの設定を行います。
Functionで受け取ったHTTPリクエストはそのままEchoのServeHTTPに渡します。
あとはEchoでリクエストを処理してくれるので、Functionの役割はこれだけです。

package functions

import (
    "github.com/sert-uw/go_echo_functions/shared"
    "net/http"
)

func init() {
    shared.InitEcho()
}

func Function(w http.ResponseWriter, r *http.Request) {
    shared.EchoServer.ServeHTTP(w, r)
}

cmd/main.go

Cloud Functionsではmain関数は使わないですが、ローカル環境で試すのに便利なので実装します。
initsharedパッケージのInitEchoを呼び出してEchoの設定を行い、mainでEchoサーバーを起動するだけです。

package main

import (
    "github.com/sert-uw/go_echo_functions/shared"
    "log"
)

func init() {
    shared.InitEcho()
}

func main() {
    log.Fatalln(shared.EchoServer.Start(":8080"))
}

動作確認

ローカル環境

main関数を実行して、curlを投げてみます。

$ go run cmd/main.go

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
$ curl 'http://localhost:8080/hello/Sert' -v
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /hello/Sert HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Content-Type: text/plain; charset=UTF-8
< Vary: Origin
< Date: Sun, 31 May 2020 16:40:27 GMT
< Content-Length: 10
<
* Connection #0 to host localhost left intact
Hello Sert

Cloud Functions

ソースコードはGCPのリポジトリにpushして以下のように設定しました。
スクリーンショット 2020-06-01 1.37.53.png

GCPのコンソール上でトリガーのエンドポイントを確認してcurlを投げてみます。

$ curl 'https://asia-northeast1-<プロジェクト名>.cloudfunctions.net/go-echo-functions/hello/Sert' -v
*   Trying 216.239.xxx.xxx...
* TCP_NODELAY set
* Connected to asia-northeast1-<プロジェクト名>.cloudfunctions.net (216.239.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=Mountain View; O=Google LLC; CN=misc.google.com
*  start date: May  5 08:23:30 2020 GMT
*  expire date: Jul 28 08:23:30 2020 GMT
*  subjectAltName: host "asia-northeast1-<プロジェクト名>.cloudfunctions.net" matched cert's "*.cloudfunctions.net"
*  issuer: C=US; O=Google Trust Services; CN=GTS CA 1O1
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fbd30006c00)
> GET /go-echo-functions/hello/Sert HTTP/2
> Host: asia-northeast1-<プロジェクト名>.cloudfunctions.net
> User-Agent: curl/7.54.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200
< access-control-allow-origin: *
< content-type: text/plain; charset=UTF-8
< function-execution-id: sdtxd0pn5lgn
< vary: Origin
< x-cloud-trace-context: 42b67e55066cded12f14db38b8da2855;o=1
< date: Sun, 31 May 2020 16:42:41 GMT
< server: Google Frontend
< content-length: 10
< alt-svc: h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
<
* Connection #0 to host asia-northeast1-<プロジェクト名>.cloudfunctions.net left intact
Hello Sert

まとめ

予想以上に簡単にEchoを動かせたので、App Engineを別のアプリケーションに使いたい場合にAPIをCloud Functionsで動かすなど、コストを抑えるためにGCPの無料枠をうまく使う選択肢の1つになるかもしれません。

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