As per the previous diagram, we have designed our application to consist of one instance of Goophr Concierge and three instances of Goophr Librarian. In order to keep our code manageable, we will split the source code into two main entities and a docker-compose file at the root level:
- Concierge
- Librarian
- docker-compose.yaml
In Chapter 1, Developer Environment for Go, we discussed how to create and run docker images. The docker run ... works great for single images, but it might get complicated when we want to create a network of docker images that interact with one another. In order to keep the setup simple, we will make use of docker-compose (https://docs.docker.com/compose/overview/). In a nutshell, docker-compose requires a YAML (Yet Another Markup Language) file with specifics such as what to name the running docker images, what ports to run them on, and which Dockerfile to use to build these docker images.
The following is the docker-compose.yaml file we will be using in our project:
version: '3' services: concierge: build: concierge/. ports: - "6060:9000" a_m_librarian: build: librarian/. ports: - "7070:9000" n_z_librarian: build: librarian/. ports: - "8080:9000" others_librarian: build: librarian/. ports: - "9090:9000"
Note that a_m_librarian, n_z_librarian, and others_librarian are built from the same docker image defined by librarian/Dockerfile. This makes our life easier than using raw docker commands to start and configure multiple instances.
Here is the project structure that we will be starting with:
$ tree . ├── concierge │ ├── api │ │ ├── feeder.go │ │ └── query.go │ ├── common │ │ ├── helpers.go │ │ └── state.go │ ├── Dockerfile │ └── main.go ├── docker-compose.yaml └── librarian ├── api │ ├── index.go │ └── query.go ├── common │ ├── helpers.go │ └── state.go ├── Dockerfile └── main.go
Even though we have an elaborate structure set up, for now, the only files that have any useful code are concierge/main.go, concierge/Dockerfile, librarian/main.go, and librarian/Dockerfile (for convenience, from here on, we will denote the files using shorthand notation {concierge,librarian}/{main.go,Dockerfile}. This notation is inspired from Bash.)
Let's take a look at main.go and Dockerfile. These two files will be almost identical for both components. For brevity, we will show each of the two types of the files once, and show where the differences lie.
Let's start with main.go:
// {concierge,librarian}/main.go package main import "fmt" func main() { fmt.Println("Hello from Concierge!") // Or, Hello from Librarian! }
Now let's look at Dockerfile:
# {concierge,librarian}/Dockerfile FROM golang:1.9.1 # In case of librarian, '/concierge' will be replaced with '/librarian' ADD . /go/src/github.com/last-ent/distributed-go/chapter5/goophr/concierge WORKDIR /go/src/github.com/last-ent/distributed-go/chapter5/goophr/concierge RUN go install github.com/last-ent/distributed-go/chapter5/goophr/concierge ENTRYPOINT /go/bin/concierge EXPOSE 9000
If we run the complete codebase, we should see an output similar to the following:
$ docker-compose up --build # ... Creating goophr_a_m_librarian_1 ... Creating goophr_concierge_1 ... Creating goophr_m_z_librarian_1 ... Creating goophr_others_librarian_1 ... Creating goophr_a_m_librarian_1 Creating goophr_m_z_librarian_1 Creating goophr_others_librarian_1 Creating goophr_others_librarian_1 ... done Attaching to goophr_a_m_librarian_1, goophr_m_z_librarian_1, goophr_concierge_1, goophr_others_librarian_1 a_m_librarian_1 | Hello from Librarian! m_z_librarian_1 | Hello from Librarian! others_librarian_1 | Hello from Librarian! concierge_1 | Hello from Concierge! goophr_a_m_librarian_1 exited with code 0 goophr_m_z_librarian_1 exited with code 0 goophr_concierge_1 exited with code 0 goophr_others_librarian_1 exited with code 0