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:
chat
goes into synonyms
, which results in a series of synonyms:sprinkle
; here they are augmented with web-friendly prefixes and suffixes, such as the following:coolify
; here the vowels are potentially tweaked:domainify
; here they are turned into valid domain names:available
; here they are checked against the WHOIS server to see whether somebody has already taken the domain or not: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.
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 |
sprinkle |
sprinkle |
synonyms |
coolify |
coolify |
sprinkle |
domainify |
domainify |
coolify |
available |
available |
domainify |
The same |
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:
13.58.51.228