Composing all five programs

Now that we have completed all five of our programs, it's time to put them all together so that we can use our tool to find an available domain name for our chat application. The simplest way to do this is to use the technique we have been using throughout this chapter: using pipes in a terminal to connect the output and input.

In the terminal, navigate to the parent folder of the five programs and run the following single line of code:

./synonyms/synonyms | ./sprinkle/sprinkle | ./coolify/coolify | ./domainify/domainify | ./available/available

Once the programs are running, type in a starting word and see how it generates suggestions before checking their availability.

For example, typing in chat might cause the programs to take the following actions:

  1. The word chat goes into synonyms and out comes a series of synonyms:
    • confab
    • confabulation
    • schmooze
  2. The synonyms flow into sprinkle where they are augmented with web-friendly prefixes and suffixes such as:
    • confabapp
    • goconfabulation
    • schmooze time
  3. These new words flow into coolify, where the vowels are potentially tweaked:
    • confabaapp
    • goconfabulatioon
    • schmoooze time
  4. The modified words then flow into domainify where they are turned into valid domain names:
    • confabaapp.com
    • goconfabulatioon.net
    • schmooze-time.com
  5. Finally, the domain names flow into available where they are checked against the WHOIS server to see whether somebody has already taken the domain or not:
    • confabaapp.com ×
    • goconfabulatioon.net
    • schmooze-time.com

One program to rule them all

Running our solution by piping programs together is an elegant architecture, but it doesn't have a very elegant interface. Specifically, whenever we want to run our solution, we have to type the long messy line where each program is listed separated by pipe characters. In this section, we are going to write a Go program that uses the os/exec package to run each subprogram while piping the output from one into the input of the next as per our design.

Create a new folder called domainfinder alongside the other five programs, and create another new folder called lib inside that folder. The lib folder is where we will keep builds of our subprograms, but we don't want to be copying and pasting them every time we make a change. Instead, we will write a script that builds the subprograms and copies the binaries to the lib folder for us.

Create a new file called build.sh on Unix machines or build.bat for Windows and insert the following code:

#!/bin/bash
echo Building domainfinder...
go build -o domainfinder
echo Building synonyms...
cd ../synonyms
go build -o ../domainfinder/lib/synonyms
echo Building available...
cd ../available
go build -o ../domainfinder/lib/available
cd ../build
echo Building sprinkle...
cd ../sprinkle
go build -o ../domainfinder/lib/sprinkle
cd ../build
echo Building coolify...
cd ../coolify
go build -o ../domainfinder/lib/coolify
cd ../build
echo Building domainify...
cd ../domainify
go build -o ../domainfinder/lib/domainify
cd ../build
echo Done.

The preceding script simply builds all of our subprograms (including domainfinder, which we are yet to write) telling go build to place them in our lib folder. Be sure to give the new script execution rights by doing chmod +x build.sh, or something similar. Run this script from a terminal and look inside the lib folder to ensure that it has indeed placed the binaries for our subprograms in there.

Tip

Don't worry about the no buildable Go source files error for now, it's just Go telling us that the domainfinder program doesn't have any .go files to build.

Create a new file called main.go inside domainfinder and insert the following code in the file:

package main
var cmdChain = []*exec.Cmd{
  exec.Command("lib/synonyms"),
  exec.Command("lib/sprinkle"),
  exec.Command("lib/coolify"),
  exec.Command("lib/domainify"),
  exec.Command("lib/available"),
}
func main() {

  cmdChain[0].Stdin = os.Stdin
  cmdChain[len(cmdChain)-1].Stdout = os.Stdout

  for i := 0; i < len(cmdChain)-1; i++ {
    thisCmd := cmdChain[i]
    nextCmd := cmdChain[i+1]
    stdout, err := thisCmd.StdoutPipe()
    if err != nil {
      log.Fatalln(err)
    }
    nextCmd.Stdin = stdout
  }

  for _, cmd := range cmdChain {
    if err := cmd.Start(); err != nil {
      log.Fatalln(err)
    } else {
      defer cmd.Process.Kill()
    }
  }

  for _, cmd := range cmdChain {
    if err := cmd.Wait(); err != nil {
      log.Fatalln(err)
    }
  }

}

The os/exec package gives us everything we need to work with running external programs or commands from within Go programs. First, our cmdChain slice contains *exec.Cmd commands in the order in which we want to join them together.

At the top of the main function, we tie the Stdin (standard in stream) of the first program to the os.Stdin stream for this program, and the Stdout (standard out stream) of the last program to the os.Stdout stream for this program. This means that, like before, we will be taking input through the standard input stream and writing output to the standard output stream.

Our next block of code is where we join the subprograms together by iterating over each item and setting its Stdin to the Stdout of the program before it.

The following table shows each program, with a description of where it gets its input from, and where its output goes:

Program

Input (Stdin)

Output (Stdout)

synonyms

The same Stdin as domainfinder

sprinkle

sprinkle

synonyms

coolify

coolify

sprinkle

domainify

domainify

coolify

available

available

domainify

The same Stdout as domainfinder

We then iterate over each command calling the Start method, which runs the program in the background (as opposed to the Run method which will block our code until the subprogram exits—which of course is no good since we have to run five programs at the same time). If anything goes wrong, we bail with log.Fatalln, but if the program starts successfully, we then defer a call to kill the process. This helps us ensure the subprograms exit when our main function exits, which will be when the domainfinder program ends.

Once all of the programs are running, we then iterate over every command again and wait for it to finish. This is to ensure that domainfinder doesn't exit early and kill off all the subprograms too soon.

Run the build.sh or build.bat script again and notice that the domainfinder program has the same behavior as we have seen before, with a much more elegant interface.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.76.138