Skip to main content

Golang

2023

Golang Tutorial: Generics with Gorm

·1741 words·9 mins· loading · loading
After months and years of talking, trying things out, and testing, we’ve finally reached a big moment in our favorite programming language. The new Golang version, 1.18, is here. We knew it would bring significant changes to Go’s codebase, even before Generics was officially released. For a long time, when we wanted to make our code more general and abstract, we used code generators in Go. Learning what the “Go way” of doing things was challenging for many of us, but it also led to many breakthroughs. It was worth the effort. Now, there are new possibilities on the horizon. Many new packages have emerged, giving us ideas on how we can improve the Go ecosystem with reusable code that makes life easier for all of us. This inspiration led me to create a small proof of concept using the Gorm library. Now, let’s take a look at it. Source code # When I wrote this article, it relied on a GitHub Repository. The code served as a Go library proof of concept, with my intention to continue working on it. However, it was not yet suitable for production use, and I had no plans to offer production support at that time. You can find the current features by following the link, and below, there is a smaller sample snippet. Example Usage package main import ( "github.com/ompluscator/gorm-generics" // some imports ) // Product is a domain entity type Product struct { // some fields } // ProductGorm is DTO used to map Product entity to database type ProductGorm struct { // some fields } // ToEntity respects the gorm_generics.GormModel interface func (g ProductGorm) ToEntity() Product { return Product{ // some fields } } // FromEntity respects the gorm_generics.GormModel interface func (g ProductGorm) FromEntity(product Product) interface{} { return ProductGorm{ // some fields } } func main() { db, err := gorm.Open(/* DB connection string */) // handle error err = db.AutoMigrate(ProductGorm{}) // handle error // initialize a new Repository with by providing // GORM model and Entity as type repository := gorm_generics.NewRepository[ProductGorm, Product](db) ctx := context.Background() // create new Entity product := Product{ // some fields } // send new Entity to Repository for storing err = repository.Insert(ctx, &product) // handle error fmt.Println(product) // Out: // {1 product1 100 true} single, err := repository.FindByID(ctx, product.ID) // handle error fmt.Println(single) // Out: // {1 product1 100 true} } Why have I picked ORM for PoC? # Coming from a background in software development with traditional object-oriented programming languages like Java, C#, and PHP, one of the first things I did was search Google for a suitable ORM for Golang. Please forgive my inexperience at the time, but that’s what I was expecting. It’s not that I can’t work without an ORM. It’s just that I don’t particularly like how raw MySQL queries appear in the code. All that string concatenation looks messy to me. On the other hand, I always prefer to dive right into writing business logic, with minimal time spent thinking about the underlying data storage. Sometimes, during the implementation, I change my mind and switch to different types of storage. That’s where ORMs come in handy.

Golang Tutorial: Unit Testing with Mocking

·3513 words·17 mins· loading · loading
Unit testing has always been my thing, almost like a hobby. There was a time when I was obsessed with it, and I made sure that all my projects had at least 90% unit test coverage. You can probably imagine how much time it can take to make such a significant change in the codebase. However, the result was worth it because I rarely encountered bugs related to business logic. Most of the issues were related to integration problems with other services or databases. Adding new business rules was a breeze because there were already tests in place to cover all the cases from before. The key was to ensure that these tests remained successful in the end. Sometimes, I didn’t even need to check the entire running service; having the new and old unit tests pass was sufficient. Once, while working on a personal project, I had to write unit tests to cover numerous Go structs and functions—more than 100 in total. It consumed my entire weekend, and late on a Sunday night, before heading out on a business trip the next day, I set an alarm clock to wake me up. I had hardly slept that night; it was one of those restless nights when you dream but are also aware of yourself and your surroundings. My brain was active the entire time, and in my dreams, I kept writing unit tests for my alarm clock. To my surprise, each time I executed a unit test in my dream, the alarm rang. It continued ringing throughout the night. And yes, I almost forgot to mention, for two years, we had zero bugs in production. The application continued to fetch all the data and send all the emails every Monday. I don’t even remember my Gitlab password anymore. Unit Testing and Mocking (in general) # In Martin Fowler’s article, we can identify two types of unit tests: Sociable unit tests, where we test a unit while it relies on other objects in conjunction with it. For example, if we want to test the UserController, we would test it along with the UserRepository, which communicates with the database. Solitary unit tests, where we test a unit in complete isolation. In this scenario, we would test the UserController, which interacts with a controlled, mocked UserRepository. With mocking, we can specify how it behaves without involving a database.

