vmaleze / dotnet-kafka

A sample dotnet-core service implemeting kafka consumer and producer

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sample project for kafka and dotnet core

This project is a sample for using kafka with .net-core It contains all the necessary tools to Produce and Consume messages

TL;DR

Run kafka with docker-compose up -d
Run DotnetKafka.program

POST a message to http://localhost:8080/api/messages :

{
	"topic": "topic",
	"message": "test"
}

Message are consumed and logged by the dotnet app

Kafka

I am using wurstmeister images as they are up to date and match my requirements.

To start kafka and zookeeper, launch docker-compose up -d

Interact with Kafka

As I don't want to import shell scripts to interact with kafka, you can use the start-kafka-shell.sh script to interact with your cluster

  • Consume a topic
    $KAFKA_HOME/bin/kafka-console-consumer.sh --topic topic --bootstrap-server kafka:29092

  • Produce in a topic
    $KAFKA_HOME/bin/kafka-console-producer.sh --topic topic --broker-list kafka:29092

Dotnet solution

To interact with kafka, we will be using the Confluent.Kafka library

Producer

Producing messages to kafka is the easy part.
We simply need the url of our kafka cluster.

First thing is to bind the Configuration of our producer :

public void ConfigureServices(IServiceCollection services)
{
    ...

    var producerConfig = new ProducerConfig();
    _configuration.Bind("Producer", producerConfig);
    services.AddSingleton(producerConfig);

    ...
}

Then create the associated Service :

public class KafkaProducer : IDisposable
{
    private readonly ILogger<KafkaProducer> _logger;
    private readonly IProducer<Null, string> _producer;

    public KafkaProducer(ILogger<KafkaProducer> logger, ProducerConfig config)
    {
        _logger = logger;
        _producer = new ProducerBuilder<Null, string>(config).Build();
    }

    public async Task ProduceAsync(string topic, string message)
    {
         await _producer.ProduceAsync(topic, new Message<Null, string> {Value = message});
         _logger.LogInformation($"Delivered message to {topic} => [{message}]");
    }

    public void Dispose()
    {
        _producer?.Dispose();
    }
}

And register it as a singleton :

public void ConfigureServices(IServiceCollection services)
{
    ...

    var producerConfig = new ProducerConfig();
    _configuration.Bind("Producer", producerConfig);
    services.AddSingleton(producerConfig);
    services.AddSingleton<KafkaProducer>();

    ...
}

To test it in our project, I simply created a MessagesController that takes a message and a topic in parameter and then calls my KafkaProducer.
As the KafkaProducer is registered as a service, you can simply inject it and use it in all of your services.

Consumer

The consumer is bit more tricky.
A typical Kafka consumer application is centered around a consume loop, which repeatedly calls the Consume method to retrieve records one-by-one that have been efficiently pre-fetched by the consumer in background threads.
Like so :

...

using (var consumer = new ConsumerBuilder<Ignore, string>(config).Build())
{
    consumer.Subscribe(topics);

    while (!cancelled)
    {
        var consumeResult = consumer.Consume(cancellationToken);

        // handle consumed message.
        ...
    }

    consumer.Close();
}

In a main method, this is easy. But how do we implement this in a dotnet core solution and register our consumer as a service.

Let's begin by creating our KafkaConsumer

public class KafkaConsumer : IDisposable
{
    private readonly ILogger<KafkaConsumer> _logger;
    private readonly IConsumer<Ignore, string> _consumer;

    public KafkaConsumer(ILogger<KafkaConsumer> logger, ConsumerConfiguration consumer)
    {
        _logger = logger;
        _consumer = new ConsumerBuilder<Ignore, string>(consumer.Configuration).Build();
        _consumer.Subscribe(consumer.Topics);
    }

    public void Consume(CancellationToken ct)
    {
        try
        {
            var consumeResult = _consumer.Consume(ct);
            _logger.LogInformation($"Consuming message => {consumeResult.Message.Value}");
        }
        catch (ConsumeException e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    public void Dispose()
    {
        _consumer?.Close();
        _consumer?.Dispose();
    }
}

And register it as always:

public void ConfigureServices(IServiceCollection services)
{
    ...

    var consumerConfig = new ConsumerConfiguration();
    _configuration.Bind("Consumer", consumerConfig);
    services.AddSingleton(consumerConfig);

    ...
}

Now, we will use background tasks to manage our service and use it:

public class EventManager : BackgroundService
{
    private readonly ILogger<EventManager> _logger;
    private readonly KafkaConsumer _consumer;
    private readonly ConsumerConfiguration _configuration;

    public EventManager(ILoggerFactory loggerFactory, ConsumerConfiguration consumerConfiguration)
    {
        _logger = loggerFactory.CreateLogger<EventManager>();
        _configuration = consumerConfiguration;
        _consumer = new KafkaConsumer(loggerFactory.CreateLogger<KafkaConsumer>(), _configuration);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation($"Starting EventManager service. Consuming to the following topics [{string.Join(", ", _configuration.Topics)}]");

        await Task.Run(() =>
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _consumer.Consume(stoppingToken);
            }
        }, stoppingToken);
    }
}

As you can see in the code, we used a Task to run our consumer. This is required because there is no ConsumeAsync method.
By doing this, we are creating a fake async consume method and we can still use ou HostedService.

Conclusion

By either using the kafka console or the dotnet-app, you can now publish and consume messages from kafka.

Links

Here are a list of links that I used to create this sample project

https://github.com/wurstmeister/kafka-docker
https://www.kaaproject.org/kafka-docker
https://docs.confluent.io/current/clients/dotnet.html
https://github.com/srigumm/dotnetcore-kafka-integration

About

A sample dotnet-core service implemeting kafka consumer and producer


Languages

Language:C# 98.3%Language:Shell 1.7%