|
|
Line 37: |
Line 37: |
| The project structure was basically a root folder with a cmd directory holding the main.go code for each binary. From there there is one folder for each component.<br> | | The project structure was basically a root folder with a cmd directory holding the main.go code for each binary. From there there is one folder for each component.<br> |
| [[File:GO Project Structure Distributed.png|300px]] | | [[File:GO Project Structure Distributed.png|300px]] |
| ==Grades Service==
| |
| This service has three aspects
| |
| *Mock data
| |
| *Grade Business Logic
| |
| *EndPoints
| |
| ===Mock Data===
| |
| There is nothing special about this data but handy to remind yourself how to do this in GO
| |
| <syntaxhighlight lang="go">
| |
| package grades
| |
|
| |
| func init() {
| |
| students = []Student{
| |
| Student{
| |
| ID: 1,
| |
| FirstName: "Averill",
| |
| LastName: "Simen",
| |
| Grades: []Grade{
| |
| Grade{
| |
| Title: "Quiz 1",
| |
| Type: GradeQuiz,
| |
| Score: 85,
| |
| },
| |
| Grade{
| |
| Title: "Week 1 Homework",
| |
| Type: GradeHomework,
| |
| Score: 94,
| |
| },
| |
| Grade{
| |
| Title: "Quiz 2",
| |
| Type: GradeQuiz,
| |
| Score: 88,
| |
| },
| |
| },
| |
| },
| |
| Student{
| |
| ID: 2,
| |
| FirstName: "Marge",
| |
| LastName: "Garrard",
| |
| Grades: []Grade{
| |
| Grade{
| |
| Title: "Quiz 1",
| |
| Type: GradeQuiz,
| |
| Score: 100,
| |
| },
| |
| Grade{
| |
| Title: "Week 1 Homework",
| |
| Type: GradeHomework,
| |
| Score: 100,
| |
| },
| |
| Grade{
| |
| Title: "Quiz 2",
| |
| Type: GradeQuiz,
| |
| Score: 88,
| |
| },
| |
| },
| |
| },
| |
| Student{
| |
| ID: 3,
| |
| FirstName: "Sydnie",
| |
| LastName: "Barber",
| |
| Grades: []Grade{
| |
| Grade{
| |
| Title: "Quiz 1",
| |
| Type: GradeQuiz,
| |
| Score: 77,
| |
| },
| |
| Grade{
| |
| Title: "Week 1 Homework",
| |
| Type: GradeHomework,
| |
| Score: 0,
| |
| },
| |
| Grade{
| |
| Title: "Quiz 2",
| |
| Type: GradeQuiz,
| |
| Score: 65,
| |
| },
| |
| },
| |
| },
| |
| Student{
| |
| ID: 4,
| |
| FirstName: "Louie",
| |
| LastName: "Easton",
| |
| Grades: []Grade{
| |
| Grade{
| |
| Title: "Quiz 1",
| |
| Type: GradeQuiz,
| |
| Score: 88,
| |
| },
| |
| Grade{
| |
| Title: "Week 1 Homework",
| |
| Type: GradeHomework,
| |
| Score: 93,
| |
| },
| |
| Grade{
| |
| Title: "Quiz 2",
| |
| Type: GradeQuiz,
| |
| Score: 84,
| |
| },
| |
| },
| |
| },
| |
| Student{
| |
| ID: 5,
| |
| FirstName: "Kylee",
| |
| LastName: "Attwood",
| |
| Grades: []Grade{
| |
| Grade{
| |
| Title: "Quiz 1",
| |
| Type: GradeQuiz,
| |
| Score: 95,
| |
| },
| |
| Grade{
| |
| Title: "Week 1 Homework",
| |
| Type: GradeHomework,
| |
| Score: 100,
| |
| },
| |
| Grade{
| |
| Title: "Quiz 2",
| |
| Type: GradeQuiz,
| |
| Score: 97,
| |
| },
| |
| },
| |
| },
| |
| }
| |
| }
| |
|
| |
| </syntaxhighlight>
| |
| ===Grade Business Logic===
| |
| This is just business logic and useful for examples in GO.
| |
| <syntaxhighlight lang="go">
| |
| package grades
| |
|
| |
| import (
| |
| "fmt"
| |
| "sync"
| |
| )
| |
|
| |
| type Student struct {
| |
| ID int
| |
| FirstName string
| |
| LastName string
| |
| Grades []Grade
| |
| }
| |
|
| |
| func (s Student) Average() float32 {
| |
| var result float32
| |
| for _, grade := range s.Grades {
| |
| result += grade.Score
| |
| }
| |
| return result / float32(len(s.Grades))
| |
| }
| |
|
| |
| type Students []Student
| |
|
| |
| var (
| |
| students Students
| |
| studentsMutex sync.Mutex
| |
| )
| |
|
| |
| func (s Students) GetByID(id int) (*Student, error) {
| |
| for i := range s {
| |
| if s[i].ID == id {
| |
| return &s[i], nil
| |
| }
| |
| }
| |
|
| |
| return nil, fmt.Errorf("Student with ID '%v' not found", id)
| |
| }
| |
|
| |
| type GradeType string
| |
|
| |
| const (
| |
| GradeTest = GradeType("Test")
| |
| GradeHomework = GradeType("Homework")
| |
| GradeQuiz = GradeType("Quiz")
| |
| )
| |
|
| |
| type Grade struct {
| |
| Title string
| |
| Type GradeType
| |
| Score float32
| |
| }
| |
| </syntaxhighlight>
| |
| ===EndPoints===
| |
| This has some interest parts
| |
| *toJSON which takes and interface and encodes whatever is provider.
| |
| *Uses split of the r.URL.Path to determine which was called
| |
| *Uses mutex to ensure thread safety
| |
| <syntaxhighlight lang="go">
| |
| package grades
| |
|
| |
| import (
| |
| "bytes"
| |
| "encoding/json"
| |
| "fmt"
| |
| "log"
| |
| "net/http"
| |
| "strconv"
| |
| "strings"
| |
| )
| |
|
| |
| func RegisterHandlers() {
| |
| handler := new(studentsHandler)
| |
| http.Handle("/students", handler)
| |
| http.Handle("/students/", handler)
| |
| }
| |
|
| |
| type studentsHandler struct{}
| |
|
| |
| func (sh studentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
| |
| pathSegments := strings.Split(r.URL.Path, "/")
| |
| switch len(pathSegments) {
| |
| case 2: // /students
| |
| sh.getAll(w, r)
| |
| case 3: // /students/{:id}
| |
| id, err := strconv.Atoi(pathSegments[2])
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusNotFound)
| |
| return
| |
| }
| |
| sh.getOne(w, r, id)
| |
| case 4: // /students/{:id}/grades
| |
| id, err := strconv.Atoi(pathSegments[2])
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusNotFound)
| |
| return
| |
| }
| |
| if strings.ToLower(pathSegments[3]) != "grades" {
| |
| w.WriteHeader(http.StatusNotFound)
| |
| return
| |
| }
| |
| sh.addGrade(w, r, id)
| |
|
| |
| default:
| |
| w.WriteHeader(http.StatusNotFound)
| |
| }
| |
| }
| |
|
| |
| func (sh studentsHandler) getAll(w http.ResponseWriter, r *http.Request) {
| |
| studentsMutex.Lock()
| |
| defer studentsMutex.Unlock()
| |
|
| |
| data, err := sh.toJSON(students)
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusInternalServerError)
| |
| log.Println(err)
| |
| return
| |
| }
| |
| w.Header().Add("content-type", "application/json")
| |
| w.Write(data)
| |
| }
| |
|
| |
| func (sh studentsHandler) getOne(w http.ResponseWriter, r *http.Request, id int) {
| |
| studentsMutex.Lock()
| |
| defer studentsMutex.Unlock()
| |
|
| |
| student, err := students.GetByID(id)
| |
| if err != nil {
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusNotFound)
| |
| log.Println(err)
| |
| return
| |
| }
| |
| }
| |
|
| |
| data, err := sh.toJSON(student)
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusInternalServerError)
| |
| log.Println(fmt.Errorf("Failed to serialize students: %q", err))
| |
| return
| |
| }
| |
| w.Header().Add("content-type", "application/json")
| |
| w.Write(data)
| |
| }
| |
|
| |
| func (studentsHandler) toJSON(obj interface{}) ([]byte, error) {
| |
| var b bytes.Buffer
| |
| enc := json.NewEncoder(&b)
| |
| err := enc.Encode(obj)
| |
| if err != nil {
| |
| return nil, fmt.Errorf("Failed to serialize students: %q", err)
| |
| }
| |
| return b.Bytes(), nil
| |
| }
| |
|
| |
| func (sh studentsHandler) addGrade(w http.ResponseWriter, r *http.Request, id int) {
| |
| studentsMutex.Lock()
| |
| defer studentsMutex.Unlock()
| |
|
| |
| student, err := students.GetByID(id)
| |
| if err != nil {
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusInternalServerError)
| |
| log.Println(err)
| |
| return
| |
| }
| |
| }
| |
|
| |
| var g Grade
| |
| dec := json.NewDecoder(r.Body)
| |
| err = dec.Decode(&g)
| |
| if err != nil {
| |
| w.WriteHeader(http.StatusBadRequest)
| |
| log.Println(err)
| |
| return
| |
| }
| |
| student.Grades = append(student.Grades, g)
| |
|
| |
| w.WriteHeader(http.StatusCreated)
| |
| data, err := sh.toJSON(g)
| |
| if err != nil {
| |
| log.Println(err)
| |
| }
| |
| w.Header().Add("content-type", "application/json")
| |
| w.Write(data)
| |
| }
| |
| </syntaxhighlight>
| |
|
| |
| ==Log Service== | | ==Log Service== |
| ===Client=== | | ===Client=== |
Elements of a Distributed System
Characteristic
Four aspects might be
- Service Discovery
- Load Balancing
- Distributed tracing and logging
- Service Monitoring
Type of Distributed System
- Hub and Spoke (Satélite approach)
- Advantages Good for load balancing and logging
- Disadvantages Bad to single point of failure. Hub is complex due to responsibilities
- Peer to Peer where each communicate directly
- Advantages No Single point of failure. Highly decoupled
- Disadvantages Service discovery and Load Balancing hard
- Message Queue System where services get work from the queue
- Advantages Easy to scale, Persistence for disaster
- Disadvantages Single Point of failure (message queue), hard to configure
- Hybrid system (none of the above)
- This might will have advantages and disadvantage of both
Architectural Element
These are the aspect you may want to consider
- Languages
- Frameworks (Recommended Go-Kit and Go-Micro)
- Transports
- Protocol
Sample App
The sample app is a hybrid app using GO
This is the components to build
Introduction
I do not usually go through large portions of code but I thought it might be useful to look at the sample code and comment on the topic and the relationship with GO as a language.
Project Structure
The project structure was basically a root folder with a cmd directory holding the main.go code for each binary. From there there is one folder for each component.
Log Service
Client
- SetClientLogger - Sets the attribute for the standard log package
- Write - Writes data to server endpoint
The Code
package log
import (
"app/registry"
"bytes"
"fmt"
stlog "log"
"net/http"
)
func SetClientLogger(serviceURL string, clientService registry.ServiceName) {
stlog.SetPrefix(fmt.Sprintf("[%v] - ", clientService))
stlog.SetFlags(0)
stlog.SetOutput(&clientLogger{url: serviceURL})
}
type clientLogger struct {
url string
}
func (cl clientLogger) Write(data []byte) (int, error) {
b := bytes.NewBuffer([]byte(data))
res, err := http.Post(cl.url+"/log", "text/plain", b)
if err != nil {
return 0, err
}
if res.StatusCode != http.StatusOK {
return 0, fmt.Errorf("Failed to send log message. Service responded with %v - %v", res.StatusCode, res.Status)
}
return len(data), nil
}
Server
This creates an instance of a custom log type, a handler and a function to write to the file.
- Run - Creates a custom log file using the standard log package
- Write - Writes data to the stream
- RegisterHandlers - Registers the "/log", reads the data and writes the message
The Code
package log
import (
"io/ioutil"
stlog "log"
"net/http"
"os"
)
var log *stlog.Logger
type fileLog string
func (fl fileLog) Write(data []byte) (int, error) {
f, err := os.OpenFile(string(fl), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return 0, err
}
defer f.Close()
return f.Write(data)
}
func Run(destination string) {
log = stlog.New(fileLog(destination), "", stlog.LstdFlags)
}
func RegisterHandlers() {
http.HandleFunc("/log", func(w http.ResponseWriter, r *http.Request) {
msg, err := ioutil.ReadAll(r.Body)
if err != nil || len(msg) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
write(string(msg))
})
}
func write(message string) {
log.Printf("%v\n", message)
}
Cmd
Service Registry
Service Registration
- Create Web Service
- Create Register Service
- Register Web Service
- Deregister Web Service
Service Discovery
- Create Grading Service
- Request Required Service On Startup
- Notify when Service Starts
- Notify when Service Shutdown