Skip to content

Este é um exemplo de como uma aplicação Golang pode trabalhar integrada ao Kafka.

Notifications You must be signed in to change notification settings

wesleysbmartins/go_kafka

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Golang My Skills Kafka Integration My Skills

Este é um exemplo de como uma aplicação Golang pode trabalhar integrada ao Kafka.

Kafka

Apache Kafka é uma plataforma de streaming de eventos distribuída e altamente escalável, projetada para processar e gerenciar grandes volumes de dados em tempo real. Originalmente desenvolvido pela LinkedIn e posteriormente aberto como um projeto de código aberto pela Apache Software Foundation, o Kafka é amplamente utilizado em diversas indústrias para várias aplicações.

OBS: Para entender melhor o que é o Kafka, como instalar e rodar em seu ambiente, acesse o meu repositório onde registrei meus estudos sobre o tema.

Hands-On

Neste momento iremos abordar de forma simples como integra o Kafka a sua aplicação Golang usando a biblioteca Sarama, utilizando padrões de desenvolvimento como Singleton, Factory, Entities e Usecases.

Após iniciar sua aplicação Go e instalar a biblioteca do Sarama, você pode criar sua conexão, criando seu Producer ou seu Consumer.

Client Kafka

Após inicar sua aplicação podemos iniciar o desenvolvimento da aplicação, neste primeiro momento vamos criar o client do Kafka para sua aplicação e instancia-lo utilizando um Singleton.

package kafka

import (
	"fmt"
	"time"

	"github.com/IBM/sarama"
)

type Kafka struct {
	host string
	port string
}

type IKafka interface {
	Connect()
}

var Client sarama.Client

func (k *Kafka) Connect() {
	if Client == nil {
		credentials := Kafka{
			host: "localhost",
			port: "9092",
		}

		config := sarama.NewConfig()
		config.Version = sarama.MaxVersion
		config.Producer.Return.Successes = true
		config.Consumer.Return.Errors = true
		config.Consumer.Offsets.AutoCommit.Enable = true
		config.Consumer.Offsets.AutoCommit.Interval = 1 * time.Second

		broker := []string{fmt.Sprintf("%s:%s", credentials.host, credentials.port)}

		client, err := sarama.NewClient(broker, config)

		if err != nil {
			fmt.Println("Client Intance Error!\n", err)
		} else {
			Client = client
		}
	}
}

Factory Kafka

Para a criação dos Producers e Consumers do Kafka vamos utilizar a abordagem de Factory.

Producer Factory

Para o factory dos producers retemos os métodos de criação do producer e o de envio de mensagens, onde esperam os parametros necessários para realizar tais operações como tópico, chave da mensagem e valor da mensagem.

package factory

import (
	"encoding/json"
	"fmt"
	"go_kafka/internal/adapters/kafka"

	"github.com/IBM/sarama"
)

type ProducerFactory struct {
	key      string
	topic    string
	instance sarama.SyncProducer
}

type IProducerFactory interface {
	Create(key string, topic string)
	SendMessage(message string) error
}

func (p *ProducerFactory) Create(key string, topic string) {
	p.key = key
	p.topic = topic
	producer, err := sarama.NewSyncProducerFromClient(kafka.Client)

	if err != nil {
		panic("Error to create new Producer!")
	} else {
		p.instance = producer
	}
}

func (p *ProducerFactory) Send(message interface{}) error {
	value, _ := json.Marshal(message)

	msg := &sarama.ProducerMessage{
		Topic: p.topic,
		Key:   sarama.StringEncoder(p.key),
		Value: sarama.StringEncoder(value),
	}

	_, _, err := p.instance.SendMessage(msg)

	if err != nil {
		fmt.Println("Send message ERROR: ", err)
	} else {
		fmt.Println("Send message SUCCESS!")
	}

	return err
}

Consumers

Temos dois tipos de implementações de consumers, um consumer de um único tópico e os consumers de grupos de tópicos.

Além disse foi criado interfaces de handlers para lidarem com as mensagens recebidas, o que representaria sua regra de negócio.

Consumer Handlers Interfaces

package factory

import "github.com/IBM/sarama"

type IConsumerHandler interface {
	Run(message sarama.ConsumerMessage)
}

type IConsumerGroupHandler interface {
	Setup(session sarama.ConsumerGroupSession) error
	Cleanup(session sarama.ConsumerGroupSession) error
	ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error
}

Consumer Factory

package factory

import (
	"fmt"
	"go_kafka/internal/adapters/kafka"
	"os"
	"os/signal"
	"syscall"

	"github.com/IBM/sarama"
)

type ConsumerFactory struct {
	topic     string
	partition int32
	instance  sarama.Consumer
}

type IConsumerFactory interface {
	Create(topic string, partition int32)
	Listen() error
}

func (c *ConsumerFactory) Create(topic string, partition int32) {
	c.topic = topic
	c.partition = partition
	consumer, err := sarama.NewConsumerFromClient(kafka.Client)

	if err != nil {
		panic("Error to create new Consumer!")
	} else {
		c.instance = consumer
	}
}

func (c *ConsumerFactory) Listen(handler IConsumerHandler) {
	consumerPartition, err := c.instance.ConsumePartition(c.topic, c.partition, sarama.OffsetOldest)

	if err != nil {
		fmt.Println("Consumer ERROR: ", err)
	}

	defer consumerPartition.Close()

	ch := make(chan os.Signal, 1)

	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

	messages := consumerPartition.Messages()

	for {
		select {
		case msg := <-ch:
			fmt.Printf("Reveived message SIGNALL: %v\n", msg)
			return
		case msg := <-messages:
			handler.Run(*msg)
		}
	}
}

Consumer Group Factory

package factory

