20200123のGoに関する記事は12件です。

Web系企業でプログラマを目指す皆様へ

少数受託開発ベンチャーでアルバイトをして感じたこと

初めに結論から述べます

結論

メリットもあればデメリットもあり
万人にオススメは出来ないということです

会社の特徴

所属している企業の特徴を説明します。
・社員数20名以下
・受託開発
・Webアプリケーション作成
・ベンチャー企業
・所在地は大阪

未経験からWeb系企業へ転職を考えている人ならば
魅力的な企業のように見えるはずです

確かに、下記のメリットはあります

メリット

・モダンな技術に触れることができる
フロントエンドはReact、React native、Typescript
バックエンドはGolang
DBはMySQL、GraphQL
インフラはAWS

・服装自由、音楽も聴ける
服装は最低限の清潔感があれば良いみたいです。

イヤホンをつけて仕事をしている人もいれば、
お菓子を食べたり、コーヒーを飲みながら仕事をしている人がいます。

・人が少ないので挑戦しやすい
研修を2日終えて、現場に入りそうな予感です。

・未経験でも潜り込める
人が足りていません。
とにかく未経験を現場で鍛え上げて戦力にしようという風潮です。

但し、メリットだけではありません。

デメリット

・社長と距離が近い故に、地雷を踏む可能性がある
社長の性格と合わなければしんどいです。
入社しないと分からないものですけどね。

・実力主義
仕事ができなければ罵声が飛ぶ現場です。

・研修が充実していない
研修でゆっくり教える時間も人もありません。
自分で力をつけていく必要があります。

以上のメリットとデメリットを踏まえて
オススメできる人、できない人の特徴を考えてみました。

オススメできる人

・自走力がある人
研修が十分でないので、自ら知識を吸収する姿勢が求められます。

・短期間で成長したい人
開発の経験に関わる可能性が高いので、次のステップアップを考えている人にとっては最適でしょう。

オススメできない人

・ゆっくり成長したい人
まったりしたい人には絶対にオススメできません。

・研修が必要な人
しっかりと知識をつけてから仕事につきたいと考えているならば
スクールに通ったり、何ヶ月も研修がある会社にいくべきです。

・会社でコミュニティを築きたい人
コミュニティを築きたいならば社員の多い会社にいく
もしくは
社外の勉強会などで繋がりを作るなど
自分で行動を起こしましょう。

・安定志向の人
人数が少ないということは資金的にも人的にも厳しいです。
つまり、零細企業です。
将来の保証は一切ないに等しいでしょう。
なので、スキルを身につけることが必要です。

アルバイトで働き始めて2日経って感じたことをまとめました。

全ての少数受託開発の企業がこうだとは思いません。
一概に上記の条件が全て正しい訳ではないが、
Web系企業を目指す人に知っておいて欲しいです。

SESやSIerなどでエンジニアを始めるのも悪くはないと考えます。

随時、更新していきます

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

Web系企業で働くことを目指す皆様へ

少数受託開発ベンチャーでアルバイトをして感じたこと

初めに結論から述べます

結論

メリットもあればデメリットもあり
万人にオススメは出来ないということです

会社の特徴

所属している企業の特徴を説明します。
・社員数20名以下
・受託開発
・Webアプリケーション作成
・ベンチャー企業
・所在地は大阪

未経験からWeb系企業へ転職を考えている人ならば
魅力的な企業のように見えるはずです

確かに、下記のメリットはあります

メリット

・モダンな技術に触れることができる
フロントエンドはReact、React native、Typescript
バックエンドはGolang
DBはMySQL、GraphQL
インフラはAWS

・服装自由、音楽も聴ける
服装は最低限の清潔感があれば良いみたいです。

イヤホンをつけて仕事をしている人もいれば、
お菓子を食べたり、コーヒーを飲みながら仕事をしている人がいます。

・人が少ないので挑戦しやすい
研修を2日終えて、現場に入りそうな予感です。

・未経験でも潜り込める
人が足りていません。
とにかく未経験を現場で鍛え上げて戦力にしようという風潮です。

但し、メリットだけではありません。

デメリット

・社長と距離が近い故に、地雷を踏む可能性がある
社長の性格と合わなければしんどいです。
入社しないと分からないものですけどね。

・実力主義
仕事ができなければ罵声が飛ぶ現場です。

・研修が充実していない
研修でゆっくり教える時間も人もありません。
自分で力をつけていく必要があります。

以上のメリットとデメリットを踏まえて
オススメできる人、できない人の特徴を考えてみました。

オススメできる人

・自走力がある人
研修が十分でないので、自ら知識を吸収する姿勢が求められます。