Golang Tutorial: Generics

·3344 words·16 mins· loading · loading
How often do we encounter significant changes in our preferred programming language? Some languages undergo frequent updates, while others remain traditional and stable. Go falls into the latter category, known for its consistency. “This is not the Go way!” is a phrase that often comes to mind. Most Go releases have focused on refining its existing principles. However, a major shift is on the horizon. The Go team has announced that Generics in Go are becoming a reality, moving beyond mere discussion and into implementation. Brace yourselves, a revolution is coming. What are Generics? # Generics allow us to parameterize types when defining interfaces, functions, and structs. Generics is not a new concept. It has been used since the first version of Ada, through templates in C++, to its modern implementations in Java and C#. To illustrate without delving into complex definitions, let’s examine a practical example. Instead of having multiple Max or Min functions like this: Without Generics func MaxInt(a, b int) int { // some code } func MaxFloat64(a, b float64) float64 { // some code } func MaxByte(a, b byte) byte { // some code } we can declare now only one method, like this: With Generics func Max[T constraints.Ordered](a, b T) T { // some code } Wait, what just happened? Instead of defining a method for each type in Go, we utilized Generics. We used a generic type, parameter T, as an argument for the method. With this minor adjustment, we can support all orderable types. The parameter T can represent any type that satisfies the Ordered constraint (we will discuss constraints later). Initially, we need to specify what kind of type T is. Next, we determine where we want to use this parameterized type. In this case, we’ve specified that both input arguments and the output should be of type T. If we execute the method by defining T as an integer, then everything here will be an integer: Execute Generic Function func main() { fmt.Println(Max[int](1, 2)) } // // this code behaves exactly like method: // Max(a, b int) int And it doesn’t stop there. We can provide as many parameterized types as we need and assign them to different input and output arguments as desired: Execute some complex Generic Function func Do[R any, S any, T any](a R, b S) T { // some code } func main() { fmt.Println(Do[int, uint, float64](1, 2)) } // // this code behaves exactly like method: // Do(a int, b uint) float64 Here we have three parameters: R, S, and T. As we can see from the any constraint (which behaves like interface{}), those types can be, well, anything. So, up to this point, we should have a clear understanding of what generics are and how we use them in Go. Let’s now focus on more exciting consequences.

Practical SOLID in Golang: Dependency Inversion Principle

·2306 words·11 mins· loading · loading
Learning a new programming language is often a straightforward process. I often hear: “The first programming language you learn in a year. The second one in a month. The third one in a week, and then each next one in a day.” Saying that is an exaggeration, but it is not too distant from the truth in some cases. For example, jumping to a language relatively similar to the previous one, like Java and C#, can be a straightforward process. But sometimes, switching is tricky, even when we switch from one Object-Oriented language to another. Many features influence such transitions, like strong or weak types, if a language has interfaces, abstract classes, or classes at all. Some of those difficulties we experience immediately after switching, and we adopt a new approach. But some issues we experience later, during unit testing, for example. And then, we learn why The Dependency Inversion Principle is essential, especially in Go. When we do not respect The Dependency Inversion # 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. Above is the definition of DIP as presented by Uncle Bob in his paper. There are also more details inside his blog. So, how can we understand this, especially in the context of Go? First, we should accept Abstraction as an object-oriented programming concept. We use this concept to expose essential behaviors and hide the details of their implementation. Second, what are high and low-level modules? In the context of Go, high-level modules are software components used at the top of the application, such as code used for presentation. It can also be code close to the top level, like code for business logic or some use-case components. It is essential to understand it as a layer that provides real business value to our application. On the other hand, low-level software components are mostly small code pieces that support the higher level. They hide technical details about different infrastructural integrations. For example, this could be a struct that contains the logic for retrieving data from the database, sending an SQS message, fetching a value from Redis, or sending an HTTP request to an external API. So, what does it look like when we break The Dependency Inversion Principle, and our high-level component depends on one low-level component?

Practical SOLID in Golang: Interface Segregation Principle

