Pivoting with Purpose: Introducing Tool-Driven Development (TDD) with PicoCLI

Key Takeaways

  • Prioritize Tools over UI: Use CLI tools to validate business logic early, avoiding the high cost and wasted effort of building UIs for shifting requirements.
  • Spring + PicoCLI Synergy: Integrating PicoCLI with Spring Boot allows commands to use existing services and dependency injection, making development fast and robust.

  • Bridge the Feedback Gap: CLI tools turn "silent" backend logic into visible, executable results that help stakeholders validate progress without waiting for a front end.

  • Agile "Add, Don't Rewrite": Using a subcommand hierarchy allows the team to pivot—like moving from single to bulk actions—by simply adding new commands to the existing toolbox.

  • Long-Term Utility: These tools evolve from development prototypes into permanent operational assets for support, data cleansing, and troubleshooting.

Ever faced a project where requirements shifted like sand and stakeholders demanded immediate progress, even before processes were finalised? This common challenge often leaves development teams scrambling for adaptable solutions. Our recent client project, managing a digital screens platform, presented just such a scenario. The core system was a backend, offering little direct output for business validation, coupled with evolving operational workflows.

Our answer? Not a complex UI or lengthy design phase, but a powerful toolbox of command-line interface (CLI) tools built with picocli - a mighty tiny command line interface, seamlessly integrated into our Spring Boot ecosystem. This approach became so fundamental to our agility that we've coined it: Tool-Driven Development (TDD).

TDD, in our context, means prioritising the creation of small, focused, and rapidly deployable CLI tools to validate assumptions, provide immediate feedback, and support the entire project lifecycle—from development and data analysis to support and operational queries.

In this article, we'll explore how PicoCLI empowered this Tool-Driven Development, transforming ambiguity into agility and establishing an indispensable operational toolbox.

 

The Uncharted Territory: Why TDD Emerged

Our client project faced significant headwinds:

Fluid Requirements: Business processes for screen management were undefined. We needed to quickly test hypotheses and observe their real-time implications.

Boring backend Silence: A backend offered no immediate visual feedback. We needed a rapid way for stakeholders to "see something working" and validate assumptions directly.

High Risk of Waste: Building full UI or API layers for uncertain requirements meant potential rework and wasted effort. We needed low-cost, high-flexibility solutions.

Operational Gaps: Beyond initial setup, there was a constant need for ad-hoc tools for queries, support, data cleansing, and direct API interaction.

 

PicoCLI & Spring: The Engine for Our TDD Toolbox

PicoCLI was our chosen engine for TDD due to its development speed, robust parsing, and focus on functional execution. Its seamless integration with Spring Boot allowed our CLI commands to leverage existing services and dependency injection.

Our Spring configuration for PicoCLI: 


@Configuration
public class PicocliConfig {

  @Autowired private ApplicationContext applicationContext;

  @Bean
  public CommandLine.IFactory picocliBeanFactory() {
    return new CommandLine.IFactory() {
      @Override
      public  K create(Class cls) throws Exception {
        return applicationContext.getBean(cls); // Spring manages command instances
      }
    };
  }
}
---
@Component
@Command(
    name = "digital-screens cli",
    mixinStandardHelpOptions = true,
    version = "1.0",
    description = "A digital-screens CLI application",
    subcommands = {
      SotiCommand.class, BroadSignCommand.class, StoreLocatorCommand.class, // Part of our toolbox
      HierarchyCommand.class, TagCommand.class, CsvToHierarchyYaml.class
    },
    usageHelpAutoWidth = true)
public class RootCommand implements Runnable {

  private final CommandLine commandLine;

  @Autowired
  public RootCommand(@Qualifier("picocliBeanFactory") CommandLine.IFactory factory) {
    this.commandLine = new CommandLine(this, factory); // Use custom factory
  }

  @Override
  public void run() {
      commandLine.usage(System.out); // Displays help if no subcommand
  }
}


This setup allowed us to expose granular business logic as executable commands. For rapid development and testing of new device registrations, our RegisterSingleCommand was a core component:


@Slf4j
@Component
@Command(name = "single", description = "Register a device related commands")
@RequiredArgsConstructor
class RegisterSingleCommand implements Runnable {

  @CommandLine.Parameters(index = "0", description = "Store ID")
  private Integer storeId;
  // ... other device parameters ...

  private final RegistrationService registrationService; // Spring-injected

  @SneakyThrows
  @Override
  public void run() {
    // ... logic to prepare request ...
    registrationService.enroll(...);
    // log.info ("Device registered for Store ID: {}", storeId);
  }
}


