- 投稿日:2020-01-23T23:20:20+09:00
Web系企業でプログラマを目指す皆様へ
少数受託開発ベンチャーでアルバイトをして感じたこと
初めに結論から述べます
結論
メリットもあればデメリットもあり
万人にオススメは出来ないということです会社の特徴
所属している企業の特徴を説明します。
・社員数20名以下
・受託開発
・Webアプリケーション作成
・ベンチャー企業
・所在地は大阪未経験からWeb系企業へ転職を考えている人ならば
魅力的な企業のように見えるはずです確かに、下記のメリットはあります
メリット
・モダンな技術に触れることができる
フロントエンドはReact、React native、Typescript
バックエンドはGolang
DBはMySQL、GraphQL
インフラはAWS・服装自由、音楽も聴ける
服装は最低限の清潔感があれば良いみたいです。イヤホンをつけて仕事をしている人もいれば、
お菓子を食べたり、コーヒーを飲みながら仕事をしている人がいます。・人が少ないので挑戦しやすい
研修を2日終えて、現場に入りそうな予感です。・未経験でも潜り込める
人が足りていません。
とにかく未経験を現場で鍛え上げて戦力にしようという風潮です。但し、メリットだけではありません。
デメリット
・社長と距離が近い故に、地雷を踏む可能性がある
社長の性格と合わなければしんどいです。
入社しないと分からないものですけどね。・実力主義
仕事ができなければ罵声が飛ぶ現場です。・研修が充実していない
研修でゆっくり教える時間も人もありません。
自分で力をつけていく必要があります。以上のメリットとデメリットを踏まえて
オススメできる人、できない人の特徴を考えてみました。オススメできる人
・自走力がある人
研修が十分でないので、自ら知識を吸収する姿勢が求められます。・短期間で成長したい人
開発の経験に関わる可能性が高いので、次のステップアップを考えている人にとっては最適でしょう。オススメできない人
・ゆっくり成長したい人
まったりしたい人には絶対にオススメできません。・研修が必要な人
しっかりと知識をつけてから仕事につきたいと考えているならば
スクールに通ったり、何ヶ月も研修がある会社にいくべきです。・会社でコミュニティを築きたい人
コミュニティを築きたいならば社員の多い会社にいく
もしくは
社外の勉強会などで繋がりを作るなど
自分で行動を起こしましょう。・安定志向の人
人数が少ないということは資金的にも人的にも厳しいです。
つまり、零細企業です。
将来の保証は一切ないに等しいでしょう。
なので、スキルを身につけることが必要です。アルバイトで働き始めて2日経って感じたことをまとめました。
全ての少数受託開発の企業がこうだとは思いません。
一概に上記の条件が全て正しい訳ではないが、
Web系企業を目指す人に知っておいて欲しいです。SESやSIerなどでエンジニアを始めるのも悪くはないと考えます。
随時、更新していきます
- 投稿日:2020-01-23T23:20:20+09:00
Web系企業で働くことを目指す皆様へ
少数受託開発ベンチャーでアルバイトをして感じたこと
初めに結論から述べます
結論
メリットもあればデメリットもあり
万人にオススメは出来ないということです会社の特徴
所属している企業の特徴を説明します。
・社員数20名以下
・受託開発
・Webアプリケーション作成
・ベンチャー企業
・所在地は大阪未経験からWeb系企業へ転職を考えている人ならば
魅力的な企業のように見えるはずです確かに、下記のメリットはあります
メリット
・モダンな技術に触れることができる
フロントエンドはReact、React native、Typescript
バックエンドはGolang
DBはMySQL、GraphQL
インフラはAWS・服装自由、音楽も聴ける
服装は最低限の清潔感があれば良いみたいです。イヤホンをつけて仕事をしている人もいれば、
お菓子を食べたり、コーヒーを飲みながら仕事をしている人がいます。・人が少ないので挑戦しやすい
研修を2日終えて、現場に入りそうな予感です。・未経験でも潜り込める
人が足りていません。
とにかく未経験を現場で鍛え上げて戦力にしようという風潮です。但し、メリットだけではありません。
デメリット
・社長と距離が近い故に、地雷を踏む可能性がある
社長の性格と合わなければしんどいです。
入社しないと分からないものですけどね。・実力主義
仕事ができなければ罵声が飛ぶ現場です。・研修が充実していない
研修でゆっくり教える時間も人もありません。
自分で力をつけていく必要があります。以上のメリットとデメリットを踏まえて
オススメできる人、できない人の特徴を考えてみました。オススメできる人
・自走力がある人
研修が十分でないので、自ら知識を吸収する姿勢が求められます。・短期間で成長したい人
開発の経験に関わる可能性が高いので、次のステップアップを考えている人にとっては最適でしょう。オススメできない人
・ゆっくり成長したい人
まったりしたい人には絶対にオススメできません。・研修が必要な人
しっかりと知識をつけてから仕事につきたいと考えているならば
スクールに通ったり、何ヶ月も研修がある会社にいくべきです。・会社でコミュニティを築きたい人
コミュニティを築きたいならば社員の多い会社にいく
もしくは
社外の勉強会などで繋がりを作るなど
自分で行動を起こしましょう。・安定志向の人
人数が少ないということは資金的にも人的にも厳しいです。
つまり、零細企業です。
将来の保証は一切ないに等しいでしょう。
なので、スキルを身につけることが必要です。アルバイトで働き始めて2日経って感じたことをまとめました。
全ての少数受託開発の企業がこうだとは思いません。
一概に上記の条件が全て正しい訳ではないが、
Web系企業を目指す人に知っておいて欲しいです。SESやSIerなどでエンジニアを始めるのも悪くはないと考えます。
随時、更新していきます
- 投稿日:2020-01-23T18:50:21+09:00
GoにてRedirectの注意点(Echo)
リダイレクトで困った事が起きました
特定のページにリダイレクトする時に、1度目のリダイレクトは成功するが、2回目以降実行しても正しく実行してくれない。
func hoge(c echo.Context)error{ //省略 return c.Redirect(http.StatusMovedPermanently, "/") }このStatusMovedPermanetlyというやつにやられました
どうやらChormeブラウザにて GETアクションでStatusMovedPermanetlyを実行するとブラウザにキャッシュが溜まるらしいです。(要するに2回目以降動かない)
これを解決するには
func hoge(c echo.Context)error{ //省略 return c.Redirect(http.StatusFound, "/") }StatusMovedPermanetlyをStatusFoundに変更してやれば大丈夫です。
これで僕は半日潰れました
- 投稿日:2020-01-23T18:50:21+09:00
GoにてRedirectの注意点(初心者向け)(Echo)
リダイレクトで困った事が起きました
特定のページにリダイレクトする時に、1度目のリダイレクトは成功するが、2回目以降実行しても正しく実行してくれない。
func hoge(c echo.Context)error{ //省略 return c.Redirect(http.StatusMovedPermanently, "/") }これはルートパスにリダイレクトしてくれというコードですね
このStatusMovedPermanetlyというやつにやられましたどうやらChormeブラウザにて GETアクションでStatusMovedPermanetlyを実行するとブラウザにキャッシュが溜まるらしいです。(要するに2回目以降動かない)
これを解決するには
func hoge(c echo.Context)error{ //省略 return c.Redirect(http.StatusFound, "/") }StatusMovedPermanetlyをStatusFoundに変更してやれば大丈夫です。
これで僕は半日潰れました
- 投稿日:2020-01-23T18:17:10+09:00
[Golang] aws Lambda で、s3へファイルをstreamingに出力する
概要
大量のデータを処理するとき、メモリにすべてを展開せずにstream的に処理する必要があるときがある。
こういうstream的な処理は、大体どの言語でもあまりサンプルがなく、調べながらやるしかない。
ということでgoでやってみたのが以下。Streamingの利点
- メモリの使用が最小限であること
- json linesフォーマットと親和性が高い
Streamingの欠点
- 通常のjsonフォーマットが使えない事
- どうしてもgoroutineを使った非同期処理が必要となる事
なので、データ量が多くないなら、Streamingに処理せず普通にメモリに展開したほうが良いかもしれない。
Lambda
以下のサンプルは、Dynamodbにデータが挿入・更新されると実行される Dynamodb streamsという仕組みで動くLamba。
出力先のS3ファイルは、一行が一つのdynamodbレコードに相当するjsonであるような、json linesフォーマット。func handler(ctx context.Context, event events.DynamoDBEvent) error { r, w := io.Pipe() go func() { defer func() { if err := w.Close(); err != nil { log.Println("writer close error: ", err) return } }() for _, r := range event.Records { if err := UnmarshalDynamoEvent(r.Change.NewImage, &data); err != nil { log.Println("unmarshal dynamo event error: ", err) w.CloseWithError(err) return } b, err := json.Marshal(&data) if err != nil { log.Println("json marshal error: ", err) w.CloseWithError(err) return } if _, err := w.Write(b); err != nil { log.Println("data buffer write error: ", err) w.CloseWithError(err) return } if _, err := w.Write(lf); err != nil { log.Println("LF buffer write error: ", err) w.CloseWithError(err) return } } }() key := fmt.Sprintf("%s/%s/%s.json", keyPrefix, now.Format("20060102"), now.Format("150405")) result, err := s3uploader.Upload(&s3manager.UploadInput{ Body: r, Bucket: aws.String(bucket), Key: aws.String(key), }) log.Println("result:", result) if err != nil { return err } return nil }こんな感じでStreamingにS3に出力することができた。
ちなみにPipeは以下のようなもの。io.Pipe
func Pipe() (*PipeReader, *PipeWriter){io.PipeWriter
func (w *PipeWriter) Write(data []byte) (n int, err error) { func (w *PipeWriter) Close() error { func (w *PipeWriter) CloseWithError(err error) error {データをWriterに書き込むわけだが、書き込み中になんらかのエラーが発生してStreamをクローズしたいとき、CloseWithErrorを使って失敗したことをReader側に通知できる。
成功したら普通にCloseで閉じる。io.PipeReader
func (r *PipeReader) Read(data []byte) (n int, err error) func (r *PipeReader) Close() error { func (w *PipeReader) CloseWithError(err error) error {Writer側で何等かのエラーが発生(CloseWithError)すると、Readに失敗してerrが帰ってくる。なので、エラーが帰ってきたら処理を中断する。
Read側でエラーが発生してWriter側に通知したい場合はこちらのCloseWithErrorを使うことになるが、main threadをRead側にするのかWrite側にするのかで通知の方向が変わりそう。結論
io.Pipe
を使う事で、データを書きこみながらio.Readerに流すことができる- Write中に処理を中断する必要がある場合、
CloseWithError
でReaderに通知する- json lines形式を入出力に使えば、streamingな処理を書きやすい
- writerのCloseを忘れずに!(closeを忘れるとdeadlockになる)
goroutineを使うコードを書くのは抵抗があるが、こういうコードを書く場合逆にgoroutineを使ったほうが綺麗に書けるきもする。
golangと仲良くなりたいが、悩みはつきない・・・
- 投稿日:2020-01-23T17:53:15+09:00
【Golang】 mysql データベース処理
概要
本記事では、Golangにおけるdatabase/sqlライブラリを用いたMySQLデータベースに対する処理例を示す。
処理例では、ユーザーデータを仮定し、エラー処理についても例を示す。以下のようになテーブルを想定する。
CREATE TABLE user_db.users ( id INT NOT NULL AUTO_INCREMENT, first_name VARCHAR(45) NULL, last_name VARCHAR(45) NULL, email VARCHAR(45) NOT NULL, data_created DATETIME NULL DEFAULT CURRENT_TIMESTAMP, status TINYINT(1) NOT NULL, PRIMARY KEY (id), UNIQUE INDEX email_UNIQUE (email ASC));pkgのインストール
mysqlのドライバーpkgをインストールする。
go get github.com/go-sql-driver/mysql処理の流れ
実行する処理は、以下のプログラムの通りである。
1. ユーザ1データの挿入
2. ユーザーデータ取得ミス例(エラー処理を示すため)
3. ユーザ2データ挿入
4. Status1のユーザデータを探索、取得
5. ユーザーデータ取得(4で取得したがGETの例を示すため)
6. ユーザーデータの更新
7. ユーザーデータの削除main.gopackage main import ( "fmt" "log" "github.com/k-washi/golang-cookbook/database/user_db/method" ) func main() { //ユーザデータの挿入 user := &method.User{ID: 1, FirstName: "Trou", LastName: "Tanaka", Email: "123s456@test.com", Status: 1} if err := user.Save(); err != nil { log.Println(err) //2020/01/23 03:15:08 email already exist } log.Println(user) //2020/01/23 04:06:32 &{1 Trou Tanaka 123s456@test.com <nil>} //ユーザーデータの取得(IDミス) user = &method.User{ID: 3} if err := user.Get(); err != nil { log.Println(err) //2020/01/23 03:26:24 user not found } log.Println(user) //ユーザデータ挿入 user = &method.User{ID: 2, FirstName: "k", LastName: "washi", Email: "654321@test.com", Status: 1} if err := user.Save(); err != nil { log.Println(err) } //ユーザデータのStatus:1をFind res, err := user.FindByStatus(1) if err != nil { log.Println(err) } for i, u := range res { fmt.Println("user :", i, u) //user : 0 {37 Trou Tanaka 123s456@test.com 2020-01-23 05:07:35 +0000 UTC 1} //ユーザデータの取得(IDを使用するため、ループ内で処理) user = &method.User{ID: u.ID} if err := user.Get(); err != nil { log.Println(err) } log.Println(user) //2020/01/23 03:03:52 &{1 Trou Tanaka 123s456@test.com 2020-01-23 02:29:56 +0000 UTC} //ユーザデータ更新 user.FirstName = "Taro2" user.LastName = "Tanaka2" user.Email = "123s456@test.com" user.Status = 1 if err := user.Update(); err != nil { log.Println(err) } log.Println(user) //2020/01/23 03:54:51 &{1 Taro2 Tanaka2 123s456@test.com 2020-01-23 02:29:56 +0000 UTC} //ユーザデータの削除 user = &method.User{ID: u.ID} if err := user.Delete(); err != nil { log.Println(err) } log.Println("Success Delete") } }データベースの初期設定
sql.OpenでDBと接続を確立し、接続を確立している。
_ "github.com/go-sql-driver/mysql"をmysqlを扱うため、インポートしている。
設定は,user:password@tcp(host:port)/dbnameの形式の引数をとり、parseTime=trueは、時刻を扱うために設定している。Client, err := sql.Open()とせず、予め、Client,errを定義し、client, err = sql.Open()としている。前者では、もしClientを関数外で定義している場合、nilになるため。
また、コメントアウトしているが、実際は、環境変数を用いて、DBの設定を行うほうが良い。
user_db/user_db.gopackage user_db import ( "database/sql" "fmt" "log" _ "github.com/go-sql-driver/mysql" ) var ( //Client user_id database Client *sql.DB err error ) /* //実際は、環境変数を読み込んで、DBの設定を行う。 //export mysql_user_name="root" const ( mysql_user_naem: "mysql_user_name" ) var userName := os.Getenv(mysql_user_naem) */ func init() { //user:password@tcp(host:port)/dbname dataSourceName := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true", "root", "", "127.0.0.1:3306", "user_db", ) log.Println(fmt.Sprintf("about to connect to %s", dataSourceName)) //open db by mysql driver and data source name. //https://github.com/go-sql-driver/mysql/issues/150 (Issue) Client, err = sql.Open("mysql", dataSourceName) if err != nil { panic(err) } if err = Client.Ping(); err != nil { panic(err) } log.Println("database successfully configured") }データベース処理
GET, POSTなどのメソッドを定義している。
ここで定義しているUser 構造体が本記事で扱っているユーザデータである。const内には、ここで扱うSQLを定義している。エラーはparseErrorとして定義している。mysqlに関するエラーは個別に取り出せ、switcで場合できることを例で示している。1062は、Uniqueに対するエラーである。
GET, SELECTの際、*.Scanを実行する場合は、アドレスを渡す必要があることに注意する。
method/method.gopackage method import ( "fmt" "log" "strings" "time" "github.com/go-sql-driver/mysql" "github.com/k-washi/golang-cookbook/database/user_db/user_db" ) const ( queryInsertUser = "INSERT INTO users(first_name, last_name, email, status) VALUES(?, ?, ?, ?);" queryGetUser = "SELECT id, first_name, last_name, email, data_created, status FROM users WHERE id=?;" queryUpdateUser = "UPDATE users SET first_name=?, last_name=?, email=?, status=? WHERE id=?;" queryDeleteUser = "DELETE FROM users WHERE id=?;" querySelectUserByStatus = "SELECT id, first_name, last_name, email, data_created, status FROM users WHERE status=?;" errorNoRow = "no rows in result set" ) //User info type User struct { ID int64 FirstName string LastName string Email string Date *time.Time Status int } var ( //UserDB = make(map[int64]*User) ) func (user *User) Get() error { stmt, err := user_db.Client.Prepare(queryGetUser) if err != nil { return err } defer stmt.Close() result := stmt.QueryRow(user.ID) if getErr := result.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.Date, &user.Status); getErr != nil { return parseError(getErr) } return nil } func (user *User) Save() error { stmt, err := user_db.Client.Prepare(queryInsertUser) if err != nil { return err } defer stmt.Close() insertResult, saveErr := stmt.Exec(user.FirstName, user.LastName, user.Email, user.Status) if saveErr != nil { return parseError(saveErr) } userID, err := insertResult.LastInsertId() if err != nil { return err } user.ID = userID log.Println("Save user data") return nil } func (user *User) Update() error { stmt, err := user_db.Client.Prepare(queryUpdateUser) if err != nil { return err } defer stmt.Close() if _, updErr := stmt.Exec(user.FirstName, user.LastName, user.Email, user.Status, user.ID); updErr != nil { return parseError(updErr) } return nil } func (user *User) Delete() error { stmt, err := user_db.Client.Prepare(queryDeleteUser) if err != nil { return err } defer stmt.Close() if _, delErr := stmt.Exec(user.ID); delErr != nil { return parseError(delErr) } return nil } func (user *User) FindByStatus(status int) ([]User, error) { stmt, err := user_db.Client.Prepare(querySelectUserByStatus) if err != nil { return nil, err } defer stmt.Close() rows, err := stmt.Query(status) if err != nil { return nil, parseError(err) } defer rows.Close() res := make([]User, 0) for rows.Next() { var user User if err := rows.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.Date, &user.Status); err != nil { return nil, parseError(err) } res = append(res, user) } return res, nil } func parseError(err error) error { sqlErr, ok := err.(*mysql.MySQLError) if !ok { if strings.Contains(err.Error(), errorNoRow) { return fmt.Errorf("user not found") } return err } //以下,sql依存のエラー switch sqlErr.Number { case 1062: //email_UNIQUE error return fmt.Errorf("email already exist") } return err }まとめ
GolangによるMySQLデータベースに対する処理の例を示しました。
コードは、k-washi/golang-cookbook/database/user_db/を参考にしてみてください。
- 投稿日:2020-01-23T17:39:33+09:00
Go言語の似た概念を触る
はじめに
本題に入る前に、前提となることを記述しておきます。
目的
目的は以下です。
- Go言語を読んでいて、似た概念を読み間違えないようにするため
それだけです。
読者の対象
読者の対象は以下です。
- プログラミング初心者
- Go初心者
- 軽くGo言語を使っている人
裏を返せば、例えば以下のような方は対象ではありません。
- Go言語を体系的に理解したい方
- 似た表現の実体を比較したい方、メモリの使い方を理解したい方
参考
おことわり
- 少しずつ文を追加しています。
- 初心者目線で「これとこれも紛らわしいから説明してくれ!」というものがあれば、連絡ください。
本題
ArraysとSlices
A Tour of Goを読んでいて、ArraysとSlicesは似ているなーと思ったのでまとめました。
Arraysは固定長、Slicesは可変長
宣言する形からしてもわかるように、Arraysは固定長でSlicesは可変長です。
Arrays/Slices 宣言 意味 Arrays var a [10]int 10個のintからなる配列a Slices var b []int 個数は問わないスライスb 見分け方
[]の中に数字が入っているかどうかですね。
数字があればArrays、なければSlicesです。
- 投稿日:2020-01-23T12:35:21+09:00
golangにusingステートメントのようなものを作ってみた
この記事の内容は以下のバージョンで作りました。
> go version go version go1.13 windows/amd6きっかけ
- ちょっとしたgoのコードを書いているときに、「リソースの開放が面倒だなぁ」とおもった。
- .NETだと、usingステートメントですごくすっきりかけるから、似たようなものを作りたい。
通常のgoの場合
// 何らかのリソースを取得する resource, err := hogehoge.GetResource() // エラーがあるかどうか調べてちょめちょめする if err != nil { // ちょめちょめ } // 解放忘れがないように、deferを定義 defer resource.Close() // ようやく本来やりたい処理みたいな感じになる。本来やりたい処理を書き始めるまでいつまでかかるんだ・・・
という感じ今回作ったもの
type IDispose interface { open() error dispose() } func Using(disposable IDispose, fn func(resource interface{}) error) (err error) { if err = disposable.open(); err != nil { return err } defer disposable.dispose() if err = fn(disposable); err != nil { return err } return nil }IDisposeな構造体と関数を受け取り、Open/実行/Closeまでを自動的にやってくれるやつ。
使い方
IDisposeな構造体を用意
type DisposableStruct struct { hogeResouce *hoge.HogeResource } func (s *DisposableStruct) open() error { return nil } func (s *DisposableStruct) dispose() { // リソースを破棄する処理 s.hogeResouce.Close() }実行部
err := Using(&DisposableStruct{hogeResouce: nil}, func(r interface{}) error { // castする必要がある。イケてない部分1 hoge := r.(*DisposableStruct) // ここに本来やりたい処理を書く // 戻り値はエラーのみ。イケてない部分2 return nil })最後に
- ジェネリクスが使えれば、もっとかっこよくかけるんだけど・・・
- golangでこんな無駄なことしてんじゃねぇよみたいに言われるかもしれないけど、気にしない。
- もっといい感じの実装がある場合はコメント欄で指摘をお待ちしております!
- 実際にはステートメントではなくただの関数です(タイトル詐欺ですまんな)
- 投稿日:2020-01-23T12:04:22+09:00
【Go】int64型・uint64型からstring型へ変換する方法
int64型からstring型へ変換する方法
// int64 var Int64 int64 = 9223372036854775807 // strconv.FormatInt(int64, 基数(10進数)) convertedInt64 := strconv.FormatInt(Int64, 10)
9223372036854775807
はint64
の最大数ですhttps://golang.org/pkg/builtin/#int64
int64 is the set of all signed 64-bit integers. Range: -9223372036854775808 through 9223372036854775807.
uint64型からstring型へ変換する方法
// uint64 var Uint64 uint64 = 18446744073709551615 // strconv.FormatUint(uint64, 基数(10進数)) convertedUint64 := strconv.FormatUint(Uint64, 10)
18446744073709551615
はuint64の最大数ですhttps://golang.org/pkg/builtin/#uint64
uint64 is the set of all unsigned 64-bit integers. Range: 0 through 18446744073709551615.
Go Playground
https://play.golang.org/p/mfNxKZS0xAM
package main import ( "strconv" "fmt" "reflect" ) func main() { var Int64 int64 = 9223372036854775807 convertedInt64 := strconv.FormatInt(Int64,10) fmt.Printf("%v with type %s \n", convertedInt64, reflect.TypeOf(convertedInt64)) var Uint64 uint64 = 18446744073709551615 convertedUint64 := strconv.FormatUint(Uint64,10) fmt.Printf("%v with type %s \n", convertedUint64, reflect.TypeOf(convertedUint64)) }実行結果
9223372036854775807 with type string 18446744073709551615 with type string【おまけ】string型からint64・uint64型へ変換からの再びstring型へ変換
https://play.golang.org/p/tjlUnDzYgKY
package main import ( "strconv" "fmt" "reflect" ) func main() { fmt.Println("\n----- convert String to Int64 and Uint64 -------------------") // int64 var strInt64 string = "9223372036854775807" convertedStrInt64, _ := strconv.ParseInt(strInt64, 10, 64) fmt.Printf("%v with type %s \n", convertedStrInt64, reflect.TypeOf(convertedStrInt64)) // uint64 var strUint64 string = "18446744073709551615" convertedStrUint64, _ := strconv.ParseUint(strUint64, 10, 64) fmt.Printf("%v with type %s \n", convertedStrUint64, reflect.TypeOf(convertedStrUint64)) fmt.Println("\n----- convert Int64 and Uint64 to String -------------------") convertedInt64 := strconv.FormatInt(convertedStrInt64,10) fmt.Printf("%v with type %s \n", convertedInt64, reflect.TypeOf(convertedInt64)) convertedUint64 := strconv.FormatUint(convertedStrUint64,10) fmt.Printf("%v with type %s \n", convertedUint64, reflect.TypeOf(convertedUint64)) }実行結果
----- convert String to Int64 and Uint64 ------------------- 9223372036854775807 with type int64 18446744073709551615 with type uint64 ----- convert Int64 and Uint64 to String ------------------- 9223372036854775807 with type string 18446744073709551615 with type string
- 投稿日:2020-01-23T10:37:48+09:00
goss を使ったサーバ構築のテスト
はじめに
以前 InSpec でデプロイを自動テスト化する という記事で InSpec というテストフレームワークの紹介をしましたが、最近 goss という Go 言語製のテストフレームワークを使い始めたのでそのご紹介です。
goss とは
goss は YAML でテストケースを記述し、その内容に従ってテストを実行できるフレームワークです。
特徴
- シングルバイナリなのでライブラリのインストールなどが不要
- 記述が YAML なので覚えることが少ない
- 速い
goss でできるテストの例
goss を使えば下記のようなことがテストできるようになります。
- ファイルが存在するか
- ファイル属性は正しいか
- プロセスやサービスは起動しているか
- パッケージはインストールされているか
- HTTP レスポンスのヘッダやボディは正しいか
インストール
開発用途の環境に入れるのであれば下記でインストールできます。
$ curl -fsSL https://goss.rocks/install | sh本番用途の環境であればこのインストール方法は推奨されてないので、代わりに下記のようにインストールします。
$ curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.9/goss-linux-amd64 -o /usr/local/bin/goss $ chmod +x /usr/local/bin/goss使い方
試しに Apache が正しく動いているかどうかをテストしてみたいと思います。
goss.yaml
という名前で下記のようにファイルを作成します。動作環境
- CentOS 7
- Apache 2.4.6
goss.yamlpackage: httpd: title: httpd 2.4.6 がインストールされていること installed: true versions: - 2.4.6 file: /etc/httpd/conf/httpd.conf: title: 設定ファイルが存在すること exists: true service: httpd: title: httpd がサービスに登録されていること enabled: true running: true process: httpd: title: httpd が起動していること running: true port: tcp:80: title: 80 ポートをリッスンしていること listening: true http: http://localhost: title: 疎通できること status: 200テストをするには
goss.yaml
を保存した場所で下記のコマンドを実行します。$ goss validate
もしテストに失敗すれば下記のようなエラーメッセージが出力されます。
$ goss validate F..F..F. Failures/Skipped: Title: 80 ポートをリッスンしていること Port: tcp:80: listening: Expected <bool>: false to equal <bool>: true Title: 疎通できること HTTP: http://localhost: status: Expected <int>: 403 to equal <int>: 200 Title: httpd がサービスに登録されていること Service: httpd: enabled: Expected <bool>: false to equal <bool>: true Total Duration: 0.045s Count: 8, Failed: 3, Skipped: 0テストレポートは JSON や JUnit 形式で出力することもできます。レポート形式を指定するには
-f
を使用します。$ goss validate -f junitgoss を使ったヘルスチェック
Serverspec や InSpec にはない機能として、goss をサーバとして起動しておき、テストのレポートを HTTP で受け取る機能が便利でした。
下記コマンドを実行すると goss サーバがポート 8080 で起動します。
$ goss serve -f junitcurl やブラウザから
http://<host>:8080/healthz
にアクセスするとテストレポートを取得できます。$ curl http://localhost:8080/healthz
テストレポートを JUnit 形式で取得できるようにしておき、これを CI で定期実行させると単体テストを CI で実行する感覚でヘルスチェックが行えるようになります。
注意点
Serverspec や InSpec はリモートサーバを対象にテストを実行できますが goss にはリモート実行の機能は実装されていません。これは goss の設計をシンプルに保つことと簡単なシェルを書けば goss ファイルの転送と実行はできるからという作者の意向によるものです。(参考:ssh and exec support)
ちなみにコンテナを対象に goss を実行する場合は dgoss という goss ラッパが使用できますが、これは単にコンテナに goss ファイルを送るだけのシンプルなシェルスクリプトとして実装されています。
リモート実行をしたい場合は dgoss と同様のスクリプトを書いて運用するのが良いと思います。ただ個人的には HTTP でテスト結果を受け取れる機能が使いやすいので、テスト対象のサーバであらかじめ goss サーバを起動させて使用しています。
さいごに
Serverspec や InSpec と比べて実行速度が圧倒的に速いことと YAML でテストを記述できるという点が goss の魅力ではないかと思いますので是非お試しください。
- 投稿日:2020-01-23T09:50:34+09:00
フォルダごとに分かれている画像を一箇所に集める
最近業務で画像のデータを使って〜する場面があったのですが、サーバー上にある画像がフォルダごとに分かれてしまっていたり、階層もばらばらだったりと不便だったので、ちょっとした効率化ツールを作ってみました。
使っているのはGo言語です。
ソースコード
package main import ( "image" "image/jpeg" "image/png" "log" "os" "path/filepath" ) const imgQt int = 60 func main() { // 実行ファイル直下のパスを取得 path, _ := os.Getwd() dataPath := filepath.Join(path, "data") dataAllPath := filepath.Join(dataPath, "*") list, _ := filepath.Glob(dataAllPath) var textList []string // 画像を1枚づつ読み込む for _, item := range list { textList = append(textList, readItem(item)...) } for _, text := range textList { exc(text) } } func exc(text string) { path, _ := os.Getwd() outputPath := filepath.Join(path, "result") println(text) // 画像を読み込む img := input(text) if img == nil { return } out := filepath.Join(outputPath, filepath.Base(text)) output(img, out, "jpg") } func readItem(item string) []string { fInfo, _ := os.Stat(item) var text []string if fInfo.IsDir() { itemList := filepath.Join(item, "*") list, _ := filepath.Glob(itemList) for _, l := range list { // 再帰処理 text = append(text, readItem(l)...) } } else { text = append(text, item) } return text } // 画像を読み込む処理 func input (filePath string) image.Image { // 画像を読み込む file, err := os.Open(filePath) if err != nil { println(err) return nil } // 変換 img, _, err := image.Decode(file) return img } func output (outputImage image.Image, filePath string, format string) { dst, err := os.Create(filePath) if err != nil { log.Fatal(err) } switch format { case "png": // PNGの場合 err = png.Encode(dst, outputImage) if err != nil { log.Fatal(err) } break case "jpg": // JPGの場合 qt := jpeg.Options{ Quality:imgQt, } err = jpeg.Encode(dst, outputImage, &qt) if err != nil { log.Fatal(err) } break case "gif": // GIFの場合 log.Println("can't output gif") break default: // 標準で対応していないフォーマットの場合 println("no file") break } }実行ファイルと同じ階層にdataフォルダ、resultフォルダを作っています。
dataフォルダ直下にはフォルダ分かれ、階層分かれした画像データが入っているフォルダを丸々入れてもらえれば、resultフォルダに画像が出力されると思います。こういうちょっとしたツールを作りやすいのがGo言語の良いところですね。
- 投稿日:2020-01-23T01:38:17+09:00
今更ながらGo言語を触ってみた(GO 1.13.6)
Go言語を触りたかったきっかけ
kubernetesを構築・活用しているうちにカスタマイズしてみたい衝動があり、
Go言語でのみカスタマイズが出来ることと
軽量のWebApiを構築することが多々あり普段はPythonで実装していたが、
この際なのでGo言語を触ってみることにしました。
と言っても実務があるので、学習期間は3日しかありませんでしたが・・・。で、作ってみたもの
簡単にwebサーバーを立ち上げれるフレームワークを実装してみました。
内容的には下記のリンク先のREADMEにも記載していますが、
yamlファイルに諸々設定することでwebサーバーが起動します。
ビジネスロジックに集中出来るようにこのフレームワークを実装しています。躓いた点
map[string]interface{}
の仕組みがいまいちわかりづらかったです。
map[string]interface{}
からvalueを取得する際に型が思っている型にならなかったりしていたので、
reflect
モジュールを使用して型を確認したりして実装しました。demo.go// yamlファイルから読み込んでますがエラー処理は省いています。 var m map[string]interface{} buf , err := ioutil.ReadFile("filename.yaml") yaml.Unmarshal(buf, &m) // valueの内容を取得したい為にValueOfで取得する。 v := reflect.ValueOf(m) // 型の種類をKindメソッドで取得する。 k := v.Kind() fmt.Print(k)また、interface{}型については全ての型を受け入れれるのですが、
map[string]interface{}
の値がstring
の時でも
cast時にmap[string]interface{}
でcast出来ず、map[interface{}]interface{}
でcastしなければなりませんでした。
この辺りがまだあやふやなので実務が落ち着いた時にでももう少し調査しようと思います。test.yamltest1: value1: value2m変数にmap[string]interface{}が設定されているとして以下を実行すると
demo.golog.Print(m["test1"].(map[string]interface{}))
panic: interface conversion: interface {} is map[interface {}]interface {}, not map[string]interface {}
というエラーが発生し、エラーの内容の通り
map[string]interface{}
をmap[interface{}]interface{}
に変換出来ないというエラーが出力されました。
エラーの通りにmap[interface{}]interface{}
でcastを行い、
keyを文字列で指定するとvalueを取得することが出来ます。sample.gobuf, _ := ioutil.ReadFile("test.yml") var m map[string]interface{} yaml.Unmarshal(buf, &m) m1 := m["test1"].(map[interface{}]interface{}) // 念の為stringへcast log.Print(m1["value1"].(string))あとは、型アサーションぐらいですかね。
上記でも記載していますが、
interface{}
型から別の型へcast(代入)する際の記述方法が
.(T)
の記載方法で型変換(代入)が可能になります。まとめ
オブジェクト指向言語を扱うことが多いので、Classが存在せず、
struct
interface
でメソッドを記述したり、属性を与えることで
オブジェクト指向の様な記述をする必要があります。
パッケージ公開でも少し躓いてしまいましたのでまた手が空いたら記事にしようと思います。