おはようございます。@mnumaです。
Makuake Product Team Advent Calendar 2018 3日目の記事です。
Makuake で最近支援したのは あべ養鶏場の燻製卵です。
株式会社マクアケではマイクロサービス化に伴い、バックエンドやツールにGo言語を採用することが増えています。というわけで今回はGoに関する記事を書きます。
Go Patterns
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)
}
実行コードはこちらです。
https://play.golang.org/p/1IwBBgA-AWd
Observer Pattern
次にObserver Pattern
です。こちらの記事などに詳しいですが、あるオブジェクトの状態変化を、観察者(Observer)に状態変化を通知する仕組みです。
サンプル通り、Event``Observer
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()})
}
}
}
- 図で見るとこのような感じです。
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)
}
https://play.golang.org/p/5gQX-8i3S9B
まとめ
以上Go Patternsに掲載されているものについて一部紹介させて頂きました。
Go Patternsの内容としては少し説明やコードが不十分なものもあり、歯抜けになっている箇所もありますが、いわゆるデザインパターン的な実装だけでない領域も網羅していて、基礎的な部分から一歩踏み込んだ資料として、ぼんやり眺めるのにも良いかなと思いました!
最後になりますが、株式会社マクアケでは一緒に働くメンバーを募集しております。💪
ご興味が有りましたらご連絡いただけると幸いです!
https://www.wantedly.com/projects/26807
Makuake Product Team Advent Calendar 2018
まだまだ続きますので、明日以降もよろしくお願いします!