·1722 words·9 mins· loading · loading
When beginners embark on their programming journey, the initial focus is typically on algorithms and adapting to a new way of thinking. After some time, they delve into Object-Oriented Programming (OOP). If this transition is delayed, it can be challenging to shift from a functional programming mindset. However, eventually, they embrace the use of objects and incorporate them into their code where necessary, sometimes even where they’re not needed. As they learn about abstractions and strive to make their code more reusable, they may overgeneralize, resulting in abstractions applied everywhere, which can hinder future development. At some point, they come to realize the importance of setting boundaries for excessive generalization. Fortunately, The Interface Segregation Principle has already provided a guideline for this, representing the “I” in the word SOLID. When we do not respect The Interface Segregation # Maintain small interfaces to prevent users from relying on unnecessary features. Uncle Bob introduced this principle, and you can find more details about it on his blog. This principle clearly states its requirement, perhaps better than any other SOLID principle. Its straightforward advice to keep interfaces as small as possible should not be interpreted as merely advocating one-method interfaces. Instead, we should consider the cohesion of features that an interface encompasses. Let’s analyze the code below: User interface type User interface { AddToShoppingCart(product Product) IsLoggedIn() bool Pay(money Money) error HasPremium() bool HasDiscountFor(product Product) bool // // some additional methods // } Let’s assume we want to create an application for shopping. One approach is to define an interface User, as demonstrated in the code example. This interface includes various features that a user can have. On our platform, a User can add a Product to the ShoppingCart, make a purchase, and receive discounts on specific Products. However, the challenge is that only specific types of Users can perform all of these actions. Guest struct type Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } func (g *Guest) IsLoggedIn() bool { return false } func (g *Guest) Pay(Money) error { return errors.New("user is not logged in") } func (g *Guest) HasPremium() bool { return false } func (g *Guest) HasDiscountFor(Product) bool { return false } We have implemented this interface with three structs. The first one is the Guest struct, representing a user who is not logged in but can still add a Product to the ShoppingCart. The second implementation is the NormalCustomer, which can do everything a Guest can, plus make a purchase. The third implementation is the PremiumCustomer, which can use all features of our system.

Practical SOLID in Golang: Liskov Substitution Principle

·1880 words·9 mins· loading · loading
I’m not really a fan of reading. Often, when I do read, I find myself losing track of the text’s topic for the past few minutes. Many times, I’ll go through an entire chapter without really grasping what it was all about in the end. It can be frustrating when I’m trying to focus on the content, but I keep realizing I need to backtrack. That’s when I turn to various types of media to learn about a topic. The first time I encountered this reading issue was with the SOLID principle, specifically the Liskov Substitution Principle. Its definition was (and still is) too complicated for my taste, especially in its formal format. As you can guess, LSP represents the letter “L” in the word SOLID. It’s not difficult to understand, although a less mathematical definition would be appreciated. When we do not respect The Liskov Substitution # The first time we encountered this principle was in 1988, thanks to Barbara Liskov. Later, Uncle Bob shared his perspective on this topic in a paper and eventually included it as one of the SOLID principles. Let’s take a look at what it says: Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T. Well, good luck with that definition. No, seriously, what kind of definition is this? Even as I write this article, I’m still struggling to fully grasp this definition, despite my fundamental understanding of LSP. Let’s give it another shot: If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program. Okay, this is a bit clearer now. If ObjectA is an instance of ClassA, and ObjectB is an instance of ClassB, and ClassB is a subtype of ClassA – if we use ObjectB instead of ObjectA somewhere in the code, the application’s functionality must not break. We’re talking about classes and inheritance here, two concepts that aren’t prominent in Go. However, we can still apply this principle using interfaces and polymorphism. Wrong implementation of Update method type User struct { ID uuid.UUID // // some fields // } type UserRepository interface { Update(ctx context.Context, user User) error } type DBUserRepository struct { db *gorm.DB } func (r *DBUserRepository) Update(ctx context.Context, user User) error { return r.db.WithContext(ctx).Delete(user).Error } In this code example, we can see one that’s quite absurd and far from best practices. Instead of updating the User in the database, as the Update method claims, it actually deletes it. But that’s precisely the point here. We have an interface, UserRepository, followed by a struct, DBUserRepository. While this struct technically implements the interface, it completely diverges from what the interface is supposed to do. In fact, it breaks the functionality of the interface rather than fulfilling its expectations. This highlights the essence of the Liskov Substitution Principle (LSP) in Go: a struct must not violate the intended behavior of the interface.

