├── .gitignore ├── Chapter08 ├── cassandra.dml ├── gorm_associations.go ├── gorm_simple.go ├── cassandra.go └── redis.go ├── Chapter04 ├── bubble_sort.go └── quick_sort.go ├── Chapter07 ├── graphql.md ├── api_hello_word.go ├── apis.md ├── go_kit.go ├── hotels_gins.go └── graphql.go ├── Chapter01, and 02 ├── pigeon │ └── pigeon.go ├── visibility.go ├── embed.go ├── methods.go └── interface.go ├── Chapter11 └── cicd_example.go ├── Chapter05 ├── rpc_client.go └── rpc_server.go ├── Chapter12 └── generics │ ├── list │ ├── list-unit.go │ └── template.go │ └── main.go ├── Chapter06 ├── readme.md ├── nsq_prod.go ├── req_reply.go ├── fan_in_select.go ├── pipes_filter.go ├── fan_in.go ├── kafka_sync_producer.go ├── nsq_cons.go ├── fan_out.go ├── kakfka_consumer.go ├── nsq_async_cons.go └── kakfka_async_producer.go ├── Chapter03 ├── proxy.go ├── decorator.go ├── singleton.go ├── template.go ├── adaptor.go ├── composite.go ├── strategy.go ├── observer.go ├── visitor.go ├── state.go ├── mememto.go ├── bridge.go ├── abstract_factory.go ├── mediator.go ├── multiple_interfaces.go ├── creational_builder_factory.go └── command.go ├── Chapter10 ├── reservation_time.go ├── readme.md ├── seller_proxy.go ├── main.go └── models.go ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | -------------------------------------------------------------------------------- /Chapter08/cassandra.dml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CREATE KEYSPACE roster WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1}; 4 | 5 | CREATE TABLE employees ( 6 | id UUID, 7 | firstname varchar, 8 | lastname varchar, 9 | age int, 10 | PRIMARY KEY(id) 11 | ); -------------------------------------------------------------------------------- /Chapter04/bubble_sort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func bubbleSort(array []int) { 4 | swapped := true; 5 | for swapped { 6 | swapped = false 7 | for i := 0; i < len(array) - 1; i++ { 8 | if array[i + 1] < array[i] { 9 | array[i + 1], array[i ] = array[i], array[i + 1] 10 | swapped = true 11 | } 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Chapter07/graphql.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Samples 2 | ### This file documents graphql requests for graphql.go 3 | 4 | #Create 5 | curl -g 'http://localhost:8080/graphql?query=mutation+_{createHotel(displayName:"HotelX",city:"NY",noRooms:300,starRating:5){id}}' 6 | 7 | #Get 8 | curl -g 'http://localhost:8080/graphql?query={hotel(id:"XVlBzgba"){displayName,city,noRooms,starRating}}' 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter01, and 02/pigeon/pigeon.go: -------------------------------------------------------------------------------- 1 | // Package pigeon is used to demonstrate visibily and scope for packages 2 | package pigeon 3 | 4 | 5 | type Pigeon struct { 6 | Name string 7 | featherLength int 8 | } 9 | 10 | func (p *Pigeon) GetFeatherLength() int { 11 | return p.featherLength 12 | } 13 | 14 | 15 | func (p *Pigeon) SetFeatherLength(length int) { 16 | p.featherLength = length 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/cicd_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("starting application..") 10 | 11 | // setup and route 12 | r := gin.Default() 13 | r.GET("/health", func(c *gin.Context) { 14 | c.JSON(200, gin.H{ 15 | "status": "ok", 16 | }) 17 | }) 18 | 19 | // listen and serve on 0.0.0.0:9000 20 | r.Run(":9000") 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Chapter07/api_hello_word.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | // setup router 11 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 12 | log.Println("path", r.URL.Path) 13 | fmt.Fprintf(w, "pong! on %s\n", r.URL.Path) 14 | }) 15 | 16 | // listen and serve 17 | err := http.ListenAndServe(":9090", nil) 18 | if err != nil { 19 | log.Fatal("ListenAndServe: ", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter01, and 02/visibility.go: -------------------------------------------------------------------------------- 1 | // This file demonstrates package visibility along with the pigeon package 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "./pigeon" 7 | ) 8 | 9 | 10 | func main() { 11 | 12 | //p := pigeon.Pigeon{"Tweety", 10} - This will not compile 13 | p := pigeon.Pigeon{Name :"Tweety", } 14 | p.SetFeatherLength(10) 15 | 16 | fmt.Println(p.Name) 17 | fmt.Println(p.GetFeatherLength()) 18 | //fmt.Println(p.featherLength) - This will not compile 19 | 20 | } -------------------------------------------------------------------------------- /Chapter05/rpc_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/rpc" 6 | ) 7 | 8 | type Args struct { 9 | A, B int 10 | } 11 | 12 | 13 | func main() { 14 | serverAddress := "127.0.0.1" //locahost 15 | 16 | client, err := rpc.DialHTTP("tcp", serverAddress+":1234") 17 | if err != nil { 18 | log.Fatal("dialing:", err) 19 | } 20 | 21 | // synchronous rpc 22 | args := Args{3, 4} 23 | var reply int 24 | client.Call("MuliplyService.Do", args, &reply) 25 | log.Printf(" %d*%d=%d", args.A, args.B, reply) 26 | } 27 | -------------------------------------------------------------------------------- /Chapter01, and 02/embed.go: -------------------------------------------------------------------------------- 1 | // This file demonstrates struct embeddeding in Go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // Bird is a sample 'super class' to demonstrate 'inheritance' via embedding 9 | type Bird struct { 10 | featherLength int 11 | classification string 12 | } 13 | 14 | // Pigeon is the derived struct 15 | type Pigeon struct { 16 | Bird 17 | featherLength float64 18 | Name string 19 | } 20 | 21 | func main() { 22 | p := Pigeon{Name :"Tweety", } 23 | p.featherLength = 3.14 24 | 25 | fmt.Println(p) 26 | } -------------------------------------------------------------------------------- /Chapter12/generics/list/list-unit.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package list 6 | 7 | type UintList struct { 8 | list []uint 9 | } 10 | 11 | func NewUintList() *UintList { 12 | return &UintList{list: []uint{}} 13 | } 14 | 15 | func (l *UintList) Add(v uint) { 16 | l.list = append(l.list, v) 17 | } 18 | 19 | func (l *UintList) Get() uint { 20 | r := l.list[0] 21 | l.list = l.list[1:] 22 | return r 23 | } 24 | -------------------------------------------------------------------------------- /Chapter07/apis.md: -------------------------------------------------------------------------------- 1 | ## Rest Samples 2 | ### This file documents requests for various resources for hotel_gins.go 3 | 4 | #create 5 | curl -d '{"id":"xyz", "display_name":"HotelXyz", "star_rating":4, "no_rooms": 150}' -H "Content-Type: application/json" -X POST 127.0.0.1:8080/v1/hotels 6 | 7 | #get 8 | curl 127.0.0.1:8080/v1/hotels/xyz 9 | 10 | #get all 11 | curl 127.0.0.1:8080/v1/hotels 12 | 13 | #update 14 | curl -d '{"id":"xyz", "star_rating":5}' -H "Content-Type: application/json" -X PUT 127.0.0.1:8080/v1/hotels/xyz 15 | 16 | 17 | #delete 18 | curl -X DELETE 127.0.0.1:8080/v1/hotels/xyz -------------------------------------------------------------------------------- /Chapter12/generics/main.go: -------------------------------------------------------------------------------- 1 | // This code demonstrates how we can use generics in GO using code generators 2 | // Here we use genny as the code generator 3 | package main 4 | 5 | import ( 6 | "./list" 7 | "fmt" 8 | ) 9 | 10 | // To install genny do 'go get github.com/cheekybits/genny' 11 | // For more information refer : https://github.com/cheekybits/genny 12 | 13 | func main() { 14 | // Create a unit List 15 | // Note that this struct and related methods are autogenerated 16 | ul := list.NewUintList() 17 | 18 | // insert 1 19 | ul.Add(1) 20 | 21 | // ensure we get it back! 22 | fmt.Println(ul.Get()) 23 | } 24 | -------------------------------------------------------------------------------- /Chapter05/rpc_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/rpc" 7 | "net" 8 | 9 | ) 10 | 11 | type Args struct { 12 | A, B int 13 | } 14 | 15 | type MuliplyService struct{} 16 | 17 | func (t *MuliplyService) Do(args *Args, reply *int) error { 18 | log.Println("inside MuliplyService") 19 | *reply = args.A * args.B 20 | return nil 21 | } 22 | 23 | func main() { 24 | service := new(MuliplyService) 25 | rpc.Register(service) 26 | rpc.HandleHTTP() 27 | l, e := net.Listen("tcp", ":1234") 28 | if e != nil { 29 | log.Fatal("listen error:", e) 30 | } 31 | http.Serve(l, nil) 32 | } 33 | -------------------------------------------------------------------------------- /Chapter06/readme.md: -------------------------------------------------------------------------------- 1 | ## Messaging 2 | ### This package contains the code shown in the messaging chapter (6). 3 | 4 | Note : each file is an independent go program, which can be run via 'go run' 5 | 6 | ### Kafka 7 | The code expects Kafka to be running lon localhost:9092 8 | You can do install Kafka on a mac using 9 | ``` 10 | brew cask install java 11 | brew install kafka 12 | ``` 13 | and then start is using 14 | ``` 15 | zkServer start 16 | kafka-server-start.sh /usr/local/etc/kafka/server.properties 17 | ``` 18 | 19 | ### nsq 20 | To install and run nsq please see [nsq quick start](https://nsq.io/overview/quick_start.html) 21 | -------------------------------------------------------------------------------- /Chapter03/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | 8 | // Proxy 9 | type HotelBoutiqueProxy struct { 10 | subject *HotelBoutique 11 | } 12 | 13 | func (p *HotelBoutiqueProxy) Book() { 14 | if p.subject == nil { 15 | p.subject = new(HotelBoutique) 16 | } 17 | fmt.Println("Proxy Delegating Booking call") 18 | 19 | // The API call will happen here 20 | // For example sake a simple delegation is implemented 21 | p.subject.Book() 22 | } 23 | 24 | // Dummy Subject 25 | type HotelBoutique struct{} 26 | 27 | func (s *HotelBoutique) Book() { 28 | fmt.Println("Booking done on external site") 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Chapter10/reservation_time.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | const reservationDateFormat = "2006-01-02" 10 | 11 | type ReservationTime time.Time 12 | 13 | func (t *ReservationTime) UnmarshalJSON(bytes []byte) error { 14 | rawT, err := time.Parse(reservationDateFormat, strings.Replace( 15 | string(bytes), 16 | "\"", 17 | "", 18 | -1, 19 | )) 20 | 21 | if err != nil { 22 | return err 23 | } 24 | 25 | *t = ReservationTime(rawT) 26 | 27 | return nil 28 | } 29 | 30 | func (t *ReservationTime) MarshalJSON() ([]byte, error) { 31 | buf := fmt.Sprintf("\"%s\"", time.Time(*t).Format(reservationDateFormat)) 32 | return []byte(buf), nil 33 | } 34 | -------------------------------------------------------------------------------- /Chapter01, and 02/methods.go: -------------------------------------------------------------------------------- 1 | // This file demonstrates methods and pointer v/s value receiver 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type Person struct { 9 | Name string 10 | Age int 11 | } 12 | 13 | // Grow method has a pointer receiver. This is Pass-By-Reference 14 | func (p *Person) Grow() { 15 | p.Age++ 16 | } 17 | 18 | // DoesNotGrow method has a value receiver. This is Pass-By-Value. Age will nto be modified here 19 | func (p Person) DoesNotGrow() { 20 | p.Age++ 21 | } 22 | 23 | func main() { 24 | p := Person{"JY", 10} 25 | p.Grow() 26 | fmt.Println(p.Age) 27 | 28 | ptr := &p 29 | ptr.DoesNotGrow() 30 | fmt.Println(p.Age) 31 | } 32 | 33 | // will print 34 | // 11 35 | // 11 36 | -------------------------------------------------------------------------------- /Chapter03/decorator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "math" 7 | ) 8 | 9 | type Function func(float64) float64 10 | 11 | // This decorate profiles the execution time of function fn 12 | func ProfileDecorator(fn Function) Function { 13 | return func(params float64) float64 { 14 | start := time.Now() 15 | result := fn(params) 16 | elapsed := time.Now().Sub(start) 17 | fmt.Println("Funtion completed with time : ", elapsed) 18 | return result 19 | } 20 | } 21 | 22 | 23 | func SquareRoot(n float64) float64 { 24 | return math.Sqrt(n) 25 | } 26 | 27 | func main() { 28 | 29 | decoratedSqaureRoot := ProfileDecorator(SquareRoot) 30 | fmt.Println(decoratedSqaureRoot(16)) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Chapter03/singleton.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type MyClass struct { 9 | attrib string 10 | 11 | } 12 | 13 | func (c* MyClass ) SetAttrib(val string) { 14 | c.attrib = val 15 | } 16 | 17 | func (c* MyClass ) GetAttrib() string { 18 | return c.attrib 19 | } 20 | 21 | var ( 22 | once sync.Once 23 | instance *MyClass 24 | ) 25 | 26 | func GetMyClass() *MyClass { 27 | once.Do(func() { 28 | instance = &MyClass{"first"} 29 | }) 30 | return instance 31 | } 32 | 33 | 34 | func main() { 35 | a := GetMyClass() 36 | a.SetAttrib("second") 37 | fmt.Println(a.GetAttrib()) // will print second 38 | b := GetMyClass() 39 | fmt.Println(b.GetAttrib()) // will also print second 40 | } -------------------------------------------------------------------------------- /Chapter04/quick_sort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "math/rand" 4 | 5 | 6 | func quickSort(array []int) []int { 7 | if len(array) <= 1 { 8 | return array 9 | } 10 | 11 | left, right := 0, len(array) - 1 12 | 13 | // Pick a pivot randomly and move it to the end 14 | pivot:= rand.Int() % len(array) 15 | array[pivot], array[right] = array[right], array[pivot] 16 | 17 | // Partition 18 | for i := range array { 19 | if array[i] < array[right] { 20 | array[i], array[left] = array[left], array[i] 21 | left++ 22 | } 23 | } 24 | 25 | // Put the pivot in place 26 | array[left], array[right] = array[right], array[left] 27 | 28 | // Recurse 29 | quickSort(array[:left]) 30 | quickSort(array[left + 1:]) 31 | 32 | return array 33 | } 34 | -------------------------------------------------------------------------------- /Chapter03/template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | 8 | 9 | // The 'abstract' MasterAlgorithm 10 | type MasterAlgorithm struct { 11 | template Template 12 | } 13 | 14 | func (c *MasterAlgorithm) TemplateMethod() { 15 | // orchestrate the steps 16 | c.template.Step1() 17 | c.template.Step2() 18 | } 19 | 20 | 21 | // The steps which can be specialized 22 | type Template interface { 23 | Step1() 24 | Step2() 25 | } 26 | 27 | 28 | // Variant A 29 | type VariantA struct{} 30 | func (c *VariantA) Step1() { 31 | fmt.Println("VariantA step 1") 32 | } 33 | func (c *VariantA) Step2() { 34 | fmt.Println("VariantA step 2") 35 | } 36 | 37 | func main() { 38 | masterAlgorithm := MasterAlgorithm{new(VariantA)} 39 | masterAlgorithm.TemplateMethod() 40 | } -------------------------------------------------------------------------------- /Chapter06/nsq_prod.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/nsqio/go-nsq" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | 10 | // Connect 11 | pCfg := nsq.NewConfig() 12 | producer, err := nsq.NewProducer("127.0.0.1:4150", pCfg) 13 | if err != nil { 14 | log.Fatalf("failed creating producer %s", err) 15 | } 16 | 17 | // Publish Async 18 | destinationTopic := "my_topic" 19 | responseChan := make(chan *nsq.ProducerTransaction) 20 | err = producer.PublishAsync(destinationTopic, []byte("a_message"), responseChan, "some_args") 21 | 22 | // Check for status 23 | // Done here inline just for showcase 24 | status := <-responseChan 25 | if status.Error != nil { 26 | log.Printf("Error received %s \n", status.Error.Error()) 27 | } else { 28 | log.Printf("Success Arg received : %s \n", status.Args[0].(string)) // should be some_args 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Chapter03/adaptor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Adaptee is the existing structure - something we need to use 8 | type Adaptee struct{} 9 | 10 | func (a *Adaptee) ExistingMethod() { 11 | fmt.Println("using existing method") 12 | } 13 | 14 | // Adapter is the structure we use to glue things together 15 | type Adapter struct { 16 | adaptee *Adaptee 17 | } 18 | 19 | func NewAdapter() *Adapter { 20 | return &Adapter{new(Adaptee)} 21 | } 22 | 23 | // ExpectedMethod is the method clients in current code are using. This honors the expected interface and fulfils it 24 | // using the Adaptee's method 25 | func (a *Adapter) ExpectedMethod() { 26 | fmt.Println("doing some work") 27 | a.adaptee.ExistingMethod() 28 | } 29 | 30 | // The code below demonstrates usage of the design pattern 31 | // 32 | func main() { 33 | adaptor := NewAdapter() 34 | adaptor.ExpectedMethod() 35 | } -------------------------------------------------------------------------------- /Chapter03/composite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // InterfaceX is the component interface 8 | type InterfaceX interface { 9 | MethodA() 10 | AddChild(InterfaceX) 11 | } 12 | 13 | type Composite struct{ 14 | children []InterfaceX 15 | } 16 | 17 | func (c *Composite) MethodA() { 18 | if len(c.children) == 0 { 19 | fmt.Println("I'm a leaf ") 20 | return 21 | } 22 | 23 | // if there are children then the component is a composite 24 | fmt.Println("I'm a composite ") 25 | for _, child := range c.children { 26 | child.MethodA() 27 | } 28 | } 29 | 30 | func (c *Composite) AddChild(child InterfaceX) { 31 | c.children = append(c.children, child) 32 | } 33 | 34 | 35 | 36 | 37 | func main() { 38 | var parent InterfaceX 39 | 40 | parent = &Composite{} 41 | parent.MethodA() // still a leaf 42 | 43 | var child Composite 44 | parent.AddChild(&child) 45 | parent.MethodA() // one composite, one leaf 46 | 47 | 48 | } -------------------------------------------------------------------------------- /Chapter06/req_reply.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Request struct { 9 | someArg string 10 | replyTo chan<- Response 11 | } 12 | 13 | type Response struct { 14 | reply string 15 | } 16 | 17 | func responder(c <-chan Request) { 18 | for request := range c { 19 | var resp Response 20 | resp.reply = "reply-to-" + request.someArg 21 | request.replyTo <- resp 22 | } 23 | } 24 | 25 | func requestor(c chan<- Request) { 26 | myChannel := make(chan Response) 27 | for i := 0; i < 5; i++ { 28 | c <- Request{fmt.Sprintf("message%d", i), myChannel} 29 | resp := <-myChannel 30 | fmt.Printf("request %d , response %s\n", i, resp.reply) 31 | } 32 | 33 | // cleanup after my work is done 34 | close(myChannel) 35 | } 36 | 37 | func main() { 38 | 39 | requestChannel := make(chan Request) 40 | go responder(requestChannel) 41 | go requestor(requestChannel) 42 | 43 | time.Sleep(time.Second * 10) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Chapter03/strategy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sort" 7 | ) 8 | 9 | 10 | type Strategy interface { 11 | FindBreadth([]int) int // the algorithm 12 | } 13 | 14 | // A O(nlgn) implementation 15 | type NaiveAlgo struct {} 16 | func (n *NaiveAlgo) FindBreadth(set []int) int { 17 | sort.Ints(set) 18 | return set[len(set)-1] - set[0] 19 | } 20 | 21 | // A O(n) implementation 22 | type FastAlgo struct {} 23 | func (n *FastAlgo) FindBreadth(set []int) int { 24 | min := math.MaxInt32 25 | max := math.MinInt64 26 | 27 | for _,x := range(set) { 28 | if x < min { 29 | min = x 30 | } 31 | if x > max { 32 | max =x 33 | } 34 | } 35 | 36 | return max - min 37 | } 38 | 39 | 40 | // The client is ignorant to the algorithm 41 | func client(s Strategy) int { 42 | a := []int { -1, 10, 3, 1} 43 | return s.FindBreadth(a) 44 | } 45 | 46 | 47 | 48 | func main() { 49 | fmt.Println(client(&NaiveAlgo{})) 50 | fmt.Println(client(&FastAlgo{})) 51 | } -------------------------------------------------------------------------------- /Chapter06/fan_in_select.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | // this combines the sources to a Fan-In channel 10 | func fanInSelect(input1, input2 <-chan string) <-chan string { 11 | out := make(chan string) 12 | go func() { 13 | for { 14 | select { 15 | case in := <-input1: 16 | out <- in 17 | case in := <-input2: 18 | out <- in 19 | } 20 | } 21 | }() 22 | return out 23 | } 24 | 25 | func main() { 26 | c := fanInSelect(emitter("Source1"), emitter("Source2")) 27 | 28 | for i := 0; i < 10; i++ { 29 | fmt.Println(<-c) // Display the output of the FanIn channel. 30 | } 31 | 32 | } 33 | 34 | // dummy function for a source 35 | func emitter(name string) <-chan string { 36 | c := make(chan string) 37 | 38 | go func() { 39 | for i := 0; ; i++ { 40 | c <- fmt.Sprintf("[%s] says %d", name, i) 41 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // Sleep for some time 42 | } 43 | }() 44 | 45 | return c 46 | } 47 | -------------------------------------------------------------------------------- /Chapter12/generics/list/template.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import "github.com/cheekybits/genny/generic" 4 | 5 | // The gogenerate command is used to create multiple instantiations from the generic type 6 | // NOTE the replacement of tag "Element" - It's important that all elements below start with that 7 | //go:generate genny -in=template.go -out=list-unit.go gen "Element=uint" 8 | 9 | // Element is a generic type which will be contained in the list 10 | type Element generic.Type 11 | 12 | // ElementList is the generic FIFO queue 13 | type ElementList struct { 14 | list []Element 15 | } 16 | 17 | // NewElementList creates a new list 18 | func NewElementList() *ElementList { 19 | return &ElementList{list: []Element{}} 20 | } 21 | 22 | // Add inserts an element to the end of the list 23 | func (l *ElementList) Add(v Element) { 24 | l.list = append(l.list, v) 25 | } 26 | 27 | // Get pops the element from the head of the list 28 | func (l *ElementList) Get() Element { 29 | r := l.list[0] 30 | l.list = l.list[1:] 31 | return r 32 | } 33 | -------------------------------------------------------------------------------- /Chapter06/pipes_filter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func emitter(till int) <-chan int { 8 | out := make(chan int) 9 | go func() { 10 | for i := 0; i < till; i++ { 11 | out <- i 12 | } 13 | close(out) 14 | }() 15 | return out 16 | } 17 | 18 | func xSquare(in <-chan int) <-chan int { 19 | out := make(chan int) 20 | go func() { 21 | for x := range in { 22 | out <- x * x 23 | } 24 | close(out) // close forward 25 | }() 26 | return out 27 | } 28 | 29 | func addC(in <-chan int, c int) <-chan int { 30 | out := make(chan int) 31 | go func() { 32 | for x := range in { 33 | out <- x + c 34 | } 35 | close(out) // close forward 36 | }() 37 | return out 38 | } 39 | 40 | func main() { 41 | // y = x*x + c 42 | out := addC( 43 | xSquare(emitter(3)), 44 | 5) 45 | 46 | for y := range out { 47 | fmt.Println(y) 48 | } 49 | 50 | // y = x*x*x*x + c 51 | 52 | out1 := addC( 53 | xSquare(xSquare(emitter(3))), 54 | 5) 55 | 56 | for y := range out1 { 57 | fmt.Println(y) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter06/fan_in.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | c := fanIn(emitter("Source1"), emitter("Source2")) 11 | 12 | for i := 0; i < 10; i++ { 13 | fmt.Println(<-c) // Display the output of the FanIn channel. 14 | } 15 | 16 | } 17 | 18 | // this combines the sources to a Fan-In channel 19 | func fanIn(input1, input2 <-chan string) <-chan string { 20 | c := make(chan string) // The FanIn channel 21 | 22 | // to avoid blocking, listen to the input channels in separate goroutines 23 | go func() { 24 | for { 25 | c <- <-input1 // Write the message to the FanIn channel, Blocking Call. 26 | } 27 | }() 28 | 29 | go func() { 30 | for { 31 | c <- <-input2 // Write the message to the FanIn channel, Blocking Call. 32 | } 33 | }() 34 | 35 | return c 36 | } 37 | 38 | // dummy function for a source 39 | func emitter(name string) <-chan string { 40 | c := make(chan string) 41 | 42 | go func() { 43 | for i := 0; ; i++ { 44 | c <- fmt.Sprintf("[%s] says %d", name, i) 45 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // Sleep for some time 46 | } 47 | }() 48 | 49 | return c 50 | } 51 | -------------------------------------------------------------------------------- /Chapter06/kafka_sync_producer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Shopify/sarama" 6 | ) 7 | 8 | func main() { 9 | 10 | // Config 11 | config := sarama.NewConfig() 12 | config.Producer.RequiredAcks = sarama.WaitForAll 13 | config.Producer.Retry.Max = 5 14 | config.Producer.Return.Errors = true // For sync producer this needs to be true 15 | config.Producer.Return.Success = true // For sync producer this needs to be true 16 | 17 | // Connect to a Kafka broker running locally 18 | brokers := []string{"localhost:9092"} 19 | producer, err := sarama.NewSyncProducer(brokers, config) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // cleanup 25 | defer func() { 26 | if err := producer.Close(); err != nil { 27 | panic(err) 28 | } 29 | }() 30 | 31 | msg := &sarama.ProducerMessage{ 32 | Topic: "currentTime", 33 | Value: sarama.StringEncoder(strconv.Itoa(int(time.Now().Unix()))), 34 | } 35 | 36 | partition, offset, err := producer.SendMessage(msg) 37 | if err != nil { 38 | fmt.Printf("FAILED to publish message: %s\n", err) 39 | } else { 40 | fmt.Printf("message sent | partition(%d)/offset(%d)\n", partition, offset) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter10/readme.md: -------------------------------------------------------------------------------- 1 | # Case Study - Travel Website 2 | 3 | ## Infra Needed 4 | 5 | ### kafka 6 | The code expects Kafka to be running lon localhost:9092 7 | You can do install Kafka on a mac using 8 | ``` 9 | brew cask install java 10 | brew install kafka 11 | ``` 12 | and then start is using 13 | ``` 14 | zkServer start 15 | kafka-server-start.sh /usr/local/etc/kafka/server.properties 16 | ``` 17 | 18 | ### mysql 19 | The code expects MySQL to be installed locally - it connects to root:@tcp(127.0.0.1:3306) 20 | You can install MySQL on a mac using 21 | ``` 22 | brew install mysql@5.7 23 | ``` 24 | 25 | Once you install mysql, you can create the schema using the following DDL 26 | ``` 27 | CREATE TABLE hotel_reservations ( 28 | customer_id int, 29 | payment_identifier int, 30 | sku int, 31 | room_id int, 32 | check_in datetime, 33 | check_out datetime, 34 | status int(10), 35 | id varchar(100), 36 | PRIMARY KEY (id) 37 | ); 38 | ``` 39 | 40 | ## API Calls 41 | 42 | # create booking api 43 | ``` 44 | curl -d '{"customer_id": 999, "payment_identifier": 66, "sku":1, "room_id": 2, "check_in" : "2018-12-07" , "check_out" : "2018-12-08"}' -H "Content-Type: application/json" -X POST 127.0.0.1:8080/v1/hotels/reservation/ 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /Chapter03/observer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | // The Subject 9 | type Subject struct { 10 | observers []Observer 11 | state string 12 | } 13 | 14 | func (s *Subject) Attach(observer Observer) { 15 | s.observers = append(s.observers, observer) 16 | } 17 | 18 | 19 | func (s *Subject) SetState(newState string) { 20 | s.state = newState 21 | for _,o := range(s.observers) { 22 | o.Update() 23 | } 24 | } 25 | 26 | func (s *Subject) GetState() string { 27 | return s.state 28 | } 29 | 30 | // The Observer Interface 31 | type Observer interface { 32 | Update() 33 | } 34 | 35 | // Concrete Observer A 36 | type ConcreteObserverA struct { 37 | model *Subject 38 | viewState string 39 | } 40 | 41 | func (ca *ConcreteObserverA) Update() { 42 | ca.viewState = ca.model.GetState() 43 | fmt.Println("ConcreteObserverA : updated view state to ", ca.viewState) 44 | } 45 | 46 | func (ca *ConcreteObserverA) SetModel(s *Subject) { 47 | ca.model = s 48 | } 49 | 50 | 51 | func main() { 52 | // create Subject 53 | s := Subject{} 54 | 55 | // create concrete observer 56 | ca := &ConcreteObserverA{} 57 | ca.SetModel(&s) // set Model 58 | 59 | // Attach the observer 60 | s.Attach(ca) 61 | 62 | s.SetState("s1") 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /Chapter06/nsq_cons.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/nsqio/go-nsq" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | // MyMessageHandler is a struct which defines a method to handle messages 12 | type MyMessageHandler struct { 13 | totalMessages int 14 | } 15 | 16 | // This is the message handler callback 17 | func (h *MyMessageHandler) HandleMessage(message *nsq.Message) error { 18 | h.totalMessages++ 19 | log.Printf("Message no %d received , body : %s \n", h.totalMessages, string(message.Body)) 20 | return nil 21 | } 22 | 23 | func main() { 24 | // Create a consumer 25 | config := nsq.NewConfig() 26 | topicName := "my_topic" 27 | channelName := "my_chan" 28 | cons, err := nsq.NewConsumer(topicName, channelName, config) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | // Add the callback for messages 34 | cons.AddHandler(&MyMessageHandler{}) 35 | 36 | // Connect to external NSQD 37 | err = cons.ConnectToNSQD("127.0.0.1:4150") 38 | if err != nil { 39 | log.Panic("Could not connect to NSQD") 40 | } 41 | 42 | // Wait for a signal to quit 43 | sigChan := make(chan os.Signal, 1) 44 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 45 | <-sigChan 46 | 47 | // Stop the consumer 48 | cons.Stop() 49 | <-cons.StopChan // wait for cleanup 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Chapter03/visitor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // the Node interface 8 | type Node interface { 9 | Accept(Visitor) 10 | } 11 | 12 | type ConcreteNodeX struct{} 13 | func (n ConcreteNodeX) Accept(visitor Visitor) { 14 | visitor.Visit(n) 15 | } 16 | 17 | type ConcreteNodeY struct{} 18 | func (n ConcreteNodeY) Accept(visitor Visitor) { 19 | // do something NodeY-specific before visiting 20 | fmt.Println("ConcreteNodeY being visited !") 21 | visitor.Visit(n) 22 | } 23 | 24 | 25 | // the Vistor interface 26 | type Visitor interface { 27 | Visit(Node) 28 | } 29 | 30 | // and an implementation 31 | type ConcreteVisitor struct{} 32 | func (v ConcreteVisitor) Visit(node Node) { 33 | fmt.Println("doing something concrete") 34 | 35 | // since there is no function overloading.. 36 | // this is one way of checking the concrete node type 37 | switch node.(type) { 38 | case ConcreteNodeX: 39 | fmt.Println("on Node X") 40 | case ConcreteNodeY: 41 | fmt.Println("on Node Y") 42 | } 43 | } 44 | 45 | 46 | func main() { 47 | // a simple aggregate 48 | aggregate := []Node {ConcreteNodeX{}, ConcreteNodeY{},} 49 | 50 | // a vistory 51 | visitor := new(ConcreteVisitor) 52 | // iterate and visit 53 | for _, node := range(aggregate){ 54 | node.Accept(visitor) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Chapter06/fan_out.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type Message struct { 10 | body string 11 | key int 12 | } 13 | 14 | func main() { 15 | evenPipe, oddPipe := fanOut(emitter()) 16 | sink("even", evenPipe) 17 | sink("odd", oddPipe) 18 | 19 | time.Sleep(10 * time.Second) 20 | 21 | } 22 | 23 | // this combines the sources to a Fan-In channel 24 | func fanOut(input <-chan Message) (<-chan Message, <-chan Message) { 25 | even := make(chan Message) // The fan-out channels 26 | odd := make(chan Message) // The fan-out channels 27 | 28 | // spawn the fan-out loop 29 | go func() { 30 | for { 31 | msg := <-input 32 | if msg.key%2 == 0 { 33 | even <- msg 34 | } else { 35 | odd <- msg 36 | } 37 | } 38 | }() 39 | 40 | return even, odd 41 | } 42 | 43 | // dummy function for a source 44 | func emitter() <-chan Message { 45 | c := make(chan Message) 46 | 47 | go func() { 48 | for i := 0; ; i++ { 49 | c <- Message{fmt.Sprintf("Message[%d]", i), i} 50 | time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) // Sleep for some time 51 | } 52 | }() 53 | 54 | return c 55 | } 56 | 57 | func sink(name string, in <-chan Message) { 58 | go func() { 59 | for { 60 | msg := <-in 61 | fmt.Printf("[%s] says %s\n", name, msg.body) 62 | } 63 | }() 64 | } 65 | -------------------------------------------------------------------------------- /Chapter03/state.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // The State Inteface with the polymorphic methods 8 | type State interface { 9 | Op1(*Context) 10 | Op2(*Context) 11 | } 12 | 13 | // The Context class 14 | type Context struct { 15 | state State 16 | 17 | } 18 | func (c *Context) Op1() { 19 | c.state.Op1(c) 20 | } 21 | func (c *Context) Op2() { 22 | c.state.Op2(c) 23 | } 24 | func (c *Context) SetState(state State) { 25 | c.state = state 26 | } 27 | func NewContext() *Context{ 28 | c := new(Context) 29 | c.SetState(new(StateA)) // Initial State 30 | return c 31 | } 32 | 33 | 34 | // Concrete States 35 | type StateA struct{} 36 | func (s *StateA) Op1(c *Context) { 37 | fmt.Println("State A : Op1 ") 38 | } 39 | func (s *StateA) Op2(c *Context) { 40 | fmt.Println("State A : Op2 ") 41 | c.SetState(new(StateB)) // <-- State Change! 42 | } 43 | 44 | type StateB struct{} 45 | func (s *StateB) Op1(c *Context) { 46 | fmt.Println("State B : Op1 ") 47 | } 48 | func (s *StateB) Op2(c *Context) { 49 | fmt.Println("State B : Op2 ") 50 | c.SetState(new(StateA)) // <-- State Change! 51 | } 52 | 53 | 54 | func main() { 55 | context := NewContext() 56 | 57 | // state operations 58 | context.Op1() 59 | context.Op2() // <- This changes state to State 2 60 | context.Op1() 61 | context.Op2() // <- This changes state back to State 1 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Chapter06/kakfka_consumer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | cluster "github.com/bsm/sarama-cluster" 6 | "log" 7 | "os" 8 | "os/signal" 9 | ) 10 | 11 | func main() { 12 | 13 | // setup config, enable errors and notifications 14 | config := cluster.NewConfig() 15 | config.Consumer.Return.Errors = true 16 | config.Group.Return.Notifications = true 17 | 18 | // specify Broker co-ordinates and topics of interest 19 | brokers := []string{"localhost:9092"} 20 | topics := []string{"topic_a", "topic_b"} 21 | 22 | // connect, and register specifiying the consumer group name 23 | consumer, err := cluster.NewConsumer(brokers, "my-consumer-group", topics, config) 24 | if err != nil { 25 | panic(err) 26 | } 27 | defer consumer.Close() 28 | 29 | // process errors 30 | go func() { 31 | for err := range consumer.Errors() { 32 | log.Printf("Error: %s\n", err.Error()) 33 | } 34 | }() 35 | 36 | // process notifications 37 | go func() { 38 | for ntf := range consumer.Notifications() { 39 | log.Printf("Rebalanced: %+v\n", ntf) 40 | } 41 | }() 42 | 43 | // process messages 44 | for msg := range consumer.Messages() { 45 | fmt.Fprintf(os.Stdout, "%s-%d-%d-%s-%s\n", 46 | msg.Topic, 47 | msg.Partition, 48 | msg.Offset, 49 | msg.Key, 50 | msg.Value) // <- Actually process message here 51 | 52 | consumer.MarkOffset(msg, "") // Commit offeset for this message 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Chapter03/mememto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Originator 8 | type Originator struct { 9 | state string 10 | } 11 | 12 | func (o *Originator) GetState() string { 13 | return o.state 14 | } 15 | 16 | func (o *Originator) SetState(state string) { 17 | fmt.Println("Setting state to " + state) 18 | o.state = state 19 | } 20 | 21 | func (o *Originator) GetMemento() Memento { 22 | // externalize state to Momemto objct 23 | return Memento{o.state} 24 | } 25 | 26 | 27 | func (o *Originator) Restore(memento Memento) { 28 | // restore state 29 | o.state = memento.GetState() 30 | } 31 | 32 | // Momento 33 | type Memento struct { 34 | serializedState string 35 | } 36 | 37 | func (m *Memento) GetState() string { 38 | return m.serializedState 39 | } 40 | 41 | 42 | // caretaker 43 | 44 | func Caretaker() { 45 | 46 | // assume that A is the original state of the Orginator 47 | theOriginator := Originator{"A"} 48 | theOriginator.SetState("A") 49 | fmt.Println("theOriginator state = ", theOriginator.GetState() ) 50 | 51 | // before mutating, get an momemto 52 | theMomemto := theOriginator.GetMemento() 53 | 54 | // mutate to unclean 55 | theOriginator.SetState("unclean") 56 | fmt.Println("theOriginator state = ", theOriginator.GetState() ) 57 | 58 | // rollback 59 | theOriginator.Restore(theMomemto) 60 | fmt.Println("RESTORED : theOriginator state = ", theOriginator.GetState() ) 61 | 62 | } 63 | 64 | func main() { 65 | Caretaker() 66 | } -------------------------------------------------------------------------------- /Chapter08/gorm_associations.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/mysql" 8 | ) 9 | 10 | // HotelChain is an entity which represents a group which has multiple hotels 11 | type HotelChain struct { 12 | gorm.Model 13 | Name string 14 | } 15 | 16 | // Hotel desribes a single Hotel entity with attributes like name, number of rooms , etc 17 | // It has a reference ( foreign key) to a HotelChain 18 | type Hotel struct { 19 | gorm.Model 20 | Name string 21 | NoRooms uint 22 | Chain HotelChain `gorm:"foreignkey:ChainId"` // use ChainId as foreign key 23 | ChainId uint 24 | } 25 | 26 | // Create two entities with associations between them 27 | func main() { 28 | // Connect to the DB 29 | db, err := gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/users?charset=utf8&parseTime=True") 30 | if err != nil { 31 | panic("failed to connect database") 32 | } 33 | defer db.Close() 34 | 35 | // Migrate the schema 36 | db.AutoMigrate(&HotelChain{}) 37 | db.AutoMigrate(&Hotel{}) 38 | // Explicitly need to mention the foreign key so that DB relationship is setup correctly 39 | // This is a quirk of GORM currently 40 | db.Model(&Hotel{}).AddForeignKey("chain_id", "hotel_chains(id)", "CASCADE", "CASCADE") 41 | 42 | // Create some entities and save 43 | taj := HotelChain{Name: "Taj"} 44 | db.Save(&taj) 45 | vivanta := Hotel{Name: "Vivanta by the sea", NoRooms: 400, Chain: taj} 46 | db.Save(&vivanta) 47 | 48 | // You will see the relationship in the DB 49 | } 50 | -------------------------------------------------------------------------------- /Chapter03/bridge.go: -------------------------------------------------------------------------------- 1 | // this file demonstrates the bridge design pattern 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | 9 | // Reservation is the main abstraction 10 | // 11 | // the abstraction here is a struct not an interface , since in Go you cant have abstract structs/interfaces , 12 | // where one can store reference to the Seller implementation 13 | type Reservation struct { 14 | // sellerRef is the reference to the implementation 15 | sellerRef Seller 16 | } 17 | 18 | func (r Reservation) Cancel() { 19 | r.sellerRef.CancelReservation(10) // charge $10 as cancellation feed 20 | } 21 | 22 | // PremiumReservation is a specialization of the interface 23 | type PremiumReservation struct { 24 | Reservation 25 | } 26 | 27 | func (r PremiumReservation) Cancel() { 28 | r.sellerRef.CancelReservation(0) // no charges 29 | } 30 | 31 | // This is the interface for all Sellers 32 | type Seller interface { 33 | CancelReservation(charge float64) 34 | } 35 | 36 | // InstitutionSeller is an specialization of the implementation 37 | type InstitutionSeller struct {} 38 | 39 | func (s InstitutionSeller) CancelReservation(charge float64) { 40 | fmt.Println("InstitutionSeller CancelReservation charge =", charge) 41 | } 42 | 43 | 44 | // SmallScaleSeller is an specialization of the implementation 45 | type SmallScaleSeller struct {} 46 | 47 | func (s SmallScaleSeller) CancelReservation(charge float64) { 48 | fmt.Println("SmallScalSeller CancelReservation charge =", charge) 49 | } 50 | 51 | 52 | 53 | func main() { 54 | res := Reservation{InstitutionSeller{}} 55 | res.Cancel() 56 | premiumRes := PremiumReservation{Reservation{SmallScaleSeller{}}} 57 | premiumRes.Cancel() 58 | } 59 | -------------------------------------------------------------------------------- /Chapter03/abstract_factory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Reservation and Invoice as two generic products 8 | type Reservation interface{} 9 | type Invoice interface{} 10 | 11 | // AbstractFactory is the abstract factory which will create two products - both which need to work with each other 12 | type AbstractFactory interface { 13 | CreateReservation() Reservation 14 | CreateInvoice() Invoice 15 | } 16 | 17 | // HotelFactory implements AbstractFactory and creates products for the Hotel family of products 18 | type HotelFactory struct{} 19 | 20 | func (f HotelFactory) CreateReservation() Reservation { 21 | return new(HotelReservation) 22 | } 23 | 24 | func (f HotelFactory) CreateInvoice() Invoice { 25 | return new(HotelInvoice) 26 | } 27 | 28 | 29 | // FlightFactory implements AbstractFactory and creates products for the Flight family of products 30 | type FlightFactory struct{} 31 | 32 | func (f FlightFactory) CreateReservation() Reservation { 33 | return new(FlightReservation) 34 | } 35 | 36 | func (f FlightFactory) CreateInvoice() Invoice { 37 | return new(FlightReservation) 38 | } 39 | 40 | // GetFactory returns a factory given a config 41 | func GetFactory(vertical string) AbstractFactory { 42 | var factory AbstractFactory 43 | switch vertical { 44 | case "flight": 45 | factory = FlightFactory{} 46 | case "hotel": 47 | factory = HotelFactory{} 48 | } 49 | 50 | return factory 51 | } 52 | 53 | 54 | // The code below demonstrates usage of the factories 55 | // 56 | 57 | type HotelReservation struct{} 58 | type HotelInvoice struct{} 59 | type FlightReservation struct{} 60 | type FlightInvoice struct{} 61 | 62 | func main() { 63 | hotelFactory := GetFactory("hotel") 64 | reservation := hotelFactory.CreateReservation() 65 | invoice := hotelFactory.CreateInvoice() 66 | 67 | fmt.Printf("%T\n", reservation) 68 | fmt.Printf("%T\n", invoice) 69 | 70 | } -------------------------------------------------------------------------------- /Chapter03/mediator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | ) 7 | 8 | // The Mediator and Colleague interfaces 9 | type Mediator interface { 10 | createColleagues() 11 | } 12 | 13 | type Colleague interface { 14 | setMediator(mediator Mediator) 15 | } 16 | 17 | 18 | // Concrete Colleauge 1 - uses state as string 19 | type Colleague1 struct { 20 | mediator Mediator 21 | state string 22 | } 23 | 24 | func (c *Colleague1) SetMediator(mediator Mediator) { 25 | c.mediator = mediator 26 | } 27 | 28 | func (c *Colleague1) SetState(state string) { 29 | fmt.Println("Colleague1 : setting state : ", state) 30 | c.state = state 31 | } 32 | 33 | func (c *Colleague1) GetState() string { 34 | return c.state 35 | } 36 | 37 | 38 | // Concrete Colleauge 2 - uses state as int 39 | type Colleague2 struct { 40 | mediator Mediator 41 | state int 42 | } 43 | 44 | func (c *Colleague2) SetState(state int) { 45 | fmt.Println("Colleague2 : setting state : ", state) 46 | c.state = state 47 | } 48 | 49 | func (c *Colleague2) GetState() int { 50 | return c.state 51 | } 52 | 53 | func (c *Colleague2) SetMediator(mediator Mediator) { 54 | c.mediator = mediator 55 | } 56 | 57 | 58 | // Concrete Mediator 59 | type ConcreteMediator struct { 60 | c1 Colleague1 61 | c2 Colleague2 62 | } 63 | 64 | func (m *ConcreteMediator) SetColleagueC1(c1 Colleague1) { 65 | m.c1 = c1 66 | } 67 | 68 | func (m *ConcreteMediator) SetColleagueC2(c2 Colleague2) { 69 | m.c2 = c2 70 | } 71 | 72 | func (m *ConcreteMediator) SetState(s string) { 73 | m.c1.SetState(s) 74 | stateAsString, err := strconv.Atoi(s) 75 | if err == nil { 76 | m.c2.SetState(stateAsString) 77 | fmt.Println("Mediator set status for both colleagues") 78 | } 79 | } 80 | 81 | func main() { 82 | mediator := ConcreteMediator{} 83 | c1 := Colleague1{} 84 | c2 := Colleague2{} 85 | 86 | mediator.SetColleagueC1(c1) 87 | mediator.SetColleagueC2(c2) 88 | 89 | mediator.SetState("10") 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Chapter03/multiple_interfaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Reservation interface { 8 | GetReservationDate() string 9 | CalculateCancellationFee() float64 10 | } 11 | 12 | type HotelReservation interface { 13 | Reservation 14 | ChangeType() 15 | } 16 | 17 | type FlightReservation interface { 18 | Reservation 19 | AddExtraLuggageAllowance(peices int) 20 | } 21 | 22 | type HotelReservationImpl struct { 23 | reservationDate string 24 | } 25 | 26 | func (r HotelReservationImpl) GetReservationDate() string { 27 | return r.reservationDate 28 | } 29 | 30 | func (r HotelReservationImpl) CalculateCancellationFee() float64 { 31 | return 1.0 // flat :P 32 | } 33 | 34 | type FlightReservationImpl struct { 35 | reservationDate string 36 | luggageAllowed int 37 | } 38 | 39 | func (r FlightReservationImpl) AddExtraLuggageAllowance(peices int) { 40 | r.luggageAllowed = peices 41 | } 42 | 43 | func (r FlightReservationImpl) CalculateCancellationFee() float64 { 44 | return 2.0 // flat but slight more than hotels :P 45 | } 46 | 47 | func (r FlightReservationImpl) GetReservationDate() string { 48 | // this might look repetitive, but the idea is to provide freedom for the 49 | // derived classes to flux independently of each other 50 | return r.reservationDate 51 | } 52 | 53 | type Trip struct { 54 | reservations []Reservation 55 | } 56 | 57 | func (t *Trip) CalculateCancellationFee() float64 { 58 | total := 0.0 59 | 60 | for _, r := range t.reservations { 61 | total += r.CalculateCancellationFee() 62 | } 63 | 64 | return total 65 | } 66 | 67 | func (t *Trip) AddReservation(r Reservation) { 68 | t.reservations = append(t.reservations, r) 69 | } 70 | 71 | func main() { 72 | var ( 73 | h HotelReservationImpl 74 | f FlightReservationImpl 75 | t Trip 76 | ) 77 | 78 | fmt.Println(f.CalculateCancellationFee()) 79 | fmt.Println(h.CalculateCancellationFee()) 80 | 81 | t.AddReservation(h) 82 | t.AddReservation(f) 83 | fmt.Println(t.CalculateCancellationFee()) 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Chapter08/gorm_simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/mysql" 8 | ) 9 | 10 | type User struct { 11 | gorm.Model 12 | Name string 13 | Age uint 14 | } 15 | 16 | func main() { 17 | db, err := gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/users?charset=utf8&parseTime=True") 18 | if err != nil { 19 | panic("failed to connect database") 20 | } 21 | defer db.Close() 22 | 23 | // Automigrate the schema 24 | // Here gorm uses reflection to get to the struct and field names and then maps them to table and column names using 25 | // either the tags or the described convention 26 | db.AutoMigrate(&User{}) 27 | 28 | // Create an entity for the purpose of this test 29 | db.Create(&User{Name: "James Bond", Age: 40}) 30 | 31 | // Read 32 | // Find an user with id 1 33 | var user User 34 | db.First(&user, 1) 35 | fmt.Println(user) 36 | 37 | // Find an user with a specific name : James Bond 38 | db.First(&user, "Name = ?", "James Bond") 39 | fmt.Println(user) 40 | 41 | // Update - update Bond's age 42 | db.Model(&user).Update("Age", 41) 43 | fmt.Println(user) 44 | 45 | // Delete - delete user 46 | db.Delete(&user) 47 | 48 | // Transactions demo 49 | // Transactions allows the application code to assume atomicity from the database over multiple DB operations 50 | createTwoUsers(db) 51 | } 52 | 53 | func createTwoUsers(db *gorm.DB) { 54 | userA := User{Name: "UserA", Age: 20} 55 | userB := User{Name: "UserB", Age: 20} 56 | 57 | // Starts a trasaction 58 | // Tx is the handle which identifies the newly started transaction 59 | tx := db.Begin() 60 | if err := tx.Create(&userA).Error; err != nil { 61 | // Rollback causes the transaction to abort and bring the database to the consistent state before the 62 | // transaction was initiated 63 | tx.Rollback() 64 | } 65 | if err := tx.Create(&userB).Error; err != nil { 66 | tx.Rollback() 67 | } 68 | 69 | // Commit the transaction 70 | tx.Commit() 71 | } 72 | -------------------------------------------------------------------------------- /Chapter08/cassandra.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/gocql/gocql" 8 | ) 9 | 10 | // User desscribes an employee entity. 11 | // We will be using this to demonstrate Cassandra create, read, update, and delete flows 12 | type User struct { 13 | Id gocql.UUID 14 | FirstName string 15 | LastName string 16 | Age int 17 | } 18 | 19 | func main() { 20 | // connect to the cluster 21 | cluster := gocql.NewCluster("127.0.0.1") 22 | cluster.Keyspace = "roster" 23 | session, _ := cluster.CreateSession() 24 | defer session.Close() 25 | 26 | // generate a unique id for the employee 27 | id := gocql.TimeUUID() 28 | // create the employee in memory 29 | newEmployee := User{ 30 | Id: id, 31 | FirstName: "James", 32 | LastName: "Bond", 33 | Age: 45, 34 | } 35 | 36 | // insert the employee 37 | if err := session.Query("INSERT INTO employees (id, firstname, lastname, age ) VALUES (?, ?, ?, ?)", 38 | newEmployee.Id, 39 | newEmployee.FirstName, 40 | newEmployee.LastName, 41 | newEmployee.Age).Exec(); err != nil { 42 | fmt.Println("insert error") 43 | log.Fatal(err) 44 | } 45 | 46 | // Use select to get the employee we just entered 47 | var userFromDB User 48 | 49 | if err := session.Query("SELECT id, firstname, lastname, age FROM employees WHERE id=?", id). 50 | Scan(&userFromDB.Id, &userFromDB.FirstName, &userFromDB.LastName, &userFromDB.Age); err != nil { 51 | fmt.Println("select error") 52 | log.Fatal(err) 53 | } 54 | fmt.Println(userFromDB) 55 | 56 | // Update James's Bond's age 57 | if err := session.Query("UPDATE employees SET age = 46 WHERE id = ?", id).Exec(); err != nil { 58 | fmt.Println("udpate error") 59 | log.Fatal(err) 60 | } 61 | 62 | var newAge int 63 | // Select and show the change 64 | iter := session.Query("SELECT age FROM employees WHERE id = ?", id).Iter() 65 | for iter.Scan(&newAge) { 66 | fmt.Println(newAge) 67 | } 68 | if err := iter.Close(); err != nil { 69 | log.Fatal(err) 70 | } 71 | 72 | // Delete the employe 73 | if err := session.Query("DELETE FROM employees WHERE id = ?", id).Exec(); err != nil { 74 | fmt.Println("delete error") 75 | log.Fatal(err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Chapter07/go_kit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-kit/kit/endpoint" 11 | httptransport "github.com/go-kit/kit/transport/http" 12 | ) 13 | 14 | // CountVowels counts vowels in strings. 15 | type VowelsService interface { 16 | Count(context.Context, string) int 17 | } 18 | 19 | // VowelsService is a concrete implementation of VowelsService 20 | type VowelsServiceImpl struct{} 21 | 22 | var vowels = map[rune]bool{ 23 | 'a': true, 24 | 'e': true, 25 | 'i': true, 26 | 'o': true, 27 | 'u': true, 28 | } 29 | 30 | func (VowelsServiceImpl) Count(_ context.Context, s string) int { 31 | count := 0 32 | for _, c := range s { 33 | if _, ok := vowels[c]; ok { 34 | count++ 35 | } 36 | } 37 | 38 | return count 39 | } 40 | 41 | // ErrEmpty is returned when an input string is empty. 42 | var ErrEmpty = errors.New("empty string") 43 | 44 | // For each method, we define request and response structs 45 | type countVowelsRequest struct { 46 | Input string `json:"input"` 47 | } 48 | 49 | type countVowelsResponse struct { 50 | Result int `json:"result"` 51 | } 52 | 53 | // An endpoint represents a single RPC in the service interface 54 | func makeEndpoint(svc VowelsService) endpoint.Endpoint { 55 | return func(ctx context.Context, request interface{}) (interface{}, error) { 56 | req := request.(countVowelsRequest) 57 | result := svc.Count(ctx, req.Input) 58 | return countVowelsResponse{result}, nil 59 | } 60 | } 61 | 62 | // Transports expose the service to the network. 63 | func main() { 64 | svc := VowelsServiceImpl{} 65 | 66 | countHandler := httptransport.NewServer( 67 | makeEndpoint(svc), 68 | decodecountVowelsRequest, 69 | encodeResponse, 70 | ) 71 | 72 | http.Handle("/count", countHandler) 73 | log.Fatal(http.ListenAndServe(":8080", nil)) 74 | } 75 | 76 | func decodecountVowelsRequest(_ context.Context, r *http.Request) (interface{}, error) { 77 | var request countVowelsRequest 78 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 79 | return nil, err 80 | } 81 | return request, nil 82 | } 83 | 84 | func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 85 | return json.NewEncoder(w).Encode(response) 86 | } 87 | -------------------------------------------------------------------------------- /Chapter06/nsq_async_cons.go: -------------------------------------------------------------------------------- 1 | // This file describes how we consumers can handle messages asynchronously 2 | package main 3 | 4 | import ( 5 | "github.com/nsqio/go-nsq" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | // messages received on NSQ are sent on this channel for background processing 13 | var delegateChannel chan *nsq.Message 14 | 15 | type MyMessageHandler struct {} 16 | func (h *MyMessageHandler) HandleMessage(m *nsq.Message) error { 17 | // Disable auto-acknoledgement of messages. 18 | // This means the actual background consumer from the channel will ack the message. 19 | // NOTE : If for some reason there is a restart of this instance, messages will be available again after timeout 20 | m.DisableAutoResponse() 21 | log.Printf("[nsq handler] received %s\n",string(m.Body)) 22 | log.Println("delegaing..\n") 23 | delegateChannel <- m 24 | return nil 25 | } 26 | 27 | 28 | 29 | func main() { 30 | 31 | delegateChannel = make(chan *nsq.Message, 50) // make a buffered channel 32 | cfg := nsq.NewConfig() 33 | cfg.MaxInFlight = 1000 //Maximum number of messages to allow in flight (concurrency knob) 34 | topicName := "my_topic" 35 | channelName := "my_async_chan" 36 | cons, err := nsq.NewConsumer(topicName, channelName, cfg) 37 | if err != nil { 38 | log.Fatalf(err.Error()) 39 | } 40 | 41 | go func() { 42 | for m := range delegateChannel { 43 | err := doSomeWork(m) // some long winded tasks 44 | if err != nil { 45 | m.Requeue(-1) 46 | continue 47 | } 48 | m.Finish() 49 | } 50 | }() 51 | 52 | // the method below is an alternative to AddHandler to enable concurrent processing 53 | // the second argument is the number of goroutines to spawn for processing 54 | cons.AddConcurrentHandlers(&MyMessageHandler{}, 20) 55 | 56 | err = cons.ConnectToNSQD("127.0.0.1:4150") 57 | if err != nil { 58 | log.Fatalf(err.Error()) 59 | } 60 | 61 | // Wait for a signal to quit 62 | sigChan := make(chan os.Signal, 1) 63 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 64 | <-sigChan 65 | 66 | // Stop the consumer 67 | cons.Stop() 68 | <-cons.StopChan // wait for cleanup 69 | 70 | } 71 | 72 | func doSomeWork(m *nsq.Message) error { 73 | // some long winded tasks 74 | log.Printf("received msg %s\n",string(m.Body)) 75 | return nil 76 | } -------------------------------------------------------------------------------- /Chapter03/creational_builder_factory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Reservation interface { 8 | GetReservationDate() string 9 | SetReservationDate(string) 10 | } 11 | 12 | type HotelReservationImpl struct { 13 | reservationDate string 14 | } 15 | 16 | type FlightReservationImpl struct { 17 | reservationDate string 18 | } 19 | 20 | func (r HotelReservationImpl) GetReservationDate() string { 21 | return r.reservationDate 22 | } 23 | 24 | func (r HotelReservationImpl) SetReservationDate(date string) { 25 | r.reservationDate = date 26 | } 27 | 28 | func (r FlightReservationImpl) GetReservationDate() string { 29 | return r.reservationDate 30 | } 31 | 32 | func (r FlightReservationImpl) SetReservationDate(date string) { 33 | r.reservationDate = date 34 | } 35 | 36 | func NewReservation(vertical, reservationDate string) Reservation { 37 | switch vertical { 38 | case "flight": 39 | return FlightReservationImpl{reservationDate} 40 | case "hotel": 41 | return HotelReservationImpl{reservationDate} 42 | default: 43 | return nil 44 | } 45 | 46 | } 47 | 48 | type ReservationBuilder interface { 49 | Vertical(string) ReservationBuilder 50 | ReservationDate(string) ReservationBuilder 51 | Build() Reservation 52 | } 53 | 54 | type reservationBuilder struct { 55 | vertical string 56 | rdate string 57 | } 58 | 59 | func (r *reservationBuilder) Vertical(v string) ReservationBuilder { 60 | r.vertical = v 61 | return r 62 | } 63 | 64 | func (r *reservationBuilder) ReservationDate(date string) ReservationBuilder { 65 | r.rdate = date 66 | return r 67 | } 68 | 69 | func (r *reservationBuilder) Build() Reservation { 70 | 71 | var builtReservation Reservation 72 | 73 | switch r.vertical { 74 | case "flight": 75 | builtReservation = FlightReservationImpl{r.rdate} 76 | case "hotel": 77 | builtReservation = HotelReservationImpl{r.rdate} 78 | } 79 | 80 | return builtReservation 81 | } 82 | 83 | func NewReservationBuilder() ReservationBuilder { 84 | return &reservationBuilder{} 85 | } 86 | 87 | func main() { 88 | hotelReservation := NewReservation("hotel", "20180101") 89 | fmt.Println(hotelReservation.GetReservationDate()) 90 | 91 | builder := NewReservationBuilder() 92 | r := builder.Vertical("hotel").ReservationDate("20180101").Build() 93 | fmt.Println(r.GetReservationDate()) 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Chapter01, and 02/interface.go: -------------------------------------------------------------------------------- 1 | // This file demonstrates interfaces in Go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type LatLong struct { 9 | Lat float64 10 | Long float64 11 | } 12 | 13 | type Animal interface { 14 | GetLocation() LatLong 15 | SetLocation(LatLong) 16 | CanFly() bool 17 | Speak() string 18 | GetName() string 19 | } 20 | 21 | // The Lion Family 22 | // 23 | 24 | type Lion struct { 25 | name string 26 | maneLength int 27 | location LatLong 28 | } 29 | 30 | func (lion *Lion) GetLocation() LatLong { 31 | return lion.location 32 | } 33 | 34 | func (lion *Lion) SetLocation(loc LatLong) { 35 | lion.location = loc 36 | } 37 | 38 | func (lion *Lion) CanFly() bool { 39 | return false 40 | } 41 | 42 | func (lion *Lion) Speak() string { 43 | return "roar" 44 | } 45 | 46 | func (lion *Lion) GetManeLength() int { 47 | return lion.maneLength 48 | } 49 | 50 | func (lion *Lion) GetName() string { 51 | return lion.name 52 | } 53 | 54 | // The Pigeon Family 55 | // 56 | 57 | type Pigeon struct { 58 | name string 59 | location LatLong 60 | } 61 | 62 | func (p *Pigeon) GetLocation() LatLong { 63 | return p.location 64 | } 65 | 66 | func (p *Pigeon) SetLocation(loc LatLong) { 67 | p.location = loc 68 | } 69 | 70 | func (p *Pigeon) CanFly() bool { 71 | return false 72 | } 73 | 74 | func (p *Pigeon) Speak() string { 75 | return "hoot" 76 | } 77 | 78 | func (p *Pigeon) GetName() string { 79 | return p.name 80 | } 81 | 82 | // The symphony 83 | // makeThemSing demonstraces how client code can work with interfaces and not worry about struct specifics 84 | func makeThemSing(animals []Animal) { 85 | for _, animal := range animals { 86 | fmt.Println(animal.GetName() + " says " + animal.Speak()) 87 | } 88 | } 89 | 90 | func main() { 91 | var myZoo []Animal 92 | 93 | Leo := Lion{ 94 | "Leo", 95 | 10, 96 | LatLong{10.40, 11.5}, 97 | } 98 | myZoo = append(myZoo, &Leo) 99 | 100 | Tweety := Pigeon{ 101 | "Tweety", 102 | LatLong{10.40, 11.5}, 103 | } 104 | myZoo = append(myZoo, &Tweety) 105 | 106 | makeThemSing(myZoo) 107 | 108 | 109 | var aAnimal Animal 110 | 111 | aAnimal = &Lion{ 112 | "Leo", 113 | 10, 114 | LatLong{10.40, 11.5}, 115 | } 116 | 117 | fmt.Println(aAnimal) 118 | 119 | } 120 | -------------------------------------------------------------------------------- /Chapter06/kakfka_async_producer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Shopify/sarama" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | // Toy message that we want to send 14 | type Message struct { 15 | Who string 16 | TimeAsString string 17 | } 18 | 19 | func main() { 20 | 21 | // Create configuration 22 | config := sarama.NewConfig() 23 | // The setting below indicates the level of reliability needed 24 | // Here we are saying we want all brokers in the ISR to ack 25 | config.Producer.RequiredAcks = sarama.WaitForAll 26 | // The total number of times to retry sending a message (default 3). 27 | config.Producer.Retry.Max = 5 28 | 29 | // you don’t need to give a list of all the brokers, just few seeds which will 30 | // tell the client about other brokers in the cluster 31 | brokers := []string{"localhost:9092"} 32 | asyncProducer, err := sarama.NewAsyncProducer(brokers, config) 33 | if err != nil { 34 | // Could not connect 35 | panic(err) 36 | } 37 | 38 | defer func() { 39 | if err := asyncProducer.Close(); err != nil { 40 | log.Fatalln(err) 41 | } 42 | }() 43 | 44 | // Trap SIGINT to break from the loop and clean up. 45 | signals := make(chan os.Signal, 1) 46 | signal.Notify(signals, os.Interrupt) 47 | exitProgram := make(chan struct{}) 48 | 49 | // Simple while(1) look to send current time. 50 | var nPublished, nErrors int 51 | go func() { 52 | for { 53 | time.Sleep(5 * time.Second) 54 | 55 | // construct a message 56 | body := Message{ 57 | Who: "aProcess", 58 | TimeAsString: strconv.Itoa(int(time.Now().Unix())), 59 | } 60 | 61 | // marshall it 62 | payload, _ := json.Marshal(body) 63 | 64 | msg := &sarama.ProducerMessage{ 65 | Topic: "currentTime", 66 | Key: sarama.StringEncoder("aKey"), 67 | Value: sarama.ByteEncoder(payload), 68 | } 69 | select { 70 | case producer.Input() <- msg: 71 | nPublished++ 72 | fmt.Println("Produce message") 73 | case err := <-producer.Errors(): 74 | nErrors++ 75 | fmt.Println("Failed to produce message:", err) 76 | case <-signals: 77 | exitProgram <- struct{}{} 78 | } 79 | 80 | log.Printf("Published: %d; Errors: %d\n", nPublished, nErrors) 81 | 82 | } 83 | }() 84 | 85 | <-exitProgram // wait here till program gets killed 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Chapter03/command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "errors" 6 | ) 7 | 8 | // The Command - encapsulates the action to be done 9 | type Report interface { 10 | Execute() // here the action is called Execute 11 | } 12 | 13 | 14 | // The Concrete Commands 15 | type ConcreteReportA struct { 16 | // The action needs to be done on this receiver object 17 | receiver *Receiver 18 | } 19 | 20 | func (c *ConcreteReportA) Execute() { 21 | c.receiver.Action("ReportA") 22 | } 23 | 24 | type ConcreteReportB struct { 25 | receiver *Receiver 26 | } 27 | 28 | func (c *ConcreteReportB) Execute() { 29 | c.receiver.Action("ReportB") 30 | } 31 | // end of concrete commands 32 | 33 | // Receiver - ancillary objects passed to command execution 34 | // This can pass useful information for 35 | type Receiver struct{} 36 | 37 | func (r *Receiver) Action(msg string) { 38 | fmt.Println(msg) 39 | } 40 | 41 | // Invoker - this object which knows how to execute a command, and optionally 42 | // does bookkeeping about the command execution. 43 | type Invoker struct { 44 | repository []Report 45 | } 46 | 47 | func (i *Invoker) Schedule(cmd Report) { 48 | i.repository = append(i.repository, cmd) 49 | } 50 | 51 | func (i *Invoker) Run() { 52 | for _, cmd := range i.repository { 53 | cmd.Execute() 54 | } 55 | } 56 | 57 | 58 | // Chain Of Responsibilty 59 | // uses Command to represent requests as objects 60 | 61 | type ChainedReceiver struct { 62 | canHandle string 63 | next *ChainedReceiver 64 | } 65 | 66 | func (r *ChainedReceiver) SetNext(next *ChainedReceiver) { 67 | r.next = next 68 | } 69 | 70 | func (r *ChainedReceiver) Finish() error { 71 | fmt.Println(r.canHandle, " Receiver Finishing") 72 | return nil 73 | } 74 | 75 | func (r *ChainedReceiver) Handle(what string) error { 76 | // Check if this receiver can handle 77 | // this of course is a dummy check 78 | if what==r.canHandle { 79 | return r.Finish() 80 | } else if r.next != nil { 81 | return r.next.Handle(what) 82 | } else { 83 | fmt.Println("No Receiver could handle the request!") 84 | return errors.New("No Receiver to Handle") 85 | } 86 | 87 | } 88 | 89 | 90 | // The code below demonstrates usage of the design pattern 91 | // 92 | 93 | func main() { 94 | receiver := new(Receiver) 95 | ReportA := &ConcreteReportA{receiver} 96 | ReportB := &ConcreteReportB{receiver} 97 | invoker := new(Invoker) 98 | invoker.Schedule(ReportA) 99 | invoker.Run() 100 | invoker.Schedule(ReportB) 101 | invoker.Run() 102 | 103 | } -------------------------------------------------------------------------------- /Chapter10/seller_proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | cluster "github.com/bsm/sarama-cluster" 7 | "log" 8 | "os" 9 | "os/signal" 10 | ) 11 | 12 | var ( 13 | signals chan os.Signal 14 | ) 15 | 16 | func init() { 17 | 18 | // setup config, enable errors and notifications 19 | config := cluster.NewConfig() 20 | config.Consumer.Return.Errors = true 21 | config.Group.Mode = cluster.ConsumerModePartitions 22 | config.Group.Return.Notifications = true 23 | 24 | // specify Broker co-ordinates and topics of interest 25 | // should be done from config 26 | brokers := []string{"localhost:9092"} 27 | topics := []string{"create_booking"} 28 | 29 | // trap SIGINT to trigger a shutdown. 30 | signals = make(chan os.Signal, 1) 31 | signal.Notify(signals, os.Interrupt) 32 | 33 | // connect, and register specifiying the consumer group name 34 | consumer, err := cluster.NewConsumer(brokers, "booking-service", topics, config) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | // process errors 40 | go func() { 41 | for err := range consumer.Errors() { 42 | log.Printf("Error: %s\n", err.Error()) 43 | } 44 | }() 45 | 46 | // process notifications 47 | go func() { 48 | for ntf := range consumer.Notifications() { 49 | log.Printf("Rebalanced: %+v\n", ntf) 50 | } 51 | }() 52 | 53 | //start the listener thread 54 | go handleCreateBookingMessage(consumer) 55 | } 56 | 57 | func handleCreateBookingMessage(consumer *cluster.Consumer) { 58 | 59 | for { 60 | 61 | select { 62 | case partition, ok := <-consumer.Partitions(): 63 | if !ok { 64 | panic("kafka consumer : error getting paritions..") 65 | } 66 | 67 | // start a separate goroutine to consume messages 68 | go func(pc cluster.PartitionConsumer) { 69 | for msg := range pc.Messages() { 70 | 71 | var reservationDTO HotelReservationDTO 72 | if err := json.Unmarshal(msg.Value, &reservationDTO); err != nil { 73 | fmt.Println("unmarshalling error", err) 74 | // Commit even on error to avoid poison pills 75 | consumer.MarkOffset(msg, "") 76 | continue 77 | } 78 | 79 | // TODO make actual booking with seller 80 | 81 | // update status in DB 82 | updateReservationStatus(&reservationDTO, BookingMade) 83 | 84 | fmt.Printf("processed create booking message %s-%d-%d-%s-%s\n", 85 | msg.Topic, 86 | msg.Partition, 87 | msg.Offset, 88 | msg.Key, 89 | msg.Value) // <- Actually process message here 90 | 91 | consumer.MarkOffset(msg, "") // Commit offeset for this message 92 | 93 | } 94 | }(partition) 95 | case <-signals: 96 | fmt.Println("consumer killed..") 97 | return 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Chapter10/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/Shopify/sarama" 7 | "github.com/gin-gonic/gin" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | var ( 13 | kafkaProducer sarama.SyncProducer 14 | ) 15 | 16 | func init() { 17 | var err error 18 | 19 | // Config 20 | config := sarama.NewConfig() 21 | config.Producer.RequiredAcks = sarama.WaitForAll 22 | config.Producer.Retry.Max = 5 23 | config.Producer.Return.Errors = true // For sync producer this needs to be true 24 | config.Producer.Return.Successes = true // For sync producer this needs to be true 25 | 26 | // Connect to a Kafka broker running locally 27 | // TODO : get the connection string from config 28 | brokers := []string{"localhost:9092"} 29 | kafkaProducer, err = sarama.NewSyncProducer(brokers, config) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | } 35 | 36 | func main() { 37 | router := gin.Default() 38 | v1 := router.Group("/v1/hotels/Chapter10") 39 | { 40 | v1.POST("/", createReservation) 41 | 42 | } 43 | router.Run() 44 | } 45 | 46 | type HotelReservationDTO struct { 47 | CustomerId uint `json:"customer_id" ` 48 | PaymentIdentifier uint `json:"payment_identifier" ` 49 | SKU uint `json:"entity_id" ` 50 | RoomId uint `json:"room_id" ` 51 | CheckIn ReservationTime `json:"check_in" gorm:"type:datetime"` 52 | CheckOut ReservationTime `json:"check_out" gorm:"type:datetime"` 53 | } 54 | 55 | func createReservation(c *gin.Context) { 56 | var ( 57 | reservationDTO HotelReservationDTO 58 | err error 59 | ) 60 | 61 | fmt.Println("In createReservation ") 62 | 63 | if err = c.ShouldBindJSON(&reservationDTO); err == nil { 64 | fmt.Printf("In createReservation : %+v\n", reservationDTO) 65 | err = persistReservation(&reservationDTO) 66 | sendMessageToPerformBooking(&reservationDTO) 67 | //return OK 68 | c.JSON(http.StatusAccepted, gin.H{"status": "created"}) 69 | } 70 | 71 | if err != nil { 72 | // some params not correct 73 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 74 | } 75 | 76 | } 77 | 78 | // Send message to Booking service to enable the next stage in the workflow 79 | func sendMessageToPerformBooking(reservationDTO *HotelReservationDTO) { 80 | log.Println("sending message to kickstart booking for ", reservationDTO) 81 | 82 | bytes, err := json.Marshal(reservationDTO) 83 | if err != nil { 84 | log.Println("error sending message to Kafka ", err) 85 | return 86 | } 87 | 88 | // We are not setting a message key, which means that all messages will 89 | // be distributed randomly over the different partitions. 90 | msg := &sarama.ProducerMessage{ 91 | Topic: "create_booking", 92 | Value: sarama.ByteEncoder(bytes), 93 | } 94 | 95 | partition, offset, err := kafkaProducer.SendMessage(msg) 96 | if err != nil { 97 | fmt.Printf("FAILED to publish message: %s\n", err) 98 | } else { 99 | fmt.Printf("message sent | partition(%d)/offset(%d)\n", partition, offset) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Chapter07/hotels_gins.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | router := gin.Default() 11 | v1 := router.Group("/v1/hotels") 12 | { 13 | v1.POST("/", createHotel) 14 | v1.GET("/", getAllHotels) 15 | v1.GET("/:id", getHotel) 16 | v1.PUT("/:id", updateHotel) 17 | v1.DELETE("/:id", deleteHotel) 18 | } 19 | router.Run() 20 | } 21 | 22 | type Hotel struct { 23 | Id string `json:"id" binding:"required"` 24 | DisplayName string `json:"display_name" ` 25 | StarRating int `json:"star_rating" ` 26 | NoRooms int `json:"no_rooms" ` 27 | Links []Link `json:"links"` 28 | } 29 | 30 | type Link struct { 31 | Href string `json:"href"` 32 | Rel string `json:"rel"` 33 | Type string `json:"type"` 34 | } 35 | 36 | var ( 37 | repository map[string]*Hotel 38 | ) 39 | 40 | func init() { 41 | repository = make(map[string]*Hotel) 42 | } 43 | 44 | func (h *Hotel) generateHateosLinks(url string) { 45 | // Book url 46 | postLink := Link{ 47 | Href: url + "book", 48 | Rel: "book", 49 | Type: "POST", 50 | } 51 | 52 | h.Links = append(h.Links, postLink) 53 | } 54 | 55 | func createHotel(c *gin.Context) { 56 | var hotel Hotel 57 | if err := c.ShouldBindJSON(&hotel); err == nil { 58 | // add HATEOS links 59 | hotel.generateHateosLinks(c.Request.URL.String()) 60 | // add hotel to repository 61 | repository[hotel.Id] = &hotel 62 | 63 | //return OK 64 | c.JSON(http.StatusAccepted, gin.H{"status": "created"}) 65 | } else { 66 | // some params not correct 67 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 68 | } 69 | } 70 | 71 | func getHotel(c *gin.Context) { 72 | // get ID from path param 73 | hotelId := c.Param("id") 74 | 75 | // get hotel object from repository 76 | hotel, found := repository[hotelId] 77 | fmt.Println(hotel, found, hotelId) 78 | if !found { 79 | c.JSON(http.StatusNotFound, gin.H{"status": "hotel with id not found"}) 80 | } else { 81 | c.JSON(http.StatusOK, gin.H{"result": hotel}) 82 | } 83 | 84 | } 85 | 86 | func updateHotel(c *gin.Context) { 87 | // get hotel object from repository 88 | hotelId := c.Param("id") 89 | hotel, found := repository[hotelId] 90 | if !found { 91 | c.JSON(http.StatusNotFound, gin.H{"status": "hotel with id not found"}) 92 | } else { 93 | //update 94 | if err := c.ShouldBindJSON(&hotel); err == nil { 95 | repository[hotel.Id] = hotel 96 | 97 | //return OK 98 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) 99 | } else { 100 | // some params not correct 101 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 102 | } 103 | } 104 | 105 | } 106 | 107 | func getAllHotels(c *gin.Context) { 108 | c.JSON(http.StatusOK, gin.H{"result": repository}) 109 | } 110 | 111 | func deleteHotel(c *gin.Context) { 112 | hotelId := c.Param("id") 113 | _, found := repository[hotelId] 114 | if !found { 115 | c.JSON(http.StatusNotFound, gin.H{"status": "hotel with id not found"}) 116 | } else { 117 | delete(repository, hotelId) 118 | //return OK 119 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Chapter10/models.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/mysql" 8 | "time" 9 | ) 10 | 11 | type Status int 12 | 13 | const ( 14 | Initial Status = 0 15 | BookingMade Status = 1 16 | EmailSent Status = 2 17 | ) 18 | 19 | type HotelReservation struct { 20 | //HotelReservationDetails `gorm:"embedded"` 21 | CustomerId uint `json:"customer_id" ` 22 | PaymentIdentifier uint `json:"payment_identifier" ` 23 | SKU uint `json:"entity_id" ` 24 | RoomId uint `json:"room_id" ` 25 | CheckIn time.Time `json:"check_in" gorm:"type:datetime"` 26 | CheckOut time.Time `json:"check_out" gorm:"type:datetime"` 27 | Id string 28 | Status Status 29 | } 30 | 31 | type AvailabilityThreshold struct { 32 | gorm.Model 33 | SKU uint 34 | RoomId uint 35 | Availability int 36 | } 37 | 38 | var ( 39 | db *gorm.DB 40 | ) 41 | 42 | func init() { 43 | var err error 44 | db, err = gorm.Open("mysql", "root:@tcp(127.0.0.1:3306)/hotels?charset=utf8&parseTime=True") 45 | if err != nil { 46 | panic("failed to connect database") 47 | } 48 | 49 | // Migrate the schema 50 | //db.AutoMigrate(&HotelReservation{}) 51 | db.AutoMigrate(&AvailabilityThreshold{}) 52 | 53 | // dummy thresholds 54 | db.Create(&AvailabilityThreshold{SKU: 1, RoomId: 2, Availability: 3}) 55 | 56 | } 57 | 58 | // generates ID for the Chapter10 from SKU, roomid and checkin date 59 | func makeId(res *HotelReservationDTO) string { 60 | // NOTE : for uniquess, non-overlapping reservations, there should be another explicit check 61 | return fmt.Sprintf("%v#%v#%v", res.SKU, res.RoomId, res.CheckIn) 62 | } 63 | 64 | func persistReservation(res *HotelReservationDTO) error { 65 | // Note the use of tx as the database handle once you are within a transaction 66 | tx := db.Begin() 67 | defer func() { 68 | if r := recover(); r != nil { 69 | tx.Rollback() 70 | } 71 | }() 72 | 73 | if tx.Error != nil { 74 | return tx.Error 75 | } 76 | 77 | //TODO : Check that there is no overlapping Chapter10 78 | 79 | if err := tx.Create(&HotelReservation{ 80 | CustomerId: res.CustomerId, 81 | PaymentIdentifier: res.PaymentIdentifier, 82 | SKU: res.SKU, 83 | RoomId: res.RoomId, 84 | CheckIn: time.Time(res.CheckIn), 85 | CheckOut: time.Time(res.CheckOut), 86 | Id: makeId(res), 87 | Status: Initial}).Error; err != nil { 88 | tx.Rollback() 89 | return err 90 | } 91 | 92 | fmt.Println("created hotel Chapter10..") 93 | 94 | // update the entry for availability threshold 95 | var threshold AvailabilityThreshold 96 | tx.Where("entity_id = ? AND room_id = ?", res.SKU, res.RoomId).First(&threshold) 97 | 98 | fmt.Printf("\nthreshold = %+v\n", threshold) 99 | tx.Model(&threshold).Where("id = ?", threshold.ID).Update("availability", threshold.Availability-1) 100 | 101 | // NOTE : availability is just a threshold for update here. 102 | // Even if availability is 0, Chapter10 is forwarded to the Seller 103 | // And availability >0 in thresholds DB is not a guarantee of Chapter10 certainity. 104 | if threshold.Availability <= 1 { 105 | // we have reached threshold 106 | sendInvaliationMessageToPriceStore(threshold.SKU, threshold.RoomId) 107 | 108 | } 109 | 110 | return tx.Commit().Error 111 | } 112 | 113 | func sendInvaliationMessageToPriceStore(eid, rid uint) { 114 | fmt.Println("sending message to invalide catalog for entity id ", eid, " room id ", rid) 115 | } 116 | 117 | func updateReservationStatus(reservationDTO *HotelReservationDTO, status Status) error { 118 | tx := db.Begin() 119 | defer func() { 120 | if r := recover(); r != nil { 121 | tx.Rollback() 122 | } 123 | }() 124 | 125 | if tx.Error != nil { 126 | return tx.Error 127 | } 128 | 129 | tx.Model(&HotelReservation{}).Where("id = ?", makeId(reservationDTO)).Update("status", status) 130 | 131 | return tx.Commit().Error 132 | } 133 | -------------------------------------------------------------------------------- /Chapter08/redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/mediocregopher/radix.v2/redis" 7 | "strconv" 8 | ) 9 | 10 | type Hotel struct { 11 | Id string 12 | Name string 13 | City string 14 | StarRating int 15 | Likes int 16 | } 17 | 18 | func main() { 19 | conn, err := redis.Dial("tcp", "localhost:6379") 20 | if err != nil { 21 | panic(err) 22 | } 23 | defer conn.Close() 24 | 25 | // save an object in redis 26 | taj := Hotel{"taj", "Hotel Taj", "Mumbai", 4, 0} 27 | fmt.Println(taj) 28 | setHotel(conn, &taj) 29 | 30 | // retrieve an object from redis 31 | hotel, err := getHotel(conn, "taj") 32 | fmt.Println(hotel, err) 33 | 34 | //simulate some likes 35 | // 3 likes for Taj 36 | err = incrementLikes(conn, "taj") 37 | if err != nil { 38 | fmt.Println("incrementLikes error ", err) 39 | } 40 | incrementLikes(conn, "taj") 41 | incrementLikes(conn, "taj") 42 | 43 | // some other hotels to test the sorted set 44 | leela := Hotel{"leela", "Leeka K", "Bangalore", 4, 0} 45 | setHotel(conn, &leela) 46 | // 2 likes for leeka 47 | incrementLikes(conn, "leela") 48 | incrementLikes(conn, "leela") 49 | 50 | notGood := Hotel{"notSoGood", "HotelXX", "Bangalore", 4, 0} 51 | setHotel(conn, ¬Good) 52 | // only 1 like 53 | incrementLikes(conn, "notSoGood") 54 | 55 | top3, err := top3LikedHotels(conn) 56 | fmt.Println(top3, err) 57 | 58 | } 59 | 60 | func getHotel(conn *redis.Client, id string) (*Hotel, error) { 61 | reply, err := conn.Cmd("HGETALL", "hotels:"+id).Map() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | h := new(Hotel) 67 | h.Id = id 68 | h.Name = reply["name"] 69 | h.City = reply["city"] 70 | if h.Likes, err = strconv.Atoi(reply["likes"]); err != nil { 71 | fmt.Println("likes err", err) 72 | return nil, err 73 | } 74 | if h.StarRating, err = strconv.Atoi(reply["rating"]); err != nil { 75 | fmt.Println("ratings err", err) 76 | return nil, err 77 | } 78 | 79 | return h, nil 80 | } 81 | 82 | func setHotel(conn *redis.Client, h *Hotel) error { 83 | resp := conn.Cmd("HMSET", 84 | "hotels:"+h.Id, 85 | "name", h.Name, 86 | "city", h.City, 87 | "likes", h.Likes, 88 | "rating", h.StarRating) 89 | if resp.Err != nil { 90 | fmt.Println("save err", resp.Err) 91 | return resp.Err 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func incrementLikes(conn *redis.Client, id string) error { 98 | 99 | // Sanity check to ensure that the hotel exists! 100 | exists, err := conn.Cmd("EXISTS", "hotels:"+id).Int() 101 | if err != nil || exists == 0 { 102 | return errors.New("no such hotel") 103 | } 104 | 105 | // Use the MULTI command to inform Redis that we are starting a new 106 | // transaction. 107 | err = conn.Cmd("MULTI").Err 108 | if err != nil { 109 | return err 110 | } 111 | 112 | // Increment the number of likes for the hotel. in the album hash by 1. 113 | // Because we have initiated a MULTI command, this HINCRBY command is queued NOT executed. 114 | // We still check the reply's Err field to check if there was an error for the queing 115 | err = conn.Cmd("HINCRBY", "hotels:"+id, "likes", 1).Err 116 | if err != nil { 117 | return err 118 | } 119 | 120 | // Now we increment the leaderboard sorted set 121 | err = conn.Cmd("ZINCRBY", "likes", 1, id).Err 122 | if err != nil { 123 | return err 124 | } 125 | 126 | // Execute both commands in our transaction atomically. 127 | // EXEC returns the replies from both commands as an array 128 | err = conn.Cmd("EXEC").Err 129 | if err != nil { 130 | return err 131 | } 132 | return nil 133 | } 134 | 135 | func top3LikedHotels(conn *redis.Client) ([]string, error) { 136 | // Use the ZREVRANGE command to fetch the hotels from likes sorted set 137 | // with the highest score first 138 | // The start and stop values are zero-based indexes, so we use 0 and 2 139 | // respectively to limit the reply to the top three. 140 | 141 | reply, err := conn.Cmd("ZREVRANGE", "likes", 0, 2).List() 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | return reply, nil 147 | 148 | } 149 | -------------------------------------------------------------------------------- /Chapter07/graphql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/graphql-go/graphql" 7 | "math/rand" 8 | "net/http" 9 | ) 10 | 11 | type Hotel struct { 12 | Id string `json:"id"` 13 | DisplayName string `json:"displayName"` 14 | City string `json:"city"` 15 | NoRooms int `json:"noRooms"` 16 | StarRating int `json:"starRating"` 17 | } 18 | 19 | // define custom GraphQL ObjectType `hotelType` for our Golang struct `Hotel` 20 | // Note that 21 | // - the fields map with the json tags for the fields in our struct 22 | // - the field types match the field type in our struct 23 | var hotelType = graphql.NewObject(graphql.ObjectConfig{ 24 | Name: "Hotel", 25 | Fields: graphql.Fields{ 26 | "id": &graphql.Field{ 27 | Type: graphql.String, 28 | }, 29 | "displayName": &graphql.Field{ 30 | Type: graphql.String, 31 | }, 32 | "city": &graphql.Field{ 33 | Type: graphql.String, 34 | }, 35 | "noRooms": &graphql.Field{ 36 | Type: graphql.Int, 37 | }, 38 | "starRating": &graphql.Field{ 39 | Type: graphql.Int, 40 | }, 41 | }, 42 | }) 43 | 44 | // define schema, with our rootQuery and rootMutation 45 | var schema, schemaErr = graphql.NewSchema(graphql.SchemaConfig{ 46 | Query: rootQuery, 47 | Mutation: rootMutation, 48 | }) 49 | 50 | func main() { 51 | 52 | if schemaErr != nil { 53 | panic(schemaErr) 54 | } 55 | 56 | http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 57 | fmt.Println("[in handler]", r.URL.Query()) 58 | result := executeQuery(r.URL.Query()["query"][0], schema) 59 | json.NewEncoder(w).Encode(result) 60 | }) 61 | 62 | fmt.Println("Graphql server is running on port 8080") 63 | http.ListenAndServe(":8080", nil) 64 | } 65 | 66 | func executeQuery(query string, schema graphql.Schema) *graphql.Result { 67 | result := graphql.Do(graphql.Params{ 68 | Schema: schema, 69 | RequestString: query, 70 | }) 71 | if len(result.Errors) > 0 { 72 | fmt.Printf("wrong result, unexpected errors: %v", result.Errors) 73 | } 74 | return result 75 | } 76 | 77 | // root mutation 78 | var rootMutation = graphql.NewObject(graphql.ObjectConfig{ 79 | Name: "RootMutation", 80 | Fields: graphql.Fields{ 81 | "createHotel": &graphql.Field{ 82 | Type: hotelType, // the return type for this field 83 | Description: "Create new hotel", 84 | Args: graphql.FieldConfigArgument{ 85 | "displayName": &graphql.ArgumentConfig{ 86 | Type: graphql.NewNonNull(graphql.String), 87 | }, 88 | "city": &graphql.ArgumentConfig{ 89 | Type: graphql.NewNonNull(graphql.String), 90 | }, 91 | "noRooms": &graphql.ArgumentConfig{ 92 | Type: graphql.NewNonNull(graphql.Int), 93 | }, 94 | "starRating": &graphql.ArgumentConfig{ 95 | Type: graphql.NewNonNull(graphql.Int), 96 | }, 97 | }, 98 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 99 | // marshall and cast the argument value 100 | displayName, _ := params.Args["displayName"].(string) 101 | city, _ := params.Args["city"].(string) 102 | noRooms, _ := params.Args["noRooms"].(int) 103 | starRating, _ := params.Args["starRating"].(int) 104 | 105 | // create in 'DB' 106 | newHotel := Hotel{ 107 | Id: randomId(), 108 | DisplayName: displayName, 109 | City: city, 110 | NoRooms: noRooms, 111 | StarRating: starRating, 112 | } 113 | hotels[newHotel.Id] = newHotel 114 | 115 | // return the new Hotel object 116 | return newHotel, nil 117 | }, 118 | }, 119 | }, 120 | }) 121 | 122 | //root query 123 | var rootQuery = graphql.NewObject(graphql.ObjectConfig{ 124 | Name: "RootQuery", 125 | Fields: graphql.Fields{ 126 | 127 | "hotel": &graphql.Field{ 128 | Type: hotelType, 129 | Description: "Get a hotel with this id", 130 | Args: graphql.FieldConfigArgument{ 131 | "id": &graphql.ArgumentConfig{ 132 | Type: graphql.String, 133 | }, 134 | }, 135 | Resolve: func(params graphql.ResolveParams) (interface{}, error) { 136 | id, _ := params.Args["id"].(string) 137 | return hotels[id], nil 138 | }, 139 | }, 140 | }, 141 | }) 142 | 143 | // repository 144 | var hotels map[string]Hotel 145 | 146 | func init() { 147 | hotels = make(map[string]Hotel) 148 | } 149 | 150 | // Random ID Generator 151 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 152 | 153 | func randomId() string { 154 | b := make([]rune, 8) 155 | for i := range b { 156 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 157 | } 158 | return string(b) 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Hands-On Software Architecture with Golang 5 | 6 | Hands-On Software Architecture with Golang 7 | 8 | This is the code repository for [Hands-On Software Architecture with Golang](https://www.packtpub.com/application-development/hands-software-architecture-golang?utm_source=github&utm_medium=repository&utm_campaign=9781788622592), published by Packt. 9 | 10 | **Design and architect highly scalable and robust applications using Go** 11 | 12 | ## What is this book about? 13 | Building software requires careful planning and architectural considerations; Golang was developed with a fresh perspective on building next-generation applications on the cloud with distributed and concurrent computing concerns. 14 | 15 | This book covers the following exciting features: 16 | * Understand architectural paradigms and deep dive into Microservices 17 | * Design parallelism/concurrency patterns and learn object-oriented design patterns in Go 18 | * Explore API-driven systems architecture with introduction to REST and GraphQL standards 19 | * Build event-driven architectures and make your architectures anti-fragile 20 | * Engineer scalability and learn how to migrate to Go from other languages 21 | * Get to grips with deployment considerations with CICD pipeline, cloud deployments, and so on 22 | * Build an end-to-end e-commerce (travel) application backend in Go 23 | 24 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1788622596) today! 25 | 26 | https://www.packtpub.com/ 28 | 29 | ## Instructions and Navigations 30 | All of the code is organized into folders. For example, Chapter02. 31 | 32 | The code will look like the following: 33 | ``` 34 | type Args struct { 35 | A, B int 36 | } 37 | 38 | type MuliplyService struct{} 39 | 40 | func (t *Arith) Do(args *Args, reply *int) error { 41 | *reply = args.A * args.B 42 | return nil 43 | } 44 | ``` 45 | 46 | **Following is what you need for this book:** 47 | Hands-On Software Architecture with Golang is for software developers, architects, and CTOs looking to use Go in their software architecture to build enterprise-grade applications. Programming knowledge of Golang is assumed. 48 | 49 | With the following software and hardware list you can run all code files present in the book (Chapter 1-12). 50 | ### Software and Hardware List 51 | | Chapter | Software required | OS required | 52 | | -------- | ------------------------------------ | ----------------------------------- | 53 | | 1-12 | CURL, Go (preferably v 0.9), Git, Cassandra, Kafka, Redis, and NSQ | Windows, Mac OS X, and Linux (Any) | 54 | 55 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](http://www.packtpub.com/sites/default/files/downloads/9781788622592_ColorImages.pdf). 56 | 57 | ### Related products 58 | * Hands-On Serverless Applications with Go [[Packt]](https://www.packtpub.com/application-development/hands-serverless-applications-go?utm_source=github&utm_medium=repository&utm_campaign=9781789134612) [[Amazon]](https://www.amazon.com/dp/B07DT9DD4V) 59 | 60 | * Mastering Go [[Packt]](https://www.packtpub.com/networking-and-servers/mastering-go?utm_source=github&utm_medium=repository&utm_campaign=9781788626545) [[Amazon]](https://www.amazon.com/dp/1788626540) 61 | 62 | ## Get to Know the Author 63 | **Jyotiswarup Raiturkar** 64 | has architected products ranging from high-volume e-commerce sites to core infrastructure products. Notable products include the Walmart Labs Ecommerce Fulfillment Platform, Intuit Mint, SellerApp, Goibibo, Microsoft Virtual Server, and ShiftPixy, to name a few. Nowadays, he codes in Golang, Python, and Java. 65 | 66 | ### Suggestions and Feedback 67 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 68 | 69 | 70 | ### Download a free PDF 71 | 72 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
73 |

https://packt.link/free-ebook/9781788622592

--------------------------------------------------------------------------------