Search code examples
togglz

Default activation strategy for a Togglz feature


Since version 2.0.0 Togglz offers Activation Strategies to go with a feature. For instance, you can connect a list of server IP addresses that shall have the feature enabled. However, how are these strategies actually attached to a feature? All I saw was that I can change the strategy in the Togglz console or even edit the data by hand in the database.

What I was looking for is some default mechanism rather similar to @EnabledByDefault. I could implement a custom state repository, it could even look for annotations, but I suspected that this solution existed out of the box.


Solution

  • Just to share my own solution.

    An annotation for defaults

    I defined annotations that should be used the way @EnabledByDefault is. Here is one for the server-ip strategy:

    /**
     * Allows to specify that the annotated feature should use
     * {@link ServerIPStrategy} if the repository doesn't have any
     * state saved.
     * 
     * @author Michael Piefel
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface UsingServerIPStrategy {
    
        /**
         * A comma-separated list of server IPs for which
         * the feature should be active.
         */
        String value();
    
    }
    

    Using the annotation

    In the feature definition, use the annotion like this:

    …
    @EnabledByDefault
    @UsingServerIPStrategy(value = "192.168.1.211")
    @Label("Run regular jobs to send status e-mails to participants")
    MAIL_CRON_JOBS;
    …
    

    State repository to evaluate

    I want to take the feature state from a repository if it already has been saved. If not, the annotations must be evaluated. For this, a delegation repository is needed:

    /**
     * A Togglz {@link StateRepository} that looks for default strategies
     * on the defined features.
     * 
     * @author Michael Piefel
     */
    @AllArgsConstructor
    public class DefaultingStateRepository implements StateRepository {
    
        private StateRepository delegate;
    
        @Override
        public FeatureState getFeatureState(Feature feature) {
            FeatureState featureState = delegate.getFeatureState(feature);
            if (featureState == null) {
                // Look for a default strategy.
                // If none is defined, a null return value is good enough.
                UsingServerIPStrategy serverIPStrategy = FeatureAnnotations
                        .getAnnotation(feature, UsingServerIPStrategy.class);
                if (serverIPStrategy != null) {
                    featureState = new FeatureState(feature,
                            FeatureAnnotations.isEnabledByDefault(feature));
                    featureState.setStrategyId(ServerIpActivationStrategy.ID);
                    featureState.setParameter(ServerIpActivationStrategy.PARAM_IPS,
                            serverIPStrategy.value());
                }
            }
    
            return featureState;
        }
    
        @Override
        public void setFeatureState(FeatureState featureState) {
            // write through
            delegate.setFeatureState(featureState);
        }
    }
    

    Wiring is it in

    Finally, to use the repository, I wired it in our TogglzConfig component, deferring to JDBC, but letting it be cached as well:

    …
    @Override
    public StateRepository getStateRepository() {
        JDBCStateRepository jdbcStateRepository = new JDBCStateRepository(dataSource);
        DefaultingStateRepository defaultingStateRepository = new
                DefaultingStateRepository(jdbcStateRepository);
        return new CachingStateRepository(defaultingStateRepository, 60_000);
    }
    …