20200328のGoに関する記事は9件です。

golang でコマンドライン引数を使う

2種類のやり方があります

  • os パッケージの Args を使う
  • flag パッケージを使う

tl; dr

  • flag を使うのが良さそう
    • 型指定やオプショナル引数、デフォルト値の設定等、色々できるから

flag

使い方

main.go
package 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, "オプション名", "初期値", "説明")
  • 戻り値がポインタなので、使うときは実体参照する
  • オプションの指定方法
    • -flag
    • -flag=x
    • -flag x // non-boolean flags only
    • (※ハイフンは二つでも認識されるらしい)

os.Args

この辺を読む

go test で flag を使う

VSCode の拡張で使いたい

  • 以下のような設定を setting.json に記述する
  • VSCode のエディタ上のテスト関数の上に表示される run test ボタンでテスト実行した際に、自動でフラグが付加される
"go.testFlags": [
    "-flag1=hoge",
    "-flag2=fuga"
  ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

Go と C++ の可変長引数

動機

go の syscall パッケージには、

go
func 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

まあソースコードから。

go
package 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++ の可変長引数はノーコスト。
fixedvari(F) の呼び方だとインライン展開されないのでちょっと遅くて、 fixed(L)vari(L) の呼び方だとインライン展開されるので速い。可変長引数かどうかではなく、インライン展開されるかどうかが重要。

あと。関数テンプレート vari__attribute__((noinline)) を付けるとだいぶ遅くなる。再帰呼び出しが全部本当に関数呼び出しに展開されてしまうので仕方ない。

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

Kubernetesで和牛を給付する

Motivation

コンテナ管理基盤であるKubernetesには、Kubernetes Operatorという概念があり
任意のオブジェクトをKubernetes上で管理することができる。
このKubernetes Operatorを用いると和牛も管理することができるので給付してみる。

ここで、Kubernetes Operatorを開発するツールは、下記の通りいくつか存在しているが
エコシステムが充実しているOperator SDKを採用した。

Operator SDK
Kubebuilder
Metacontroller

QuickStart

環境

和牛の配り方

Quay.ioのアカウント登録

quay.png

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.io

DockerイメージのPush

docker push quay.io/$USER/wagyu-operator

Quay.ioでリポジトリをpublicにする

make_repository_public.png

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.yaml

Operator用のサービスアカウント、ロールを用意

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も対応しているので利用してみたい。

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

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のドキュメントがよめれば結構有用だと思う。

そのほかにも、単純計算パッケージである、

https://golang.org/pkg/math/

にも三角関数やπなどの無理数が定数として入っていて非常に使いやすかった。

さいごに

始めてGoを触ったのですが、オブジェクト指向に慣れ親しんだ人だと結構面を食らいます。
複素数型が入っているのに、計算を行う関数は複素数型に生えておらず全く別の関数として存在しているという点からも、Goの思想がにじみ出ていると感じました。

個人的には、複数の返り値の受け取り方や、組み込み関数の形的に静的型チェックを追加したpythonにC++を混ぜて、オブジェクト指向を削ったような印象です。

ただ、静的型チェックなども含めて非常にシンプル且つ安全なコードを書きやすいと感じました。
それではよきGoライフを

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

アプリケーション間で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.java
import 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.java
import 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.properties
logging.file.path=logs/
logging.level.org.springframework=INFO

リクエストを投げてログを確認

http://localhost:8080/api/にGETのエンドポイントを作っておく

SampleController.java
import 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.java
import 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楽しい)。
分かり辛いところ、もっと効率のいいやり方、アドバイスあればぜひコメントください

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

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

W3sDesign_Chain_of_Responsibility_Design_Pattern_UML.jpg

UML class diagram

designpattern-chain_of_responsibility01.gif
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)

□ 備忘録

Chain of Responsibilityパターンは、昔のお役所仕事的な「たらい回し」な業務スタイルを連想できます。
人に要求がやってくる。その人がそれを処理できれば処理をする。処理できないなら、その要求を「次の人」にたらい回しする。次の人がそれを処理できるなら処理する。処理できなければ、その要求を「さらに次に人」にたらい回しする。
Chain of Responsibilityパターンは、所謂、線形リストのような処理モデルと理解しました。

Chain_of_responsibility.png

■ "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.go
package 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.go
package 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.py
class Trouble:
    def __init__(self, number):
        self.__number = number

    def getNumber(self):
        return self.__number

    def __str__(self):
        return '[Trouble {0}]'.format(self.__number)

■ 参考URL

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

Golangで、デザインパターン「Proxy」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Proxy」を学ぶ"

今回は、Pythonで実装した”Proxy”のサンプルアプリをGolangで実装し直してみました。

■ Proxy(プロキシ・パターン)

Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。

UML class and sequence diagram

W3sDesign_Proxy_Design_Pattern_UML.jpg

UML class diagram

proxy.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "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.go
package 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.go
type 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.go
package 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

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

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.sql
CREATE TABLE sample(
  id VARCHAR(26) PRIMARY KEY comment 'ID'
);
20200319010701_create_table.down.sql
drop table if exists sample;
  • {version}_create_table.up.sql
  • {version}_create_table.down.sql

こんな感じで同じversionでupとdownを用意しておく

ソースコード

main.go
package main

func main() {
    infrastructure.NewSqlHandler()
}

sql_handler.go
package 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とのコネクションを確立したタイミングでマイグレーションできるっぽい。簡単

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