- 投稿日:2020-03-26T22:15:21+09:00
Azure Functions で Go 言語を使おう(Go 1.14.2 待ち)
Azure Functions custom handler というものを使うと、Web API を作れる言語なら、なんでも Azure Functions の上で動かせるようになります!!まだプレビューですけど、楽しみな機能です!!
端的に言うと、Azure Functions がサポートしていない言語での開発や、Azure Functions がサポートしている言語でも特定の Web API を開発するフレームワークを使って開発するといったことが出来ます。サポートしていない言語での開発だと Go 言語で開発出来たり、後者の例だと Node.js で express を使って開発したり C# で ASP.NET Core を使って開発したりといったことが出来ます。
ドキュメント:Azure Functions custom handlers (preview)
仕組み
host.json
に裏で動く HTTP サーバーを起動するコマンドを設定しておくと、裏で HTTP サーバーを起動して Azure Functions の関数が呼ばれたときに/関数名
という名前のパスに HTTP リクエスト投げてくれる。レスポンスの JSON を特定の形式に従っておくと、Functions のバインドによしなに渡してくれるといった感じです。シンプルな HTTP トリガーで起動して HTTP レスポンスを返すだけなら普通に作れば OK です。そうじゃない Queue トリガーなどの他のトリガーや、バインドを使う場合は、ドキュメントからの引用ですが、こんな感じの function.json の場合は
{ "bindings": [ { "name": "myQueueItem", "type": "queueTrigger", "direction": "in", "queueName": "messages-incoming", "connection": "AzureWebJobsStorage" }, { "name": "$return", "type": "queue", "direction": "out", "queueName": "messages-outgoing", "connection": "AzureWebJobsStorage" } ] }こんな感じの JSON がリクエスト Body に入ってきます
{ "Data": { "myQueueItem": "{ message: \"Message sent\" }" }, "Metadata": { "DequeueCount": 1, "ExpirationTime": "2019-10-16T17:58:31+00:00", "Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865", "InsertionTime": "2019-10-09T17:58:31+00:00", "NextVisibleTime": "2019-10-09T18:08:32+00:00", "PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=", "sys": { "MethodName": "QueueTrigger", "UtcNow": "2019-10-09T17:58:32.2205399Z", "RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9" } } }レスポンスの JSON は
{ "Outputs": { "出力バインドの名前": 出力バインドに渡す値 }, "Logs": ["log message1", "log message2"], "returnValue": $return に渡す値 }のような感じになります。
試してみよう
シンプルな HTTP トリガー対応なら純粋に JSON を受け取って JSON を返すようなものがあればいいので、やってみましょう。とりあえず Go 言語で!
package main import ( "encoding/json" "net/http" "os" "time" "fmt" "log" ) type user struct { Name string Age int } func handleRequests() { http.HandleFunc("/Hello", func(w http.ResponseWriter, r *http.Request) { t := time.Now() fmt.Println(t.Month()) fmt.Println(t.Day()) fmt.Println(t.Year()) ua := r.Header.Get("User-Agent") fmt.Printf("user agent is: %s \n", ua) invocationid := r.Header.Get("X-Azure-Functions-InvocationId") fmt.Printf("invocationid is: %s \n", invocationid) json.NewEncoder(w).Encode(user{ Name: "Kazuki from /hello", Age: 39, }) }) httpInvokerPort, exists := os.LookupEnv("FUNCTIONS_HTTPWORKER_PORT") if exists { fmt.Println("FUNCTIONS_HTTPWORKER_PORT: " + httpInvokerPort) } log.Println("Go server Listening...on httpInvokerPort:", httpInvokerPort) log.Fatal(http.ListenAndServe(":"+httpInvokerPort, nil))} func main() { handleRequests() }このコードを
go build main.go
で main.exe にして、host.json を以下のようにします。{ "version": "2.0", "httpWorker": { "description": { "defaultExecutablePath": "main.exe" } } }そして、
/Hello
でリクエストを受け取るので Hello フォルダーを作って、その中にfunction.json
を作ってシンプルな Http トリガーの関数の定義をします。{ "bindings": [ { "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "get" ] }, { "type": "http", "direction": "out", "name": "$return" } ] }因みに、ファイルは以下のような位置関係で配置しています。
そして、ローカルで実行してみます。
func host start
で実行すると、以下のように Hello 関数がちゃんと Http で受け付けるようになっているのがわかります。実はこの時 main.exe が裏で起動しています。タスクマネージャーで見てみるとちゃんといますね。
関数の URL を叩いてみると Go 言語で返した JSON がちゃんと帰ってきました!
いいね。
Azure 上で動かしてみよう
デプロイ自体は、とりあえず試すだけなら
func azure functionapp publish デプロイ先の関数アプリ名 --force
というコマンドでデプロイできます。(main.go とかも、もれなく一緒にデプロイされてしまいますが…!!)
ただ、残念ながら Azure App Service (Azure Functions とかが動くサービス)上で Go 言語の 1.14.1 でビルドしたバイナリを動かそうとすると以下のようなエラーが出ます。調べてみたら Go 1.14.2 で修正されるみたいなので、もうちょっとお預けですね…!
PowerRegisterSuspendResumeNotification error on Azure App Services with go 1.13.7 [1.14 backport]
Issue の最終更新が 16 時間前とか、ホカホカの Issue でした。
ということで Go 1.14.2 がでたら、また試してみようと思います!
まとめ
Azure Functions 上で任意の言語で開発したプログラム動くようになるのは個人的にはアツイ。関数の実行時間が制限時間をオーバーしたときにどうなるの?とか気になる点はありますが、正式リリースのころにはドキュメントなども整備されると思うので、その時にまた調べてみようと思います。
- 投稿日:2020-03-26T18:28:33+09:00
【Go】Lambda + RDS 接続にRDS Proxyを使ってみた
はじめに
現在、API Gateway + Lambda + RDSを使ってWebアプリケーションを作っています!2019年末に行われたre:Invent 2019で発表された、RDS Proxy(現在はプレビュー版です。)を試してみたので備忘録です。
RDS Proxyってなに?
簡単に言うと、データベースへのコネクションプールを確立、管理することで、アプリケーションからのデータベース接続数を少なく抑えることができるサービスです。
Lambda関数は、呼び出すごとに新しいコネクションを作成する必要があります。しかし、LambdaからRDSへの同時接続数には上限があり、これまではコネクション数が上限に達しないようにする必要がありました。これを解決してくれるのがこのRDS Proxyです。RDS Proxyを利用することで、既存のコネクションを再利用することができ、コネクション数を抑えることができます。
つまり、Lambda + RDSの構成が避けられていた原因の1つの同時接続数問題が解決できるのです!
構成図
このような構成で作成しました!本記事では、Lambda、RDS Proxy、RDS、踏み台のEC2にフォーカスしています。
手順
下記の流れで進めていきます。
- VPC、サブネットの作成
- セキュリティグループの作成
- RDSの構築
- 踏み台EC2の構築
- テーブル作成
- DBユーザの作成
- RDS Proxyの構築
- Lambda関数の作成
やってみる
1. VPC、サブネットの作成
事前準備として、VPCを作成し、作成したVPCの中にプライベートサブネット、パブリックサブネットを作成します。特別な設定は不要なので作成方法は省略します。
2. セキュリティグループの作成
事前準備として、各リソースのセキュリティグループの作成を行います。
Lambda
セキュリティグループ name : sg-lambda
インバウンド
タイプ ポート ソース ー ー ー 特にどこからも許可していなくてもAPI Gatewayからは叩くことができます!
EC2
セキュリティグループ name : sg-ec2-bastion
インバウンド
タイプ ポート ソース SSH 22 許可したいIPアドレス RDS Proxy
セキュリティグループ name : sg-rdsproxy
インバウンド
タイプ ポート ソース MySQL/Aurora 3306 sg-lambda RDS
セキュリティグループ name : sg-rds
インバウンド
タイプ ポート ソース MySQL/Aurora 3306 sg-ec2-bastion MySQL/Aurora 3306 sg-rdsproxy 3. RDSの構築
プライベートサブネットにRDSを立てます。
今回使用したMySQLのバージョン : MySQL 5.7.22
セキュリティグループは2で作成したsg-rdsを選択してください。その他、特に特別な設定は不要なので省略します。
4. 踏み台EC2の構築
RDSに接続して、ユーザやテーブルを作成するための踏み台EC2をたてます。
今回使用したOS : Amazon Linux 2
セキュリティグループは2で作成したsg-ec2-bastionを選択してください。こちらも、特に特別な設定は不要なので省略します。
ただ、IPv4パブリックIPを使ってsshで接続するため、自動割り当てパブリックIPは有効に設定する。(これをせずに、プライベートIPで接続を試みましたが、できませんでした。)※上記の方法(自動割り当てパブリックIP)では、EC2を再起動するごとにパブリックIPアドレスが変更になるのでお気をつけてください。
5. テーブル作成
RDS内にテーブルを作成します。
テーブル内のデータはcsvファイルから取り込むようにしたかったので、今回はcsvファイルを準備しましたが、ただデータをRDSに保存するだけです。csvファイルのアップロード
ローカルから踏み台のEC2にcsvファイルを移動させました。
$ scp -i [キーペア名].pem [ファイル名].csv ec2-user@[パブリックIP]:/home/ec2-user/(EC2内の保存したいディレクトリを指定)EC2にssh接続
$ ssh -i [キーペア名].pem ec2-user@[IPv4パブリックIP]MySQLのインストール
$ sudo yum update $ sudo yum install mysqlRDSへ接続
$ mysql --local-infile=1 -h [RDSエンドポイント] -u [マスタユーザ名] -pテーブル内のデータはcsvファイルから読み込むために、
--local-infile=1
のオプションをつけました。テーブルの作成
まず、テーブルの枠を作成します。
> CREATE TABLE [テーブル名] ([カラムの指定]); # 例 > CREATE TABLE m1_champion (id INT(2) AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(30) NOT NULL, champion YEAR NOT NULL, formed YEAR NOT NULL, note VARCHAR(30));次に、下記のようなcsvファイルをインポートします。
> LOAD DATA LOCAL INFILE "[ファイルパス]/[ファイル名].csv" INTO TABLE [テーブル名] FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n';これでテーブルが完成しました。
id name champion formed note 1 ミルクボーイ 2019 2007 コーンフレーク 2 霜降り明星 2018 2013 null 3 とろサーモン 2017 2002 null 6. DBユーザの作成
上記の手順通り進めば、現在DBにログインしているので、このままLambdaから接続したときに使うユーザを作成します。
> CREATE USER '[ユーザ名]'@'%' IDENTIFIED BY '[パスワード]'; > GRANT SELECT, INSERT, UPDATE, DELETE ON [対象のDB].[対象のテーブル] TO '[ユーザー名]'@'%';上記はSELECT, INSERT, UPDATE, DELETEの権限を許可しています。また、ホスト名には
%
=ワイルドカードを使用し、どこからのアクセスも受け入れるように設定しています。ちなみに、全てのDBとテーブルが対象の場合は*.*
とします。7. RDS Proxyの構築
いよいよRDS Proxyの構築に入ります。
Secrets Manager シークレットの作成
先ほど作成したDBのユーザ名とパスワードを入力し、作成します。
下記URLのAWSの公式ブログに沿って作成してください。
参考:AWS LambdaでAmazon RDS Proxyを使用するIAMロールの作成
ユースケースは
RDS - Add Role to Database
を選択します。
そして、必要なポリシーをアタッチします。
Secrets Managerへのアクセス権限のポリシー
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "secretsmanager:GetResourcePolicy", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", "secretsmanager:ListSecretVersionIds" ], "Resource": "[シークレットARN]" } ] }拡張ログを取得したい場合はCloud Watch Logsへのアクセス権限も必要です。ただ、以下のログに関しては、Cloud Watch Logsへのアクセス権限がなくてもロググループに出力されます。
- RDS Proxyの起動終了
- DBへの接続開始終了
- 警告
RDS Proxyの作成
今回はRDS Proxyのコンソールから作成します。
ちなみに、Lambda関数のコンソール上からも作成でき、IAM認証で接続する場合はLambdaと紐付ける手間を省くことができます。ただ、今回のようにDBのユーザ名とパスワードを用いて接続する場合は紐付けは必要ないようなので、RDS Proxyのコンソールから作成しました。
プロキシ識別子を入力します。
先ほど作成したRDSを選択します。
先ほど作成したSecrets ManagerシークレットとIAMロールを選択します。
サブネットはRDSと同じプライベートサブネットを選択します。
セキュリティグループは2で作成したsg-rdsproxyを選択してください。Cloud Watch Logsでデバッグログを取得したい場合は拡張されたログ記録を有効にするにチェックを入れてください。(Cloud Watch Logsへのアクセス権限も必要です。)
8. Lambda関数の作成
IAMロール
LambdaはVPC内にあるのでENI生成用のポリシーを作成してアタッチします。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "ec2:DescribeNetworkInterfaces", "ec2:DeleteNetworkInterface", "ec2:CreateNetworkInterface" ], "Resource": "*" } ] }ログを取得したい場合はCloud Watch Logsへのアクセス権限も必要です。
VPC
Lambda関数の編集画面で設定できます。
カスタムVPCを選択し、RDSとRDS Proxyと同様のVPCとパブリックサブネットを選択します。
セキュリティグループは2で作成したsg-lambdaを選択してください。ソースコード
package main import ( "database/sql" "encoding/json" "fmt" "github.com/aws/aws-lambda-go/lambda" _ "github.com/go-sql-driver/mysql" "os" ) type Response struct { ID int `json:"id"` Name string `json:"name"` Champion string `json:"champion"` Formed string `json:"formed"` Note sql.NullString `json:"note"` } // os.Getenv()でLambdaの環境変数を取得 var dbUser = os.Getenv("dbUser") // DBに作成したユーザ名 var dbPass = os.Getenv("dbPass") // パスワード var dbEndpoint = os.Getenv("dbEndpoint") // RDS Proxyのプロキシエンドポイント var dbName = os.Getenv("dbName") // テーブルを作ったDB名 func RDSConnect() (*sql.DB, error) { connectStr := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=%s", dbUser, dbPass, dbEndpoint, "3306", dbName, "utf8", ) db, err := sql.Open("mysql", connectStr) if err != nil { panic(err.Error()) } return db, nil } func RDSProcessing(db *sql.DB) (interface{}, error) { var id int var name string var champion string var formed string var note sql.NullString responses := []Response{} responseMap := Response{} getData, err := db.Query("SELECT * FROM m1_champion") defer getData.Close() if err != nil { return nil, err } for getData.Next() { if err := getData.Scan(&id, &name, &champion, &formed, ¬e); err != nil { return nil, err } fmt.Println(id, name, champion, formed, note) responseMap.ID = id responseMap.Name = name responseMap.Champion = champion responseMap.Formed = formed responseMap.Note = note responses = append(responses, responseMap) } params, _ := json.Marshal(responses) fmt.Println(string(params)) defer db.Close() return string(params), nil } func run() (interface{}, error) { fmt.Println("RDS接続 start!") db, err := RDSConnect() if err != nil { panic(err.Error()) } fmt.Println("RDS接続 end!") fmt.Println("RDS処理 start!") response, err := RDSProcessing(db) if err != nil { panic(err.Error()) } fmt.Println("RDS処理 end!") return response, nil } /************************** メイン **************************/ func main() { lambda.Start(run) }実行結果
上記の手順でRDS Proxyを用いての接続は完了です。(あれ意外と簡単)
Lambdaでの実行結果がこちらです。RDSから値が取得できました!
びっくりするのは応答時間!!!ご法度とされていたVPCLambdaですが、こんなにはやくなっているんです。これは使わない手はない!
おわりに
無事、LambdaからRDS Proxyを介してRDSに接続が可能になりました!これで同時接続数問題も気にしなくていい!!また、VPC内にLambdaを設置したときのコールドスタートが改善されたので、Lambdaを非VPCに設置しなくてもいけるし、RDSがプライベートサブネットに置ける!!素晴らしい!ただ、プレビュー版なのでお気をつけください。
次回こそはAPI Gatewayをたたいて、データをブラウザに表示します!
- 投稿日:2020-03-26T17:13:22+09:00
ゴリラ言語の読み方
先日、「ウ」と「ホ」と改行文字だけでプログラミングできる「ゴリラ言語」を作りました。
そういえば先日、「ウ」と「ホ」と改行だけでプログラミング出来るゴリラ言語作ったのでソース置いておきますね。「ウホッ」でもいいよ。https://t.co/XcXFcvDhRy
— mattn (@mattn_jp) March 24, 2020コード短いのでそのまま張っておきます。
package main import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "os" "strconv" "strings" ) type Op int const ( Push Op = iota Signed Dup Copy Swap Discard Slide Add Sub Mul Div Mod Store Retrieve Mark Unsigned Call Jump Jz Jn Ret Exit OutC OutN InC InN Nop ) type opcode struct { code Op arg int } type opcodes []opcode func (p *opcodes) jmp(arg int, pc *int) { for i, t := range *p { if t.code == Mark && t.arg == arg { *pc = i } } } var optable = map[string]Op{ " ": Push, " \n ": Dup, " \n\t": Swap, " \n\n": Discard, "\t ": Add, "\t \t": Sub, "\t \n": Mul, "\t \t ": Div, "\t \t\t": Mod, "\t\t ": Store, "\t\t\t": Retrieve, "\n ": Mark, "\n \t": Call, "\n \n": Jump, "\n\t ": Jz, "\n\t\t": Jn, "\n\t\n": Ret, "\n\n\n": Exit, "\t\n ": OutC, "\t\n \t": OutN, "\t\n\t ": InC, "\t\n\t\t": InN, //" \t ": Copy, //" \t\n": Slide, } type stack []int func (s *stack) push(v int) { *s = append(*s, v) } func (s *stack) pop() int { l := len(*s) if l == 0 { return -1 } v := (*s)[l-1] *s = (*s)[:l-1] return v } func (s *stack) dup() { l := len(*s) if l == 0 { return } v := (*s)[l-1] *s = append(*s, v) } func uho(src []rune) { for i, r := range src { switch r { case 'ウ': src[i] = ' ' case 'ホ': src[i] = '\t' } } // remove needless comments for { pos := strings.IndexFunc(string(src), func(r rune) bool { return r != ' ' && r != '\t' && r != '\n' }) if pos < 0 { break } if pos == 0 { src = src[1:] } else { src = append(src[:pos], src[pos+1:]...) } } // parse uho into tokens tokens := opcodes{} for len(src) > 0 { op := "" code := Nop for k, v := range optable { if strings.HasPrefix(string(src), k) { op = k code = v break } } if op == "" { src = src[1:] continue } src = src[len(op):] var arg int switch code { case Push: // handle argument handle_signed_arg: for i := 1; i < len(src); i++ { switch src[i] { case ' ': arg = (arg << 1) | 0 case '\t': arg = (arg << 1) | 1 case '\n': // Push take singed argument if src[0] == '\t' { arg = -arg } src = src[i+1:] break handle_signed_arg } } case Mark, Call, Jump, Jz, Jn: // handle argument handle_unsigned_arg: for i := 0; i < len(src); i++ { switch src[i] { case ' ': arg = (arg << 1) | 0 case '\t': arg = (arg << 1) | 1 case '\n': src = src[i+1:] break handle_unsigned_arg } } } tokens = append(tokens, opcode{code, arg}) } pc := 0 ps := stack{} cs := stack{} heap := map[int]int{} for { token := tokens[pc] code, arg := token.code, token.arg //fmt.Println(pc, code, arg) pc++ switch code { case Push: ps.push(arg) case Mark: case Dup: ps.dup() case OutN: fmt.Print(ps.pop()) case OutC: fmt.Print(string(rune(ps.pop()))) case Add: rhs := ps.pop() lhs := ps.pop() ps.push(lhs + rhs) case Sub: rhs := ps.pop() lhs := ps.pop() ps.push(lhs - rhs) case Mul: rhs := ps.pop() lhs := ps.pop() ps.push(lhs * rhs) case Div: rhs := ps.pop() lhs := ps.pop() ps.push(lhs / rhs) case Mod: rhs := ps.pop() lhs := ps.pop() ps.push(lhs % rhs) case Jz: if ps.pop() == 0 { tokens.jmp(arg, &pc) } case Jn: if ps.pop() < 0 { tokens.jmp(arg, &pc) } case Jump: tokens.jmp(arg, &pc) case Discard: ps.pop() case Exit: os.Exit(0) case Store: v := ps.pop() address := ps.pop() heap[address] = v case Call: cs.push(pc) tokens.jmp(arg, &pc) case Retrieve: ps.push(heap[ps.pop()]) case Ret: pc = cs.pop() case InC: var b [1]byte os.Stdin.Read(b[:]) heap[ps.pop()] = int(b[0]) case InN: scanner := bufio.NewScanner(os.Stdin) if scanner.Scan() { i, _ := strconv.Atoi(scanner.Text()) heap[ps.pop()] = i } case Swap: ps[len(ps)-1], ps[len(ps)-2] = ps[len(ps)-2], ps[len(ps)-1] default: panic(fmt.Sprintf("Unknown opcode: %v", code)) } } } func main() { var content bytes.Buffer for _, f := range os.Args[1:] { b, err := ioutil.ReadFile(f) if err != nil { fmt.Fprintln(os.Stderr, "read file:", err) os.Exit(1) } content.Write(b) } if content.Len() == 0 { if _, err := io.Copy(&content, os.Stdin); err != nil { fmt.Fprintln(os.Stderr, "read stdin:", err) os.Exit(1) } } uho([]rune(content.String())) }以下のコードをコマンドラインから標準入力に食わせると、FizzBuzz が実行されます。
ウウウホッ ウウウウ ウ ウウ ウウウウホホ ホウホッホ ホウウホ ウウウホッウホ ホウホッホ ホウウホウ ウ ウホ ウホッ ウ ウホウウ ウウウホッ ウウウホッウウウホホウ ホ ウウウウウホッホウホッウウホ ホ ウウ ウホッウホホ ウウウホッウホ ホウホッホ ホウウホウ ウ ウホウウ ウウウホッウ ウウウホウウウウホッウ ホ ウウウウウホッホホウホウホッ ホ ウウ ウホウホッホ ウ ウホウウ ウウウホホ ウウウホッホホホウホッウ ウ ウホッ ウウホ ウウ ホ ウウウホッウウ ウウウホウホッウ ホ ウウウウウホ ホウウウウ ウウウウホッホウウホッウホ ホウウホ ホホウウ ウ便利ですね。また以下のコードを食わせると「ウホッ!」と表示されます。
ウッウウ ウウウホウホウ ウウウッホッウウウウホ ウウウッホホウウウウッホホウッウウウッホホ ウウウホホウウウッウホホウホッホウホホ ウウウッホッホウウウウッホウホウウホホウ ウウホウウウッウーウウウウウホウッウウホウウーホウウ ウ ウ ホウッホホホウホウウウッホウホウウ ホ ウウ ウ ホウウウッウウウウウウッホウウーウホウウホウウ ウウホホホウホウウウホッウホウウ ウ勘のいい方はコード見なくてもこの説明だけで気付いたかもしれません。そう「whitespace」です。
https://ja.wikipedia.org/wiki/Whitespace
Whitespace(ホワイトスペース)は、プログラミング言語のひとつであり、またそれを動作させるインタプリタを指している。WhitespaceはGPLにより配布されている。実用言語ではない難解プログラミング言語のひとつ。
本来 "whitespace" とは「空白」や「余白」を意味する英単語である。多くの一般的なプログラミング言語では空白に相当する文字(スペース、タブ、言語によっては改行も)は他の言語要素間の区切りとして使われている。しかし、言語 Whitespace においてはプログラムは空白文字だけで構成される(それ以外の文字列はコメント扱いで無視される)。そのため、一見するとプログラムであることすらわからないという珍しい言語である。ゴリラ言語は、空白を「ウ」、タブ文字を「ホ」に見立てた Esolang です。この言語で目的の文字を表示するコードをどうやって書いているか想像もつかないかもしれません。実は whitespace のコンパイラを作っておられる方がいます。
https://vii5ard.github.io/whitespace/
ウェブ上の IDE になっています。例えばここの Files の中にある
nerd.wsa
を開くと以下のコードが表示されます。; Push the zero-terminated string ; onto stack in reverse order. push "Hello Nerd!\n" ; Loop while top of stack is non-zero. print: dup jz end printc jmp print ; Graceful end end: drop end独自のアセンブラ言語になっていて、コンパイルすると whitespace のソースが出力されます。この
Hello Nerd!
をウホッ!
書き換えると空白とタブ文字と改行文字で作られる whitespace のソースコードが出来上がります。真っ白けですね。vim 等を使い、空白を「ウ」、タブ文字を「ホ」に置換してみます。
ウウウ ウウウホウホウ ウウウホホホホホホホホウウウウウウウホ ウウウホホウウウウホホウウウウホホ ウウウホホウウウウホホウホホウホホ ウウウホホウウウウホウホウウホホウ ウウホウウウウウウウウウホウウウホウウホウウ ウ ウ ホウホホホウホウウウホウホウウ ホ ウウ ウ ホウウウウウウウウウホウウウホウウホウウ ウウホホホウホウウウホウホウウ ウ読めそうな気がしてきましたね。読める気がしましたよね?
読んでみましょう。ゴリラ言語の仕様は以下の通り。
IMP [ウ] スタック操作 [ホ][ウ] 演算 [ホ][ホ] ヒープアクセス [LF] フロー制御 [ホ][LF] I/O STACK OPERATION [ウ] 数値:数値をスタックに積む [LF][ウ]:スタックの一番上を複製する [ホ][ウ] 数値:スタックのn番目をコピーして一番上に積む [LF][ホ]:スタックの1番目と2番目を交換する [LF][LF]:スタックの一番上の物を捨てる CALCULATE [ウ][ウ]:加算 [ウ][ホ]:引き算 [ウ][LF]:かけ算 [ホ][ウ]:割り算 [ホ][ホ]:剰余始めはデータ部ですね。push ([ウ])に続く引数、0(なし), 10(1<<1, 2<<1, 5<<1), 65281(1<<1, 2<<1, 4<<1, 8<<1, 16<<1, 33<<1), 12483(略), 12507(略), 12454(略) を積んでいます。スタックですから取り出す時には逆から取り出されます。つまりこれは「12454, 12507, 12483, 65281, 10, 0」、文字に直すと「ウホッ!」です。続いて制御部
ウウホウウウウウウウウウホウウウホウウホウウ ウ ウ ホウホホホウホウウウホウホウウ ホ ウウ ウ ホウウウウウウウウウホウウウホウウホウウ ウウホホホウホウウウホウホウウ ウマーク([LF][ウ][ウ])でラベル定義([ホ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ホ][ウ][ウ][ウ][ホ][ウ][ウ][ホ][ウ][ウ])、スタックから取り出し([ウ][LF][ウ])、ゼロ比較([LF][ホ][ウ])、0 であればラベル
ホホホウホウウウホウホウウ
にジャンプしています。0 でない場合は画面に取り出した値を文字として表示([ホ][LF][ウ])します。そしてループの最初に戻る為にホウウウウウウウウウホウウウホウウホウウ
へジャンプ([LF][ウ][LF])です。0 比較時のジャンプ先としてマーク([LF][ウ][ウ])でラベル定義([ホ][ホ][ホ][ウ][ホ][ウ][ウ][ウ][ホ][ウ][ホ][ウ][ウ])しています。ループから出た後は、スタックの一番上を捨て([ウ][LF][LF])、プログラムを終了([LF][LF][LF])します。
簡単ですね!もうこれでいつ会社で「先輩、このゴリラ言語わからないので読んでもらえますか?」と聞かれても読めますよね。
ちなみにこれを実行すると以下の様に画面に表示されます。
言ってしまえば whitespace のパクリ言語なのですが、2文字書き換えるだけでオレオレ言語が作れます。「ピ」と「ヨ」と改行でヒヨコ言語を作る事も出来ます。ぜひ楽しんで下さい。
- 投稿日:2020-03-26T14:41:43+09:00
API設計について調べたことのまとめ
API設計について、最近得た知見をまとめました。私がGoで開発しているのでGoベースの話になります。
間違いがあればコメントで教えていただければ幸いです。参考資料
- GopherCon 2018 : GopherCon 2018 - How Do You Structure Your Go Apps?
- デモのソースコード : katzien/go-structure-examples
- クリーンアーキテクチャのソースコード : go-clean-arch
- SlackのGophers, StudentGoなどの得た情報
- 株式会社オージス総研 クラウドインテグレーションサービス部 : なぜAPIのアーキテクチャが重要なのか
設計
設計とは
設計は、ユーザー目線で利便性を考える 「インターフェース設計」 と 開発者目線で利便性を考える 「アーキテクチャ設計」 があります。「インターフェース設計」で最近多くみられるのはREST APIと言われる設計方法が主流です。また、「アーキテクチャ設計」では、様々なアーキテクチャがあり、自分の開発しているAPIに適したアーキテクチャを使用します。
インターフェース設計
インターフェース設計をする上で考えるべきことは、外部の大多数の開発者向けに開発する
LSUDs(Large Set of Unknown Developers)
と ある特定のアプリケーションのための開発者のためのSSKDs(Small Set of Known Developers)
の二つのどちらかなのかを明確にすることです。LSUDsでは、RESTなAPI設計が主流です。しかし、SSKDsは、必ずしもRESTなAPI設計に従って開発する必要はありません。開発しているアプリケーションによってAPI設計を考える必要があります。SSKDsで重要なことは、1画面1リクエストを意識することです。Rest (REpresentational State Transfer)
Rest設計については下記の記事が参考になりました。
翻訳: WebAPI 設計のベストプラクティスアーキテクチャ設計
アーキテクチャには数多くの種類があります。そのため、どのアーキテクチャを採用するべきか見極めることが大変難しいです。
GopherCon 2018の記事(参考資料)では、このような悩みを持つユーザーのために、アーキテクチャが紹介されていました。
この記事で紹介されている最も簡単なアーキテクチャは、 Flat structure 、 Layered、 Modular です。また、 Domain Driven Development(ドメイン駆動設計) や Hexagonal Architecture も紹介されています。この記事の他にも、 クリーンアーキテクチャ や オニオンアーキテクチャ などもあります。これらのアーキテクチャの特徴を簡単にまとめたいと思います。Flat structure
このアーキテクチャは、簡素なAPIに適しているアーキテクチャだと思われます。パッケージ分をせずに全て
package main
で統一します。そのため規模が大きくなると読みにくいアーキテクチャになると思います。Layered
このアーキテクチャは、機能ごとにディレクトリを分けています。そうすることで、packegeごとの役割を明確にすることができます。
問題点は、違うディレクトリ同士で値を共有したい場合にどうやって共有するかか問題になります。Modular
このアーキテクチャは、ハンドラごとにディレクトリを分けています。部品ごとに分けて開発をするので、追加や修正が簡単にできることが良い点だと思います。
その他
他のアーキテクチャは、また別の記事で深くまとめようと思います。
開発しているAPIに合った設計の見つけ方
自分が開発しているAPIは、どのアーキテクチャに向いているのかを見つける方法があります。その方法は、機能が増えたり規模が大きくなったらどこが問題になるかを考えることです。注意点としては、自分のAPIにカッチリ当てはまるアーキテクチャはほとんど無いので、試行錯誤する必要があります。試行錯誤には、かなりの理解が必要なので、「様々なアーキテクチャを実際に使用すること」や「実際に使用されているコードを読むこと」が重要なのではないのかと考えています。
終わりに
記事を書いていて新しく発見した知識も多かったです。自分が使用していたディレクトリ分けは、Layeredアーキテクチャに似ていると感じました。多くのアーキテクチャを学び、適しているアーキテクチャを見つけられるようになりたいです!
ありがとうございました。
- 投稿日:2020-03-26T09:00:24+09:00
GoでEd25519+SHA256のアドレス導出してみる(Symbol,Catapult,RC5)
はじめに
catapult-serverのv0.9.3.1から、公開鍵の導出方法が変わりました。
https://github.com/nemtech/catapult-server/releases/tag/v0.9.3.1
いままでは、Ed25519+Keccak256だったのが、標準のEd25519に変わりました。
ということで、GoのEd25519ライブラリが使えるんじゃないかと思うので、やってみたいと思います。
秘密鍵から公開鍵とアドレスを導出する
こちらのNIP10を参考にやっていきます。
https://github.com/nemtech/NIP/blob/eef91708a05fc6ce24a903d742ff46682008a8b7/NIPs/nip-0010.md
公開鍵
NIP10に例示してある秘密鍵と公開鍵は、
private: 575dbb3062267eff57c970a336ebbc8fbcfe12c5bd3ed7bc11eb0481d7704ced public: 2e834140fd66cf87b254a693a2c7862c819217b676d3943267156625e816ec6fなので、これをやってみる。
package main import "fmt" import "encoding/hex" import "crypto/ed25519" // https://golang.org/pkg/crypto/ed25519/ func main() { seed3, _ := hex.DecodeString("575dbb3062267eff57c970a336ebbc8fbcfe12c5bd3ed7bc11eb0481d7704ced") priv3 := ed25519.NewKeyFromSeed(seed3) var pub3 interface{} = priv3.Public() var pub3a ed25519.PublicKey = pub3.(ed25519.PublicKey) fmt.Println(hex.EncodeToString(pub3a)) }
seed3
に代入しているのが秘密鍵です。実行結果
2e834140fd66cf87b254a693a2c7862c819217b676d3943267156625e816ec6fできました。
NIP10にはオールゼロの秘密鍵も例示してあります。
private : 0000000000000000000000000000000000000000000000000000000000000000 public : 3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29
seed3
にオールゼロを代行してみます。実行結果
3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29アドレス
次にアドレスです。NIP10には以下のものが例示されています。
public : 2e834140fd66cf87b254a693a2c7862c819217b676d3943267156625e816ec6f address: TATNE7Q5BITMUTRRN6IB4I7FLSDRDWZA37JGO5UWハッシュ化アドレス
次にアドレスです。
NIP10によると、次の手順が必要です。
- 公開鍵をSHA3-256する
- それをRIPEMD160する
- ネットワークIDを付加する
- それをSHA3-256して先頭4バイトを取得する
- 3にそれを付加する
6.の「BASE32する」は次でやります。
package main import ( "encoding/hex" "fmt" "io" ) import "golang.org/x/crypto/sha3" import "golang.org/x/crypto/ripemd160" // https://godoc.org/golang.org/x/crypto/sha3 // https://godoc.org/golang.org/x/crypto/ripemd160 func main() { var publicKey string = "2e834140fd66cf87b254a693a2c7862c819217b676d3943267156625e816ec6f" pubByte, _ := hex.DecodeString(publicKey) hash1 := sha3.Sum256(pubByte) rip := ripemd160.New() _, _ = io.WriteString(rip, string(hash1[:])) hash2 := rip.Sum(nil) prefix, _ := hex.DecodeString("98") rawAddress := make([]byte, 20) copy(rawAddress, hash2) rawAddress = append(rawAddress[:1], rawAddress[0:]...) rawAddress[0] = prefix[0] checksum := sha3.Sum256(rawAddress) rawAddress = append(rawAddress, checksum[:4]...) fmt.Println(hex.EncodeToString(rawAddress)) }実行結果
9826d27e1d0a26ca4e316f901e23e55c8711db20dfd2677696この段階ではNIP10と一致しているかははっきりとしません。次に行きます。
BASE32
先ほど導出したハッシュ化アドレスをBASE32エンコーディングします。
package main import ( "encoding/base32" "encoding/hex" "fmt" ) func main() { address := "9826d27e1d0a26ca4e316f901e23e55c8711db20dfd2677696" addressByte, _ := hex.DecodeString(address) encode := base32.StdEncoding.EncodeToString(addressByte) fmt.Println(encode) }実行結果
TATNE7Q5BITMUTRRN6IB4I7FLSDRDWZA37JGO5UWNIP10と同じものが導出できました。
おわりに
Goに存在する一般的なライブラリ類を使って、秘密鍵から公開鍵とアドレスを導出することができました。
ソースの書き方がメチャクチャなのは許してください。
- 投稿日:2020-03-26T08:09:04+09:00
Golangで、デザインパターン「Decorator」を学ぶ
GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Decorator」を学ぶ"今回は、Pythonで実装した”Decorator”のサンプルアプリをGolangで実装し直してみました。
■ Decorator(デコレータ・パターン)
Decoratorパターン(デコレータ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする。
UML class and sequence diagram
UML class diagram
□ 備忘録
オブジェクトにどんどんデコレーション(飾り付け)を施していくようなデザインパターンとのことです。
スポンジケーキに、クリームを塗り、イチゴを載せればストロベリーショートケーキになるような感じです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。■ "Decorator"のサンプルプログラム
実際に、Decoratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 文字列
Hello, world.
をそのまま表示する- 文字列
Hello, world.
の前後に、#
文字を差し込んで表示する- 文字列
Hello, world.
の前後に、#
文字を差し込んで、さらに、 枠線で囲んで表示する- 文字列
HELLO
を 枠線で囲んで、前後に、*
文字を差し込んで、さらに、 枠線で2回囲んで、、、(以下、略)$ go run Main.go Hello, world. #Hello, world.# +---------------+ |#Hello, world.#| +---------------+ /+-----------+/ /|+---------+|/ /||*+-----+*||/ /||*|HELLO|*||/ /||*+-----+*||/ /|+---------+|/ /+-----------+/見た目、ハノイの塔みたいな表示になりました。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Decorator
- ディレクトリ構成
. ├── Main.go └── decorator ├── border.go └── display.go(1) Componentの役
機能を追加するときの核に役です。
Component
役は。スポンジケーキのインタフェースだけを定めます。
サンプルプログラムでは、display
構造体と、displayInterface
インタフェースが、この役を努めます。decorator/display.gopackage decorator import "fmt" type displayInterface interface { getColumns() int getRows() int getRowText(row int) string } type display struct { myDisplay displayInterface } // Show func for printing something func (d *display) Show() { str := "" for i := 0; i < d.myDisplay.getRows(); i++ { str += d.myDisplay.getRowText(i) + "\n" } fmt.Printf("%s", str) } ... (snip)(2) ConcreteComponentの役
Component
役のインタフェースを実装している具体的なスポンジケーキです。
サンプルプログラムでは、StringDisplay
構造体が、この役を努めます。decorator/display.go// StringDisplay is struct type StringDisplay struct { *display String string } // NewStringDisplay func for initalizing StringDisplay func NewStringDisplay(str string) *StringDisplay { stringDisplay := &StringDisplay{ display: &display{}, String: str, } stringDisplay.myDisplay = stringDisplay return stringDisplay } func (s *StringDisplay) getColumns() int { return len(s.String) } func (s *StringDisplay) getRows() int { return 1 } func (s *StringDisplay) getRowText(row int) string { if row == 0 { return s.String } return "" }(3) Decorator(装飾者)の役
Component
役と同じインタフェースを持ちます。そして、さらに、このDecorator
役が飾る対象となるComponent
役を持っています。この役は、自分が飾っている対象を「知っている」わけです。
サンプルプログラムでは、border
構造体が、この役を努めます。decorator/border.gopackage decorator type border struct { *display neighorDisplay displayInterface } ... (snip)(4) ConcreteDecorator(具体的な装飾者)の役
具体的な
Decorator
の役です。
サンプルプログラムでは、SideBorder
構造体と、FullBorder
構造体が、この役を努めます。decorator/border.go// SideBorder is struct type SideBorder struct { *border borderChar string } // NewSideBorder func for initalizing SideBorder func NewSideBorder(displayIf displayInterface, borderChar string) *SideBorder { sideBorder := &SideBorder{ border: &border{ display: &display{}, neighorDisplay: displayIf, }, borderChar: borderChar, } sideBorder.myDisplay = sideBorder return sideBorder } func (s *SideBorder) getColumns() int { return len(s.borderChar)*2 + s.neighorDisplay.getColumns() } func (s *SideBorder) getRows() int { return s.neighorDisplay.getRows() } func (s *SideBorder) getRowText(row int) string { return s.borderChar + s.neighorDisplay.getRowText(row) + s.borderChar }decorator/border.go// FullBorder is struct type FullBorder struct { *SideBorder } // NewFullBorder func for initalizing SideBorder func NewFullBorder(displayIf displayInterface) *FullBorder { fullBorder := &FullBorder{ SideBorder: &SideBorder{ border: &border{ display: &display{}, neighorDisplay: displayIf}}, } fullBorder.myDisplay = fullBorder return fullBorder } func (f *FullBorder) getColumns() int { return 2 + f.neighorDisplay.getColumns() } func (f *FullBorder) getRows() int { return 2 + f.neighorDisplay.getRows() } func (f *FullBorder) getRowText(row int) string { if row == 0 { return "+" + f.makeLine("-", f.neighorDisplay.getColumns()) + "+" } else if row == f.neighorDisplay.getRows()+1 { return "+" + f.makeLine("-", f.neighorDisplay.getColumns()) + "+" } else { return "|" + f.neighorDisplay.getRowText(row-1) + "|" } } func (f *FullBorder) makeLine(char string, count int) string { buf := "" for i := 0; i < count; i++ { buf += char } return buf }(5) Client(依頼人)の役
サンプルプログラムでは、
startMain
関数が、この役を努めます。Main.gopackage main import ( "fmt" "./decorator" ) func startMain() { b1 := decorator.NewStringDisplay("Hello, world.") b2 := decorator.NewSideBorder(b1, "#") b3 := decorator.NewFullBorder(b2) b1.Show() b2.Show() b3.Show() fmt.Println("") b4 := decorator.NewSideBorder( decorator.NewFullBorder( decorator.NewFullBorder( decorator.NewSideBorder( decorator.NewFullBorder( decorator.NewStringDisplay("HELLO"), ), "*", ), ), ), "/", ) b4.Show() } func main() { startMain() }■ 参考URL
- 投稿日:2020-03-26T05:48:38+09:00
Golangで、デザインパターン「Composite」を学ぶ
GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Composite」を学ぶ"今回は、Pythonで実装した”Composite”のサンプルアプリをGolangで実装し直してみました。
■ Composite(コンポジット・パターン)
Compositeパターン(コンポジット・パターン)とは、GoF (Gang of Four; 4人のギャングたち) によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Compositeパターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。UML class and object diagram
UML class diagram
□ 備忘録
書籍「増補改訂版Java言語で学ぶデザインパターン入門」の引用ですが、腹落ちしました。
ディレクトリの中には、ファイルが入っていたり、別のディレクトリ(サブディレクトリ)が入っていたりします。そしてまた、そのサブディレクトリの中には他のファイルやサブディレクトリが入っていることもあります。
ディレクトリは、そのような「入れ子」になった構造、再帰的な構造を作り出しています。
... (snip)
Compositeパターンは、このような構造を作るためのものであり、容器と中身を同一視し、再帰的な構造を作るデザインパターン■ "Composite"のサンプルプログラム
実際に、Compositeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- ルートエントリの
ディレクトリ
に、サブディレクトリ
およびファイル
を追加してみる- ルートエントリの
ディレクトリ
に、ユーザエントリのディレクトリ
を追加して、さらに、サブディレクトリ
およびファイル
を追加してみる- 敢えて、
ファイル
に、ディレクトリ
を追加して、失敗することを確認する$ go run Main.go Making root entries... /root (30000) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (0) Making user entries... /root (31500) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (1500) /root/usr/yuki (300) /root/usr/yuki/diary.html (100) /root/usr/yuki/Composite.java (200) /root/usr/hanako (300) /root/usr/hanako/memo.tex (300) /root/usr/tomura (900) /root/usr/tomura/game.doc (400) /root/usr/tomura/junk.mail (500) Occurring Exception... FileTreatmentException■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Composite
- ディレクトリ構成
. ├── Main.go └── composite ├── directory.go ├── entry.go └── file.go(1) Leaf(葉)の役
「中身」を表す役です。この役の中には、他のものを入れることができません。
サンプルプログラムでは、File
構造体が、この役を努めます。composite/file.gopackage composite import ( "fmt" ) // File is sturct type File struct { name string size int } // NewFile func for initializing File func NewFile(name string, size int) *File { return &File{ name: name, size: size, } } func (f *File) getName() string { return f.name } func (f *File) getSize() int { return f.size } // Add func for adding file func (f *File) Add(entry entry) { if err := doError(); err != nil { fmt.Println(err) } } // PrintList func for printing directory name func (f *File) PrintList(prefix string) { f.print(prefix) } func (f *File) print(prefix string) { fmt.Printf("%s/%s (%d)\n", prefix, f.getName(), f.getSize()) } func doError() error { msg := "FileTreatmentException" return fmt.Errorf("%s", msg) }(2) Composite(複合体)の役
「容器」を表す役です。
Leaf
役や、Composite
役を入れることができます。
サンプルプログラムでは、Directory
構造体が、この役を努めます。composite/directory.gopackage composite import "fmt" // Directory is sturct type Directory struct { name string directory []entry } // NewDirectory func for initializing Directory func NewDirectory(name string) *Directory { return &Directory{ name: name, } } func (d *Directory) getName() string { return d.name } func (d *Directory) getSize() int { size := 0 for _, entry := range d.directory { size += entry.getSize() } return size } // Add func for adding directory func (d *Directory) Add(entry entry) { d.directory = append(d.directory, entry) } // PrintList func for printing directory name func (d *Directory) PrintList(prefix string) { d.print(prefix) for _, e := range d.directory { e.PrintList(prefix + "/" + d.name) } } func (d *Directory) print(prefix string) { fmt.Printf("%s/%s (%d)\n", prefix, d.getName(), d.getSize()) }(3) Componentの役
Leaf
役とComposite
役を同一視するための役です。
サンプルプログラムでは、Entry
インタフェースが、この役を努めます。composite/entry.gopackage composite type entry interface { PrintList(prefix string) getSize() int }(4) Client(依頼人)の役
サンプルプログラムでは、
startMain
関数が、この役を努めます。Main.gopackage main import ( "fmt" "./composite" ) func startMain() { fmt.Println("Making root entries...") rootdir := composite.NewDirectory("root") bindir := composite.NewDirectory("bin") tmpdir := composite.NewDirectory("tmp") usrdir := composite.NewDirectory("usr") rootdir.Add(bindir) rootdir.Add(tmpdir) rootdir.Add(usrdir) bindir.Add(composite.NewFile("vi", 10000)) bindir.Add(composite.NewFile("latex", 20000)) rootdir.PrintList("") fmt.Println("") fmt.Println("Making user entries...") yuki := composite.NewDirectory("yuki") hanako := composite.NewDirectory("hanako") tomura := composite.NewDirectory("tomura") usrdir.Add(yuki) usrdir.Add(hanako) usrdir.Add(tomura) yuki.Add(composite.NewFile("diary.html", 100)) yuki.Add(composite.NewFile("Composite.java", 200)) hanako.Add(composite.NewFile("memo.tex", 300)) tomura.Add(composite.NewFile("game.doc", 400)) tomura.Add(composite.NewFile("junk.mail", 500)) rootdir.PrintList("") fmt.Println("") fmt.Println("Occurring Exception...") tmpfile := composite.NewFile("junk.mail", 500) bindir = composite.NewDirectory("bin") tmpfile.Add(bindir) } func main() { startMain() }(5) その他
エラー時の振る舞いを追加します
composite/file.gofunc doError() error { msg := "FileTreatmentException" return fmt.Errorf("%s", msg) }■ 参考URL