Candi

repository·master·Indexed 40 days ago

https://github.com/golangid/candi

A library and utility suite for building Golang microservices. It provides a CLI for scaffolding services and monorepos and supports multiple communication protocols and worker patterns, including Kafka, RabbitMQ, Cron workers, and Postgres Change Data Capture (CDC) event listeners.

Tokens
30K
Snippets
77
Records
103
Agent score
47%

What's inside Candi

  1. Candi core features

    master

    Candi provides several built-in capabilities for microservices development:

    • Tracing: Integrated support for distributed tracing using Jaeger.
    • Graceful Shutdown: Automatic graceful shutdown handling for all servers and workers.
    • Extensibility: Supports plugins via the Candi Plugin.
    • Diverse Handlers: Supports various delivery layers including REST, gRPC, and GraphQL, as well as multiple worker types (Cron, Kafka, Redis, Task Queue, Postgres, and RabbitMQ).
  2. Register a Kafka worker handler in a module

    master

    To make your Kafka handler active, you must register it within your module's constructor (typically NewModules). Add the handler to the workerHandlers map using types.Kafka as the key. This map expects values that satisfy the interfaces.WorkerHandler interface.

    package examplemodule
    
    import (
    	"example.service/internal/modules/examplemodule/delivery/workerhandler"
    
    	"github.com/golangid/candi/codebase/factory/dependency"
    	"github.com/golangid/candi/codebase/factory/types"
    	"github.com/golangid/candi/codebase/interfaces"
    )
    
    type Module struct {
    	// ...another delivery handler
    	workerHandlers map[types.Worker]interfaces.WorkerHandler
    }
    
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			// ...another worker handler
    			// ...
    			types.Kafka: workerhandler.NewKafkaHandler(),
    		},
    	}
    }
  3. Create a Redis delivery handler

    master

    To handle asynchronous jobs via Redis, implement a handler struct that satisfies the worker handler pattern. You must implement a MountHandlers method that accepts a *types.WorkerHandlerGroup to register your topic-to-function mappings. The handler functions receive a *candishared.EventContext, which provides access to the message payload via .Message() and the execution context via .Context().

    package workerhandler
    
    import (
    	"context"
    	"time"
    
    	"github.com/golangid/candi/candishared"
    	"github.com/golangid/candi/codebase/factory/types"
    	"github.com/golangid/candi/logger"
    )
    
    type RedisHandler struct {
    }
    
    func NewRedisHandler() *RedisHandler {
    	return &RedisHandler{}
    }
    
    // MountHandlers registers topic keys to specific handler functions
    func (h *RedisHandler) MountHandlers(group *types.WorkerHandlerGroup) {
    	group.Add("scheduled-job", h.handleScheduledJob)
    }
    
    func (h *RedisHandler) handleScheduledJob(eventContext *candishared.EventContext) error {
    	// Access context and message
    	ctx := eventContext.Context()
    	msg := eventContext.Message()
    	
    	log.Printf("message received. message: %s\n", msg)
    	return nil
    }
  4. Build and run a Candi service

    master

    Once a service has been generated, you can build and run it using make.

    Running the service

    $ cd {{service_name}}
    $ make run

    Generating gRPC files

    If your service includes a gRPC handler, you must generate the RPC files from your .proto definitions. This requires the protoc compiler (minimum version libprotoc 3.14.0).

    $ make proto
    $ cd {{service_name}}
    $ make run
  5. Register a Cron Worker in a Module

    master

    To make your cron worker active, you must register it within your module's constructor. Add the handler instance to the workerHandlers map using types.Scheduler as the key. This map is typically part of the Module struct and is initialized within the NewModules function.

    package examplemodule
    
    import (
    	"example.service/internal/modules/examplemodule/delivery/workerhandler"
    
    	"github.com/golangid/candi/codebase/factory/dependency"
    	"github.com/golangid/candi/codebase/factory/types"
    	"github.com/golangid/candi/codebase/interfaces"
    )
    
    type Module struct {
    	workerHandlers map[types.Worker]interfaces.WorkerHandler
    }
    
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			// Register the CronHandler using the types.Scheduler key
    			types.Scheduler: workerhandler.NewCronHandler(),
    		},
    	}
    }
  6. Register RabbitMQ workers in a module

    master

    To make your RabbitMQ handlers active, you must register them within your module's NewModules constructor.

    Add the handler to the workerHandlers map using types.RabbitMQ as the key. The value should be an instance of your handler created via its constructor.

    Example mapping:

    workerHandlers: map[types.Worker]interfaces.WorkerHandler{
        types.RabbitMQ: workerhandler.NewRabbitMQHandler(usecaseUOW.User(), deps.GetValidator()),
    }
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			types.RabbitMQ: workerhandler.NewRabbitMQHandler(usecaseUOW.User(), deps.GetValidator()),
    		},
    	}
    }
  7. Register a Postgres Listener in a Module

    master

    To activate the Postgres listener, you must register it within your module's NewModules constructor. Add the handler to the workerHandlers map using types.PostgresListener as the key. This tells the Candi framework to treat this handler as a Postgres CDC listener.

    package examplemodule
    
    import (
    	"example.service/internal/modules/examplemodule/delivery/workerhandler"
    	"github.com/golangid/candi/codebase/factory/dependency"
    	"github.com/golangid/candi/codebase/factory/types"
    	"github.com/golangid/candi/codebase/interfaces"
    )
    
    type Module struct {
    	workerHandlers map[types.Worker]interfaces.WorkerHandler
    }
    
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			// Register the handler using the types.PostgresListener key
    			types.PostgresListener: workerhandler.NewPostgresListenerHandler(usecaseUOW.User(), deps.GetValidator()),
    		},
    	}
    }
  8. Register Task Queue Handlers in a Module

    master

    To enable the task queue worker within your service, you must register your handler in the module's NewModules constructor. Add the handler to the workerHandlers map using the types.TaskQueue key.

    type Module struct {
    	workerHandlers map[types.Worker]interfaces.WorkerHandler
    }
    
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			types.TaskQueue: workerhandler.NewTaskQueueHandler(),
    		},
    	}
    }
  9. Add a Task Job to the Queue

    master

    Depending on whether the worker is running in the same runtime or on a separate server, use one of the following methods to add a job to the queue.

    // Same runtime
    err := taskqueueworker.AddJob(ctx, "{{task-queue-worker-host}}", &taskqueueworker.AddJobRequest{
    	TaskName: "{{task_name}}", 
    	MaxRetry: 5, 
    	Args: []byte("{{arguments/message}}"),
    })
    
    // Separate server via HTTP request
    jobID, err := taskqueueworker.AddJobViaHTTPRequest(context.Background(), "{{task-queue-worker-host}}", &taskqueueworker.AddJobRequest{
    	TaskName: "{{task_name}}", 
    	MaxRetry: 1, 
    	Args: []byte("{{arguments/message}}"),
    })
  10. Register a Redis worker handler in a module

    master

    To activate your Redis worker handler, you must register it within your module's constructor (typically NewModules). Add the handler to the workerHandlers map using types.RedisSubscriber as the key. This map expects values that implement the interfaces.WorkerHandler interface.

    package examplemodule
    
    import (
    	"example.service/internal/modules/examplemodule/delivery/workerhandler"
    	"github.com/golangid/candi/codebase/factory/dependency"
    	"github.com/golangid/candi/codebase/factory/types"
    	"github.com/golangid/candi/codebase/interfaces"
    )
    
    type Module struct {
    	workerHandlers map[types.Worker]interfaces.WorkerHandler
    }
    
    func NewModules(deps dependency.Dependency) *Module {
    	return &Module{
    		workerHandlers: map[types.Worker]interfaces.WorkerHandler{
    			// Register the handler using the RedisSubscriber key
    			types.RedisSubscriber: workerhandler.NewRedisHandler(),
    		},
    	}
    }
  11. Configure Kafka broker in your service

    master

    To use Kafka, register the Kafka broker in your service's configuration file (typically configs/configs.go) by calling broker.InitBrokers with broker.NewKafkaBroker().

    To enable a Kafka consumer, set the environment variable USE_KAFKA_CONSUMER=true.

    // In configs/configs.go
    func LoadServiceConfigs(baseCfg *config.Config) (deps dependency.Dependency) {
        // ...
        brokerDeps := broker.InitBrokers(
            broker.NewKafkaBroker(),
        )
        // ...
    }
  12. Install the Candi CLI

    master

    You can install the Candi CLI using pre-built binaries or by building from source using go install.

    Linux

    $ wget -O candi https://storage.googleapis.com/agungdp/bin/candi/candi-linux && chmod +x candi && sudo mv candi /usr/local/bin
    $ candi

    macOS

    $ wget -O candi https://storage.googleapis.com/agungdp/bin/candi/candi-osx && chmod +x candi && mv candi /usr/local/bin
    $ candi

    Windows

    Download the appropriate executable:

    • 64 bit: https://storage.googleapis.com/agungdp/bin/candi/candi-x64.exe
    • 32 bit: https://storage.googleapis.com/agungdp/bin/candi/candi-x86.exe

    Build from source

    $ go install github.com/golangid/candi/cmd/candi@latest
    $ candi
    go install github.com/golangid/candi/cmd/candi@latest