A practical tutorial showing how to transform a monolithic data-collection flow into a modular, testable, and extensible design.
Let’s say you’re building a CLI that interviews the user, asking for name, birth date, and document number. The initial version works, but soon becomes a hard-to-maintain block of code:
how do you add validations without increasing complexity? how do you implement confirmation and retry logic for each input? 1. Initial Scenario package main import ( "fmt" "log" "strings" "time" "github.com/AlecAivazis/survey/v2" ) func main() { var name, dateStr, document string if err := survey.AskOne(&survey.Input{Message: "Name:"}, &name); err != nil { log.Fatal(err) } if len(strings.TrimSpace(name)) < 2 { log.Fatal("Name must have at least 2 characters") } if err := survey.AskOne(&survey.Input{Message: "Birth date (MM/DD/YYYY):"}, &dateStr); err != nil { log.Fatal(err) } birthDate, err := time.Parse("01/02/2006", dateStr) if err != nil || birthDate.After(time.Now()) { log.Fatal("Invalid birth date") } if err := survey.AskOne(&survey.Input{Message: "Document:"}, &document); err != nil { log.Fatal(err) } if len(document) == 0 { log.Fatal("Document cannot be empty") } fmt.Printf("Collected: %s | %s | %s\n", name, birthDate.Format("2006-01-02"), document) } Here we see some problems: Difficulty adding new steps: every new field or validation requires modifying the whole flow, increasing the risk of bugs. Validation rules mixed with data collection: it’s hard to isolate or reuse validations. Rigid, inflexible flow: not simple to reorder, skip, or repeat steps without rewriting code. No organized retry mechanism: any error terminates the program, no fine-grained retry control. Low clarity about the flow: as code grows, it becomes harder to understand the order and logic of each step. 2. Reasoning for Decoupling We want to split the process into independent steps, each responsible for: • Asking; • Validating; • Deciding whether to retry or move forward.
...