Composing all five programs

Now that we have completed all five 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, which results in a series of synonyms:
    • confab
    • confabulation
    • schmooze
  2. The synonyms flow into sprinkle; here they are augmented with web-friendly prefixes and suffixes, such as the following:
    • confabapp
    • goconfabulation
    • schmooze time
  3. These new words flow into coolify; here the vowels are potentially tweaked:
    • confabaapp
    • goconfabulatioon
    • schmoooze time
  4. The modified words then flow into domainify; here they are turned into valid domain names:
    • confabaapp.com
    • goconfabulatioon.net
    • schmooze-time.com
  5. Finally, the domain names flow into available; here they are checked against the WHOIS server to see whether somebody has already taken the domain or not:
    • confabaapp.com Composing all five programs
    • goconfabulatioon.net Composing all five programs
    • schmooze-time.com Composing all five programs

One program to rule them all

Running our solution by piping programs together is an elegant form of 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 and 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 to 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 this folder. The lib folder is where we will keep builds of our subprograms, but we don't want to copy and paste 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 into it 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 our subprograms (including domainfinder, which we are yet to write), telling go build to place them in our lib folder. Be sure to give execution rights to the new script 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.

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 into 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 to run 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 with the os.Stdin stream of this program and the Stdout (standard out stream) of the last program with the os.Stdout stream of 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 stream 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 exists which would be no good since we will have to run five programs at the same time). If anything goes wrong, we bail with log.Fatalln; however, if the program starts successfully, we 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 the programs start running, we 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.

The following screenshot shows the output from our programs when we type clouds; we have found quite a few available domain name options:

One program to rule them all

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

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