Practical SOLID in Golang: Open/Closed Principle

·1369 words·7 mins· loading · loading
Many different approaches and principles can lead to long-term improvements in our code. Some of them are well-known in the software development community, while others remain somewhat under the radar. In my opinion, this is the case with The Open/Closed Principle, represented by the letter O in the word SOLID. In my experience, only those genuinely interested in SOLID principles tend to understand what this principle means. We may have applied this principle without even realizing it in some instances, such as when working with the Strategy pattern. However, the Strategy pattern is just one application of the Open/Closed Principle. In this article, we will delve into the full purpose of this principle, with all examples provided in Go. When we do not respect the Open/Closed Principle # You should be able to extend the behavior of a system without having to modify that system. The requirement for the Open/Closed Principle, as seen above, was provided by Uncle Bob in his blog. I prefer this way of defining The Open/Closed Principle because it highlights its full brilliance. At first glance, it may seem like an absurd requirement. Seriously, how can we extend something without modifying it? I mean, is it possible to change something without changing it? By examining the code example below, we can see what it means for certain structures not to adhere to this principle and the potential consequences. The bad Authentication Service type AuthenticationService struct { // // some fields // } func (s *AuthenticationService) Authenticate(ctx *gin.Context) (*User, error) { switch ctx.GetString("authType") { case "bearer": return c.authenticateWithBearerToken(ctx.Request.Header) case "basic": return c.authenticateWithBasicAuth(ctx.Request.Header) case "applicationKey": return c.authenticateWithApplicationKey(ctx.Query("applicationKey")) } return nil, errors.New("unrecognized authentication type") } func (s *AuthenticationService) authenticateWithApplicationKey(key string) (*User, error) { // // authenticate User from Application Key // } func (s *AuthenticationService) authenticateWithBasicAuth(h http.Header) (*User, error) { // // authenticate User from Basic Auth // } func (s *AuthenticationService) authenticateWithBearerToken(h http.Header) (*User, error) { // // validate JWT token from the request header // } The example presents a single struct, AuthenticationService. Its purpose is to authenticate a User from the web application’s Context, supported by the Gin package. Here, we have the main method, Authenticate, which checks for specific authentication type associated with the data within the Context. How User is retrieved from the Context may vary based on whether the User uses a bearer JWT token, basic authentication, or an application key.

Practical SOLID in Golang: Single Responsibility Principle

·2013 words·10 mins· loading · loading
There aren’t too many opportunities for a breakthrough in software development. They usually arise from either rewiring our logic after initial misunderstandings or filling in gaps in our knowledge. I appreciate that feeling of deeper understanding. It can happen during a coding session, while reading a book or an online article, or even while sitting on a bus. An internal voice follows, saying, “Ah, yes, that’s how it works.” Suddenly, all past mistakes seem to have a logical reason, and future requirements take shape. I experienced such a breakthrough with the SOLID principles, which were first introduced in a document by Uncle Bob and later expounded upon in his book, “Clean Architecture.” In this article, I intend to embark on a journey through all the SOLID principles, providing examples in Go. The first principle on the list, representing the letter ‘S’ in SOLID, is the Single Responsibility Principle. When we do not respect Single Responsibility # The Single Responsibility Principle (SRP) asserts that each software module should serve a single, specific purpose that could lead to change. The sentence above comes directly from Uncle Bob himself. Initially, its application was linked to modules and the practice of segregating responsibilities based on the organization’s daily tasks. Nowadays, SRP has a broader scope, influencing various aspects of software development. We can apply its principles to classes, functions, modules, and naturally, in Go, even to structs. Some Frankenstein of EmailService type EmailService struct { db *gorm.DB smtpHost string smtpPassword string smtpPort int } func NewEmailService(db *gorm.DB, smtpHost string, smtpPassword string, smtpPort int) *EmailService { return &EmailService{ db: db, smtpHost: smtpHost, smtpPassword: smtpPassword, smtpPort: smtpPort, } } func (s *EmailService) Send(from string, to string, subject string, message string) error { email := EmailGorm{ From: from, To: to, Subject: subject, Message: message, } err := s.db.Create(&email).Error if err != nil { log.Println(err) return err } auth := smtp.PlainAuth("", from, s.smtpPassword, s.smtpHost) server := fmt.Sprintf("%s:%d", s.smtpHost, s.smtpPort) err = smtp.SendMail(server, auth, from, []string{to}, []byte(message)) if err != nil { log.Println(err) return err } return nil } Let’s analyze the code block above. In this code, we have a struct called EmailService, which contains only one method, Send. This service is intended for sending emails. Although it may seem okay at first glance, upon closer inspection, we realize that this code violates the Single Responsibility Principle (SRP) in several ways.

