|
5 | 5 | "errors"
|
6 | 6 | "github.com/IBM/sarama"
|
7 | 7 | "github.com/violetpay-org/event-queue/queue"
|
| 8 | + "log" |
8 | 9 | )
|
9 | 10 |
|
10 | 11 | type ConsumeOperator[Msg any] struct {
|
@@ -100,6 +101,148 @@ func (k *ConsumeOperator[Msg]) StopConsume() error {
|
100 | 101 | return nil
|
101 | 102 | }
|
102 | 103 |
|
| 104 | +// AckConsumeOperator 는 Acknowledge (메세지 브로커에게 보내는 메세지 정상 수신 응답) 을 다루는 컨슈머입니다. 실행되는 흐름은 다음과 같습니다. |
| 105 | +// |
| 106 | +// 1. 내부 컨슈머가 메세지를 받습니다. |
| 107 | +// |
| 108 | +// 2. 애크 콜백 함수 (AckCallback) 를 호출합니다. |
| 109 | +// 만약 AckCallback 함수가 true 를 반환하면, 메세지를 정상적으로 수신했음을 브로커에 알립니다. 이후 다음 과정으로 진행합니다. |
| 110 | +// 만약 AckCallback 함수가 false 를 반환하면, 메세지를 수신했음을 브로커에 알리지 않습니다. 이후 다음 과정으로 진행하지 않고 즉시 처리 과정을 종료합니다. |
| 111 | +// |
| 112 | +// 3. 콜백 함수 (Callback) 를 호출합니다. |
| 113 | +// |
| 114 | +// 4. 종료합니다. |
| 115 | +type AckConsumeOperator[Msg any] struct { |
| 116 | + serializer queue.MessageSerializer[*sarama.ConsumerMessage, Msg] |
| 117 | + |
| 118 | + ackCallback queue.AckCallback[Msg] |
| 119 | + callback queue.Callback[Msg] |
| 120 | + |
| 121 | + initialized bool |
| 122 | + cancel *context.CancelFunc |
| 123 | + |
| 124 | + consumer *MarkableConsumer |
| 125 | + brokers []string |
| 126 | + topic string |
| 127 | + groupId string |
| 128 | + config *sarama.Config |
| 129 | +} |
| 130 | + |
| 131 | +func NewAckConsumeOperator[Msg any](serializer queue.MessageSerializer[*sarama.ConsumerMessage, Msg], ackCallback queue.AckCallback[Msg], callback queue.Callback[Msg], brokers []string, topic string, groupId string, config *sarama.Config) *AckConsumeOperator[Msg] { |
| 132 | + return &AckConsumeOperator[Msg]{ |
| 133 | + serializer: serializer, |
| 134 | + ackCallback: ackCallback, |
| 135 | + callback: callback, |
| 136 | + brokers: brokers, |
| 137 | + topic: topic, |
| 138 | + groupId: groupId, |
| 139 | + config: config, |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +func (k *AckConsumeOperator[Msg]) QueueName() string { |
| 144 | + return k.topic |
| 145 | +} |
| 146 | + |
| 147 | +func (k *AckConsumeOperator[Msg]) Serializer() queue.MessageSerializer[*sarama.ConsumerMessage, Msg] { |
| 148 | + return k.serializer |
| 149 | +} |
| 150 | + |
| 151 | +func (k *AckConsumeOperator[Msg]) AckCallback() queue.AckCallback[Msg] { |
| 152 | + return k.ackCallback |
| 153 | +} |
| 154 | + |
| 155 | +func (k *AckConsumeOperator[Msg]) Callback() queue.Callback[Msg] { |
| 156 | + return func(msg Msg) { |
| 157 | + k.callback(msg) |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +// BeforeConsume 은 Serializer 를 호출한 후 AckCallback 함수를 실행하는 함수입니다. |
| 162 | +// 실제 컨슘할 때에는 AckCallback 결과에 따라 Callback 함수를 실행할지 결정합니다. |
| 163 | +func (k *AckConsumeOperator[Msg]) BeforeConsume(msg *sarama.ConsumerMessage, ack func()) (sMsg Msg, ok bool) { |
| 164 | + var err error |
| 165 | + sMsg, err = k.serializer.Serialize(msg) |
| 166 | + if err != nil { |
| 167 | + log.Fatal("Serializing error: ", err) |
| 168 | + return sMsg, false |
| 169 | + } |
| 170 | + |
| 171 | + if !k.ackCallback(sMsg) { |
| 172 | + return sMsg, false |
| 173 | + } |
| 174 | + |
| 175 | + ack() |
| 176 | + return sMsg, true |
| 177 | +} |
| 178 | + |
| 179 | +// Consume 은 Serializer 를 호출한 후 Callback 함수를 실행하는 함수입니다. |
| 180 | +// 주의: 실제 컨슘할 때의 로직과는 다릅니다. 실제로는 BeforeConsume 함수를 통해 AckCallback 함수를 실행한 뒤 Ack 결과에 따라 Callback 을 실행합니다. |
| 181 | +// ConsumeOperator 를 구현함과 동시에 테스트 가능성을 높이기 위해 이 함수를 따로 분리했습니다. |
| 182 | +func (k *AckConsumeOperator[Msg]) Consume(msg *sarama.ConsumerMessage) { |
| 183 | + sMsg, err := k.serializer.Serialize(msg) |
| 184 | + if err != nil { |
| 185 | + return |
| 186 | + } |
| 187 | + |
| 188 | + k.callback(sMsg) |
| 189 | +} |
| 190 | + |
| 191 | +func (k *AckConsumeOperator[Msg]) Init() { |
| 192 | + consumer := NewMarkableConsumer(func(msg *sarama.ConsumerMessage, ack func()) { |
| 193 | + sMsg, ok := k.BeforeConsume(msg, ack) |
| 194 | + if !ok { |
| 195 | + return |
| 196 | + } |
| 197 | + |
| 198 | + // goroutine 으로 실행! BeforeConsume 이 완료되면 Consume된 상태이므로 goroutine으로 실행합니다.. |
| 199 | + go k.callback(sMsg) |
| 200 | + }) |
| 201 | + |
| 202 | + k.consumer = consumer |
| 203 | + k.initialized = true |
| 204 | +} |
| 205 | + |
| 206 | +func (k *AckConsumeOperator[Msg]) StartConsume() error { |
| 207 | + if !k.initialized { |
| 208 | + k.Init() |
| 209 | + } |
| 210 | + |
| 211 | + client, err := sarama.NewConsumerGroup(k.brokers, k.groupId, k.config) |
| 212 | + if err != nil { |
| 213 | + return err |
| 214 | + } |
| 215 | + |
| 216 | + ctx, cancel := context.WithCancel(context.Background()) |
| 217 | + k.cancel = &cancel |
| 218 | + |
| 219 | + go func() { |
| 220 | + for { |
| 221 | + if err := client.Consume(ctx, []string{k.topic}, k.consumer); err != nil { |
| 222 | + if errors.Is(err, sarama.ErrClosedConsumerGroup) { |
| 223 | + return |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + if ctx.Err() != nil { |
| 228 | + return |
| 229 | + } |
| 230 | + } |
| 231 | + }() |
| 232 | + |
| 233 | + return nil |
| 234 | +} |
| 235 | + |
| 236 | +func (k *AckConsumeOperator[Msg]) StopConsume() error { |
| 237 | + if k.cancel == nil { |
| 238 | + return errors.New("tried to stop consume before starting (or operator no started, but StopConsume called)") |
| 239 | + } |
| 240 | + |
| 241 | + (*k.cancel)() |
| 242 | + |
| 243 | + return nil |
| 244 | +} |
| 245 | + |
103 | 246 | type BytesProduceOperatorCtor struct {
|
104 | 247 | pool *ProducerPool
|
105 | 248 |
|
|
0 commit comments