・短期間で成長したい人
開発の経験に関わる可能性が高いので、次のステップアップを考えている人にとっては最適でしょう。

オススメできない人

・ゆっくり成長したい人
まったりしたい人には絶対にオススメできません。

・研修が必要な人
しっかりと知識をつけてから仕事につきたいと考えているならば
スクールに通ったり、何ヶ月も研修がある会社にいくべきです。

・会社でコミュニティを築きたい人
コミュニティを築きたいならば社員の多い会社にいく
もしくは
社外の勉強会などで繋がりを作るなど
自分で行動を起こしましょう。

・安定志向の人
人数が少ないということは資金的にも人的にも厳しいです。
つまり、零細企業です。
将来の保証は一切ないに等しいでしょう。
なので、スキルを身につけることが必要です。

アルバイトで働き始めて2日経って感じたことをまとめました。

全ての少数受託開発の企業がこうだとは思いません。
一概に上記の条件が全て正しい訳ではないが、
Web系企業を目指す人に知っておいて欲しいです。

SESやSIerなどでエンジニアを始めるのも悪くはないと考えます。

随時、更新していきます

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

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, "/")
}

StatusMovedPermanetlyStatusFoundに変更してやれば大丈夫です。

これで僕は半日潰れました

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

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, "/")
}

StatusMovedPermanetlyStatusFoundに変更してやれば大丈夫です。

これで僕は半日潰れました

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

[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側にするのかで通知の方向が変わりそう。

結論

  1. io.Pipeを使う事で、データを書きこみながらio.Readerに流すことができる
  2. Write中に処理を中断する必要がある場合、CloseWithErrorでReaderに通知する
  3. json lines形式を入出力に使えば、streamingな処理を書きやすい
  4. writerのCloseを忘れずに!(closeを忘れるとdeadlockになる)

goroutineを使うコードを書くのは抵抗があるが、こういうコードを書く場合逆にgoroutineを使ったほうが綺麗に書けるきもする。
golangと仲良くなりたいが、悩みはつきない・・・

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

【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.go
package 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.go
package 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.go
package 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/を参考にしてみてください。

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

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です。

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

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でこんな無駄なことしてんじゃねぇよみたいに言われるかもしれないけど、気にしない。
  • もっといい感じの実装がある場合はコメント欄で指摘をお待ちしております!
  • 実際にはステートメントではなくただの関数です(タイトル詐欺ですまんな)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】int64型・uint64型からstring型へ変換する方法

int64型からstring型へ変換する方法

// int64
var Int64 int64 = 9223372036854775807

// strconv.FormatInt(int64, 基数(10進数))
convertedInt64 := strconv.FormatInt(Int64, 10)

9223372036854775807int64の最大数です

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 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.yaml
package:
  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 junit

goss を使ったヘルスチェック

Serverspec や InSpec にはない機能として、goss をサーバとして起動しておき、テストのレポートを HTTP で受け取る機能が便利でした。

下記コマンドを実行すると goss サーバがポート 8080 で起動します。

$ goss serve -f junit

curl やブラウザから 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 の魅力ではないかと思いますので是非お試しください。

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

フォルダごとに分かれている画像を一箇所に集める

最近業務で画像のデータを使って〜する場面があったのですが、サーバー上にある画像がフォルダごとに分かれてしまっていたり、階層もばらばらだったりと不便だったので、ちょっとした効率化ツールを作ってみました。

使っているのは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言語の良いところですね。

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

今更ながらGo言語を触ってみた(GO 1.13.6)

Go言語を触りたかったきっかけ

kubernetesを構築・活用しているうちにカスタマイズしてみたい衝動があり、
Go言語でのみカスタマイズが出来ることと
軽量のWebApiを構築することが多々あり普段はPythonで実装していたが、
この際なのでGo言語を触ってみることにしました。
と言っても実務があるので、学習期間は3日しかありませんでしたが・・・。

で、作ってみたもの

簡単にwebサーバーを立ち上げれるフレームワークを実装してみました。
内容的には下記のリンク先のREADMEにも記載していますが、
yamlファイルに諸々設定することでwebサーバーが起動します。
ビジネスロジックに集中出来るようにこのフレームワークを実装しています。

httprouter_wrapper

躓いた点

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.yaml
test1:
  value1: value2

m変数にmap[string]interface{}が設定されているとして以下を実行すると

demo.go
log.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.go
buf, _ := 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でメソッドを記述したり、属性を与えることで
オブジェクト指向の様な記述をする必要があります。
パッケージ公開でも少し躓いてしまいましたのでまた手が空いたら記事にしようと思います。

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