Welcome to the Software pool 2023! π
During this week, you will learn the base of the modern web programming, core concept of frontend, backend, data storage... with a piece of devops.
Day purposes
βοΈ Setup a Go project.
βοΈ Learn basics of Go.
βοΈ Discover software development good practices.
βοΈ Introduce you to design pattern, especially MVC.
βοΈ Create an interactive application using your terminal.
Go is a powerful language with many features and capabilities, you can manage any Go project from its complete CLI.
With it, you can build a go binary, download dependencies, format your code... If you are curious, you can look at the CLI documentation π
Start by installing the CLI, you can verify that everything works using the command below:
# Version must be at least 1.16
go version
# go version go1.19.4 linux/amd64
If you have any issue don't hesitate to ask the staff for help π
Go is an open source programming language created and maintained by Google.
It solves several problems of the modern software world thanks to its simplicity
and incredible standard library. It's also really strong to build
microservices
and software that required high level of performance.
Go has many usage, you can write powerful cloud & networks, CLI,
API or DevOps tools.
Because it's a compiled language, it's really fast, much faster than Javascript for instance π
Here's an exhaustive list of Go killer features:
- Easily solve complex concurrency issues with go routine.
- Strongly statically typed
- Amazing dependency management with go module
- Multi-paradigm from OOP to functional
Let's begin this pool with a simple warm up: The Go Tour
This tour is a set of exercise to learn Go basics.
You can also try the Go Playground, a useful tool to test or share pieces of codes.
You will also need a good IDE, we recommend Visual Studio Code or GoLand.
In summary, here's a bunch of links that will be very useful during the pool:
As usual, every exercise must be pushed to a git repository.
To make it easier, we will use a GitHub classroom! Follow this link to create your git repository.
Then, you can clone your repository.
git clone [email protected]:PoC-Community/software-pool-days-{YOUR-GITHUB-USERNAME}.git
For each day, we will create a new folder. Start with the day01
.
mkdir -p day01
Inside it, let's initialize a Go project
go mod init SoftwareGoDay1
> go: creating new go.mod: module SoftwareGoDay1
You should see a go.mod
file appear in your directory.
You can now create your first go file: main.go
π
package main
import "fmt"
func main() {
fmt.Println("You are ready to start the 2023 PoC software pool!")
}
π‘ If you are curious about this, check the fmt package documentation
Let's execute it:
# Use `go run` to execute your program without building the binary
go run SoftwareGoDay1 # Or go run ./main.go
# You can also build and run it in separate command
go build SoftwareGoDay1
./SoftwareGoDay1
# You should have the following output
> You are ready to start the 2023 PoC software pool!
Before going further, do the Go Tour and read the package documentation, those core concepts are important to understand the rest of the pool π―
It's time to start coding! We will start with a simple helloWorld
package.
Let's create a folder helloWorld
with a file helloWorld.go
in it.
mkdir -p helloWorld
You will implement a function HelloWorld
that will display Hello World!
in your terminal.
Don't forget to add the correct package name at the top of
helloWorld.go
π
Update the file main.go
to call HelloWorld
.
π‘ Here's a couple of slides about what is a good naming in Go.
The purpose of this step is to use what you have learned during the Go Tour π
Let's create a function to sort numbers so we can cover useful concepts such as loops, conditions, arrays and tests π₯³
Your function must:
- Sort a list in ascending order
- Remove odd numbers
- Format the result to return
Create a new package getEvenNumbers
with a file getEvenNumbers.go
.
mkdir -p getEvenNumbers
Create a function GetEvenNumbers
that will take an array of int.
It must sort the array in ascending order and return a string that
contains only pairs number in ascending order, split by a -
.
Update main.go
to call GetEvenNumbers
and display the result:
array := []int{
1,
0,
19,
17,
16,
8,
13,
24,
}
fmt.PrintLn(getEvenNumbers.GetEvenNumbers(array))
You should have the following result:
0 - 8 - 16 - 24
It's really important to correctly test your code, a code not tested can lead to unpredictable bugs and the time lost to debug it could be avoided by creating some tests.
To do so, Go provides a built-in test package, with a clean way to organize your tests π₯
Let's create a test file named getEvenNumbers_test.go
.
To make sure your getSortedEvenNumbers
works as intended, you can write a test for each of these cases:
- Only positive numbers
- Only negative numbers
- Mixed numbers
You can then execute them with go test
, which should produce a result similar to this one:
go test ./getEvenNumbers -v
=== RUN TestGetEvenNumbersCasePositive
--- PASS: TestGetEvenNumbersCasePositive (0.00s)
=== RUN TestGetEvenNumbersCaseNegative
--- PASS: TestGetEvenNumbersCaseNegative (0.00s)
=== RUN TestGetEvenNumbersCasePositiveAndNegative
--- PASS: TestGetEvenNumbersCasePositiveAndNegative (0.00s)
PASS
ok SoftwareGoDay1/getEvenNumbers 0.001s
Now it's time to learn how to read input from the terminal π€©
Create a new package whatIsYourName
with a file whatIsYourName.go
and a function WhatIsYourName
inside it.
mkdir -p whatIsYourName
This function must display a prompt in the terminal to ask a user to fill his name and retrieve it.
To do so, you can use the bufio and os packages π
You should end up with the following behavior:
What is your name ?
# User fill input
> Slim Shady
# Display input
Your name is Slim Shady
Don't forget to call it from main.go
and correctly manage errors.
π‘ Display your errors using the log package and
fmt.Errorf
.
It's really hard to test I/O with unit test, but that's why functional tests exist!
Download them from GitHub and extract sources in your directory π
Then, create a binary of your project using go build
.
You can now simply execute testWhatIsYourName
to launch the tests of your function:
OK - TEST 1 PASSED
OK - TEST 2 PASSED
OK - TEST 3 PASSED
Don't hesitate to add more tests and modify your program to make sure all of them pass π§ͺ
As we said in the introduction, Go come with great built-ins to help you during your development.
One of these tools is gofmt, a powerful formatting tool that will help you have standard coding style rules in your code.
Its really helpful on the format, but having a clean code architecture is up to you π
To use gofmt, just launch go fmt .
at the
root of your module π
You can also configure VSCode or GoLand to format your code on save.
You are ready for a complex exercise. Let's create our first application!
It will be a program that help you manage your favorite artists from your terminal.
We will not code everything in one step, that would be too huge for this moment.
Let's start with a simple implementation. The goal is to have a CLI similar to this one:
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Leave
# User tip input
> 3
Tip 1 or 2.
# User tip input
> 1
Here's your favorite artists:
-- 1 -- B2O
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
What do you want to do?
1 - List my favorite artists
2 - Leave
# User tip input
> 2
See you!
To do this, we must build a program that follows a strong architecture. We will use one of the most popular: MVC.
MVC stands for Model - View - Controller. It's an architecture where your code logic is split into smaller parts to easily maintain and scale a project.
We will now adapt MVC to our need, don't worry if we do not strictly follow the architecture.
Here's a schema of your architecture:
Let's code step by step π
Router corresponds to the entrypoint of your program and the main loop.
It has different roles:
- Display actions to user.
- Catch the user input.
- Call
controllers
to execute the action asked by the user.
A good architecture also requires a good folder management in your code.
Each resource must be in its own package and with the router
making the link
between them.
- Create a package
artistsBook
with a filerouter.go
in it. - Create a function
Router
that will do the loop described in the example.
You are big boy/girl now, you got the keys to do it by yourself πͺ
- Update
main.go
to callRouter
.
A controller is in charge of the business logic that manages your resources.
Its only purpose is to create the link between the function that manage your
data storage (for now it will be a simple JSON
file) and functions exposed
to the user.
- Create a package
controllers
inartistsBook
folder. - Create a sub-package
artists
in it. - Create a file
display.go
- Write the function
DisplayAll
that controls the display of the user's favorite artists
A repository is responsible for all interactions with the data storage.
- Create a package
repositories
in theartistsBook
folder. - Create a sub-package
artists
in it. - Create a file
get.go
- Write the function
GetAll
that retrieves user's favorite artists
You will need the os and json packages to read a file (see more information below).
A view expose a list of functions to the user to make it interact with a resource.
- Create a package
views
inartistsBook
folder. - Create a sub-package
artists
in it. - Create a file
display.go
- Write the function
DisplayAll
that display the user's favorite artists in the terminal.
A model defines the type of the stored data.
- Create a package
models
inartistsBook
folder. - Create a sub-package
artist
- Create a file
artist.go
in it - Export a struct
Artist
that contains a fieldname
of typestring
Data defines your storage, it can be a database, an Excel file or whatever that
can store data. Here, it will be a simple JSON
file.
JSON stands for JavaScript Object Notation. It's a standard like
CSV
,XML
orYAML
used to define a structured data.
We'll discover real databases tomorrow π
- Create a directory
data
in theartistsBook
folder. - Create the file
artists.json
with the following content :
[
{
"name": "B2O"
},
{
"name": "SCH"
},
{
"name": "Laylow"
},
{
"name": "Billie Eilish"
}
]
Finally, you should have the following architecture:
artistsBook/
router.go
controllers/
artists/
display.go
repositories/
artists/
get.go
views/
artists/
display.go
models/
artist.go
data/
artists.json
This exercise may seems hard but if you write your code step by step, it will be a piece of cake π°!
You've built the base of your MVC architecture, it's time to improve it π
For now, you can only read data, let's add operations to create, update or delete it.
Those four primitive operations are mandatory to manage a resource in a data storage. They are usually called CRUD, which stands for CREATE - READ - UPDATE - DELETE.
Let's add the missing ones π
Update your codebase to allow a user to add a new artist to his list.
Add a file create.go
in your artists repository that will contain
the function Create
to add an artist to the database.
β οΈ You will need to write the wholeartists.json
file
Take care to not lose data when you rewrite the file.
- Create the file
ask.go
in theviews/artists
directory. - Write the function
AskName
that retrieve an artist's name from the terminal.
π‘ You can copy code from the precedent step to win time
- Create the file
create.go
incontrollers/artists
directory. - Write the function
Create
that controls the artist addition.
Add the possibility to create an artist in router.go
.
You can code other functions to make your code works. For example, a function to
display the artist name if it has been correctly added to the list.
This function should be in the views, in display.go
for example.
You should have the following result:
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley has been added to your favorite artists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 1
Here's your favorite artists:
-- 1 -- B2O
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
-- 5 -- Bob Marley
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley already exists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 3
See you!
Update your application to allow the user to update an artist in his list.
Create the file update.go
in your repository/artists
folder.
Add the function Update
to modify an artist in the data storage.
Add the function AskNewName
in your views/artists
directory.
I'm sure you have understood the logic π§
Don't forget to update your controller
, the router
and add anything
required to correctly update an artist.
You should have a result similar to this one:
Welcome into your Artists Book!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Leave
# User tip input
> 2
What's the artist's name?
# User tip input
> Bob Marley
Bob Marley has been added to your favorite artists!
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 3
What's the name of the artist you want to update?
# User tip input
> unknown
What will be the new name of unknown?
# User tip input
> Mac Miller
unknown is not in your favorite list.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 3
What's the name of the artist you want to update?
# User tip input
> B20
What will be the new name of unknown?
# User tip input
> Booba
B2O has been successfully updated.
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 1
Here's your favorite artists:
-- 1 -- Booba
-- 2 -- SCH
-- 3 -- Laylow
-- 4 -- Billie Eilish
-- 5 -- Bob Marley
What do you want to do?
1 - List my favorite artists
2 - Add an artist to my favorite
3 - Update an artist in my favorite
4 - Leave
# User tip input
> 4
See you!
Update your codebase to allow user to remove an artist from his list.
It's up to you to find the best way to do it π
You have implemented a complete MVC architecture, that's excellent π
Only one thing is missing, our data is too basic, it only has a name.
Let's add some fields by updating the Artist
structure:
id
: A unique identifier of typestring
top
: Best song, as astring
fans
:number
of fanslistenedTime
: the amount of time you listened to this artist. It must be stored as aTime
π‘ It's common to put a unique identifier when you store data, this way, you can easily distinguish them.
You'll have to update all your codebase to support those new fields.
Don't worry, it's not that hard because you have build a strong architecture!
And if you struggle, remember that the staff is here to help you out π
It's time to learn another important concept in Go: interfaces.
To do so, we will do a simple example with geometric shapes π₯
Let's create a package geometricShape
.
mkdir -p geometricShape
Inside it, create an interface GeometricShape
with a method CalcPerimeter
.
This method must have the following prototype:
CalcPerimeter() float64
Create a function CalcPerimeter
that will take this time an interface
GeometricShape
as parameter and return the result of the function
CalcPerimeter
called from the interface.
Here's a prototype to help you:
func CalcPerimeter(shape GeometricShape) float64
Create the struct Circle
with a field Radius
.
Create the struct Rectangle
with a field X
and Y
.
Create the struct Triangle
with a field X
, Y
and Z
.
For each shape, implement the method CalcPerimeter
that will return
the parameter of the shape.
Here's an example of prototype for a Square
:
func (square Square) CalcPerimeter() float64 {}
π‘ As you may have noticed, you can write a Go function in this order too (parameters before the function name)
Update the file main.go
to create 3 shapes:
- A
Circle
withradius
:12.0
- A
Rectangle
withX
:2.0
andY
:3.0
- A
Triangle
withX
:5.0
,Y
:2.0
andZ
:1.0
For each shape, display their perimeter using CalcPerimeter
from the package geometricShape
.
Here's an example of output:
&{12} has a perimeter of 75.398224
&{2 3} has a perimeter of 10.000000
&{5 2 1} has a perimeter of 8.000000
As you can see, it's not clear... Let's use the power of interfaces to fix that π
The package fmt
call the method String
of given the given argument to display it properly.
So if our structures implement the method
String
, you can control the way it's displayed byfmt
π
Update your structures to print it correctly, here's an example of prototype
with Square
:
func (square Square) String() string {}
Thanks to interfaces, you do not have to change anything in main.go
,
just rerun the program and appreciate the moment π
# You can configure `String()` to display this
Circle: { radius = 12 } has a perimeter of 75.398224
Rectangle: { X = 2, Y = 3 } has a perimeter of 10.000000
Triangle: { X = 5, Y = 2, Z = 1 } has a perimeter of 8.000000
Since you now know interfaces, why not use it in your MVC?
The purpose is to update Artist
to correctly display it with fmt
package.
In your artist
model, implement the method String
.
You can also update DisplayAll
from your views
to use a simple fmt.Printf
to display Artist
.
First, congratulation! You've survived day 1 π
Your MVC currently manages only one resource: Artist
.
You can create a new resources named Music
containing the following data:
id
: Resource unique identifierartist
: Owner of the musicname
: Music's titlelistened
: Number of listeninglink
: A link to the music (E.g:spotify
,youtube
...)
You're free to add any features to your application.
You can maybe start by adding the CRUD for your new Music
resource.
π Don't hesitate to follow us on our different networks, and put a star π on
PoC's
repositories.