public async Task <TReply> RequestTransaction(TRequest rq)
        {
            // Generate our request ID. Could be used for idempotency.
            // Should be provided by our calling client so that also that part can be made idempotent
            var requestId = "API-" + Guid.NewGuid().ToString("B");

            rq.RequestId = requestId;

            // Set up receiving our response
            pendingRequests[requestId] = null;

            // Note: you can carry over information in headers like this. An example would be to put the reply topic in a header,
            // so that you can let consumers know where to expect back the reply. Or add the correlation IDs in the headers.
            // For example, this would be the place where you want to think of distributed tracing. In this example, we will carry over
            // our Jaeger correlation ID, so that consumers can be part of the vary same trace.

            // Specifically, we are going to carry over our reply group ID, which the consumer must communicate back to use.
            // Our Tracing identifiers are taken care of in the KafkaSender class.

            var rqHeaders = new List <Tuple <string, byte[]> > {
                new Tuple <string, byte[]>("reply-group-id", Encoding.ASCII.GetBytes(replyGroup.MyUniqueConsumerGroup))
            };

            // Send our request
            var sendSuccess = await sender.SendToBusWithoutRetries(rq, "transaction-requests", rqHeaders);

            if (!sendSuccess)
            {
                return(null);
            }

            // Wait for response and return it.
            var traceBuilder = tracer.BuildSpan("Wait for Kafka reply");

            using (traceBuilder.StartActive(true))
            {
                try
                {
                    using var ts = new CancellationTokenSource();
                    ts.CancelAfter(TimeSpan.FromSeconds(5));
                    await WaitForReplyById(requestId, ts.Token);
                }
                catch (TaskCanceledException ex)
                {
                    // If we fail, return null
                    logger.LogError("Task cancelled waiting for response", ex);
                    return(null);
                }
            }

            if (pendingRequests.TryRemove(requestId, out var reply))
            {
                return(reply);
            }

            // we failed for some weird reason
            return(null);
        }
Esempio n. 2
0
        private async Task ProcessMessage(ConsumeResult <Ignore, TransactionRequest> cr)
        {
            var rq = cr.Message.Value;

            logger.LogInformation($"Consumed message '{rq}' (amount: {rq.AmountCents})'.");

            var replyGroupId = cr.Message.Headers.SingleOrDefault(p => p.Key == "reply-group-id");

            // We are going to pretend that we do some processing here and then send out two events:
            // * Reply to the Request that it has been processesed
            // * Publish the fact that the transaction has been created onto the bus

            // This is obviously not a real world implementation. We're sending two messages independently, without transactions.
            // We're also not concerned about commits and idempotency upon reprocessing
            // Also, if we are doing other work, such as databases, things can get complex because you will have multiple transactions, each
            // of which can fail independently.

            // Do our 'processing'
            var reply = new TransactionReply {
                RequestId = rq.RequestId, Status = $"Transaction of value {rq.AmountCents} has been processed"
            };
            var replyHeaders = new List <Tuple <string, byte[]> >();

            if (replyGroupId != null)
            {
                replyHeaders.Add(new Tuple <string, byte[]>("reply-group-id", replyGroupId.GetValueBytes()));
            }

            var createdEvent = new TransactionCreated
            {
                AmountCents   = rq.AmountCents, FromAccount = rq.FromAccount, ToAccount = rq.ToAccount,
                CreatedAt     = DateTime.UtcNow,
                TransactionId = Guid.NewGuid().ToString("B")
            };

            await createdSender.SendToBusWithoutRetries(createdEvent, "transactions");

            await replySender.SendToBusWithoutRetries(reply, "transaction-replies", replyHeaders);
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            logger.LogInformation("Beginning direct transaction producer");
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    var rq        = GenerateNewRandomTransaction();
                    var rqHeaders = new List <Tuple <string, byte[]> > {
                        new Tuple <string, byte[]>("reply-group-id", Encoding.ASCII.GetBytes("irrelevant-for-me"))
                    };
                    await sender.SendToBusWithoutRetries(rq, "transaction-requests", rqHeaders);

                    await Task.Delay(250, stoppingToken);
                }
                catch (Exception e)
                {
                    logger.LogInformation("Failure in producer: " + e.Message, e);
                    await Task.Delay(1000, stoppingToken);
                }
            }

            logger.LogInformation("Stopping direct transaction producer");
        }