Page 39 of 170

Rails + Responders + Trailblazer – Flash messages with proper model names from Trailblazer operations

Responders gem is really awesome. It sets flash message based on the controller action and resource status allowing you to dry up your Rails controllers. Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture. When combining both of them together, you can achieve really slim and functional controllers:

class ContractsController < BaseController
  def index
    respond_with(collection Contracts::Index)
  end

  def show
    respond_with(present Contracts::Update)
  end

  def new
    respond_with(form Contracts::Create)
  end

  def create
    respond_with(run Contracts::Create)
  end

  def edit
    respond_with(form Contracts::Update)
  end

  def update
    respond_with(run Contracts::Update)
  end

  def destroy
    respond_with(run Contracts::Destroy)
  end
end

Unfortunately there's one downside: Since Responders #respond_with method expects a model as a first argument, it will think that your operation is a model (if you return it the way I did) and try to figure out it's human name. Probably it won't end up as you expect and you will see a message similar to this one:

Users/campaigns/update was successfully updated.

It happens because of this piece of code from Responders:

resource_name = if resource.class.respond_to?(:model_name)
  resource.class.model_name.human
else
  resource.class.name.underscore.humanize
end

Trailblazer operations don't respond to #model_name, that's why all the operations will be translated using the else statement. To make it work as expected, we just need to implement the #model_name class method:

class ApplicationOperation < Trailblazer::Operation
  def self.model_name
    ::ActiveModel::Name.new(model_class)
  end
end

I follow the ApplicationController, ApplicationRecord, ApplicationHelper "pattern" also with operations. That way I don't need to add this code into Trailblazer::Operation or into each of the operations separately. #model_class method is a method that will return the model class that we've set using Trailblazer::Operation #method:

class Operation < ApplicationOperation
 include Model
 model Contract
end

Operation.model_class #=> Contract
Operation.model_name #=> ActiveModel::Name:0x0000000575b778 @name="Contract"...

From now on, all the Trailblazer operations will introduce themselves to Responders with the name of the model on which we operate.

Ruby: Karafka framework 0.4 – Routing engine

13313336

We've finally released a new version of Karafka framework. Apart from many tweaks and bug fixes it contains three huge improvements:

  • ApplicationWorker and ApplicationController
  • Routing engine
  • Offset commit after data processing (not only after fetching)

In this article I will focus on the routing engine.

Pre-routing times

First version of Karafka framework routed incoming messages based on controllers names. For example:

  • videos topic => VideosController
  • movies_subtitles => Movies::SubtitlesController

There was also a possibility to change that by setting the topic property of a controller:

class SuperController < Karafka::BaseController
  self.topic = :my_data
end

Unfortunately it turned out that this approach had way more disadvantages than advantages:

  • Fat, non-isolated routing logic bound tightly to controllers
  • No possibility to use inheritance
  • No possibility to use same controller logic for multiple routes (at least not directly)
  • A lot non-controller related logic that had to be there because of consistency of the framework DSL (interchanger, group, etc)

Luckily those times are gone and now we have a nice shiny routing engine.

Note: If you're not familiar with terms like topic, producer, etc - please refer to an excellent post about that: Kafka for Rubyists - Part 1

Karafka routing basics

Karafka has a "Rails like" routing that allows you to describe how messages from each topic should be handled. Such separation layer (between topics, controllers and workers) allows you to have better control over the message "flow".

App.routes.draw do
  topic :example do
    controller ExampleController
  end
end

By default Karafka requires topic and controller. Note that controllers should be provided as class names, not as strings/symbols (like in Rails). For most of the cases this will be all you need. Everything else will be built automatically under the hood.

Kafka has a flat topic structure. There are no namespaces or nested topics (however it might change in the future, since there's a discussion about that). It means that your Karafka routes will be flat as well: single topic block and within all the details on how to handle messages from it.

There are following options that you can use inside a topic block:

  • Controller (required)
  • Group (optional)
  • Worker (optional)
  • Parser (optional)
  • Interchanger (optional)

Here's an example on how to use all the options together

App.routes.draw do
  topic :binary_video_details do
    group :composed_application
    controller Videos::DetailsController
    worker Workers::DetailsWorker
    parser Parsers::BinaryToJson
    interchanger Interchangers::Binary
  end

Karafka controller

Karafka controller is similar to Rails controller. It is responsible for handling each of incoming messages.

Karafka group

Taken from Apache Kafka documentation:

Consumers label themselves with a consumer group name, and each message published to a topic is delivered to one consumer instance within each subscribing consumer group. Consumer instances can be in separate processes or on separate machines.

If you're not planning to build multiple applications (not processes) from which only one needs to consume a given message, don't worry about that option at all. Karafka will take care of that for you.

Karafka worker

Karafka by default will build a worker that will correspond to each of your controllers (so you will have a pair - controller and a worker). All of them will inherit from ApplicationWorker and will share all its settings.

You can overwrite this by providing your own worker. Just keep in mind, that if it does not inherit from ApplicationWorker it won't have interchanging, unparsing logic and it won't execute your controller #perform code. In cases like that you'll have to implement your business logic inside the worker.

Karafka parser

Kafka is a cross-platform tool. It means that when integrating with other applications, you might not receive JSON data (producer dependent). Custom parsers allow you to handle any data format you specify. Karafka by default will parse messages with JSON parser. Custom parser needs to have a #parse method and raise error that is a ::Karafka::Errors::ParserError descendant when problem appears during parsing process. Parsing failure won't stop the application flow. Instead, Karafka will assign the raw message inside the :message key of params. That way you can handle raw message inside the Sidekiq worker (you can implement error detection, etc - any "heavy" parsing logic can and should be implemented there).

Karafka interchanger

Custom interchangers target issues with non-default (binary, etc) data that we want to store when we do #perform_async. This data might be corrupted when fetched in a worker (see this issue). With custom interchangers, you can encode/compress data before it is being passed to scheduling and decode/decompress it when it gets into the worker.

Note: It is not equal to parsers. Parsers parse incoming data and convert them to "Ruby friendly" format. Interchangers allow you to add an additional layer of encoding, so for example your binary data won't be escaped by Sidekiq.

Warning: if you decide to use slow interchangers, they might significantly slow down Karafka.

Summary

Karafka's routing engine might seems simple, however it is simple only until you decide to use all the non-required features.

Thanks to this approach you can prototype and design your application flow really fast, however on demand you can also handle any non-standard case.

Is the current routing engine a final one? To be honest it is strongly dependent on Apache Kafka's development. We will see in the future :-)

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