- 投稿日:2020-06-01T22:00:44+09:00
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使えってことかぁ?何か有益な情報があれば私めにお教え下さいませ。
- 投稿日:2020-06-01T18:50:48+09:00
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/xls2020/06/01 時点では、うまく読み込めないものがあります。
うまく読み込めないもの
数式の含まれたファイル
例えば 参照セル
=A2
というような数式となっていると、紹介したライブラリでは読めなさそう?
※数式の入ったセルの値が空文字となるパスワードがかかったもの
パスワードかかったファイルの読み込みには対応していない
なんとかして読み込む
そんなファイルたちをOLEで読み込んでみましょう。
Object Linking and Embedding - Wikipedia
https://ja.wikipedia.org/wiki/Object_Linking_and_EmbeddingただOLEを使う場合、デメリットもあります。
- 動作速度が遅い
- 命令を送ってアプリケーションを操作してというような処理なのであまり早くはならない
- サーバーサイドで使いにくい
- Excelを直接動かすため、サーバーサイドでは使いにくいですね
- メモリやパワーがいる
- 裏でExcelが立ち上がって、なんやかんやするのでそれなりに負荷はあります
実装
ただ値を取りたいだけなのですが、かなりめんどくさい処理になってます。
※いい方法があったら教えてください単にちょっとだけ情報取りたいだけなら
OLEで完結させてもいいかもしれません。
※OLE経由でセル情報を取得することができるすべてのセルを見たい場合は、
別の扱いやすいファイルに直したほうが早いかなと思ったので
以下の方法を考えてみました。
- テンポラリファイルパスを作る (ioutil.TempFile)
- Excelを立ち上げる (OLE)
- xlsファイルを開く (OLE)
- csvファイル(テンポラリ)へ保存する (OLE)
- 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
- 投稿日:2020-06-01T12:31:18+09:00
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通りあるのかなと思います。
- どんなものを作るのかから検討する段階で、どのぐらいの人数・メンバーでいつまでに開発するか分かっていない。
- 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の方が書きやすい
- 主観が含まれる部分なので具体性にかけると思いますが、PythonのDjangoやJavaのJPA系ORMを経験してきたメンバーは、SQLBoilerの方が馴染みやすいかと思います。
- 参考:gormの書き方とsqlboilerの書き方を比較
他のORMからSQLBoilerに移行したという記事はあるが、逆がない。
デメリット
- 単純にGORMのwatch・start・forkとSQLBoilerのwatch・start・forkを比べると、GORMの方が多い。
- とはいえ、スター数や知名度などからメジャーな ORM 感を醸し出しているが、使えば使うほど粗が見えてしまいという意見もある。
メリットでもデメリットでもないところ。
- 自動生成
- 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を選択することにしました。
理由は、
- もともとパネイルはPythonistaの集団であり、Pythonの経験やノウハウはあるが、Goはない。
- GoにもWebフレームワークはあるがまだ未成熟な段階であり、Pythonには十分に成熟したDjangoがある。そして、新規プロダクトはWebアプリケーションである。
- ORMを一例として、コミュニティの充実度がPythonの方が高い。我々がGolangでbuilt a strong ecosystem or communityしていくだけのスキルと覚悟があるなら、Goを選択すべきだが、そうではない。
- 新規プロダクトはパネイルとって重要なプロダクトである。Goの技術的優位性がない限り、ノウハウのあるPythonで作るべきである。サービス開始までの時間は限られている。
- 経験者の多いPythonで作った方がリリースに向けて、およびリリース以後も要員拡充がしやすく、事業優先度の高いプロダクトに対して手厚く開発体制を確保できる。
- 今後、Goで開発していくことのメリット(処理速度の速さ等)を受けられるようなプロダクトをパネイルが持っているかというと現状そうではない。
- 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.)のために開発された言語をパネイルが使用することの優位性が見当たらない。
でした。
参考にしたサイトとしては、
- なぜ私達は Python から Go に移行したのか
- なぜGo言語 (golang) はよい言語なのか・Goでプログラムを書くべき理由
- Goのメリット、デメリット
- Go vs Python: Select the Best One For Your Business
- Golang vs. Python: Which One to Choose?
- Python vs Go: What's the Difference?
- GO vs. Python: If You Had to Pick One…
となります。
一番のトリガーは何だったのか。
もうこれは自分で実装していて気づいた点なのですが、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を考慮した場合に実績のある技術を採用する、という判断を取りました。
最後に
プロダクト自体はまだリリースされておらず、目下、開発中です。
じゃあ、この判断自体の成否も判断できないのでは、というところですが、リリースに間に合ったからといって正解とも言えないように思うのです。
ただ、そうそう会社単位で頻繁に起こりうる判断でもないと思いますし、記憶の新しいうちに書き留めておくことで多少なりとも解像度を高く、今回のことを振り返ればと思って記載しました。良くも悪くも世の中のエンジニアの参考になればと思います。
- 投稿日:2020-06-01T06:30:03+09:00
【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 */
- 投稿日:2020-06-01T01:57:10+09:00
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
init
でshared
パッケージの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関数は使わないですが、ローカル環境で試すのに便利なので実装します。
init
でshared
パッケージの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 SertCloud Functions
ソースコードはGCPのリポジトリにpushして以下のように設定しました。
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つになるかもしれません。