A Feature Toggle Story


In the following article, I’ll talk about how the feature toggle technique helped me to introduce a new feature even when not all dependencies were ready. How the same technique enabled the delivery of a big refactor. Finally, another use case I will touch on is how was possible to altered the behavior of a system based on configurable feature toggles and user info.

Delivering a new feature with dependencies

Not so long ago I had to modify the flow of the PDF generator service in order to integrate the PDF signing feature. The main class looked like following:

@AllArgsConstructor
@Service
class ReportService {
    PDFGenerator generator;
    StorageService storageService;

    void generateReport(Payload payload) {
        OutputStream reportStream = generator.generate(payload);
        storageService.upload(reportStream);
    }
}

The tricky part of this task was the fact that I didn’t get a certificate straight away. The discussion about certificate provider and other specifics started to get tedious, and I didn’t have the certainty that it will be ready in a few hours or weeks.

During analysis I find out that the PDF generation is an important step and could not suffer any disruptions. An aspect that I took into consideration during the design phase.

For the signing step I created an interface:

interface SignerService {
    OutputStream sign(OutputStream payloadToSign);
}

And integrated it into the main logic:

@AllArgsConstructor
@Service
class ReportService {
    PDFGenerator generator;
    SignerService signerService;
    StorageService storageService;

    void generateReport(Payload payload) {
        OutputStream reportStream = generator.generate(payload);
        OutputStream signedReportStream = signerService.sign(reportStream);
        storageService.upload(signedReportStream);
    }
}

After this I wanted to ensure that the service is working as before and created a noop implementation:

@Service
class NoOpSignerService implements SignerService {

    @Override
    public OutputService sign(OutputService payloadToSign) {
        log.info("bypassing signing step");
        return payloadToSign;
    }
}

Finally, it was the time to get to the problem at hand and actually sign the report. For this part I defined another implementation, that is @Primary and loaded only in certain conditions:

@Service
@Primary
@ConditionalOnProperty(name = "activateSigning", havingValue = "true")
class BCSignerService implements SignerService {

    @Override
    public OutputService sign(OutputService payloadToSign) {
        //... sign and return the payload
    }
}

This means that we have two implementations, but the second one which takes precedence over any other beans is loaded into the Spring context only when activateSigning is true.

It seems like a trivial thing to do, but thanks to this class structure I was able to develop, merge into the main branch (so the dev brach does not get stale) and even demo to the client (with a self signed certificate). When the certificate was finally available, I just switched the flag activateSigning to true, did a restart of the service, and everything worked perfectly (preferably to restart is blue-green deployment so you don’t have any downtime).

About how I used the feature toggle for other goals mentioned in the beginning, details will follow soon.

Related Posts

Unit Testing Anti-Patterns

REST Search API with QueryDSL

Running Java shebang with Kubernetes

How to collect more than a single collection or a single scalar

How to Organize the Code in the Ports and Adapters Architecture

Functional Programming Concepts in Java

An Introduction to Java Sealed Classes and Interfaces