import (
	"context"
	"fmt"
	"go_kafka/internal/adapters/kafka"
	"os"
	"os/signal"
	"syscall"

	"github.com/IBM/sarama"
)

type ConsumerGroupFactory struct {
	groupId  string
	topics   []string
	instance sarama.ConsumerGroup
}

type IConsumerGroupfactory interface {
	Create(groupId string, topics []string)
	Listen(handler IConsumerGroupHandler)
}

func (c *ConsumerGroupFactory) Create(groupId string, topics []string) {
	c.groupId = groupId
	c.topics = topics
	consumerGroup, err := sarama.NewConsumerGroupFromClient(groupId, kafka.Client)
	if err != nil {
		panic("Error to create Consumer Group!")
	} else {
		c.instance = consumerGroup
	}
}

func (c *ConsumerGroupFactory) Listen(handler IConsumerGroupHandler) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		for {

			if err := c.instance.Consume(ctx, c.topics, handler); err != nil {
				panic(fmt.Sprintf("Consumer Group Error!\n%s", err.Error()))
			}

			if ctx.Err() != nil {
				return
			}
		}
	}()

	ch := make(chan os.Signal, 1)

	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

	select {
	case <-ctx.Done():
		fmt.Println("Context Cancelled!")
	case signal := <-ch:
		fmt.Printf("Signal Event: %v\n", signal)
	}
}

Entities

Para exemplificar uma mensagem foi criado uma struct Activity.

package entities

type Activity struct {
	Id          string `json:"id"`
	Title       string `json:"title"`
	Description string `json:"description"`
}

Usecases

Structs e métodos responsáveis por implementar as interfaces esperadas pelos consumers como handler e também, sua regra de negócio.

Consumer Usecase

package usecases

import (
	"fmt"

	"github.com/IBM/sarama"
)

type ConsumerUsecase struct{}

func (c *ConsumerUsecase) Run(message sarama.ConsumerMessage) {
	fmt.Printf("Consumer Usecase Received Message - Topic: %q - Value: %s\n", message.Topic, message.Value)
}

Consumer Group Usecase

package usecases

import (
	"fmt"

	"github.com/IBM/sarama"
)

type ConsumerGroupUsecase struct{}

func (ConsumerGroupUsecase) Setup(_ sarama.ConsumerGroupSession) error {
	return nil
}

func (ConsumerGroupUsecase) Cleanup(_ sarama.ConsumerGroupSession) error {
	return nil
}

func (h ConsumerGroupUsecase) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
	for msg := range claim.Messages() {

		fmt.Printf("Consumer Group Usecase Received Message - Topic: %q - Value: %s\n", msg.Topic, msg.Value)

		session.MarkMessage(msg, "Readed")
	}

	return nil
}

Main

Sendo assim, basta brincar com o código desenvolvido até aqui, use isto no seu main, execute e veja a mágica acontecer:

package main

import (
	"go_kafka/internal/adapters/kafka"
	factory_consumer "go_kafka/internal/adapters/kafka/factory/consumer"
	factory_producer "go_kafka/internal/adapters/kafka/factory/producer"
	"go_kafka/internal/entities"
	"go_kafka/internal/usecases"
)

func init() {
	kafka_client := &kafka.Kafka{}
	kafka_client.Connect()
}

const (
	partition = 0
	groupId   = "activity-group"
	topic     = "activity-topic"
	key       = "activity"
)

func main() {
	activityProducer := &factory_producer.ProducerFactory{}
	activityProducer.Create(key, topic)

	// activityConsumer := &factory_consumer.ConsumerFactory{}
	// activityConsumer.Create(topic, partition)

	consumerGroup := &factory_consumer.ConsumerGroupFactory{}
	consumerGroup.Create(groupId, []string{topic})

	consumerGrouphandler := &usecases.ConsumerGroupUsecase{}

	consumerGroup.Listen(consumerGrouphandler)

	activities := []entities.Activity{
		{
			Id:          "1",
			Title:       "Example 01",
			Description: "KAFKA IMPLEMENTATION DESCRIPTION 01",
		}, {
			Id:          "2",
			Title:       "Example 02",
			Description: "KAFKA IMPLEMENTATION DESCRIPTION 02",
		},
		{
			Id:          "3",
			Title:       "Example 03",
			Description: "KAFKA IMPLEMENTATION DESCRIPTION 03",
		},
	}

	for _, activity := range activities {
		activityProducer.Send(activity)
	}

	// consumerUsecase := &usecases.ConsumerUsecase{}

	// activityConsumer.Listen(consumerUsecase)
}

Resultado

Executando o projeto, você deve obter um resultado semelhante a este:

Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"1","title":"Example 01","description":"KAFKA IMPLEMENTATION DESCRIPTION 01"}
Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"2","title":"Example 02","description":"KAFKA IMPLEMENTATION DESCRIPTION 02"}
Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"3","title":"Example 03","description":"KAFKA IMPLEMENTATION DESCRIPTION 03"}
Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"1","title":"Example 01","description":"KAFKA IMPLEMENTATION DESCRIPTION 01"}
Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"2","title":"Example 02","description":"KAFKA IMPLEMENTATION DESCRIPTION 02"}
Consumer Group Usecase Received Message - Topic: "activity-topic" - Value: {"id":"3","title":"Example 03","description":"KAFKA IMPLEMENTATION DESCRIPTION 03"}
Signal Event: interrupt
Send message SUCCESS!
Send message SUCCESS!
Send message SUCCESS!

Para parar a aplicação basta pressionar CTRL + C em seu terminal:

Reveived message SIGNALL: interrupt

Assim voce tem uma aplicação Golang integrada ao Kafka, sendo capaz de realizar operações de envio ou leitura de mensagens ou eventos.