GOでCQRSパターンを使用できますか?

パターン(CQRS-コマンドとクエリの責任分離)は、データを変更または追加するためのコマンドからデータを読み取るための分離コマンドです。 これにより、最大のパフォーマンス、スケーラビリティ、セキュリティを実現できます。また、時間の経過に伴うシステムの変更に対する柔軟性を高め、通常はドメインレベルでのデータ処理によって引き起こされるシステムのロジックを複雑にする際のエラーの数を減らすことができます。



このパターンは新しいものではなく、Eiffel言語に属します。 しかし、 グレッグヤングマーティンファウラーの努力のおかげで、特に.NETの世界で生まれ変わりました。



私自身の個人的な経験から、このパターンは非常に便利であり、ビジネスロジックを分離するのは非常に簡単で、非常によく維持され、初心者が簡単に「消化」でき、エラーの分離も得意です。 ハブで彼に関する多くの情報が書かれています 次に、このパターンをGoに移植することに完全に集中することにしました。



それでは始めましょう...



CQRSは必ずしもイベントソーシングを意味するわけではありませんが、CQRS + DDD +イベントソーシングの組み合わせで使用されることが多いのは、後者の実装が単純であるためです。 Event Sourcingを使用して、または使用せずにプロジェクトを試してみましたが、どちらの場合もCQRSパターンはビジネスロジックでうまくいきましたが、Event Sourcingを使用すると、非正規化データベース、データの保存および読み取りの速度を実装するときに独自の利点がもたらされることに注意してくださいデータの理解、移行、レポート作成を複雑にします。

簡単に言えば、イベントソーシングではCQRSを使用する必要がありますが、その逆は不要です。

したがって、記事を複雑にしないために、イベントソーシングについては触れません。



コマンドを処理するには、バスが必要です。 基本インターフェースを定義する



type EventBus interface { Subscribe(handler handlers.Handler) error Publish(eventName string, args ...interface{}) Unsubscribe(eventName string) error }
      
      





また、コマンド処理ロジックをどこかに保存する必要があります。 これを行うには、ハンドラーインターフェイスを定義します-ハンドラー



 type Handler interface { Event() string Execute(... interface{}) error OnSubscribe() OnUnsubscribe() }
      
      





多くのコントラクト言語では、通常、コマンドのインターフェイスが追加されますが、Goでのインターフェイスの実装により、これは意味をなさず、コマンドはイベント(多くのハンドラーで処理できるイベント)に置き換えられます。



すでにお気付きの場合は、特定のタイプではなく、Publishメソッド(eventName文字列、args ... interface {})がPublishメソッドに渡されます。 その結果、任意のタイプまたはタイプのセットをメソッドに渡すことができます。



EventBusの完全な実装は次のようになります。



 type eventBus struct { mtx sync.RWMutex handlers map[string][] handlers.Handler } // Execute appropriate handlers func (b *eventBus) Publish(eventName string, args ...interface{}) { b.mtx.RLock() defer b.mtx.RUnlock() if hs, ok := b.handlers[eventName]; ok { rArgs := createArgs(args) for _, h := range hs { // In case of Closure "h" variable will be reassigned before ever executed by goroutine. // Because if this you need to save value into variable and use this variable in closure. h_in_goroutine := h go func() { //Handle Panic in Handler.Execute. defer func() { if err := recover(); err != nil { log.Printf("Panic in EventBus.Publish: %s", err) } }() h_in_goroutine.Execute(rArgs) }() } } } // Subscribe Handler func (b *eventBus) Subscribe(h handlers.Handler) error { b.mtx.Lock() //Handle Panic on adding new handler defer func() { b.mtx.Unlock() if err := recover(); err != nil { log.Printf("Panic in EventBus.Subscribe: %s", err) } }() if h == nil { return errors.New("Handler can not be nil.") } if len(h.Event()) == 0 { return errors.New("Handlers with empty Event are not allowed.") } h.OnSubscribe() b.handlers[h.Event()] = append(b.handlers[h.Event()], h) return nil } // Unsubscribe Handler func (b *eventBus) Unsubscribe(eventName string) error { b.mtx.Lock() //Handle Panic on adding new handler defer func() { b.mtx.Unlock() if err := recover(); err != nil { log.Printf("Panic in EventBus.Unsubscribe: %s", err) } }() if _, ok := b.handlers[eventName]; ok { for i, h := range b.handlers[eventName] { if h != nil { h.OnUnsubscribe() b.handlers[eventName] = append(b.handlers[eventName][:i], b.handlers[eventName][i+1:]...) } } return nil } return fmt.Errorf("event are not %s exist", eventName) } func createArgs(args []interface{}) []reflect.Value { reflectedArgs := make([]reflect.Value, 0) for _, arg := range args { reflectedArgs = append(reflectedArgs, reflect.ValueOf(arg)) } return reflectedArgs } // New creates new EventBus func New() EventBus { return &eventBus{ handlers: make(map[string][] handlers.Handler), } }
      
      





Publishメソッド内では、ゴルーチンにラップされたイベントハンドラーメソッドが、予期しない状況の場合にパニック処理で呼び出されます。



ご覧のとおり、実装は非常に単純で、.NETまたはJavaで実装できるよりもはるかに単純です。



次の例で完全なコードをダウンロードできます: github



All Articles