Journey from Object Oriented language to Go

Journey from Object Oriented language to Go

During this talk speaker will share his experience of migrating from OOP style applications to the Golang. How view on doing things shifted through time and how it changed development. He will talk about success, struggle and mistakes. After this talk it will be easier for you to convince your friends/colleagues to move to the Golang.

A9e23925dc5753d5925cb6ef55d71765?s=128

Sergey Kibish

October 21, 2018
Tweet

Transcript

  1. Journey from Object Oriented language to Sergey Kibish @s_kibish sergeykibish.com

  2. Who am I Software Engineer Creator Open minded Into music

  3. a Story of Transformation

  4. About… • Object Oriented programming and Go • Mistakes •

    New way of thinking
  5. In the end • Will to try Go • Make

    it 1# language of choice • Convince somebody to switch to Go
  6. Object-oriented programming (OOP) is a programming paradigm based on the

    concept of "objects", which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods
  7. OOP is a programming language model organized around objects rather

    than "actions" and data rather than logic
  8. None
  9. None
  10. None
  11. None
  12. None
  13. • Encapsulation • Inheritance • Polymorphism • ? • ?

    • ?
  14. • Encapsulation • Inheritance • Polymorphism • Encapsulation • InheritanceComposition

    • Polymorphism
  15. Encapsulation

  16. public class Vehicle { protected final String brand; protected final

    Integer wheels; public Vehicle(String brand, Integer wheels) { this.brand = brand; this.wheels = wheels; } public Integer getSpeed() { return this.wheels * 5; } public String getBrand() { return this.brand; } }
  17. public class Vehicle { protected final String brand; protected final

    Integer wheels; public Vehicle(String brand, Integer wheels) { this.brand = brand; this.wheels = wheels; } public Integer getSpeed() { return this.wheels * 5; } public String getBrand() { return this.brand; } }
  18. public class Vehicle { protected final String brand; protected final

    Integer wheels; public Vehicle(String brand, Integer wheels) { this.brand = brand; this.wheels = wheels; } public Integer getSpeed() { return this.wheels * 5; } public String getBrand() { return this.brand; } }
  19. type Vehicle struct { brand string wheels int } func

    (v *Vehicle) GetBrand() string { return v.brand } func (v *Vehicle) GetSpeed() int { return v.wheels * 5 }
  20. type Vehicle struct { brand string wheels int } func

    (v *Vehicle) GetBrand() string { return v.brand } func (v *Vehicle) GetSpeed() int { return v.wheels * 5 }
  21. type Vehicle struct { brand string wheels int } func

    (v *Vehicle) GetBrand() string { return v.brand } func (v *Vehicle) GetSpeed() int { return v.wheels * 5 }
  22. Inheritance

  23. public class Car extends Vehicle { public Car(String brand) {

    super(brand, 4); } } Car car = new Car("Ford"); System.out.println(car.getBrand()); System.out.println(car.getSpeed()); >> Ford >> 20
  24. type Car struct { Vehicle } type Motorcycle struct {

    Base Vehicle }
  25. car := &Car{ Vehicle{"Ford", 4}, } fmt.Println(car.GetBrand()) fmt.Println(car.GetSpeed()) >> Ford

    >> 20
  26. motorcycle := &Motorcycle{ Vehicle{"BMW", 2}, } fmt.Println(motorcycle.Base.GetBrand()) fmt.Println(motorcycle.Base.GetSpeed()) >> BMW

    >> 10
  27. Polymorphism

  28. public class Car extends Vehicle { public Car(String brand) {

    super(brand, 4); } @Override public Integer getSpeed() { return wheels * 3; } } Car car = new Car("Ford"); System.out.println(car.getBrand()); System.out.println(car.getSpeed()); >> Ford >> 12
  29. func (v *Vehicle) ComputeSpeed() int { return v.wheels * 5

    }
  30. func (v *Vehicle) ComputeSpeed() int { return v.wheels * 5

    } type Speeder interface { GetSpeed() int } func (c *Car) GetSpeed() int { return c.wheels * 3 } func (m *Motorcycle) GetSpeed() int { return m.Base.ComputeSpeed() }
  31. func (v *Vehicle) ComputeSpeed() int { return v.wheels * 5

    } type Speeder interface { GetSpeed() int } func (c *Car) GetSpeed() int { return c.wheels * 3 } func (m *Motorcycle) GetSpeed() int { return m.Base.ComputeSpeed() } fmt.Println(car.GetSpeed()) fmt.Println(motorcycle.GetSpeed())
  32. type Speeder interface { GetSpeed() int } func printSpeed(s Speeder)

    { fmt.Println(s.GetSpeed()) } printSpeed(car) printSpeed(motorcycle)
  33. Constructors

  34. func NewFord() *Car { return &Car{ Vehicle{"Ford", 4}, } }

    func main() { car := NewFord() printSpeed(car) }
  35. func main() { car := &Car{} printSpeed(car) }

  36. func main() { car := &Car{} printSpeed(car) } >> 0

  37. Default values

  38. type Vehicle struct { brand string // "" wheels int

    // 0 }
  39. Enums

  40. public enum VehicleType { CAR, // 0 MOTORCYCLE, // 1

    OTHER // 2 }
  41. const ( CarType = 0 MotorcycleType = 1 OtherType =

    2 )
  42. const ( CarType = iota // 0 MotorcycleType // 1

    OtherType // 2 )
  43. Generics

  44. Generics

  45. https://go.googlesource.com/proposal/ +/master/design/go2draft-generics- overview.md

  46. Exceptions

  47. value, err := SomeNotSafeOperaion() if err != nil { log.Fatal(err)

    }
  48. panic("Oh, no")

  49. panic("Oh, no")

  50. Is not an OO language

  51. What is about?

  52. • K.I.S.S. • Minimalism • Transparency • Predictability

  53. None
  54. Let’s write a Service

  55. Service • Stores and renders information • Simple CRUD •

    REST API (JSON in body) • DB (Redis)
  56. Tools • libraries • IoC • tools for testing

  57. Tools •Frameworks

  58. None
  59. None
  60. @RestController @RequestMapping("/pages") public class PagesController { @Post public IdDto create()

    { return "id"; } @Get("/{id}") public PagesDto get(@PathVariable String id) { } }
  61. @JsonInclude(Include.NON_NULL) public class PagesDto { private String id; private String

    title; private String body; // getters and setter } @Entity public class PagesDao { @Id private String id; private String title; private String body; // getters and setters }
  62. @Repository public interface PagesRepository implements CrudRepository<PagesDao, String> {}

  63. @Service public class PagesService { @Autowire private PagesRepository repository; @Autowire

    private PagesMapper mapper; public String create(PagesDto dto) { PagesDao dao = mapper.map(dto); return repository.save(dao); } public PagesDto get(String id) { PagesDto dto = mapper.map(repository.getById(id)); return dto; }
  64. @RestController @RequestMapping("/pages") public class PagesController { @Autowire private PagesService service;

    @Post public IdDto create(@RequestBody PagesDto dto) { String id = service.create(dto); return new IdDto(id); } @Get("/{id}") public PagesDto get(@PathVariable String id) { return service.get(id); } }
  65. . !"" controllers # $"" PagesController.java !"" dao # $""

    PagesDao.java !"" dto # !"" IdDto.java # $"" PagesDto.java !"" mappers # $"" PagesMapper.java !"" repositories # $"" PagesRepository.java $"" services $"" PagesService.java
  66. Summary (good) • Framework was used • Fast and easy

    to develop • IoC out of the box (@Autowire)
  67. Summary (bad) • Annotations • Not transparent • A lot

    of things where you don’t have control (it just works) • Super high level of abstraction
  68. Simple: a line of code that does something very small

    and specific. Easy: a line of code that does a lot by calling a framework function causing thousands of lines of code to be executed. https://ilya-sher.org/2016/05/19/tips-for-beginning-systems-and-software-engineers/
  69. Easy: a line of code that does a lot by

    calling a framework function causing thousands of lines of code to be executed. https://ilya-sher.org/2016/05/19/tips-for-beginning-systems-and-software-engineers/
  70. None
  71. Thinking not as a Go developer Mistake

  72. None
  73. None
  74. @davecheney @idanyliuk @peterbourgon @_rsc @Sajma @goinggodotnet @spf13 @mitchellh @rakyll @francesc

    @rob_pike @kelseyhightower @matryer
  75. Learn Go as Go developer and from Go developers

  76. Framework Mistake

  77. STD + Other libraries K.I.S.S. Minimalism

  78. package api // api/api.go type API struct { storage *storage.Storage

    server *http.Server } func NewAPI(storage *storage.Storage) *API { return &API{ storage: storage, } } func (a *API) Start(port string) error { a.server = &http.Server{ Addr: ":" + port, Handler: a.bootRouter(), } return a.server.ListenAndServe() } func (a *API) Shutdown() error { return a.server.Shutdown(context.Background()) } func (a *API) bootRouter() *httprouter.Router { router := httprouter.New() router.POST("/pages", a.Create) router.GET("/pages", a.GetAll) router.GET("/pages/:id", a.Get) router.PUT("/pages/:id", a.Update) router.DELETE("/pages/:id", a.Delete) return router }
  79. package api // api/api.go type API struct { storage *storage.Storage

    server *http.Server } func NewAPI(storage *storage.Storage) *API { return &API{ storage: storage, } } func (a *API) Start(port string) error { a.server = &http.Server{ Addr: ":" + port, Handler: a.bootRouter(), } return a.server.ListenAndServe() } func (a *API) Shutdown() error { return a.server.Shutdown(context.Background()) } func (a *API) bootRouter() *httprouter.Router { router := httprouter.New() router.POST("/pages", a.Create) router.GET("/pages", a.GetAll) router.GET("/pages/:id", a.Get) router.PUT("/pages/:id", a.Update) router.DELETE("/pages/:id", a.Delete) return router }
  80. package api // api/api.go type API struct { storage *storage.Storage

    server *http.Server } func NewAPI(storage *storage.Storage) *API { return &API{ storage: storage, } } func (a *API) Start(port string) error { a.server = &http.Server{ Addr: ":" + port, Handler: a.bootRouter(), } return a.server.ListenAndServe() } func (a *API) Shutdown() error { return a.server.Shutdown(context.Background()) } func (a *API) bootRouter() *httprouter.Router { router := httprouter.New() router.POST("/pages", a.Create) router.GET("/pages", a.GetAll) router.GET("/pages/:id", a.Get) router.PUT("/pages/:id", a.Update) router.DELETE("/pages/:id", a.Delete) return router }
  81. package api // api/api.go type API struct { storage *storage.Storage

    server *http.Server } func NewAPI(storage *storage.Storage) *API { return &API{ storage: storage, } } func (a *API) Start(port string) error { a.server = &http.Server{ Addr: ":" + port, Handler: a.bootRouter(), } return a.server.ListenAndServe() } func (a *API) Shutdown() error { return a.server.Shutdown(context.Background()) } func (a *API) bootRouter() *httprouter.Router { router := httprouter.New() router.POST("/pages", a.Create) router.GET("/pages", a.GetAll) router.GET("/pages/:id", a.Get) router.PUT("/pages/:id", a.Update) router.DELETE("/pages/:id", a.Delete) return router }
  82. package api // api/api.go type API struct { storage *storage.Storage

    server *http.Server } func NewAPI(storage *storage.Storage) *API { return &API{ storage: storage, } } func (a *API) Start(port string) error { a.server = &http.Server{ Addr: ":" + port, Handler: a.bootRouter(), } return a.server.ListenAndServe() } func (a *API) Shutdown() error { return a.server.Shutdown(context.Background()) } func (a *API) bootRouter() *httprouter.Router { router := httprouter.New() router.POST("/pages", a.Create) router.GET("/pages", a.GetAll) router.GET("/pages/:id", a.Get) router.PUT("/pages/:id", a.Update) router.DELETE("/pages/:id", a.Delete) return router }
  83. package main // main.go func main() { apiPort := flag.String("port",

    "8080", "service port") flag.Parse() s := storage.NewStorage(redis.NewClient(&redis.Options{Addr: "localhost:6379"})) a := api.NewAPI(s) // shutdown gracefully go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) <-sigs log.Println("performing shutdown...") if err := a.Shutdown(); err != nil { log.Printf("failed to shutdown server: %v", err) } }() log.Printf("service is ready to listen on port: %s", *apiPort) if err := a.Start(*apiPort); err != http.ErrServerClosed { log.Printf("server failed: %v", err) os.Exit(1) } }
  84. package main // main.go func main() { apiPort := flag.String("port",

    "8080", "service port") flag.Parse() s := storage.NewStorage(redis.NewClient(&redis.Options{Addr: "localhost:6379"})) a := api.NewAPI(s) // shutdown gracefully go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) <-sigs log.Println("performing shutdown...") if err := a.Shutdown(); err != nil { log.Printf("failed to shutdown server: %v", err) } }() log.Printf("service is ready to listen on port: %s", *apiPort) if err := a.Start(*apiPort); err != http.ErrServerClosed { log.Printf("server failed: %v", err) os.Exit(1) } }
  85. package main // main.go func main() { apiPort := flag.String("port",

    "8080", "service port") flag.Parse() s := storage.NewStorage(redis.NewClient(&redis.Options{Addr: "localhost:6379"})) a := api.NewAPI(s) // shutdown gracefully go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) <-sigs log.Println("performing shutdown...") if err := a.Shutdown(); err != nil { log.Printf("failed to shutdown server: %v", err) } }() log.Printf("service is ready to listen on port: %s", *apiPort) if err := a.Start(*apiPort); err != http.ErrServerClosed { log.Printf("server failed: %v", err) os.Exit(1) } }
  86. package main // main.go func main() { apiPort := flag.String("port",

    "8080", "service port") flag.Parse() s := storage.NewStorage(redis.NewClient(&redis.Options{Addr: "localhost:6379"})) a := api.NewAPI(s) // shutdown gracefully go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) <-sigs log.Println("performing shutdown...") if err := a.Shutdown(); err != nil { log.Printf("failed to shutdown server: %v", err) } }() log.Printf("service is ready to listen on port: %s", *apiPort) if err := a.Start(*apiPort); err != http.ErrServerClosed { log.Printf("server failed: %v", err) os.Exit(1) } }
  87. package main // main.go func main() { apiPort := flag.String("port",

    "8080", "service port") flag.Parse() r := redis.NewClient(&redis.Options{Addr: "localhost:"+*apiPort}) s := storage.NewStorage(r) a := api.NewAPI(s) // shutdown gracefully go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) <-sigs log.Println("performing shutdown...") if err := a.Shutdown(); err != nil { log.Printf("failed to shutdown server: %v", err) } }() log.Printf("service is ready to listen on port: %s", *apiPort) if err := a.Start(*apiPort); err != http.ErrServerClosed { log.Printf("server failed: %v", err) os.Exit(1) } }
  88. package storage // storage/storage.go type Storage struct { redis *redis.Client

    } func NewStorage(redis *redis.Client) *Storage { return &Storage{ redis, } } type Page struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"description"` }
  89. package storage // storage/storage.go type Storage struct { redis *redis.Client

    } func NewStorage(redis *redis.Client) *Storage { return &Storage{ redis, } } type Page struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"description"` }
  90. func (s *Storage) GetPageByID(id string) (p *Page, err error) {

    b, err := s.redis.Get(dbKey + id).Bytes() if err != nil { err = fmt.Errorf("failed to get page %s: %v", id, err) return } err = json.Unmarshal(b, &p) if err != nil { err = fmt.Errorf("failed to unmarshal %s: %v", id, err) return } return }
  91. func (s *Storage) GetPageByID(id string) (p *Page, err error) {

    b, err := s.redis.Get(dbKey + id).Bytes() if err != nil { err = fmt.Errorf("failed to get page %s: %v", id, err) return } err = json.Unmarshal(b, &p) if err != nil { err = fmt.Errorf("failed to unmarshal %s: %v", id, err) return } return }
  92. func (s *Storage) GetPageByID(id string) (p *Page, err error) {

    b, err := s.redis.Get(dbKey + id).Bytes() if err != nil { err = fmt.Errorf("failed to get page %s: %v", id, err) return } err = json.Unmarshal(b, &p) if err != nil { err = fmt.Errorf("failed to unmarshal %s: %v", id, err) return } return }
  93. func (a *API) Update(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

    id := ps.ByName("id") var page storage.Page err := json.NewDecoder(r.Body).Decode(&page) if err != nil { log.Printf("failed to decode body: %v", err) write(w, 400, nil) return } err = a.storage.UpdatePageByID(id, &page) if err != nil { log.Printf("failed to update page: %v", err) write(w, 500, nil) return } write(w, 200, okResponse()) }
  94. func (a *API) Update(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

    id := ps.ByName("id") var page storage.Page err := json.NewDecoder(r.Body).Decode(&page) if err != nil { log.Printf("failed to decode body: %v", err) write(w, 400, nil) return } err = a.storage.UpdatePageByID(id, &page) if err != nil { log.Printf("failed to update page: %v", err) write(w, 500, nil) return } write(w, 200, okResponse()) } func okResponse() []byte { return []byte(`{"message":"Ok"}`) } func write(w http.ResponseWriter, statusCode int, body []byte) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) _, err := w.Write(body) if err != nil { log.Printf("failed to write: %v", err) } }
  95. func (a *API) Update(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

    id := ps.ByName("id") var page storage.Page err := json.NewDecoder(r.Body).Decode(&page) if err != nil { log.Printf("failed to decode body: %v", err) write(w, 400, nil) return } err = a.storage.UpdatePageByID(id, &page) if err != nil { log.Printf("failed to update page: %v", err) write(w, 500, nil) return } write(w, 200, okResponse()) } func okResponse() []byte { return []byte(`{"message":"Ok"}`) } func write(w http.ResponseWriter, statusCode int, body []byte) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) _, err := w.Write(body) if err != nil { log.Printf("failed to write: %v", err) } }
  96. . !"" api # !"" api.go # $"" handlers.go !""

    storage # $"" storage.go $"" main.go
  97. Organized around objects rather than "actions" and data rather than

    logic Mistake
  98. . !"" api # !"" api.go # $"" handlers.go !""

    storage # $"" storage.go $"" main.go
  99. Summary (good) • Simple • One way of doing things

    (mostly) • Transparent • Explicit dependencies
  100. Summary (bad) • Verbose • Obvious code (boring)

  101. Simple: a line of code that does something very small

    and specific. Easy: a line of code that does a lot by calling a framework function causing thousands of lines of code to be executed. https://ilya-sher.org/2016/05/19/tips-for-beginning-systems-and-software-engineers/
  102. Simple: a line of code that does something very small

    and specific. https://ilya-sher.org/2016/05/19/tips-for-beginning-systems-and-software-engineers/
  103. No magic

  104. Go is a sobering language

  105. colorTemp := map[bool]string{false: "blue", true: "red"}[color > 100]

  106. colorTemp := map[bool]string{false: "blue", true: "red"}[color > 100]

  107. • Go Proverbs by Rob Pike • 7 common mistakes

    in Go and when to avoid them by Steve Francia • Best Practices for Industrial Programming by Peter Bourgon • Advanced Testing with Go by Mitchell Hashimoto • Things in Go I Never Use by Mat Ryer • JustForFunc: Programming in Go by Francesc Campoy • Go Lift by John Cinnamond • Twelve Go Best Practices by Francesc Campoy
  108. Sergey Kibish @s_kibish sergeykibish.com Thank You