- 投稿日:2020-03-28T23:52:12+09:00
golang でコマンドライン引数を使う
2種類のやり方があります
tl; dr
- flag を使うのが良さそう
- 型指定やオプショナル引数、デフォルト値の設定等、色々できるから
flag
使い方
main.gopackage main import ( "flag" "fmt" ) func main() { flag.Parse() fmt.Println(flag.Args()) }$ go run main.go foo bar baz [foo bar baz]
- 解析には flag.Parse() を呼び出します
- flag.Parse の内部では os.Args[1:] が渡されて解析される
- os.Args[0:] には実行コマンドが格納されている
- Parse は全ての flag が定義された後かつ、いづれかの flag にアクセスする前にコールする必要がある
- これを守らないと
flag provided but not defined: -hogehoge
みたいなエラーが出る//github.com/golang/go/blob/master/src/flag/flag.go#L963-L966// Parse parses flag definitions from the argument list, which should not // include the command name. Must be called after all flags in the FlagSet // are defined and before flags are accessed by the program. // The return value will be ErrHelp if -help or -h were set but not defined.オプショナルな引数を使う
こんな感じpackage main import ( "fmt" "flag" "os" ) func main(){ f := flag.String("flag1", hoge, "flag 1") flag.Parse() fmt.Println("Hello %s", *f) }$ go run sample.go -flag1=fuga Hello fuga $ go run sample.go Hello hoge
- オプションを定義するには以下の二つの方法がある
- var str = flag.String("オプション名", "初期値", "説明")
- flag.StringVar(&str, "オプション名", "初期値", "説明")
- string や int 以外にもいろんな型がサポートされている(詳しくはhttps://golang.org/pkg/flag/)
- 戻り値がポインタなので、使うときは実体参照する
- オプションの指定方法
- -flag
- -flag=x
- -flag x // non-boolean flags only
- (※ハイフンは二つでも認識されるらしい)
os.Args
この辺を読む
go test で flag を使う
- 普通に
go test
コマンドにフラグを渡してやれば良い- Go1.13からテスト実行時のフラグの処理順序が変更されたっぽく、init 関数でフラグの定義をしている場合、parse する前に
testing.Init()
しないとテストが落ちるようになったらしいので注意
https://budougumi0617.github.io/2019/10/30/go-testing2019/#testing-init-%E9%96%A2%E6%95%B0%E3%81%8C%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%81%9FVSCode の拡張で使いたい
- 以下のような設定を setting.json に記述する
- VSCode のエディタ上のテスト関数の上に表示される
run test
ボタンでテスト実行した際に、自動でフラグが付加される"go.testFlags": [ "-flag1=hoge", "-flag2=fuga" ]
- 投稿日:2020-03-28T23:40:40+09:00
yukicoder contest 241 参戦記
yukicoder contest 241 参戦記
A 1009 面積の求め方
8分半で突破. ヒント通り区分求積法でスパッと解いてみた. 区間をどれくらいの数で割ればいいのかに悩んだが、適当にえいやで4096でやったら一発目で良さげな精度が出たので、提出してみたら無事 AC.
a, b = map(int, input().split()) x = a result = 0 t = 1 / 4096 while x < b: result += abs((x - a) * (x - b) * t) x += t print(result)B 1010 折って重ねて
41分で突破. 整数で処理するコードを書いて、なんで AC しないんだと延々と悩んでいた. アホすぎる. 縦と横で短い方を限界まで折ってから、長い方を限界まで折ればいい.
x, y, h = map(int, input().split()) if x < y: x, y = y, x x *= 1000 y *= 1000 result = 0 while y > h: y /= 2 h *= 2 result += 1 while x > h: x /= 2 h *= 2 result += 1 print(result)整数で処理することもできる. 相対的に数字が正しく変わればいいのだ.
x, y, h = map(int, input().split()) if x < y: x, y = y, x x *= 1000 y *= 1000 result = 0 while y > h: x *= 2 h *= 4 result += 1 while x > h: y *= 2 h *= 4 result += 1 print(result)C 1011 Infinite Stairs
敗退. 素直に DP すると O(N2d) なので TLE する. よくよく考えると、i 段目にたどり着くのは i-d .. i-1 段目、i + 1 段目にたどり着くのは i - d + 1 .. i 段目と端の2箇所以外は同じである. であれば、O(d) ではなく O(1) で処理できるので O(N2) になり解けた.
package main import ( "bufio" "fmt" "os" "strconv" ) func main() { N := readInt() d := readInt() K := readInt() buf0 := make([]int, d*N+K+1) buf1 := make([]int, d*N+K+1) buf0[0] = 1 for i := 0; i < N; i++ { t := 0 for j := 0; j < d; j++ { t += buf0[j] t %= 1000000007 buf1[j] = t } for j := d; j < (i+1)*d; j++ { t -= buf0[j-d] if t < 0 { t += 1000000007 } t += buf0[j] t %= 1000000007 buf1[j] = t } buf0, buf1 = buf1, buf0 } fmt.Println(buf0[K-N]) } const ( ioBufferSize = 1 * 1024 * 1024 // 1 MB ) var stdinScanner = func() *bufio.Scanner { result := bufio.NewScanner(os.Stdin) result.Buffer(make([]byte, ioBufferSize), ioBufferSize) result.Split(bufio.ScanWords) return result }() func readString() string { stdinScanner.Scan() return stdinScanner.Text() } func readInt() int { result, err := strconv.Atoi(readString()) if err != nil { panic(err) } return result }# PyPy なら AC N, d, K = map(int, input().split()) buf0 = [0] * (d * N + K + 1) buf1 = [0] * (d * N + K + 1) buf0[0] = 1 for i in range(N): t = 0 for j in range(d): t += buf0[j] t %= 1000000007 buf1[j] = t for j in range(d, (i + 1) * d): t -= buf0[j - d] t += buf0[j] t %= 1000000007 buf1[j] = t buf0, buf1 = buf1, buf0 print(buf0[K - N])
- 投稿日:2020-03-28T15:41:53+09:00
Go と C++ の可変長引数
動機
go の syscall パッケージには、
gofunc Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)のように、引数の数に応じて関数が用意されている。
go には可変長引数という素晴らしい仕組みがあるのになぜ? もしかして可変長引数遅いの? と思って今日も楽しいマイクロベンチマーク。
ソースコード
go
まあソースコードから。
gopackage main import ( "fmt" "os" "strconv" "time" ) func vari6(n ...uintptr) uintptr { return n[0] + n[1] + n[2] + n[3] + n[4] + n[5] } func fixed6(a, b, c, d, e, f uintptr) uintptr { return a + b + c + d + e + f } func benchV(title string, num uintptr, proc func(n ...uintptr) uintptr) { t0 := time.Now() sum := uintptr(0) for i := uintptr(0); i < num; i++ { sum += proc(i&1, i&2, i&4, i&8, i&16, i&32) } t1 := time.Now() fmt.Println(title, t1.Sub(t0), sum) } func benchF(title string, num uintptr, proc func(a, b, c, d, e, f uintptr) uintptr) { t0 := time.Now() sum := uintptr(0) for i := uintptr(0); i < num; i++ { sum += proc(i&1, i&2, i&4, i&8, i&16, i&32) } t1 := time.Now() fmt.Println(title, t1.Sub(t0), sum) } func main() { num, err := strconv.ParseInt(os.Args[1], 10, 64) if err != nil { panic(err) } for i := 0; i < 3; i++ { benchV("vari", uintptr(num), vari6) benchF("fixed", uintptr(num), fixed6) } }
vari6
を渡すためのbenchV
と、fixed6
を渡すためのbenchF
。
全く同じ内容なのに別の定義になっているのが無様だが仕方ない。c++
ついでに、 go とは全く異なる仕組みで可変長引数を実現している C++ 版も
c++17#include <chrono> #include <cstdint> #include <cstdlib> #include <iostream> using std_clock = std::chrono::high_resolution_clock; std::uintptr_t fixed6(std::uintptr_t a, std::uintptr_t b, std::uintptr_t c, std::uintptr_t d, std::uintptr_t e, std::uintptr_t f) { return a + b + c + d + e + f; } std::uintptr_t vari() { return 0; } template <typename... inttypes> // std::uintptr_t vari(std::uintptr_t v0, inttypes... rest) { return v0 + vari(rest...); } template <typename proc_t> void bench(char const *title, std::uintptr_t num, proc_t proc) { auto t0 = std_clock::now(); std::uintptr_t sum = 0; for (std::uintptr_t i = 0; i < num; ++i) { sum += proc(i & 1, i & 2, i & 4, i & 8, i & 16, i & 32); } auto t1 = std_clock::now(); auto diff_us = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count(); std::cout << title << " " << (diff_us * 1e-6) << "ms " << sum << std::endl; } int main(int argc, char const *argv[]) { std::uintptr_t num = argc < 2 ? 100 : std::atoi(argv[1]); using u = std::uintptr_t; for (std::uintptr_t i = 0; i < 3; ++i) { bench("fixed", num, fixed6); bench("fixed(L)", num, [](u a, u b, u c, u d, u e, u f) -> u { return fixed6(a, b, c, d, e, f); }); bench("vari(F)", num, vari<u, u, u, u, u>); bench("vari(L)", num, [](u a, u b, u c, u d, u e, u f) -> u { return vari(a, b, c, d, e, f); }); } }go より数行短くなっている。
C++ の場合、可変長引数関数は関数ではなく関数テンプレートなので別の関数にそのまま渡すことができない(できるかもしれなけど、やり方がわからなかった...)。
そこで。
引数の数と型をテンプレート引数で指定したり、ラムダに埋め込んだりしてみた。結果
# go ./main 10000000 | tail -2 vari 344.428796ms 315000000 fixed 40.811772ms 315000000 # C++(clang) ./a.clang.out 10000000 | tail -4 fixed 21.4052ms 315000000 fixed(L) 1.68218ms 315000000 vari(F) 21.3051ms 315000000 vari(L) 1.80019ms 315000000 # C++(g++-9) ./a.gcc9.out 10000000 | tail -4 fixed 24.349ms 315000000 fixed(L) 3.9ms 315000000 vari(F) 24.405ms 315000000 vari(L) 3.75ms 315000000なんか go と C++ で順序が違うけど気にしない。
まとめ
どうも、go の可変長引数は遅そうな感じ。
手元のマシンで一回当たり、30ナノ秒ぐらいかかっている模様。一方。C++ の可変長引数はノーコスト。
fixed
やvari(F)
の呼び方だとインライン展開されないのでちょっと遅くて、fixed(L)
やvari(L)
の呼び方だとインライン展開されるので速い。可変長引数かどうかではなく、インライン展開されるかどうかが重要。あと。関数テンプレート
vari
に__attribute__((noinline))
を付けるとだいぶ遅くなる。再帰呼び出しが全部本当に関数呼び出しに展開されてしまうので仕方ない。
- 投稿日:2020-03-28T12:44:19+09:00
Kubernetesで和牛を給付する
Motivation
コンテナ管理基盤であるKubernetesには、Kubernetes Operatorという概念があり
任意のオブジェクトをKubernetes上で管理することができる。
このKubernetes Operatorを用いると和牛も管理することができるので給付してみる。ここで、Kubernetes Operatorを開発するツールは、下記の通りいくつか存在しているが
エコシステムが充実しているOperator SDKを採用した。Operator SDK
Kubebuilder
MetacontrollerQuickStart
環境
- kind
- Docker
- Mac
和牛の配り方
Quay.ioのアカウント登録
https://github.com/operator-framework/operator-sdk#quick-start
operator-sdkを用いて和牛Operatorの雛形を作成
TMPDIR=`mktemp -d` && cd $TMPDIR mkdir $TMPDIR && cd $TMPDIR operator-sdk new wagyu-operator --repo github.com/delicious/wagyu cd wagyu-operator operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=Wagyu operator-sdk add controller --api-version=app.example.com/v1alpha1 --kind=Wagyu和牛コントローラの処理を一部書き換え
実際にはデプロイされるPodのImageを差し替えている
sed 's|Image: "busybox"|Image: "quay.io/iaoiui727/wagyu:latest"|g' -i pkg/controller/wagyu/wagyu_controller.go和牛OperatorのDockerイメージ作成
export USER=<quay.ioで作成したユーザ名> operator-sdk build quay.io/$USER/wagyu-operator docker login quay.ioDockerイメージのPush
docker push quay.io/$USER/wagyu-operatorQuay.ioでリポジトリをpublicにする
Kubernetesクラスタの構築
kind create cluster和牛Operatorのマニフェスト内で、先ほどquayにアップロードしたDockerイメージを使うよう変更
sed -i "/Command/d" pkg/controller/wagyu/wagyu_controller.go sed -i "s|REPLACE_IMAGE|quay.io/$USER/wagyu-operator|g" deploy/operator.yamlOperator用のサービスアカウント、ロールを用意
kubectl create -f deploy/service_account.yaml kubectl create -f deploy/role.yaml kubectl create -f deploy/role_binding.yaml和牛のCRD(Custom Resource Definition)を生成
和牛というCRDリソースがKubernetesに登録される
kubectl create -f deploy/crds/app.example.com_wagyus_crd.yaml和牛というCRDリソースの定義を確認できるようになる
k get wagyu NAME AGE example-wagyu 1m和牛Operatorを起動
kubectl create -f deploy/operator.yaml和牛Operatorの起動を確認
kubectl get po NAME READY STATUS RESTARTS AGE wagyu-operator-7d87f94966-c7rbt 1/1 Running 0 18s和牛インスタンスを起動
kubectl create -f deploy/crds/app.example.com_v1alpha1_wagyu_cr.yaml正常に起動していることを確認
kubectl get pod -l app=example-wagyu kubectl describe appservice example-appservice給付された和牛を確認
kubectl logs example-wagyu-pod ?Clean up
kind delete clusterまとめ
Operator SDKを用いてKubernetes上で和牛を給付できた
今回はGo言語で検証したが、Operator SDKではAnsible, Helmも対応しているので利用してみたい。
- 投稿日:2020-03-28T12:01:38+09:00
Goの複素数を触ってみた
自分でGo関連の情報を収集してたら、Goだと複素数が組み込みで入ってるらしいので、触ってみることにした。
やること
シンプルに複素数の積と偏角の和の性質を確かめる。
その過程で使える便利関数を探してみる。角度計算をしてみたコード
package main import ( "fmt" "math" ) func main() { const X complex128 = complex(1,1) ratio := real(X)/imag(X) // 実部と虚部を取り出して比を計算 const radian := math.Atan(ratio) // 比からタンジェントの逆関数で偏角を計算 fmt.Println(radian * 180 / math.Pi) // ラジアンから度数に変換 45度になる }複素数型である complex128 を宣言して、
real関数: 実部を取得
imag関数: 虚部を取得と、組み込み関数で簡単に取り出せるのはすごく楽
ただ、探した感じラジアンを度数法に変換する組み込み関数は見つからなかったので、そこは自分で実装してみた。複素数の積を計算してみたコード
package main import ( "fmt" "math/cmplx" "math" ) func main() { const X complex128 = complex(1,1) const Y complex128 = complex(2,2) const multi = X * Y // 乗算 _, theta := cmplx.Polar(multi) //極座標の形で距離と偏角を返す fmt.Println(theta * 180 / math.Pi) // ラジアンから度数に変換 90度になる }二つの複素数の積は二つの複素数の偏角の和となるかをさくっと検証してみたコード。
今回の乗算結果の複素数は90度になるので、普通にタンジェントだと計算できない(0除算できない)が、cmplx.Polarで極座標の形で返した場合はちゃんと90度が返ってきた
極座標変換を実装するのは面倒臭いので、組み込み関数で行えるのは本当にありがたい。
複素数の感想
https://golang.org/pkg/math/cmplx/
上記の複素数計算パッケージを見る限り、一通りの計算方法が実装されていて、複素数の性質とGoのドキュメントがよめれば結構有用だと思う。
そのほかにも、単純計算パッケージである、
にも三角関数やπなどの無理数が定数として入っていて非常に使いやすかった。
さいごに
始めてGoを触ったのですが、オブジェクト指向に慣れ親しんだ人だと結構面を食らいます。
複素数型が入っているのに、計算を行う関数は複素数型に生えておらず全く別の関数として存在しているという点からも、Goの思想がにじみ出ていると感じました。個人的には、複数の返り値の受け取り方や、組み込み関数の形的に静的型チェックを追加したpythonにC++を混ぜて、オブジェクト指向を削ったような印象です。
ただ、静的型チェックなども含めて非常にシンプル且つ安全なコードを書きやすいと感じました。
それではよきGoライフを
- 投稿日:2020-03-28T09:31:58+09:00
アプリケーション間でRequestIdを送りあってみる
やりたいこと
サービスが複数またがってている場合、運用時にサービス間を跨いだログを見たくなる時があるので、その方法をかく。
順番は
1. Spring BootでRequestIdを生成して、ログファイルに出しておく
2. 他アプリケーション(今回はGin)に対して、リクエストを投げる
3. Gin側でRequestIdを受け取るソース
Spring Boot
Gin環境
- Java
- Spring Boot
- Go
- Gin
Spring Boot側にRequestIdの設定を入れる
ログファイルにRequestIdが表示されるようにする
MDCを使用して、リクエスト毎にUUIDを生成して、ログに仕込めるようにしておく
SampleFilter.javaimport org.slf4j.MDC; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.UUID; public class SampleFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestIdKey = "X-REQUEST-ID"; String uuid = UUID.randomUUID().toString(); // どこからでもリクエストIDが取得できるように設定しておく request.setAttribute(requestIdKey, uuid); try { // ログに出力できるように設定 MDC.put(requestIdKey, uuid); filterChain.doFilter(request, response); } finally { MDC.remove(requestIdKey); } } }Filterを使えるようにBeanに登録する
/api
配下にフィルターがかかるように設定するSampleConfiguration.javaimport org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SampleConfiguration { @Bean public FilterRegistrationBean<SampleFilter> hogeFilter() { FilterRegistrationBean<SampleFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new SampleFilter()); bean.addUrlPatterns("/api/*"); bean.setOrder(Integer.MIN_VALUE); return bean; } }Logbackの設定
SampleFilter.java
で定義したX-REQUEST-ID
を<pattern>
にも設定するlogback-spring.xml<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <springProperty name="loggingFilePath" source="logging.file.path" /> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${loggingFilePath}/spring.log</file> <encoder> <pattern>[%-5level] [%X{X-REQUEST-ID}] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%logger{36}] - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> </configuration>今回はINFO以上のレベルしか出さないようにする
application.propertieslogging.file.path=logs/ logging.level.org.springframework=INFOリクエストを投げてログを確認
http://localhost:8080/api/
にGETのエンドポイントを作っておくSampleController.javaimport org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api") public class SampleController { private static final Logger logger = LoggerFactory.getLogger(SampleController.class); @GetMapping public String get() { logger.info("info!"); return "get"; } }リクエストの結果のログファイル
spring.log[INFO ] [2389c607-f5ed-4789-95a3-efd78be1e8d9] [2020-XX-XX 23:26:25.536] [c.e.log.logdemo.SampleController] - info!
2389c607-f5ed-4789-95a3-efd78be1e8d9
が生成したUUID
ちゃんと出てるっぽいねRestTemplateで他サービスにリクエストする時にRequestIdを付与する
RestTemplateを使えるようにBeanに登録しておく
(特にここで定義する必要はなかったですが楽だったので)LogDemoApplication.java@SpringBootApplication public class LogDemoApplication { public static void main(String[] args) { SpringApplication.run(LogDemoApplication.class, args); } @Bean public RestTemplate setRestTemplate(){ return new RestTemplate(); } }他アプリケーションに投げるためのエンドポイントを用意しておく
SampleController.javaimport org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @RestController @RequestMapping("api") public class SampleController { private static final Logger logger = LoggerFactory.getLogger(SampleController.class); // 追記 RestTemplate restTemplate; // 追記 public SampleController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping public String get() { logger.info("info!"); return "get"; } // 追記 @GetMapping("to-other-app") public String toOtherApp() { String requestId = (String) RequestContextHolder .getRequestAttributes() .getAttribute("X-REQUEST-ID", RequestAttributes.SCOPE_REQUEST); logger.info("他のアプリケーションにリクエスト投げます!"); HttpHeaders headers = new HttpHeaders(); headers.set("X-REQUEST-ID", requestId); HttpEntity<String> entity = new HttpEntity<>("headers", headers); ResponseEntity<OtherAppResponse> response = restTemplate.exchange("http://localhost/api", HttpMethod.GET, entity, OtherAppResponse.class); return response.getBody().getValue(); } }これでSpring Boot側の設定は完了です!
Gin側のアプリケーションを作成する
別で書いたの記事のものを流用します
docker-compose up -d
を叩くだけなので、簡単
http://localhost:80/api
にアクセスすると、{"key": "value"}
が返ってくるだけのAPIがありますお互いのログを確認
Spring Bootは
http://localhost:8080/
Giはhttp://localhost:80/
で起動しますSpring Boot側でリクエスト
http://localhost:8080/api/to-other-app
にアクセスspring.log[INFO ] [549ef61a-44c9-4fe4-9c0f-3d923976d32f] [2020-XX-XX 00:19:00.274] [c.e.log.logdemo.SampleController] - 他のアプリケーションにリクエスト投げます!ログが見えました
リクエストIDは549ef61a-44c9-4fe4-9c0f-3d923976d32f
Gin側のログファイルを確認
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /api --> main.main.func1 (4 handlers) [GIN-debug] Listening and serving HTTP on :3000 [GIN] 2020/XX/XX - 15:16:44 | 200 | 1.005ms | 172.19.0.3 | GET "/api" {"time":"2020-XX-XXT15:19:00.3482677Z","level":"-","prefix":"-","file":"main.go","line":"30","message":"RequestId=549ef61a-44c9-4fe4-9c0f-3d923976d32f"}送られてきたリクエストIDは
549ef61a-44c9-4fe4-9c0f-3d923976d32f
どうやら一致したようですね(日付はぼかしてます)まとめ
さらっとやりましたが、Spring BootでリクエストIDをどこでも使えるようにするだとか、Ginでアプリケーション作るだとかに時間がかかりました。Spring Boot側の
RequestContextHolder
とか、「これでええんかな...」感はありますが、他にやり方が浮かばなかったので、もし知っていたら教えていただけるととても嬉しいです。Gin側でSpring Bootに返すのはHeader設定するだけで簡単っぽいので割愛しました。なんでGinなのか、ギョームではSpring Boot + NodeJSで実現してますが、最近Goに興味が湧いたので(Go楽しい)。
分かり辛いところ、もっと効率のいいやり方、アドバイスあればぜひコメントください
- 投稿日:2020-03-28T09:02:43+09:00
Golangで、デザインパターン「Chain of Responsibility」を学ぶ
GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Chain of Responsibility」を学ぶ"今回は、Pythonで実装した”Chain of Responsibility”のサンプルアプリをGolangで実装し直してみました。
■ Chain of Responsibility(チェーン オブ レスポンシビリティ・パターン)
「Chain of Responsibility」という英単語は、「責任の連鎖」を意味します。
このパターンは、ある要求の受取り対象となる複数のオブジェクトに鎖状の関係を構築し、要求を処理する事が可能なオブジェクトに渡るまで、順次、構築した鎖状の関係に沿って要求を受流していくパターンです。
このパターンを適用すると、「この要求はこのオブジェクトが処理する」などという司令塔的な役割り(結び付き)を利用者側が意識しなくて良くなり、利用者側は「連鎖関係にある任意のオブジェクトに要求を投げるだけ」、処理側は「流れてきた要求が自身で処理できる場合は処理し、できない場合は、その要求を次のオブジェクトに渡すだけ」という役割分担が明確となります。(利用者側は、処理の詳細まで意識する必要はない)UML class and sequence diagram
UML class diagram
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)□ 備忘録
Chain of Responsibility
パターンは、昔のお役所仕事的な「たらい回し」な業務スタイルを連想できます。
人に要求がやってくる。その人がそれを処理できれば処理をする。処理できないなら、その要求を「次の人」にたらい回しする。次の人がそれを処理できるなら処理する。処理できなければ、その要求を「さらに次に人」にたらい回しする。
Chain of Responsibility
パターンは、所謂、線形リストのような処理モデルと理解しました。■ "Chain of Responsibility"のサンプルプログラム
実際に、Chain of Responsibilityパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- トラブル解決に必要な案件に対して、
トラブル番号
を採番して、以下のトラブル解決のたらい回しな業務フローに沿って、トラブルを解決する- トラブルが解決した場合は、その解決結果を出力する
- トラブルが解決できなければ、最終的に、"解決できず"で終了する
<トラブル解決のたらい回しな業務フロー>
(1) 事前に、たらい回しの順番を決めておく("Alice"->"Bob"->"Charlie"->"Diana"->"Elmo"->"Fred")
(2) まずは、Alice
がトラブル解決の要求を受け付ける。Alice
は、サポートしない役割なので、次のBod
に要求をたらい回しする
(3)Bod
が要求を受け付ける。Bod
は、制限付きサポート(Trouble番号の上限"100")の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のCharlie
に要求をたらい回しする
(4)Charlie
が要求を受け付ける。Charlie
は、特別サポート(Trouble番号が"429"のみ)の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のDiana
に要求をたらい回しする
(5)Diana
が要求を受け付ける。Diana
は、制限付きサポート(Trouble番号の上限"200")の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のElmo
に要求をたらい回しする
(6)Elmo
が要求を受け付ける。Elmo
は、Trouble番号が奇数のみサポートの役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のFred
に要求をたらい回しする
(7)Fred
が要求を受け付ける。Fred
は、制限付きサポート(Trouble番号の上限"300")の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、最終的に、"解決できず"で終了する$ go run Main.go [Trouble 0] is resolved by [Bob]. [Trouble 33] is resolved by [Bob]. [Trouble 66] is resolved by [Bob]. [Trouble 99] is resolved by [Bob]. [Trouble 132] is resolved by [Diana]. [Trouble 165] is resolved by [Diana]. [Trouble 198] is resolved by [Diana]. [Trouble 231] is resolved by [Elmo]. [Trouble 264] is resolved by [Fred]. [Trouble 297] is resolved by [Elmo]. [Trouble 330] cannot be resolved. [Trouble 363] is resolved by [Elmo]. [Trouble 396] cannot be resolved. [Trouble 429] is resolved by [Charlie]. [Trouble 462] cannot be resolved. [Trouble 495] is resolved by [Elmo].なんか、よくわかりづらい出力結果になりました。
サンプルプログラムの出力結果を理解するには、サンプルプログラムの詳細を確認する方が早そうです。■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Chain_of_Responsibility
- ディレクトリ構成
. ├── Main.go └── chain_of_responsibility ├── support.go └── trouble.go(1) Handler(処理者)の役
Handler
役は、要求を処理するインタフェースを定める役です。
「次の人」を保持しておき、自分で処理ができない要求がきたら、その人にたらい回しします。もちろん、「次の人」もHandler
役です。
サンプルプログラムでは、support
構造体とsupportInterface
インタフェースが、この役を努めます。
要求を処理するメソッドは、Handle
メソッドでした。chain_of_responsibility/support.gopackage chainOfResponsibility import "fmt" type supportInterface interface { resolve(trouble *Trouble) bool Handle(trouble *Trouble) SetNext(next supportInterface) supportInterface } type support struct { name string own supportInterface next supportInterface } // SetNext func for relating to next Supporter func (s *support) SetNext(next supportInterface) supportInterface { s.next = next return next } // Handle func for handling trouble func (s *support) Handle(trouble *Trouble) { if s.own.resolve(trouble) { s.done(trouble) } else if s.next != nil { s.next.Handle(trouble) } else { s.fail(trouble) } } func (s *support) print() string { return fmt.Sprintf("[%s]", s.name) } func (s *support) done(trouble *Trouble) { fmt.Printf("%s is resolved by %s.\n", trouble.print(), s.print()) } func (s *support) fail(trouble *Trouble) { fmt.Printf("%s cannot be resolved.\n", trouble.print()) }(2) ConcreteHandler(具体的処理者)の役
ConcreteHandler
役は、要求を処理する具体的な役です。
サンプルプログラムでは、NoSupport
,LimitSupport
,OddSupport
,SpecialSupport
の各構造体が、この役を努めます。chain_of_responsibility/support.go// NoSupport is struct type NoSupport struct { *support } // NewNoSupport func for initializing NoSupport func NewNoSupport(name string) *NoSupport { noSupport := &NoSupport{ support: &support{ name: name, }, } noSupport.own = noSupport return noSupport } func (n *NoSupport) resolve(trouble *Trouble) bool { return false }chain_of_responsibility/support.go// LimitSupport is struct type LimitSupport struct { *support limit int } // NewLimitSupport func for initializing LimitSupport func NewLimitSupport(name string, limit int) *LimitSupport { limitSupport := &LimitSupport{ support: &support{ name: name, }, limit: limit, } limitSupport.own = limitSupport return limitSupport } func (l *LimitSupport) resolve(trouble *Trouble) bool { if trouble.getNumber() < l.limit { return true } return false }chain_of_responsibility/support.go// OddSupport is struct type OddSupport struct { *support } // NewOddSupport func for initializing OddSupport func NewOddSupport(name string) *OddSupport { oddSupport := &OddSupport{ support: &support{ name: name, }, } oddSupport.own = oddSupport return oddSupport } func (o *OddSupport) resolve(trouble *Trouble) bool { if trouble.getNumber()%2 == 1 { return true } return false }chain_of_responsibility/support.go// SpecialSupport is struct type SpecialSupport struct { *support number int } // NewSpecialSupport func for initializing SpecialSupport func NewSpecialSupport(name string, number int) *SpecialSupport { specialSupport := &SpecialSupport{ support: &support{ name: name, }, number: number, } specialSupport.own = specialSupport return specialSupport } func (s *SpecialSupport) resolve(trouble *Trouble) bool { if trouble.getNumber() == s.number { return true } return false }(3) Client(依頼人)の役
Client
役は、最初のConcreteHandler
役に要求を出す役です。
サンプルプログラムでは、startMain
関数が、この役を努めます。Main.gopackage main import ( chainOfResponsibility "./chain_of_responsibility" ) func startMain() { alice := chainOfResponsibility.NewNoSupport("Alice") bob := chainOfResponsibility.NewLimitSupport("Bob", 100) charlie := chainOfResponsibility.NewSpecialSupport("Charlie", 429) diana := chainOfResponsibility.NewLimitSupport("Diana", 200) elmo := chainOfResponsibility.NewOddSupport("Elmo") fred := chainOfResponsibility.NewLimitSupport("Fred", 300) alice.SetNext(bob).SetNext(charlie).SetNext(diana).SetNext(elmo).SetNext(fred) for i := 0; i < 500; i += 33 { alice.Handle(chainOfResponsibility.NewTrouble(i)) } } func main() { startMain() }(4) その他
トラブル番号を一元管理します。
trouble.pyclass Trouble: def __init__(self, number): self.__number = number def getNumber(self): return self.__number def __str__(self): return '[Trouble {0}]'.format(self.__number)■ 参考URL
- 投稿日:2020-03-28T05:54:26+09:00
Golangで、デザインパターン「Proxy」を学ぶ
GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Proxy」を学ぶ"今回は、Pythonで実装した”Proxy”のサンプルアプリをGolangで実装し直してみました。
■ Proxy(プロキシ・パターン)
Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。UML class and sequence diagram
UML class diagram
■ "Proxy"のサンプルプログラム
実際に、Proxyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- まず、プリンタの代理人として、"Alice"を受け付ける
- プリンタ本体が使用できるよう準備する(所要時間は、約10秒)
- 実際に、プリンタを使って、以下のプリントアウト処理"Nice to meet you"を実施する。
- つぎに、プリンタの代理人として、"Bob"を受け付ける
- 実際に、プリンタを使って、以下のプリントアウト処理"Hello, World"を実施する。
<プリントアウト処理>
(1) 現在のプリンタの代理人を、プリンタ使用者として扱う
(2) 前後に"==="で囲って、プリンタ使用者の名前を表示する
(3) 文字列を表示する。$ go run Main.go Printer代理人の名前は現在(Alice)です Printerのインスタンス(Alice)を作成中..........完了 === Printer使用者(Alice) === Nice to meet you Printer代理人の名前は現在(Bob)です === Printer使用者(Bob) === Hello, Worldここでの確認ポイントは、Printerのインスタンス(xxx)を作成中..........完了"の箇所が、一度しか表示されていない点です。
実際のサンプルプログラムでは、複雑なオブジェクトのインスタンスを一度だけ生成できるよう、Singletonパターンを活用しています。■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Proxy
- ディレクトリ構成
. ├── Main.go └── proxy └── printer_proxy.go(1) Subject(主体)の役
Proxy
役とRealSubject
役を同一視するためのインタフェースを定めます。
Subject
役があるおかげで、Client
役は、Proxy
役とRealProxy
役の違いを意識する必要がありません。
サンプルプログラムでは、Printable
インタフェースが、この役を努めます。proxy/printer_proxy.gopackage proxy import ( "fmt" "time" ) // Printable is inteface type Printable interface { SetPrinterName(name string) GetPrinterName() string MyPrint(str string) }(2) Proxy(代理人)の役
Proxy
役はClient
役からの要求をできるだけ処理します。
もしも、自分だけで処理できなかったら、Proxy
役はRealSubject
役に仕事をお任せします。
Proxy
役は、本当にRealSubject
役が必要になってからRealSubject
役を生成します。
Proxy
役は、Subject
役で定められているインタフェースを実装しています。
サンプルプログラムでは、PrinterProxy
構造体が、この役を努めます。proxy/printer_proxy.go// PrinterProxy is struct type PrinterProxy struct { name, real string } // NewPrinterProxy func for initializing PrinterProxy func NewPrinterProxy(name string) *PrinterProxy { return &PrinterProxy{ name: name, } } // SetPrinterName func for setting name in printer func (p *PrinterProxy) SetPrinterName(name string) { p.name = name } // GetPrinterName func for fetching name func (p *PrinterProxy) GetPrinterName() string { return p.name } // MyPrint func for printing something func (p *PrinterProxy) MyPrint(str string) { real := getPrinter(p.name) real.MyPrint(str) }(3) RealSubject(実際の主体)の役
「代理人」の
Proxy
役では手が負えなくなったときに登場するのが、「本人」のRealSubject
役です。
この役も、Proxy
役と同じくSubject
役で定められているインタフェースを実装しています。
サンプルプログラムでは、printer
構造体が、この役を努めます。proxy/printer_proxy.gotype printer struct { name string } var instance *printer func getPrinter(name string) *printer { if instance == nil { instance = newPrinter(name) } else { instance.name = name } return instance } func newPrinter(name string) *printer { prt := &printer{name: name} prt.heavyJob(fmt.Sprintf("Printerのインスタンス(%s)を作成中", name)) return prt } func (p *printer) SetPrinterName(name string) { p.name = name } func (p *printer) GetPrinterName() string { return p.name } func (p *printer) MyPrint(str string) { fmt.Printf("=== Printer使用者(%s) ===\n", p.name) fmt.Println(str) fmt.Println("") } func (p *printer) heavyJob(msg string) { fmt.Printf(msg) for i := 0; i < 10; i++ { time.Sleep(time.Second * 1) fmt.Printf(".") } fmt.Println("完了") }(4) Client(依頼人)の役
Proxy
役を利用する役です。
サンプルプログラムでは、startMain
関数が、この役を努めます。Main.gopackage main import ( "fmt" "./proxy" ) func startMain(p proxy.Printable) { fmt.Printf("Printer代理人の名前は現在(%s)です\n", p.GetPrinterName()) p.MyPrint("Nice to meet you") p.SetPrinterName("Bob") fmt.Printf("Printer代理人の名前は現在(%s)です\n", p.GetPrinterName()) p.MyPrint("Hello, World") } func main() { startMain(proxy.NewPrinterProxy("Alice")) }■ 参考URL
- 投稿日:2020-03-28T01:21:23+09:00
gormとgolang-mitateでオートマイグレーションをやってみた
gormとgolang-mitateでオートマイグレーションをやってみた
goのマイグレーション探してたらgolang-migrateが良さそうだったので使ってみた
https://github.com/golang-migrate/migrate
今回やったのはgormを使った場合の実装
https://github.com/jinzhu/gormパッケージ構成
. ├── cmd │ └── main.go ├── internal │ └── infrastructure │ └── sql_handler.go └── migrations ├── 20200319010701_create_table.down.sql └── 20200319010701_create_table.up.sqlマイグレーションファイル
20200319010701_create_table.up.sqlCREATE TABLE sample( id VARCHAR(26) PRIMARY KEY comment 'ID' );20200319010701_create_table.down.sqldrop table if exists sample;
- {version}_create_table.up.sql
- {version}_create_table.down.sql
こんな感じで同じversionでupとdownを用意しておく
ソースコード
main.gopackage main func main() { infrastructure.NewSqlHandler() }sql_handler.gopackage infrastructure func NewSqlHandler() *gorm.DB { user := "root" password := "secret" host := "localhost" port := "3306" dbname := "db" url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", user, password, host, port, dbname) db, err := gorm.Open("mysql", url) driver, _ := mysql.WithInstance(db.DB(), &mysql.Config{}) m, _ := migrate.NewWithDatabaseInstance( "file://migrations", // マイグレーションファイルがあるディレクトリの指定 "mysql", driver, ) m.Steps(2) if err != nil { log.Fatalln(err) } return db }起動
$go run cmd/server.go今回はmysqlで実装したんだけど、
?multiStatements=true
のパラメータつけてクライアントを作成しないといけないっぽい。パラメータなしだと上手くできなかった。これだけで起動時にDBとのコネクションを確立したタイミングでマイグレーションできるっぽい。簡単