Distributed Applications with GO: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
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===

Revision as of 11:51, 24 August 2021

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

Comments for the code

  • 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

Comments for the code

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