- Clone the repository to your local machine.
- Create a branch (
/feature,/bugfix, or/hotfix) based on your needs. - Make the necessary changes.
- Push your changes to the Git repository.
- Create a pull request.
- Add appropriate labels to the pull request.
You can also use the provided docker-compose file for local testing. More information about local environment setup can be found here.
Note! Don't forget to validate your code via make validate (for GO only) or make validate_codebase (for whole CodeBase)
This document outlines the coding conventions and practices for the Go project, specifically tailored for GoLang development using Gin Gonic. Adhering to these standards ensures code consistency, readability, and maintainability across the project.
- General Coding Standards
- Layer-Specific Guidelines
- Best Practices for PRs
- Commit Messages
- Conclusion
Use camelCase for variable.
- Additional words can be added to disambiguate similar names, for example userCount and projectCount.
- Do not simply drop letters to save typing. For example
sandboxis preferred overSbx, particularly for exported names. - Omit types and type-like words from most variable names.
-
- For a number,
userCountis a better name thannumUsersorusersInt.
- For a number,
-
- For a slice,
usersis a better name thanuserSlice.
- For a slice,
- Omit words that are clear from the surrounding context. For example, in the implementation of a
UserCountmethod, a local variable calleduserCountis probably redundant;count,usersare just as readable. - Use
camelCasefor function names if they are not exported, andPascalCaseif they are exported. For example,getUserByIDis a private function, andGetUserByIDis a public function. All that is not exported should be added after the // private comment.
Use PascalCase for struct names.
Good Example:
type User struct {
ID int
Name string
}
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
var userCount int
func GetUserByID(userID int) {
// Implementation
}
// private
func getUserByID(userID int) {
// Implementation
}Bad Example:
type UserStruct struct {
UserID int
UserName string
}
userSlice := []UserStruct{
{UserID: 1, UserName: "Alice"},
{UserID: 2, UserName: "Bob"},
}
var user_count int
func getUserByID(userID int) {
// Implementation
}
// private
func GetUserByID(userID int) {
// Implementation
}- Interfaces should be prefixed with
I(e.g.,IService) to indicate it's an interface.
Good Example:
type IUserService interface {
GetUserByID(id int) (*User, error)
}Bad Example:
type UserService interface {
GetUserByID(id int) (*User, error)
}Use MixedCaps for constants.
- MixedCaps: For example, a constant is MaxLength (not MAX_LENGTH) if exported and maxLength (not max_length) if unexported.
Good Example:
const MaxLength = 10
const minPasswordLength = 8Bad Example:
const MAX_LENGTH = 10
const MIN_PASSWORD_LENGTH = 8Go package names should be short and contain only lowercase letters. A package name composed of multiple words should be left unbroken in all lowercase. For example, the package tabwriter is not named tabWriter, TabWriter, or tab_writer
Use descriptive names, and separate words with underscores (e.g., user.go, user_validations.go).
Organize code into meaningful packages and folders (e.g., controller/, service/, repository/, model/)
Name test files with _test.go suffix (e.g., user_test.go).
Good Example:
package user
package uservalidationsapp/
├── controller/
│ ├── user.go
| ├── user_test.go
│ └── ...
├── service/
│ ├── user.go
| ├── user_test.go
│ └── ...
├── repository/
│ ├── user.go
| ├── user_test.go
│ └── ...
|── model/
| ├── user.go
└── main.go
Bad Example:
package User
package user_validationsapp/
├── controllers/
│ ├── controller_user.go
| ├── controller_userTest.go
│ └── ...
├── services/
│ ├── userService.go
| ├── userService_test.go
│ └── ...
├── repositories/
│ ├── userRepository.go
| ├── userRepositorytest.go
│ └── ...
|── models/
| ├── user_model.go
└── main.go
Ensure there is a newline after } when there is a return or new var and before var or any other line following a closing brace. Also, include a newline after each case in a switch statement.
Good Example:
func example() {
if condition {
return
}
var x int
x = 10
if x > 5 {
fmt.Println("x is greater than 5")
}
switch x {
case 1:
fmt.Println("x is 1")
case 2:
fmt.Println("x is 2")
default:
fmt.Println("x is neither 1 nor 2")
}
return x
}Bad Example:
func example() {
if condition {
return
}
var x int
x = 10
if x > 5 {
fmt.Println("x is greater than 5")
}
switch x {
case 1:
fmt.Println("x is 1")
case 2:
fmt.Println("x is 2")
default:
fmt.Println("x is neither 1 nor 2")
}
return x
}Add comments to explain complex logic or non-obvious code.
Prefer returning errors explicitly instead of using panic.
Good Example:
func getUserByID(userID int) (*User, error) {
user, err := userRepository.FindByID(userID)
if err != nil {
return nil, fmt.Errorf("finding user by ID failed: %v", err)
}
return user, nil
}Bad Example:
func getUserByID(userID int) *User {
user, err := userRepository.FindByID(userID)
if err != nil {
panic(err)
}
return user
}Provide meaningful error messages when returning errors.
Good Example:
func getUserByID(userID int) (*User, error) {
user, err := userRepository.FindByID(userID)
if err != nil {
return nil, fmt.Errorf("finding user by ID failed: %v", err)
}
return user, nil
}Bad Example:
func getUserByID(userID int) (*User, error) {
user, err := userRepository.FindByID(userID)
if err != nil {
return nil, err
}
return user, nil
}Use the err shorthand for error variables.
Good Example:
if err := someFunction(); err != nil {
return err
}Bad Example:
err := someFunction()
if err != nil {
return err
}Avoid using one-letter variable names except in cases like loop indices (i, j, k).
Good Example:
func calculateArea(length, width int) int {
return length * width
}Bad Example:
func calculateArea(l, w int) int {
return l * w
}Enums should be defined within the model package with their respective constants.
Good Example:
package enum
type Coverage string
const (
CoverageMostSegments Coverage = "MOST_SEGMENTS"
CoverageAtLeastOneSegment Coverage = "AT_LEAST_ONE_SEGMENT"
CoverageAllSegments Coverage = "ALL_SEGMENTS"
)
var Coverages = []string{
string(CoverageMostSegments),
string(CoverageAtLeastOneSegment),
string(CoverageAllSegments),
}Bad Example:
package enum
const (
MostSegments = "MOST_SEGMENTS"
AtLeastOneSegment = "AT_LEAST_ONE_SEGMENT"
AllSegments = "ALL_SEGMENTS"
)
var Coverages = []string{
MostSegments,
AtLeastOneSegment,
AllSegments,
}The software architecture that we are using is "Layered Architecture" or "Layered Design Pattern." In this architecture, the different components of the system are organized into separate layers, each with its own specific responsibility.
- Define routes using Gin Gonic's Router.
- Use meaningful route paths and HTTP methods.
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// Example route with handler
r.GET("/users/:id", getUserHandler)
r.Run(":8080")
}
func getUserHandler(c *gin.Context) {
// Implementation to fetch user by ID
userID := c.Param("id")
// Call service layer to fetch user details
user, err := userService.GetUserByID(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, user)
}- Controllers should handle HTTP request/response lifecycle.
- Validate request, query, path parameters, and call service methods.
Example: UserController
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
// UserController handles user-related requests
type UserController struct {
userService IUserService
}
// NewUserController creates a new UserController
func NewUserController(us IUserService) *UserController {
return &UserController{userService: us}
}
// GetUserByID retrieves a user by ID
func (uc *UserController) GetUserByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id parameter"})
return
}
user, err := uc.userService.GetUser(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, user)
}- Business logic should reside here.
- Follow Single Responsibility Principle (SRP) for service methods.
Example: UserService
package services
import (
"errors"
)
// UserService provides user-related operations
type UserService struct {
// Service dependencies if any
}
// NewUserService creates a new instance of UserService
func NewUserService() *UserService {
return &UserService{}
}
// GetUser retrieves a user by ID
func (us *UserService) GetUser(id int) (*User, error) {
// Example logic to fetch user from repository layer
user, err := userRepository.FindByID(id)
if err != nil {
return nil, errors.New("failed to fetch user")
}
return user, nil
}- Data access layer for interacting with databases or external services.
- Implement data persistence logic.
Example: UserRepository
package repositories
// UserRepository provides data access operations for users
type UserRepository struct {
// Repository dependencies if any
}
// NewUserRepository creates a new instance of UserRepository
func NewUserRepository() *UserRepository {
return &UserRepository{}
}
// FindByID retrieves a user by ID from the database
func (ur *UserRepository) FindByID(id int) (*User, error) {
// Example implementation to fetch user from database or external service
// Placeholder code for demonstration purposes
user := &User{
ID: id,
Name: "John Doe",
Age: 30,
}
return user, nil
}- Consistency: Ensure adherence to coding standards and guidelines.
-
- Review your code before creating a PR to catch any errors or issues.
- Code Reviews: All PRs must be reviewed by at least one team member.
- Formatting: Check for proper indentation, brace placement, and comment usage.
Conventional commits are used for commit messages.
Commit messages should be clear and descriptive. They should follow the format type: subject where type is one of the following:
feat: a new featurefix: a bug fixdocs: changes to documentationstyle: formatting, missing semi colons, etc; no code changerefactor: refactoring production codetest: adding tests, refactoring test; no production code change
By following these coding conventions and best practices, we aim to maintain a high standard of code quality, readability, and maintainability across the Go project. Consistency in coding style and structure will help streamline development and collaboration among team members. For any questions or suggestions regarding these practices, please reach out to the team.