- 投稿日:2020-06-02T22:59:33+09:00
【Go】コンテナを理解するためにコンテナを自分で”造る”【初心者】
コンテナを理解するためにコンテナを自分で"造る"
今回はこちらを参考にして自作コンテナを作ってみる。
コンテナとは
詳しくは他の方の説明を受けてください。
Goでコンテナを"造る"
環境
今回は
VMware
でUbuntu
を稼働させ、VM
上で造りました。Ubuntu 20.04 LTS 64-bit go version go1.13.8 linux/amd64まずはコンテナのルートディレクトリとなるFile Systemを作る
/home/rootfs
ディレクトリ下に今回のルートディレクトリとなるFile System(fs)
を作成します。/home/rootfs/ ├── bin ├── lib ├── lib32 ├── lib64 ├── proc \\空のディレクトリ └── sbin
bin
,lib
,lib32
,lib64
,sbin
ディレクトリは/usr
ディレクトリ下と同じものをコピーしました。
proc
ディレクトリは空の状態で作成しておきます。※今回はただコンテナとして動くことだけを目的としているので最小の
fs
にしています。コンテナを稼働させる
src
【Go】
src
は次のようにしました。package main import ( "fmt" "os" "os/exec" "syscall" ) func main() { switch os.Args[1] { case "run": run() case "child": child() default: panic("what??") } } func run() { cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID, } must(cmd.Run()) } func child() { fmt.Printf("running %v as PID %d\n", os.Args[2:], os.Getpid()) cmd := exec.Command(os.Args[2], os.Args[3:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr must(syscall.Chroot("/home/rootfs")) must(os.Chdir("/")) must(syscall.Mount("proc", "proc", "proc", 0, "")) must(cmd.Run()) } func must(err error) { if err != nil { panic(err) } }実際にコンテナを起動してみる
まずはホストの
fs
とProcess
の確認
Terminal
でホストのfs
を確認しましょう。~$ cd / ~$ ls bin dev lib libx32 mnt root snap sys var boot etc lib32 lost+found opt run srv tmp cdrom home lib64 media proc sbin swapfile usr次に、ホストの
Process
を確認~$ ps PID TTY TIME CMD 1927 pts/0 00:00:00 bash 2950 pts/0 00:00:00 ps
PID(Process ID)
が大きい値であるのが確認できます。コンテナを起動
満を持してコンテナを起動しましょう。
先ほどの
src
があるディレクトリに移動し、Terminal
で次のようにコマンドを入力します。~:/home/{username}/{directoryname}$ sudo go run main.go run /bin/bash running [/bin/bash] as PID 1 bash-5.0#おめでとうございます!
コンテナが起動しました!
コンテナがコンテナであるか確認する
ホストの時と同じように
fs
とProcess
を確認してみましょう。bash-5.0# cd / bash-5.0# ls bin lib lib32 lib64 proc sbinコンテナ内で
/
ディレクトリに移動してもホストの/home/rootfs
ディレクトリに移動しますね。bash-5.0# ps PID TTY TIME CMD 1 ? 00:00:00 exe 6 ? 00:00:00 bash 8 ? 00:00:00 ps
PID
ちゃんと小さい値です。ついでに
history
も確認しておきましょう。bash-5.0# history 1 cd / 2 ls 3 ps 4 history
history
もコンテナ内の記録しか表示されません。最後にコンテナを停止するには
コンテナ内で
exit
と入力すればOKbash-5.0# exit ~:/home/{username}/{directoryname}$
- 投稿日:2020-06-02T21:19:10+09:00
マイクロサービスでも使えそうなエラー処理を考えてみた
元ネタ
考えてみた
といっても全くのオリジナルではなくて、GoogleさんのDeveloper PageのError Handlingみてたらこれ応用したら汎用的に使えそうだなという感じでつくってみました。元ネタは以下のようなものです。
Pattern.1 Cloud API
ref. https://cloud.google.com/apis/design/errors
jsonだとこのようになります。
codeにHTTP Status
statusにgRPCのステータス
messageにエラーの内容
詳細は配列で複数記載できる様になっています{ "error": { "code": 401, "message": "Request had invalid credentials.", "status": "UNAUTHENTICATED", "details": [{ "@type": "type.googleapis.com/google.rpc.RetryInfo", ... }] } }Protobuf だとこのように定義されてました
// The error schema for Google REST APIs. NOTE: this schema is not used for // other wire protocols. message Error { // This message has the same semantics as `google.rpc.Status`. It has an extra // field `status` for backward compatibility with Google API Client Library. message Status { // This corresponds to `google.rpc.Status.code`. int32 code = 1; // This corresponds to `google.rpc.Status.message`. string message = 2; // This is the enum version for `google.rpc.Status.code`. google.rpc.Code status = 4; // This corresponds to `google.rpc.Status.details`. repeated google.protobuf.Any details = 5; } // The actual error payload. The nested message structure is for backward // compatibility with Google API client libraries. It also makes the error // more readable to developers. Status error = 1; }Pattern.2 Calender API
ref. https://developers.google.com/calendar/v3/errors
多少の際はあるものの、考え方は同じなようです。
レスポンスがJSONになる前提なためかstatus
はありません。{ "error": { "errors": [ { "domain": "calendar", "reason": "fullSyncRequired", "message": "Sync token is no longer valid, a full sync is required.", "locationType": "parameter", "location": "syncToken", } ], "code": 410, "message": "Sync token is no longer valid, a full sync is required." } }コンセプト
jsonのレスポンス
基本形(単一サービスの場合)
元ネタの形とほとんど同じになります。 errorsの配列の中身と、他のエラー内容はほぼ一致してます。
{ "error": { "errors": [ { "domain": "サービスの識別名", "reason": "エラー原因の区分", "message": "エラーの説明" } ], "code": "HTTPステータス", "status": "gRPCのステータス", "message": "エラーの説明" } }複数のサービスを経由した場合
(ユーザのリクエスト) <-> サービスA <-> サービスB <-> サービスC という経路でリクエストがあった場合に、サービスBでエラーが発生したためにエラーを返すケースを考えてみます。
code、status、messageはほぼ同じですが、
errrors
が異なります。
errors
はstack traceのような扱いで、サービスをまたいでエラーを追いかけられるようにしてみました。{ "error": { "errors": [ { "domain": "サービスAの識別名", "reason": "サービスAのエラー原因の区分", "message": "サービスAのエラーの説明(サービスBでエラーが発生したためにサービスAで発生したエラー)" }, { "domain": "サービスBの識別名", "reason": "サービスBのエラー原因の区分", "message": "サービスBのエラーの説明" } ], "code": "最終的にユーザに返却するHTTPステータス", "status": "最終的にユーザに返却するgRPCのステータス", "message": "サービスAのエラーの説明" } }エラーレスポンスをエラーとして扱う
上記のエラーレスポンスの受け渡しだけだと、このレスポンスを格納した変数やstructureをどこかに保存しておいて、受け渡しのたびにこれを使うことになります。
Goなどの言語ではエラー用のメソッドをはやしてあげることで、エラー扱いができます。
これによってエラーレスポンスをエラーとして引き回して最終的にJSONレスポンスとしてユーザに返却するだけで済みます。実装
共通処理
Struct
メイン部分の構造体はこのようになります。
jsonレスポンスの一番外にあるerror
は普段あると不便なので、外しています。
こちらに関してはjsonのところで説明します。ポイントとしては
Cause.message
が、error型になっていることです。
ただのstringだと文字列で書かれている情報のみになってしまいますが、errorだと文字列以外の情報(エラーの発生場所など)も持てるのでこちらのほうが利点があります。それに対して
Detail.Message
は通常のstringです。
これは、サービス感で情報の受け渡しがあったときに、ここをerrorにしていたとしても文字列以外の情報はjsonレスポンスにした際に欠損してしまうので、errorにしていたところであまり利点がなく、逆に扱いづらくなってしまうためです。// Cause ... type Cause struct { code int status string message error details []Detail } // Detail ... type Detail struct { Domain string Reason string Message string }エラー処理
先程の構造体をエラーとして取り扱えるようにしてあげます。
Goの場合はダックタイピングでfunc Error() string
をはやしてあげるとエラー扱いになるので、それを用いてエラーにします。
Go 1.13からfmt.Errorf
で%w
を使うことでエラーをWrapできるようになったのでこの機能を用いてエラー原因とメッセージを結合して出力してあげるようにしています// Error return error message func (c *Cause) Error() string { reason := "" if len(c.details) > 0 { reason = c.details[0].Reason } return fmt.Errorf("%s : %w", reason, c.message).Error() }ただそれだけだと扱いづらいので、Goの1.13から標準実装されたUnwrapも使用してmessageをそのまま出力してあげるものも作りました
// Unwrap implements errors.Unwrap method func (c *Cause) Unwrap() error { return c.message }json
jsonレスポンスの受け渡しの説明です。
まずはjsonレスポンス用の構造体を作ってあげます。
外枠をjsonResponse
で定義して、Causeに当たる部分をjsonResponseElement
Causeをそのまま使っていないのは、errorとかjsonで扱いづらいところがあるためで、jsonの受け渡し専用なので、扱いやすいように型などを一部変更しています。
最初Marshal
/Unmarshal
内で無名Structとして作っていたのですが、扱いづらかったので、名前をつけてあげました。これで、jsonに変換したときに、コンセプトのときの形ができあがります。
// jsonResponse ... type jsonResponse struct { Error jsonResponseElement `json:"error"` } // jsonResponseElement type jsonResponseElement struct { Code int `json:"code"` Status string `json:"status"` Message string `json:"message"` Details []Detail `json:"details"` }Marshalerの処理はとてもかんたんです。
Causeの内容をjsonResonseに置き換えてからjson.Marshalをかけてあげるだけです。// MarshalJSON implements the json.Marshaler interface func (c *Cause) MarshalJSON() ([]byte, error) { return json.Marshal(jsonResponse{ Error: jsonResponseElement{ Code: c.code, Status: c.status, Message: c.message.Error(), Details: c.details, }, }) }Unmarshalerはまず、レスポンスを格納した、jsonResponseを
*Cause
に変換してあげます。
その後、Appendでエラーをくっつけてあげます。
Appendは*Cause.details
を参照して、呼び出し元のdetails
にappendするように作っています。// UnmarshalJSON implements the json.Unmarshaler interface func (c *Cause) UnmarshalJSON(b []byte) (err error) { var je jsonResponse if err = json.Unmarshal(b, &je); err != nil { return } ce := &Cause{ code: je.Error.Code, status: je.Error.Status, message: errors.New(je.Error.Message), details: je.Error.Details, } c.Append(ce) return nil }一般的にAppendはjsonレスポンス経由で使われますが、手動でエラーをくっつけてあげることもできます。
その場合、エラーは必ずしも*Cause
であるとは限りません。
なので、その場合は、デフォルトでBackend Error (Internal Server Error)にerrorを格納したDetailを生成してからdetailsにappendするようにしました。この場合、引数となるerrorはdomainを知りません。
そのため、ServiceDomainというグローバル変数を用意しています。
これによってDomainを補完して設定することができます。// Append one or more elements onto the end of details func (c *Cause) Append(e error) { if c == nil || c.IsZero() { c.set(e) return } if v, ok := e.(*Cause); ok { c.details = append(c.details, v.details...) return } c.details = append(c.details, Detail{ Domain: ServiceDomain, Reason: StatusBackendError, Message: e.Error(), }) }Causeエラーの生成
Causeエラーを新規作成する処理を考えてみます。
func New(err error, domain string, code int, status string, reason string) error { return &Cause{ code: code, status: status, message: err, details: []Detail{ { Domain: domain, Reason: reason, Message: err.Error(), }, }, } }これだと、生成はできるものの引数が多いので扱いづらいです。
code
、status
、reason
はグルーピングできそうなので、グルーピングするstructを作ってあげました。
外部のパッケージでカスタマイズできるようにpublicにしています。// ErrCase ... type ErrCase struct { Code int Status string Reason string } // NewCause ... func NewCause(err error, domain string, c ErrCase) error { return NewCauseWithStatus(err, domain, c.Code, c.Status, c.Reason) } // NewCauseWithStatus ... func NewCauseWithStatus(err error, domain string, code int, status string, reason string) error { return &Cause{ code: code, status: status, message: err, details: []Detail{ { Domain: domain, Reason: reason, Message: err.Error(), }, }, } }全体像
package errors import ( "encoding/json" "errors" "fmt" "net/http" ) // Status const ( // ServiceDomainGlobal ... ServiceDomainGlobal = "global" // Status // StatusUnauthenticated ... StatusUnauthenticated = "UNAUTHENTICATED" // StatusPermissionDenied ... StatusPermissionDenied = "PERMISSION_DENIED" // StatusNotFound ... StatusNotFound = "NOT_FOUND" // StatusAborted ... StatusAborted = "ABORTED" // StatusAlreadyExists ... StatusAlreadyExists = "ALREADY_EXISTS" // StatusResourceExhausted ... StatusResourceExhausted = "RESOURCE_EXHAUSTED" // StatusUnavailable ... StatusUnavailable = "UNAVAILABLE" // StatusBackendError ... StatusBackendError = "INTERNAL" // Reason // ReasonUnauthenticated ... ReasonUnauthenticated = "unauthenticated" // ReasonPermissionDenied ... ReasonPermissionDenied = "permissionDenied" // ReasonNotFound ... ReasonNotFound = "notFound" // ReasonAborted ... ReasonAborted = "abourtedRequest" // ReasonAlreadyExists ... ReasonAlreadyExists = "alreadyExists" // ReasonResourceExhausted ... ReasonResourceExhausted = "userRateLimitExceeded" // ReasonUnavailable ... ReasonUnavailable = "unavailable" // ReasonBackendError ... ReasonBackendError = "backendError" ) var ( // ServiceDomain is default service domain name ServiceDomain = ServiceDomainGlobal // CaseUnauthenticated ... CaseUnauthenticated = ErrCase{ Code: http.StatusUnauthorized, Status: StatusUnauthenticated, Reason: ReasonUnauthenticated, } // CasePermissionDenied ... CasePermissionDenied = ErrCase{ Code: http.StatusForbidden, Status: StatusPermissionDenied, Reason: ReasonPermissionDenied, } // CaseNotFound ... CaseNotFound = ErrCase{ Code: http.StatusNotFound, Status: StatusNotFound, Reason: ReasonNotFound, } // CaseAborted ... CaseAborted = ErrCase{ Code: http.StatusConflict, Status: StatusAborted, Reason: ReasonAborted, } // CaseAlreadyExists ... CaseAlreadyExists = ErrCase{ Code: http.StatusConflict, Status: StatusBackendError, Reason: ReasonBackendError, } // CaseResourceExhausted ... CaseResourceExhausted = ErrCase{ Code: http.StatusTooManyRequests, Status: StatusResourceExhausted, Reason: ReasonResourceExhausted, } // CaseUnavailable ... CaseUnavailable = ErrCase{ Code: http.StatusServiceUnavailable, Status: StatusUnavailable, Reason: ReasonUnavailable, } // CaseBackendError ... CaseBackendError = ErrCase{ Code: http.StatusInternalServerError, Status: StatusBackendError, Reason: ReasonBackendError, } ) // Cause ... type Cause struct { code int status string message error details []Detail } // Detail ... type Detail struct { Domain string Reason string Message string } // ErrCase ... type ErrCase struct { Code int Status string Reason string } // jsonResponse ... type jsonResponse struct { Error jsonResponseElement `json:"error"` } // jsonResponseElement type jsonResponseElement struct { Code int `json:"code"` Status string `json:"status"` Message string `json:"message"` Details []Detail `json:"details"` } // NewCause ... func NewCause(err error, domain string, c ErrCase) error { return NewCauseWithStatus(err, domain, c.Code, c.Status, c.Reason) } // NewCauseWithStatus ... func NewCauseWithStatus(err error, domain string, code int, status string, reason string) error { return &Cause{ code: code, status: status, message: err, details: []Detail{ { Domain: domain, Reason: reason, Message: err.Error(), }, }, } } // Error return error message func (c *Cause) Error() string { reason := "" if len(c.details) > 0 { reason = c.details[0].Reason } return fmt.Errorf("%s : %w", reason, c.message).Error() } // Unwrap implements errors.Unwrap method func (c *Cause) Unwrap() error { return c.message } // Append one or more elements onto the end of details func (c *Cause) Append(e error) { if c == nil || c.IsZero() { c.set(e) return } if v, ok := e.(*Cause); ok { c.details = append(c.details, v.details...) return } c.details = append(c.details, Detail{ Domain: ServiceDomain, Reason: StatusBackendError, Message: e.Error(), }) } // MarshalJSON implements the json.Marshaler interface func (c *Cause) MarshalJSON() ([]byte, error) { return json.Marshal(jsonResponse{ Error: jsonResponseElement{ Code: c.code, Status: c.status, Message: c.message.Error(), Details: c.details, }, }) } // UnmarshalJSON implements the json.Unmarshaler interface func (c *Cause) UnmarshalJSON(b []byte) (err error) { var je jsonResponse if err = json.Unmarshal(b, &je); err != nil { return } ce := &Cause{ code: je.Error.Code, status: je.Error.Status, message: errors.New(je.Error.Message), details: je.Error.Details, } c.Append(ce) return nil } // IsZero checks empty func (c *Cause) IsZero() bool { return (c.code == 0 || c.status == "" || c.message == nil || len(c.details) == 0) } // set overwrites error func (c *Cause) set(e error) { v, ok := e.(*Cause) if !ok { v = NewCause(e, ServiceDomain, CaseBackendError).(*Cause) } // c = v <== fail staticcheck. SA4006: this value of `c` is never used c.code = v.code c.status = v.status c.message = v.message c.details = v.details }各サービスごとの処理
今までは共通処理としてのエラーを見てきました。各サービスでは、
ServiceDomain
などは固定できそうなので、そういった箇所をWrapしたエラーを用意しますconst ( // ServiceDomain ... ServiceDomain = "foobar" ) func init() { errors.ServiceDomain = ServiceDomain } // NewCause ... func NewCause(err error, c errors.ErrCase) error { return errors.NewCause(err, ServiceDomain, c) } // NewCauseWithStatus ... func NewCauseWithStatus(err error, code int, status string, reason string) error { return errors.NewCauseWithStatus(err, ServiceDomain, code, status, reason) }後書き
土日に思いつきで作ったものなので、まだまだ改良の余地はありそうですが、よくあるCodeとメッセージだけのエラーなどよりは置いやすいのではないでしょうか?
- 投稿日:2020-06-02T18:26:28+09:00
【Golang】ゴルーチン④range,close
【Golang】ゴルーチン④range,close
Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。
package main //channelのrangeとclose //結果を随時出力 import ( "fmt" ) func goroutin2(s []int, c chan int) { sum := 0 for _, v := range s { //全部足す sum += v //すぐに送る c <- sum } //終わったらchannelを閉じる //これ以上ない時は閉じる //閉じないと、中身以上取り出そうとしエラーになる close(c) } func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //実質1つしか入らないので、バッファは1でもいい c := make(chan int, len(s)) goroutin2(s, c) //go goroutine1(s, c)の結果をforで回す //channelの場合、インデックスは無く、値を出力 //並列処理なので、値が入る度に出力される //回数分i変数に入力 for i := range c { fmt.Println(i) } //中は0になる fmt.Println(len(c)) /* 1 3 6 10 15 21 28 36 45 55 0 */ } /* 別パターンが本にはある。スターティングGo言語 P191~P192 */
- 投稿日:2020-06-02T15:40:01+09:00
【GCP】Cloud Functions の Go 1.13 でファイル構造に変更があった
Cloud Functions Go 1.13 GA
GCF/Go 1.13 が GA になったので Go 1.11 から移行してみた。しかし、デプロイに失敗するようになった。
https://cloud.google.com/functions/docs/release-notes#May_28_2020
原因
コード内で設定ファイルを読み込むようにしていたが、GCF/Go 1.13 からファイルの構造が変更され読み込みエラーが起きてしまった。
調査
どのようにファイルの構造が変更があったのか調べたところ、ファイルの構造を調査したリポジトリを発見。
https://github.com/bcollard/find-go-cloud-function
どうやら、静的ファイル保存ディレクトリは以下に保存されるようだ。
/workspace/serverless_function_source_code/
ファイル構造について、公式ドキュメントでは見つけることはできなかった。
対応
コードの静的ファイルを指定している箇所を下記のように変更した。
- ./xxx.json + /workspace/serverless_function_source_code/xxx.json無事、デプロイに成功した。
- 投稿日:2020-06-02T10:13:14+09:00
【Heroku】デプロイ後にcode=H14 desc="No web processes running"が発生する場合はProcfileを作成して対処すればOK
発生したエラー
2020-06-02T00:41:22.345388+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=POST path="/xxxx" host=xxxx.herokuapp.com request_id=xxxx fwd="xx.xx.xx.xx" dyno= connect= service= status=503 bytes= protocol=https私の場合
Go/echoで開発しており、メインのアプリの他にアプリケーションのディレクトリ内にmain関数を持つgoファイルを作成してデプロイしたら発生しました。
main関数が2つになり、Herokuがどちらを実行すべきが判定できなくなったんですかね。(適当)対処方法1
公式ドキュメントに基づき、アプリケーションのルートディレクトリで、以下のコマンドを実行する
$ heroku ps:scale web=1対処方法2
1でダメな場合は、アプリケーションのルートディレクトリで、以下のようにProcfileを作成する
$ cd `your-app-dir` $ echo "web: $(basename `pwd`)" > Procfile補足:Procfileとは
Heroku apps include a Procfile that specifies the commands that are executed by the app on startup. You can use a Procfile to declare a variety of process types, including:
参考
- 投稿日:2020-06-02T09:03:21+09:00
Goの組込みデータ型を整理した
コードらしいコードは書いてない...
概略
- 論理型
- bool
- 文字列型
- string
- 整数型
- 符号付き整数型
- int
- int8
- int16
- int32 ( = rune)
- int64
- 符号なし整数型
- uint
- uint8 ( = byte)
- uint16
- uint32
- uint64
- uintptr
- 浮動小数点型
- float32
- float64
複素数型
- complex64
- complex128
エラー型
- Error
論理型
bool
true
またはfalse
しか取りうる値が存在しない最も単純なデータ型文字列型
string
- 8bit/byteの文字列型
- UTF-8 encodedではない場合もある
- 空文字でも
nil
でもよい- immutableである
整数型
符号付き整数型
int
最低32bit以上の符号付き整数型だが、
int32
型のエイリアスではない。int8
8bitの符号付き整数型で、-128~127の範囲の整数を表現できる。
int16
16bitの符号付き整数型で、-32768~32767の範囲の整数を表現できる。
int32 ( = rune)
32bitの符号付き整数型で、-2147483648~2147483647の範囲の整数を表現できる。
rune
はint32
型のエイリアスで、これについてはThe Go Blogに記述がある。To summarize, here are the salient points:
- Go source code is always UTF-8.
- A string holds arbitrary bytes.
- A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.
- Those sequences represent Unicode code points, called runes.
- No guarantee is made in Go that characters in strings are normalized.
和訳した。
要約すると、以下のようになります。
- Goのソースコードは常にUTF-8です。
- 文字列は任意のバイトを保持します。
- 文字列リテラルは、バイトレベルのエスケープを除いて、常に有効な UTF-8 シーケンスを保持します。
- これらのシーケンスは、runeと呼ばれる Unicode コードポイントを表しています。
- Goでは、文字列内の文字が正規化されていることを保証するものではありません。
まだちょっとわからないので調べると、こんな記述を見つけた。(出典:String と Rune — プログラミング言語 Go | text.Baldanders.infoにて)
この記事も見ると良さそう。
int64
64bitの符号付き整数型で、-9223372036854775808~9223372036854775807の範囲の整数を表現できる。
符号なし整数型
uint
最低32bit以上の符号なし整数型だが、
uint32
型のエイリアスではない。uint8 ( = byte)
8bitの符号なし整数型で、0~255の範囲の整数を表現できる。
byte
はuint8
のエイリアスで、公式Docでは以下のように説明されている。byte is an alias for uint8 and is equivalent to uint8 in all ways. It is used, by convention, to distinguish byte values from 8-bit unsigned integer values.
byte は uint8 の別名であり、すべての点で uint8 と同等です。慣習的に、バイト値を8ビットの符号なし整数値と区別するために使用されます。
uint16
16bitの符号なし整数型で、0~65535の範囲の整数を表現できる。
uint32
32bitの符号なし整数型で、0~4294967295の範囲の整数を表現できる。
uint64
64bitの符号なし整数型で、0~18446744073709551615の範囲の整数を表現できる。
uintptr
ドキュメントを直訳すると「uintptr は、任意のポインタのビットパターンを保持するのに十分な大きさの整数型です。」となる(DeepL様様)。
どんな場面で使うんだ...と思ってたが、StackOverflowで触れられていた。
The short answer is "never use
uintptr
". ?The long answer is that
uintptr
is there to bypass the type system and allow the Go implementors to write Go runtime libraries, including the garbage collection system, in Go, and to call C-callable code including system calls using C pointers that are not handled by Go at all.If you're acting as an implementor—e.g., providing access to system calls on a new OS—you'll need
uintptr
. You will also need to know all the special magic required to use it, such as locking your goroutine to an OS-level thread if the OS is going to do stack-ish things to OS-level threads, for instance. (If you're using it with Go pointers, you may also need to tell the compiler not to move your goroutine stack, which is done with special compile-time directives.)どうやら低レイヤーの処理を実装しない限りはめったに使うことがないらしい。
当面は気にしないことにした。
浮動小数点型
IEEE-754式については 以下を参照。
float32
IEEE-754式の32bit浮動小数点型
float64
IEEE-754式の64bit浮動小数点型
ちなみに数学計算するための
math
パッケージで定義された関数のほとんどは引数も返り値もfloat64
型でしか定義されていないで、注意が必要。まあGoにはオーバーロードがないので当然っちゃぁ当然。複素数型
複素数型の扱いを確認するならこの辺の記事を見ると良さそう。
complex64
実部と虚部をfloat32型で表現する複素数型
complex128
実部と虚部をfloat64型で表現する複素数型
エラー型
error
Goにはtry-catch-finallyといったエラーハンドリング表現が存在しないので、このerror型のinterfaceに対してエラー処理を実装する。
type error interface { Error() string }
- 投稿日:2020-06-02T01:31:20+09:00
Goでよく使われるgormを理解する:目次
はじめに
golangでアプリケーション開発を行なっている方だと、gormを使用されている方も多いのではないでしょうか。
自分もここ最近、golangを使ったWEBアプリケーションの開発を業務で行なっていますが、十分にgormの仕様を理解できておらず、実装で詰まってしまうことがあったので、この辺りで改めてgormのドキュメントと向き合い、Qiitaにまとめていければと思っています。基本的には、GORMドキュメントに沿って挙動を確認していきますので、ドキュメントも合わせて見ていただけるとよいかと思います。ただし、個人的に確認不要と判断した箇所については割愛していますので、予めご了承ください。
目次
はじめに
・はじめにCRUD Interface
・Goでよく使われるgormを理解する:Query編Associations
・Goでよく使われるgormを理解する:Associations編
・Goでよく使われるgormを理解する:Preloading編
- 投稿日:2020-06-02T00:06:34+09:00
Goでよく使われるgormを理解する:Query編
目次
はじめに
・はじめにCRUD Interface
・Goでよく使われるgormを理解する:Query編Associations
・Goでよく使われるgormを理解する:Associations編
・Goでよく使われるgormを理解する:Preloading編Query
Queryを用いることで、データの抽出条件を細かく設定することができます。
Where
gormのドキュメントには、AND検索について以下のような例が載っていますが、
// AND db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users) //// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;gormの場合、Whereをつなげて記述したときのデフォルトがANDなので、以下のように記述することもできます。
db.Where("name = ?", jinzhu).Where("age >= ?", "22").Find(&users)例えば、以下のようなstructがあったとします。
type User struct { Model UserName *string `gorm:"" json:"userName"` // ユーザー名 CompanyID *int `gorm:"" json:"companyId"` // 所属企業ID Company *Company `gorm:"" json:"company"` Languages []*Language `gorm:"many2many:user_languages;association_autoupdate:false" json:"language"` // 使用可能言語 UserBirthday *time.Time `gorm:"" json:"userBirthday"` // 生年月日 Bio *string `gorm:"" json:"bio"` // 自己紹介文 GithubAccount *string `gorm:"" json:"githubAccount"` // Githubアカウント TwitterAccount *string `gorm:"" json:"twitterAccount"` // Twitterアカウント }ここで、DBに誕生日(UserBirthday)の登録があり(NULLではなく)、所属企業のID(company_id)が1のユーザーを探す場合、以下のように記述することができます。
func GetAllUsers() (ml []*User, err error) { tx := db.Begin() tx = tx.Where("user_birthday is not null").Where("company_id = ?", 1) err = tx.Find(&ml).Commit().Error return ml, err }ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((user_birthday is not null) AND (company_id = ?))[1] 1gormのドキュメントに記載されている書き方だと、以下のようになりますが、ログを見ても基本的には同じSQLが発行されているのがわかります。
※前述の書き方だと、「user_birthday is not null」と「company_id = ?」がそれぞれ括弧で囲われており、以下の書き方だとその括弧がなくなっているという違いはありますが、括弧は処理のまとまりを表しているだけなので、おそらくパフォーマンス等への影響はないと思います。func GetAllUsers(limit int64, offset int64) (ml []*User, err error) { tx := db.Begin() tx = tx.Where("user_birthday is not null AND company_id = ?", 1) err = tx.Find(&ml).Commit().Error return ml, err }ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((user_birthday is not null AND company_id = ?))[1] 1FirstOrInit
最初にマッチしたレコードを得る、または渡された条件 (構造体、マップの場合のみ機能) を元にレコードを作成します
例えば、以下のようなstructがあり、
type User struct { Model UserName *string `gorm:"" json:"userName"` // ユーザー名 }Firstを使用して、DBに登録されていないIDを取得しようとすると、
func GetUser(UserID int64) (user User, err error) { err = db.First(&user, UserID).Error return user, err }該当するレコードがないため、
ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((`users`.`id` = 6)) ORDER BY `users`.`id` ASC LIMIT 1[] 0通常は、以下のようなエラーが返ってきます。
出力// => record not foundしかし、
FirstOrInit()
を使用すると、func GetUser(UserID int64) (user User, err error) { err = db.FirstOrInit(&user, UserID).Error return user, err }該当するレコードがなくても、
SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((`users`.`id` = 6)) ORDER BY `users`.`id` ASC LIMIT 1[] 0設定された内容を元に、仮のレコードを返してくれるみたいです。
※「設定された内容」と書きましたが、今回は設定をしていないので項目はすべてゼロ値になっています。「設定」については、次項のAttrsで行うことができます。{ "id": 0, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "deletedAt": null, "userName": null, "companyId": null, "company": null, "language": null, "userBirthday": null, "bio": null, "githubAccount": null, "twitterAccount": null }なお、gormのドキュメントには、「レコードが作成されます」と書いてありましたが、DBを確認したところレコード自体は作成されていませんでした。
Attrs
レコードが取得できたかどうかによって初期化するかどうかが決まります
先ほどと同様、以下のようなstructがあったとします。
type User struct { Model UserName *string `gorm:"" json:"userName"` // ユーザー名 }前項で使用したFirstOrInit()にAttrsの記述を追加して、以下のように書くと、
func GetUser(UserID int64) (user User, err error) { userName := "No Name" err = db.Attrs(User{UserName: &userName}).FirstOrInit(&user, UserID).Error return user, err }該当するレコードがある場合は、DBのレコードを返しますが、
出力{ "id": 1, "createdAt": "2020-06-01T20:32:19+09:00", "updatedAt": "2020-06-01T20:32:19+09:00", "deletedAt": null, "userName": "user1" }ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((`users`.`id` = 1)) ORDER BY `users`.`id` ASC LIMIT 1[] 1該当するレコードがない場合は、Attrsで設定した内容をvalueに含んだ仮のレコードを返します。
出力{ "id": 0, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "deletedAt": null, "userName": "No Name" }ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((`users`.`id` = 6)) ORDER BY `users`.`id` ASC LIMIT 1[] 0FirstOrCreate
最初にマッチしたレコードを得る、または渡された条件 (構造体、マップの場合のみ機能) を元にレコードを作成します。
gormのドキュメントの説明だけ見ると、FirstOrInitと何が違うの!?と思いますが(笑)、こちらは該当するレコードがない場合、正真正銘DBにレコードが作成されます。
func GetUser(UserID int64) (user User, err error) { userName := "No Name" err = db.Attrs(User{UserName: &userName}).FirstOrCreate(&user, UserID).Error return user, err }出力{ "id": 6, "createdAt": "2020-06-01T23:19:25.2557082+09:00", "updatedAt": "2020-06-01T23:19:25.2557082+09:00", "deletedAt": null, "userName": "No Name", "companyId": null, "company": null, "language": null, "userBirthday": null, "bio": null, "githubAccount": null, "twitterAccount": null }ログを見ると、たしかに、先ほどまでと同様のSELECT文に加え、INSERT文でAttrsで設定した内容を含むレコードが作成されていることがわかります。
ログSELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND ((`users`.`id` = 6)) ORDER BY `users`.`id` ASC LIMIT 1[] 0 INSERT INTO `users` (`created_at`,`updated_at`,`deleted_at`,`user_name`,`company_id`,`user_birthday`,`bio`,`github_account`,`twitter_account`) VALUES (?,?,?,?,?,?,?,?,?)[2020-06-01 23:19:25.2557082 +0900 JST m=+16.576992801 2020-06-01 23:19:25.2557082 +0900 JST m=+16.576992801 <nil> 0xc0000204d0 <nil> <nil> <nil> <nil> <nil>] 1高度なクエリ
SubQuery
SubQueryを使用することで、より複雑なデータの抽出を行うことができるようになります。
例えば、以下のようなstructがあり、Activity側からTourのIDに応じたActivityを抽出したいとします。type Activity struct { Model ActivityName *string `gorm:"" json:"activityName"` }type ActivityPlan struct { Model ActivityPlanName *string `gorm:"" json:"activityPlanName"` TourScheduleID *int `gorm:"" json:"tourScheduleId"` ActivityID *int `gorm:"" json:"activityId"` Activity *Activity `gorm:"" json:"activity"` }type TourSchedule struct { Model TourScheduleDate *time.Time `gorm:"" json:"tourScheduleDate"` TourID *int `gorm:"" json:"tourId"` ActivityPlans []*ActivityPlan `gorm:"" json:"activityPlan"` }type Tour struct { Model TourName *string `gorm:"" json:"tourName"` TourSchedules []*TourSchedule `gorm:"" json:"tourSchedule"` }先ほどまでのものに比べると、抽出の仕方が少々複雑な気がしますね。
TourScheduleはTourと一対多の関係にあり、ActivityPlanはTourScheduleと一対多の関係にあります。また、今回Getのfunctionを記述したいActivityのstructは直接TourIDを持っておらず、TourScheduleのstructにあるTourIDをうまく条件に組み込む必要がありそうです。例えば、以下のような形はどうでしょう。
func GetAllActivities(tourID int64) (ml []*Activity, err error) { tx := db.Begin() // ツアーIDに紐づくActivityを抽出 tx = tx.Where("id in (?)", tx.Table("activity_plans"). Select("distinct(activity_id)"). Joins("left join tour_schedules on activity_plans.tour_schedule_id = tour_schedules.id"). Where("tour_schedules.tour_id = ?", tourID). SubQuery()) err = tx.Find(&ml).Commit().Error return ml, err }すると、以下のようなデータが出力されました。
出力[ { "id": 7, "createdAt": "2020-06-01T23:38:47+09:00", "updatedAt": "2020-06-01T23:38:47+09:00", "deletedAt": null, "activityName": "アクティビティG" }, { "id": 8, "createdAt": "2020-06-01T23:38:47+09:00", "updatedAt": "2020-06-01T23:38:47+09:00", "deletedAt": null, "activityName": "アクティビティH" }, { "id": 9, "createdAt": "2020-06-01T23:38:47+09:00", "updatedAt": "2020-06-01T23:38:47+09:00", "deletedAt": null, "activityName": "アクティビティI" }, { "id": 10, "createdAt": "2020-06-01T23:38:47+09:00", "updatedAt": "2020-06-01T23:38:47+09:00", "deletedAt": null, "activityName": "アクティビティJ" } ]ログを見ると、activity_plansテーブルとtour_schedulesテーブルをleft joinし、tour_schedulesテーブルのtour_idが1になるレコードを探してきていますね。また、重複しない(distinct)ようにactivity_plansテーブルのactivity_idを抽出し、そのactivity_id郡と一致するactivitiesのレコードを抽出できているので、目的通りの抽出ができていそうです。
このように、SubQueryを使用することで、複雑な抽出条件の設定が可能になります。ログSELECT * FROM `activities` WHERE `activities`.`deleted_at` IS NULL AND ((id in ((SELECT distinct(activity_id) FROM `activity_plans` left join tour_schedules on activity_plans.tour_schedule_id = tour_schedules.id WHERE (tour_schedules.tour_id = ?)))))[1] 4