Dynamic Feature Toggles with Spring Cloud Bus

Feature toggles (or feature flags) are a powerful technique for managing application behavior without code deployments. They allow you to turn features on or off dynamically, enabling techniques like A/B testing, canary releases, and kill switches. In this blog post, we’ll explore how to implement dynamic feature toggles in a Spring Boot application using Spring Cloud Bus.

Why Spring Cloud Bus?

Spring Cloud Bus provides a lightweight communication channel between different instances of a Spring Boot application. This makes it ideal for propagating feature toggle updates across a distributed system. When a toggle changes in one instance, the change can be broadcast to all other instances via the bus, ensuring consistency.

Implementation

Let’s break down the implementation into three parts: the Toggle entity, the ToggleService, and the ToggleClient.

1. The Toggle Entity

This simple entity represents a feature toggle:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Toggle {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String key;
    private boolean enabled;

    // Getters and setters
}

2. The ToggleService

This service provides CRUD operations for toggles and publishes updates via Spring Cloud Bus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ToggleService {

    @Autowired
    private ToggleRepository toggleRepository;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public Toggle createToggle(Toggle toggle) {
        Toggle createdToggle = toggleRepository.save(toggle);
        publishToggleUpdates();
        return createdToggle;
    }

    public Toggle updateToggle(Long id, Toggle toggle) {
        Toggle existingToggle = toggleRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Toggle not found with id: " + id));
        existingToggle.setKey(toggle.getKey());
        existingToggle.setEnabled(toggle.isEnabled());
        Toggle updatedToggle = toggleRepository.save(existingToggle);
        publishToggleUpdates();
        return updatedToggle;
    }

    public void deleteToggle(Long id) {
        toggleRepository.deleteById(id);
        publishToggleUpdates();
    }

    public Toggle getToggle(Long id) {
        return toggleRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Toggle not found with id: " + id));
    }

    public List<Toggle> getAllToggles() {
        return toggleRepository.findAll();
    }

    @EventListener
    public void handleToggleUpdateRequest(RemoteApplicationEvent event) {
        if (event instanceof ToggleUpdateRequest) { 
            publishToggleUpdates();
        }
    }

    private void publishToggleUpdates() {
        List<Toggle> toggles = toggleRepository.findAll();
        eventPublisher.publishEvent(new ToggleUpdatedEvent(this, null, toggles));
    }
}

3. The ToggleClient

This service listens for toggle updates and provides a method to check if a toggle is enabled:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Service
public class ToggleClient {

    private List<Toggle> toggles = new ArrayList<>();

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @PostConstruct
    public void requestToggleUpdatesOnStartup() {
        sendToggleUpdateRequest();
    }

    public void sendToggleUpdateRequest() {
        eventPublisher.publishEvent(new ToggleUpdateRequest(this, null));
    }

    @EventListener
    public void handleToggleUpdates(ToggleUpdatedEvent event) {
        this.toggles = event.getToggles();
        System.out.println("Received updated toggles: " + toggles);
    }

    public List<Toggle> getToggles() {
        return toggles;
    }

    public boolean isToggleEnabled(String key) {
        return toggles.stream()
                .filter(toggle -> toggle.getKey().equals(key))
                .findFirst()
                .map(Toggle::isEnabled)
                .orElse(false);
    }
}

Custom Events

We also need two custom events for communication over the bus:

import org.springframework.cloud.bus.event.RemoteApplicationEvent;

public class ToggleUpdateRequest extends RemoteApplicationEvent {
    public ToggleUpdateRequest(Object source, String originService) {
        super(source, originService);
    }
}

import org.springframework.cloud.bus.event.RemoteApplicationEvent;
import java.util.List;

public class ToggleUpdatedEvent extends RemoteApplicationEvent {

    private List<Toggle> toggles;

    public ToggleUpdatedEvent(Object source, String originService, List<Toggle> toggles) {
        super(source, originService);
        this.toggles = toggles;
    }

    public List<Toggle> getToggles() {
        return toggles;
    }
}

Putting it all together

With this setup, you can now dynamically manage feature toggles in your Spring Boot application. The ToggleService allows you to create, update, and delete toggles, while the ToggleClient keeps track of the latest toggle state and provides a convenient method to check if a toggle is enabled. Spring Cloud Bus ensures that all instances of your application are synchronized with the latest toggle configuration.

This is a basic implementation, and you can extend it further by adding features like user-specific toggles, versioning, and audit trails.

Benefits

  • Real-time updates: Changes to toggles are propagated instantly to all microservices.
  • Centralized management: Provides a single point of control for managing all feature toggles.
  • Improved development workflow: Enables continuous delivery and deployment by decoupling feature releases from code deployments.
  • Reduced risk: Allows for gradual rollout of features and easy rollback if issues arise.

This setup provides a robust and flexible foundation for implementing feature toggles in your Spring Boot microservices. You can further enhance it with features like:

  • User segmentation: Target specific user groups with different toggles.
  • Permissions: Control who can create, update, or delete toggles.
  • Audit logging: Track changes made to toggles.
  • UI: Build a user interface for easier toggle management.

By leveraging the power of Spring Cloud Bus and a well-designed toggle service, you can gain greater control over your application’s features and improve your development process.


Discover more from GhostProgrammer - Jeff Miller

Subscribe to get the latest posts sent to your email.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.