Help us understand the problem. What is going on with this article?

Go Patternsで学ぶGo

おはようございます。@mnumaです。
Makuake Product Team Advent Calendar 2018 3日目の記事です。
Makuake で最近支援したのは あべ養鶏場の燻製卵です。

株式会社マクアケではマイクロサービス化に伴い、バックエンドやツールにGo言語を採用することが増えています。というわけで今回はGoに関する記事を書きます。

Go Patterns

image.png (473.2 kB)

Go PatternsはGoのデザインパターン、レシピ、イディオムとサンプルコード集です。今日はこの中から勉強になったものをいくつか紹介したいと思います。

Functional Options

ここで紹介されている Functional OptionsはGo言語で柔軟性の高いオプションやデフォルト値が指定できたりするものです。
サンプルで紹介されていたものは少し複雑ったので、今回はこのパターンを使ってHTTPリクエストのオプションを組み立てるようなものを作成してみました。

オプションとオプションを定義する関数を定義します。

type RequestOptions struct {
    page    int
    perPage int
    sort    string
}

// Option型を引数にとる関数型 `Option` を定義します
type Option func(request *RequestOptions)

func Page(p int) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.page = p
        }
    }
}

func PerPage(pp int) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.perPage = pp
        }
    }
}

func Sort(s string) Option {
    return func(r *RequestOptions) {
        if r != nil {
            r.sort = s
        }
    }
}

次にコンストラクタを作ります。この際初期値とするのがデフォルト値となります。

func NewRequest(opts ...Option) *RequestOptions {
    // デフォルト値
    r := &RequestOptions{page: 1, perPage: 30, sort: "desc"}
    for _, opt := range opts {
        opt(r)
    }
    return r
}

Usage

func main() {
    r := NewRequest()
    // `Pageを設定`
    r = NewRequest(Page(10))
    fmt.Println(r)
    // `Page`と`PerPage``Sort`を設定`
    r = NewRequest(Page(10), PerPage(2), Sort("asc"))
    fmt.Println(r)
}

実行コードはこちらです。
image.png (933 B) https://play.golang.org/p/1IwBBgA-AWd

Observer Pattern

次にObserver Patternです。こちらの記事などに詳しいですが、あるオブジェクトの状態変化を、観察者(Observer)に状態変化を通知する仕組みです。

サンプル通り、EventObserver Notifierを定義していきます。

type (
    Event struct {
        Data int64
    }

    Observer interface {
        OnNotify(Event)
    }

    Notifier interface {
        Register(Observer)
        Deregister(Observer)
        Notify(Event)
    }
)
type (
    eventObserver struct{
        id int
    }

    eventNotifier struct{
        observers map[Observer]struct{}
    }
)

func (o *eventObserver) OnNotify(e Event) {
    fmt.Printf("*** Observer %d received: %d\n", o.id, e.Data)
}

func (o *eventNotifier) Register(l Observer) {
    o.observers[l] = struct{}{}
}

func (o *eventNotifier) Deregister(l Observer) {
    delete(o.observers, l)
}

func (p *eventNotifier) Notify(e Event) {
    for o := range p.observers {
        o.OnNotify(e)
    }
}

Usage

func main() {
    n := eventNotifier{
        observers: map[Observer]struct{}{},
    }

    // オブザーバーを登録します
    n.Register(&eventObserver{id: 1})
    n.Register(&eventObserver{id: 2})

    stop := time.NewTimer(10 * time.Second).C
    tick := time.NewTicker(time.Second).C
    for {
        select {
        case <- stop:
            return
        case t := <-tick:
            n.Notify(Event{Data: t.UnixNano()})
        }
    }
}
  • 図で見るとこのような感じです。

image.png (114.7 kB)

image.png (933 B) https://play.golang.org/p/fkraXpBOY83

Semaphore Pattern

最後にchannelを使ったセマフォのパターンの実装です。
こちらの例では処理のタイムアウトを意識したセマフォの紹介がされています。

var (
    ErrNoTickets      = errors.New("semaphore: could not aquire semaphore")
    ErrIllegalRelease = errors.New("semaphore: can't release the semaphore without acquiring it first")
)

type Interface interface {
    Acquire() error
    Release() error
}

type implementation struct {
    sem     chan struct{}
    timeout time.Duration
}

func (s *implementation) Acquire() error {
    select {
    case s.sem <- struct{}{}:
        return nil
    case <-time.After(s.timeout):
        return ErrNoTickets
    }
}

func (s *implementation) Release() error {
    select {
    case _ = <-s.sem:
        return nil
    case <-time.After(s.timeout):
        return ErrIllegalRelease
    }
}

func New(tickets int, timeout time.Duration) Interface {
    return &implementation{
        sem:     make(chan struct{}, tickets),
        timeout: timeout,
    }
}

Usage

func main() {
    // 同時処理数10、タイムアウト6秒
    tickets, timeout := 10, 6*time.Second
    s := New(tickets, timeout)

    for i := 0; i <= 100; i++ {
        if err := s.Acquire(); err != nil {
            panic(err)
        }

        go func(i int) {
            // なにか重い処理
            something(i)

            if err := s.Release(); err != nil {
                panic(err)
            }
        }(i)
    }
}

func something(i int) {
    fmt.Println(i)
    time.Sleep(5 * time.Second)
}

image.png (933 B) https://play.golang.org/p/5gQX-8i3S9B

まとめ

以上Go Patternsに掲載されているものについて一部紹介させて頂きました。
Go Patternsの内容としては少し説明やコードが不十分なものもあり、歯抜けになっている箇所もありますが、いわゆるデザインパターン的な実装だけでない領域も網羅していて、基礎的な部分から一歩踏み込んだ資料として、ぼんやり眺めるのにも良いかなと思いました!

最後になりますが、株式会社マクアケでは一緒に働くメンバーを募集しております。💪
ご興味が有りましたらご連絡いただけると幸いです!
https://www.wantedly.com/projects/26807

Makuake Product Team Advent Calendar 2018
まだまだ続きますので、明日以降もよろしくお願いします!

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away