Introduction to Learning Go
Go can be downloaded from golang.org
Test your go
installation:
go version
Run Go in Docker
We can also run go in a small docker container:
cd golang\introduction
docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version
Code Structure
https://golang.org/doc/code.html
-
Package:
- Source files in same directory that are compiled together
- Have visibility on all source files in the same package
-
Modules:
- Collection of packages that are released together
Our repository can contain one or more go modules, but usually 1.
- At the root of the repo
go.mod
Declares module path + import path for packages. (Where to download them)
- When we write our own program, we can define a module path
- This allows us to publish our code (if we want), so others can download it
- The module path could be something like
github.com/google/go-cmp
- Makes it easy for other programs to consume our module
Our first Program
- Create a folder containing our application
mkdir app
- Define a module path (github.com/docker-development-youtube-series/golang/introdouction/app)
# change directory to your application source code
cd app
# create a go module file
go mod init github.com/docker-development-youtube-series/golang/introdouction/app
- Create basic source code
In the app
folder, create a program called app.go
Paste the following content into it.
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
- Run your application code
You can run your application
go run app.go
Building our Program
Build your application into a static binary:
go build
This will produce a compiled program called app
You can run this program easily:
./app
Install your application (optional)
"This command builds the app command, producing an executable binary.
It then installs that binary as $HOME/go/bin/app (or, under Windows, %USERPROFILE%\go\bin\app.exe)"
go install github.com/docker-development-youtube-series/golang/introdouction/app
The Code
Functions
In the video we cover writing functions.
It allows us to execute a block of code
You want to give your function a single purpose
Functions can have an input and return an output
Well thought out functions makes it easier to write tests
Instead of doing a boring x + y
function that adds two numbers, let's do something a little
more realistic but still basic:
// This function returns some data
// The data could be coming from a database, or a file.
// The person calling the function should not care
// Since the function does not leak its Data provider
func getData(inputs)(outputs){
}
// functions can take multiple inputs, and return multiple outputs
// lets say we have 1) customers and 2) the cities they are from
// we may want to 1) get a list of customers and 2) get a list of cities
// therefore we have 2 types of data, 1) customers 2) cities
// let's improve our function so its gets data based on the type
func getData(customerId int) (customer string) {
}
Variables
To hold data in programming languages, we use variables.
Variables take up space in memory, so we want to keep it minimal.
Let's declare variables in our function
func getData(customerId int) (customer string) {
var firstName = "Marcel"
lastName := "Dempers"
fullName := firstName + " " + lastName
return fullName
//or we can return the computation instead of adding another variable!
return firstName + " " + lastName
//or we dont even need to declare variables :)
return "Marcel Dempers"
}
Control Flows (if\else)
You can see we're not using the customerId
input in our function.
Let's use it!
Control flows allow us to add "rules" to our code.
"If this is the case, then do that, else do something else".
So let's say we have a customer ID 1 coming in, we may only want to
return our customer if it matches the customerId
func getData(customerId int) (customer string) {
if customerId == 1 {
return "Marcel Dempers"
} else if customerId == 2 {
return "Bob Smith"
} else {
return ""
}
}
Let's invoke our function :
//in the main() function
//get our customer
customer := getData(1)
fmt.Println(customer)
//get the wrong customer
customer := getData(3)
fmt.Println(customer)
Arrays
At the moment, we can only return 1 customer at a time on our function.
Realistically we need the ability to return more data, not just a single customer.
Arrays allow us to make a collection of variables of the same type.
We can now return a list of customers!
Let's change our function to get an array of customers!
func getData() (customers [2]string) {
//create 1 record
customer := "Marcel Dempers"
//assign our customer to the array
customers[0] = customer
//OR we can assign it like this
customers[1] = "Bob Smith"
//send it back to the caller
return customers
}
Now we also have to change our calling function to expect an array:
customers := getData()
fmt.Println(customers)
Slices
Since arrays are fixed size, Slices are a dynamically-sized view into arrays. Let's create a slice instead of array so we can add customers dynamically!
func getData() (customers []string) {
//initialise our slice of type string
customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"}
//add more legendary customers dynamically
customers = append(customers, "Ben Spain")
customers = append(customers, "Aleem Janmohamed")
customers = append(customers, "Jamie le Notre")
customers = append(customers, "Victor Savkov")
customers = append(customers, "P The Admin")
customers = append(customers, "Adrian Oprea")
customers = append(customers, "Jonathan D")
//send it back to the caller
return customers
}
Loops
Loops are used to iterate over collections, lists, arrays etc.
Let's say we need to loop through our customers
In the main()
function, we can grab the list of customers and loop them.
In this demo, we'll cover a basic for loop, but there are several approaches to writing loops.
//loop forever
for {
//any code in here will run forever!
fmt.Println("Infinite Loop 1")
time.Sleep(time.Second)
//unless we break out the loop like this
break
}
//loop for x number of loops
for x := 0; x < 10; x++ {
//any code in here will run 10 times! (unless we break!)
fmt.Println(customers[x])
}
//loop for ALL our customer
for x, customer := range customers {
//we can access the "customer" variable in this approach
customer = customers[x]
fmt.Println(customer)
//OR
//we can use the supplied customer from the loop
// and silence the x variable, replace it with a _ character
fmt.Println(customer)
}
Structs
So far so good, however, customer data is not useful as strings.
Customers can have a firstname, lastname, and more properties.
For this purpose we'd like to group some variables into a single variable.
This is what struct
allows us to do.
Let's create a struct
for our customer
Let's create a new go
file called customers.go
package main
Customer struct {
FirstName string
LastName string
FullName string
}
Let's put it all together:
In customers.go
, let's create a function to get customers
func GetCustomers()(customers []Customer) {
//we can declare customers like this:
marcel := Customer{ FirstName: "Marcel", LastName: "Dempers" }
customers = append(customers,
Customer{ FirstName: "Marcel", LastName: "Dempers" },
Customer{ FirstName: "Ben", LastName: "Spain" },
Customer{ FirstName: "Aleem", LastName: "Janmohamed" },
Customer{ FirstName: "Jamie", LastName: "le Notre" },
Customer{ FirstName: "Victor", LastName: "Savkov" },
Customer{ FirstName: "P", LastName: "The Admin" },
Customer{ FirstName: "Adrian", LastName: "Oprea" },
Customer{ FirstName: "Jonathan", LastName: "D" },
)
return customers
}
In main()
we can now call our shiny new function
customers := GetCustomers()
for _, customer := range customers {
//we can access the "customer" variable in this approach
fmt.Println(customer)
}
Docker
For our dev environment, we have a simple image using go
We also set a work directory and alias the target as dev
This means we can use this container layer as a development environment.
Later down the track we can add debuggers in here for example.
Checkout my debugging video for go: https://youtu.be/kToyI16IFxs
Development environment
FROM golang:1.15 as dev
WORKDIR /work
Building our code
FROM golang:1.15 as build
WORKDIR /app
COPY ./app/* /app/
RUN go build -o app
The Runtime
FROM alpine as runtime
COPY --from=build /app/app /
CMD ./app
Building the Container
docker build . -t customer-app
Running the Container
docker run customer-app