Practical DDD in Golang: Specification

·1596 words·8 mins· loading · loading
There are not many code structures that bring me joy whenever I need to write them. The first time I implemented such code was with a lightweight ORM in Go, back when we didn’t have one. However, I used ORM for many years, and at some point, when you rely on ORM, using QueryBuilder becomes inevitable. Here, you may notice terms like “predicates”, and that’s where we can find the Specification pattern. It’s hard to find any pattern we use as Specification, yet we do not hear its name. I think the only thing harder is to write an application without using this pattern. The Specification has many applications. We can use it for querying, creation, or validation. We may provide a unique code that can do all this work or provide different implementations for different use cases. For Validation # The first use case for the Specification pattern is validation. Typically, we validate data in forms, but this is at the presentation level. Sometimes, we perform validation during creation, such as for Value Objects. In the context of the domain layer, we can use Specifications to validate the states of Entities and filter them from a collection. So, validation at the domain layer has a broader meaning than for user inputs. Base Product Specification type Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { IsValid(product Product) bool } A simple Product Specification type HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) IsValid(product Product) bool { return product.Quantity >= h.pieces } In the example above, there is an interface called ProductSpecification. It defines only one method, IsValid, which expects instances of Product and returns a boolean value as a result if the Product passes validation rules. A simple implementation of this interface is HasAtLeast, which verifies the minimum quantity of the Product. Function as a Product Specification type FunctionSpecification func(product Product) bool func (fs FunctionSpecification) IsValid(product Product) bool { return fs(product) } func IsPlastic(product Product) bool { return product.Material == Plastic } func IsDeliverable(product Product) bool { return product.IsDeliverable } More interesting validators are two functions, IsPlastic and IsDeliverable. We can wrap those functions with a specific type, FunctionSpecification. This type embeds a function with the same signature as the two mentioned. Besides that, it provides methods that respect the ProductSpecification interface. This example is a nice feature of Go, where we can define a function as a type and attach a method to it so that it can implicitly implement some interface. In this case, it exposes the method IsValid, which executes the embedded function.

Practical DDD in Golang: Repository

·2108 words·10 mins· loading · loading
Today, it is hard to imagine writing an application without accessing some form of storage at runtime. This includes not only writing application code but also deployment scripts, which often need to access configuration files, which are also a type of storage in a sense. When developing applications to solve real-world business problems, connecting to databases, external APIs, caching systems, or other forms of storage is practically unavoidable. It’s no surprise, then, that Domain-Driven Design (DDD) includes patterns like the Repository pattern to address these needs. While DDD didn’t invent the Repository pattern, it added more clarity and context to its usage. The Anti-Corruption Layer # Domain-Driven Design (DDD) is a principle that can be applied to various aspects of software development and in different parts of a software system. However, its primary focus is on the domain layer, which is where our core business logic resides. While the Repository pattern is responsible for handling technical details related to external data storage and doesn’t inherently belong to the business logic, there are situations where we need to access the Repository from within the domain layer. Since the domain layer is typically isolated from other layers and doesn’t directly communicate with them, we define the Repository within the domain layer, but we define it as an interface. This interface serves as an abstraction that allows us to interact with external data storage without tightly coupling the domain layer to specific technical details or implementations. A Simple Repository example type Customer struct { ID uuid.UUID // // some fields // } type Customers []Customer type CustomerRepository interface { GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error) DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) } The interface that defines method signatures within our domain layer is referred to as a “Contract.” In the example provided, we have a simple Contract interface that specifies CRUD (Create, Read, Update, Delete) methods. By defining the Repository as this interface, we can use it throughout the domain layer. The Repository interface always expects and returns our Entities, such as Customer and Customers (collections with specific methods attached to them, as defined in Go). It’s important to note that the Entity Customer doesn’t contain any information about the underlying storage type, such as Go tags for defining JSON structures, Gorm columns, or anything of that sort. This kind of low-level storage configuration is typically handled in the infrastructure layer.