Golang Tutorials For in Depth
Golang Tutorials For in Depth
Introduction
Go, also known as Golang, is an open-source programming language developed by Google. It
is designed for simplicity, efficiency, and reliability, particularly suited for building distributed
systems and scalable network servers.
Key Features of Go
1. Concurrency: Go has built-in support for concurrent programming with goroutines and
channels, making it easy to build applications that efficiently use multiple cores.
2. Simplicity and Readability: The language syntax is straight forward and easy to learn,
focusing on simplicity and readability.
3. Performance: Compiled to machine code, Go programs are fast and efficient. The
garbage collector is optimized to minimize latency.
4. Standard Library: Go has a rich standard library that provides many useful packages
for common tasks such as I/O, text processing, and networking.
5. Strong Typing and Safety: Go is a statically typed language, which helps catch errors
at compile-time and enhances the safety and robustness of the code.
Topics
1. Introduction to Go
○ History and features
○ Setting up the Go environment
○ Writing and running a basic Go program
2. Basic Syntax
○ Packages and imports
○ Functions
○ Variables and constants
○ Basic data types (int, float, string, bool)
○ Type inference
3. Control Structures
○ If-else statements
○ For loops
○ Switch statements
○ Select statement
4. Collections
○ Arrays
○ Slices
○ Maps
5. Functions
○ Function parameters and return values
○ Variadic functions
○ Anonymous functions
○ Higher-order functions
○ Closures
○ Defer, Panic, and Recover
Intermediate Topics
Specialised Topics
1. Static Typing
In Go, types are checked at compile time. This means that type errors are caught early in the
development process, reducing the chances of runtime errors.
2. Strong Typing
Go enforces strong typing, which means that the type system strictly defines how values of
different types can interact. Implicit type conversions are not allowed, and explicit conversions
are required to change from one type to another.
var x int = 10
var y float64 = 20.0
x = y // This will cause a compile-time error
x = int(y) // This is allowed with an explicit conversion
3. Type Inference
Go supports type inference through the := operator, which allows the compiler to infer the type
of a variable based on the value assigned to it. This feature balances type safety with reduced
verbosity.
4. Interface Types
Type assertions and type switches allow developers to check and work with specific types at
runtime in a type-safe manner.
switch v := i.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
default:
fmt.Println("unknown type")
}
6. Composite Types
Go provides several composite types such as arrays, slices, maps, and structs, which allow
grouping multiple values into a single entity while maintaining type safety.
type Person struct {
Name string
Age int
}
7. Type Embedding
Type embedding allows the composition of types in a way that promotes code reuse and
encapsulation while ensuring type safety.
8. Zero Values
Go assigns default zero values to variables based on their type, which helps prevent
uninitialized variables and ensures type safety.
Go’s type safety features contribute to writing reliable, maintainable, and bug-free code by
enforcing strict type checking and clear type conversions. This approach minimizes runtime
errors and enhances the overall robustness of applications developed in Go.
OOP in Golang
Object-Oriented Programming (OOP) is a paradigm based on the concept of objects that can
contain data and code to manipulate that data. The four pillars of OOP are Encapsulation,
Abstraction, Inheritance, and Polymorphism. While Go is not a pure OOP language and does
not have classes, it supports OOP principles through structs and interfaces. Here’s how you can
implement the four pillars of OOP in Go:
1. Encapsulation
Encapsulation is the bundling of data and methods that operate on that data within one unit. In
Go, encapsulation is achieved using structs and methods. By controlling the visibility of the
fields and methods using capitalized names (exported) and uncapitalized names (unexported),
you can encapsulate data.
package main
import (
"fmt"
)
func main() {
person := NewPerson("Alice", 30)
fmt.Println("Name:", person.Name)
fmt.Println("Age:", person.GetAge())
person.SetAge(35)
fmt.Println("Updated Age:", person.GetAge())
}
2. Abstraction
Abstraction is the concept of hiding the complex implementation details and showing only the
essential features of the object. In Go, interfaces are used to define abstract types.
package main
import (
"fmt"
)
// Person struct
type Person struct {
Name string
}
// Speak method implementation for Person
func (p Person) Speak() string {
return "Hello, I am " + p.Name
}
// Dog struct
type Dog struct {
Name string
}
func main() {
var s Speaker
s = Person{Name: "Alice"}
fmt.Println(s.Speak())
s = Dog{Name: "Buddy"}
fmt.Println(s.Speak())
}
3. Inheritance
Inheritance is the mechanism by which one type can inherit the fields and methods of another
type. Go does not support traditional inheritance like other OOP languages, but it supports
composition, which achieves a similar effect.
package main
import (
"fmt"
)
// Animal struct
type Animal struct {
Name string
}
func main() {
dog := Dog{Animal: Animal{Name: "Buddy"}, Breed: "Golden
Retriever"}
fmt.Println(dog.Speak()) // Inherited Speak method
fmt.Println("Breed:", dog.Breed)
}
4. Polymorphism
Polymorphism allows objects of different types to be treated as objects of a common super type.
In Go, polymorphism is achieved through interfaces.
package main
import (
"fmt"
)
// Speaker interface
type Speaker interface {
Speak() string
}
// Person struct
type Person struct {
Name string
}
// Dog struct
type Dog struct {
Name string
}
func main() {
alice := Person{Name: "Alice"}
buddy := Dog{Name: "Buddy"}
Announce(alice)
Announce(buddy)
}
Summary
By using these principles, Go can support object-oriented design, even though it does not follow
the traditional class-based OOP model.
SOLID principles
The SOLID principles are a set of five design principles intended to make software designs
more understandable, flexible, and maintainable. These principles were introduced by Robert C.
Martin and are widely used in object-oriented design and programming. Even though Go is not a
purely object-oriented language, these principles can still be applied effectively using Go's
types, interfaces, and other features.
A class should have only one reason to change, meaning it should have only one job or
responsibility.
Example in Go:
package main
import (
"fmt"
"time"
)
func main() {
order := Order{ID: "123", CustomerID: "456", Amount: 99.99,
CreatedAt: time.Now()}
processor := PaymentProcessor{}
processor.ProcessPayment(order)
}
Software entities should be open for extension but closed for modification. This means that the
behavior of a module can be extended without modifying its source code.
Example in Go:
package main
import (
"fmt"
)
// Shape interface
type Shape interface {
Area() float64
}
// Rectangle struct
type Rectangle struct {
Width, Height float64
}
// Circle struct
type Circle struct {
Radius float64
}
// AreaCalculator struct
type AreaCalculator struct{}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circ := Circle{Radius: 7}
calculator := AreaCalculator{}
totalArea := calculator.TotalArea(rect, circ)
fmt.Printf("Total Area: %.2f\n", totalArea)
}
Objects of a superclass should be replaceable with objects of a subclass without affecting the
correctness of the program. This principle ensures that a subclass can stand in for its
superclass.
Example in Go:
package main
import (
"fmt"
)
// Bird interface
type Bird interface {
Fly() string
}
// Sparrow struct
type Sparrow struct{}
// Ostrich struct
type Ostrich struct{}
func main() {
sparrow := Sparrow{}
ostrich := Ostrich{}
letTheBirdFly(sparrow)
letTheBirdFly(ostrich) // Violates LSP: Ostrich can't fly but
implements Bird interface
}
In this example, Ostrich violates LSP because it can't fly, although it implements the Bird
interface. A better design would separate flight capability into another interface.
Example in Go:
package main
import (
"fmt"
)
// Separate interfaces
type Printer interface {
Print() string
}
func main() {
mfp := MultiFunctionPrinter{}
PrintDocument(mfp)
}
High-level modules should not depend on low-level modules. Both should depend on
abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Example in Go:
package main
import (
"fmt"
)
// Abstraction: NotificationService
type NotificationService interface {
SendNotification(message string) string
}
// High-level module
type OrderProcessor struct {
Notifier NotificationService
}
func main() {
emailNotifier := EmailNotification{}
smsNotifier := SMSNotification{}
emailOrderProcessor.ProcessOrder("123")
smsOrderProcessor.ProcessOrder("456")
}
Summary
● Single Responsibility Principle (SRP): Each type should have one responsibility.
● Open/Closed Principle (OCP): Types should be open for extension but closed for
modification.
● Liskov Substitution Principle (LSP): Subtypes should be replaceable for their base
types without altering the correctness.
● Interface Segregation Principle (ISP): Clients should not be forced to depend on
interfaces they do not use.
● Dependency Inversion Principle (DIP): Depend on abstractions, not on concrete
implementations.
Applying these principles in Go, even though it is not a traditional OOP language, can
significantly enhance the design and maintainability of your software.
Key Concepts
1. Repository Interface: An interface that defines the methods for accessing the data.
2. Repository Implementation: A struct that implements the repository interface,
containing the actual data access logic.
3. Domain Models: Structs representing the data entities in the application.
4. Service Layer: Business logic that uses the repository interface to perform operations.
1. Separation of Concerns: Decouples the business logic from the data access logic.
2. Testability: Makes it easier to mock the repository for unit testing the business logic.
3. Maintainability: Simplifies the management of data access code, especially when
dealing with multiple data sources.
4. Flexibility: Allows for easy replacement or modification of the data access layer without
affecting the business logic.
By using the Repository Pattern, you create a clean and maintainable codebase that adheres to
good software design principles.
Example of DDD in Go
Let's build an example of an e-commerce system focusing on the order management domain.
User Story: As a customer, I want to place an order so that I can buy products.
● Customer (Entity)
● Order (Entity)
● OrderItem (Value Object)
● Product (Entity)
import "time"
import "github.com/yourusername/yourproject/domain"
package repository
import (
"errors"
"github.com/yourusername/yourproject/domain"
"sync"
)
The service layer contains business logic that orchestrates the interactions between entities,
value objects, and repositories.
package service
import (
"github.com/yourusername/yourproject/domain"
"github.com/yourusername/yourproject/repository"
)
// OrderService provides business logic for managing orders.
type OrderService struct {
orderRepo repository.OrderRepository
}
1. Alignment with Business: Ensures that the software accurately models the business
domain.
2. Modularity: Encourages separation of concerns and modular design.
3. Testability: Promotes the use of interfaces and dependency injection, making the code
more testable.
4. Maintainability: Facilitates the evolution of the domain model without affecting other
parts of the system.
By following the principles of Domain-Driven Design, you can create a well-structured and
maintainable codebase that closely aligns with the business requirements.
Dependency Injection
Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC) by
injecting dependencies into a class rather than having the class instantiate them itself. This
approach promotes loose coupling, enhances testability, and makes the code more modular and
maintainable.
Here's how you might use these components in a main function or a controller with dependency
injection:
package main
import (
"fmt"
"github.com/yourusername/yourproject/domain"
"github.com/yourusername/yourproject/repository"
"github.com/yourusername/yourproject/service"
"log"
"time"
)
func main() {
// Initialize the repository and service with dependency injection
orderRepo := repository.NewInMemoryOrderRepository()
orderService := service.NewOrderService(orderRepo)
By applying Dependency Injection in your Go application, you can enhance its modularity,
flexibility, and testability, leading to a more maintainable and scalable codebase.
References
https://gobyexample.com/
https://refactoring.guru/design-patterns
https://dev.to/karankumarshreds/concurrency-patterns-in-go-3jfc