Beyond development, our TDD approach created an operational toolbox. The StoreLocatorCommand, for example, became essential for support and data analysis:


@Slf4j
@Component
@Command(name = "locator", description = "Store Locator related command")
class StoreLocatorCommand implements Runnable {

  @CommandLine.Parameters(index = "0", description = "storeId")
  private Integer storeId;

  private final StoreLocatorApiService storeLocatorApiService;

  StoreLocatorCommand(StoreLocatorApiService storeLocatorApiService) {
    this.storeLocatorApiService = storeLocatorApiService;
  }

  @Override
  public void run() {
    try {
      prettyPrint(storeLocatorApiService.getStoreDetailsRaw(storeId));
      // log.info ("Retrieved store details for ID: {}", storeId);
    } catch (IOException e) {
      log.error("Error retrieving store details for ID {}: {}", storeId, e.getMessage());
      System.out.println("Error during store search: " + e.getMessage());
    }
  }
}


This StoreLocatorCommand allowed support engineers to quickly fetch raw store details, with pretty-printed JSON output for immediate readability. We organized our growing toolbox using PicoCLI's subcommand hierarchy:


@Component
@Command(
    name = "bs", description = "BroadSign commands", subcommands = {
      RegisterCommand.class, BroadSignTestCommand.class, RefreshCampaignsCommand.class,
    })
class BroadSignCommand implements Runnable { /* ... */ }

@Component
@Command(
    name = "register", description = "Register commands", subcommands = {
      RegisterCSVCommand.class, RegisterSingleCommand.class, RegisterCSVValidateCommand.class
    })
class RegisterCommand implements Runnable { /* ... */ }


This structure facilitated clear navigation, enabling commands like digital-screens cli bs register single ... or digital-screens cli locator 12345.

 

TDD in Action: Fast Feedback, Rapid Pivots, Rich Toolbox

Our Tool-Driven Development approach provided several critical advantages:

Rapid Prototyping: New hypotheses or process tweaks translated into commands within minutes or hours. We quickly added options to existing commands or created new ones to test scenarios like different "loop policies" for screens.

Immediate Feedback & Progress: CLI tools became our "visible output." Instead of abstract discussions, we could demonstrate direct execution, like "Run this command, and here's the immediate result." This built trust and accelerated validation.

Agile Pivoting: When the business shifted from single-device to bulk CSV registration, our existing RegisterSingleCommand wasn't discarded. We simply added RegisterCSVCommand and RegisterCSVValidateCommand as new subcommands, reusing core services. This "add, don't rewrite" approach made pivots efficient.

Minimised Waste: By focusing on CLI first, we avoided heavy investment in UIs or APIs that might have become obsolete. We built only what was necessary to test assumptions.

Enduring Operational Toolbox: Beyond initial development, the CLI became indispensable. Commands for data cleansing, ad-hoc reports, API simulations, and troubleshooting (StoreLocatorCommand being a prime example) significantly streamlined operations and support.

 

The Power of TDD: Tangible Benefits

The Power of TDD: Tangible BenefitsTool-Driven Development yielded profound impacts:

Empowered Stakeholders: Business users, support engineers, and data analysts could directly interact with core functionality, seeing immediate results and actively contributing to validation and operations.

Accelerated Decision-Making: Faster feedback loops led to quicker, more informed decisions on process flows and data structures.

Developer Efficiency & Morale: Less rework and clear, achievable goals boosted team productivity and satisfaction.

Streamlined Operations: The comprehensive CLI toolbox dramatically reduced time spent on manual queries, support tasks, and data management.

 

Lessons Learned: Adopting Tool-Driven Development

Our journey with PicoCLI highlights key principles for adopting TDD:

Embrace "CLI First" (Strategically): For projects with high ambiguity or a need for rapid iteration and feedback, start with CLI tools to validate core logic and gather requirements.

PicoCLI + Spring = Agile Power: This combination builds flexible, testable, and maintainable CLI tools.

Don't Fear Disposable Tools: Some commands are temporary, built to answer specific questions. Their value is in the insight they provide, not their longevity.

Cultivate an Operational Toolbox: Think beyond initial development. PicoCLI excels at creating focused tools for ongoing support, analysis, and automation.

Bridge the Gap: CLI tools effectively bridge the space between backend logic and business validation when traditional UIs are premature or overkill.

 

Conclusion

Our client project, initially defined by uncertainty, transformed into a showcase of Tool-Driven Development thanks to PicoCLI. By enabling us to build a versatile and rapidly deployable toolbox of command-line tools, we provided immediate progress, tested options, pivoted quickly, and empowered various teams with efficient operational capabilities.

Back to Blog