Our Journey Moving from PHP to Go

Mert Simsek
Beyn Technology
Published in
8 min readJun 1, 2021

--

In this article, I’d like to cover the struggles that we have faced moving PHP to Go as a team. We started writing digestible small applications in Go. Normally, we have a big application to handle many processes. We are trying to change them into small services. In the beginning, it wasn’t easy for us. We have been using Go for a few months however I think this post would be useful for the community and newcomers. We got to know a ton of new things better performance, easier deployments, and higher test coverage. Let’s set the ball rolling.

https://i.morioh.com/b6102f3966.png

Interpreting vs Compiling

Go is converted directly into machine code that the processor can run. As a result, they tend to be faster and more efficient to execute than interpreted languages. Go needs a “build” step. We need to “rebuild” the program every time if we need to make a change. The specified files must be Go source files and all parts of the same package. The same compiler is used for all target operating systems and architectures. The GOOS and GOARCH environment variables set the desired target.

PHP runs through a program line by line and executes each command. Whereas Go is supposed to be built. So, it was really hard to get used to at first, and it caused difficulties during the development process. I admit that it depends on the developer but it was difficult for us. One major advantage to having interpreted code is that all memory used by the script is managed by PHP, and the language automatically cleans up after every script has finished. This means that you do not need to worry about closing database links, freeing memory assigned to images, and so on because PHP will do it for you. Finally, we were supposed to change this way of thinking while moving to Go.

Dynamic and Static Typing

PHP is a dynamically typed language, you don’t have to initialize variables, which is a big bonus for us. We could use a variable at will when required without having to initialize it. PHP changed this approach with 7 versions. Despite this, it works in the same way. Let’s inspect this PHP code.

<?php 
$words = "this is a string";
$words= $foo + 2; // it isn't a runtime error, it might be warning
echo $words;
?>

Go is a statically and strongly typed language so all variables have a type assigned to them. Variables in functions are assigned using the := operator and this operator will automatically set the variable type for you. For example, let’s look at the example.

company := "Beyn" // string/* To create a variable without setting any data in it or to create one outside of a function you have to use the var */
var city string
city = "İstanbul"

Go doesn’t need to be defined before the variables are used. This implies that static typing has to do with the explicit declaration (or initialization) of variables before they’re operated.

The Project Structure

We were a bit confused about the project structure and we needed to answer questions such as how should we split the files? Or, how should we isolate the code? Obviously, there are no precise standards for structuring the folders and the project. In PHP, we could use some frameworks and they strictly provide folder structuring. For example, Symfony, Laravel, or Phalcon ensure certain directories and files. Most developers got used to using them. There is no doubt or question to adjust directories or files.

In Go, there are a lot of recommendations and project samples. In the beginning, it was difficult to decide for us. Ultimately we inspected some API examples on Git repositories. We noticed the vast majority were following the same way over time. Thus, we have created a structure that was proper for us. We consumed the following resource a lot.

beyn-api
- cmd/
- api/
- pkg/
- api/
- db/
- services/
- user
- product
- ...
- go.mod
- go.sum
- .env.<environment>
- README.md

We have 3 fundamental directories in this structure: api, pkg, and services. Separating the services by resources eases telling that what they work for. and code navigation when making future changes. We don’t need to overcomplicate services. With this, we aim to grow your project from a couple of files to a serious web service.

  • api/
    We determine how to connect the APIs by DB services, HTTP routing, and configuration services that we use in the services files. Generally, we have got a single Load(c *Config) a method that provides called from cmd/api/main.go.
  • db/
    It tells everything regarding itself. That makes sure the connection to the database successful. We don’t use migration at the moment. Therefore we don’t have a directory called “migrations”.
  • services/
    We use the services directory for handling resources. Resources are obviously meaning that the tables in the database. For example, if we need to fetch data from related tables such as user or product. We are requesting these files. In further, we will create a new directory called “repository” and we will call the methods from “services” to “repository” for these kinds of methods.

Dependencies

PHP doesn’t have any built-in package management tool, and all the dependent and required packages need to be manually copied to the correct location to work properly. Whereas it offers a magnificent dependency manager tool called composer. It’s really useful and straightforward. It works by providing a file for you to list the dependencies that you are using. For instance, you can list the dependencies for your project in a composer. json file, which will enable you to access those files whenever you need them.

Go has a built-in tool to fetch and install third-party libraries, and it follows very strict approaches while using external package libraries. Go has 2 files to manage dependencies called go.mod and go.sum. We adopted them easily because they truly ease the dependency management processes. A module is a collection of Go packages stored in a file tree with a go.mod file at its root. In addition to go.mod, the go command maintains a file name go.sum containing the expected cryptographic hashes of the content of specific module versions.

Designing the Code

We have been written the OOP codes for a few years, so moving to Go wasn’t smooth. The most crucial was to chunk out all about classes and objects. In Go, we were supposed to use structs and interfaces. It was annoying, whereas, after a few weeks, we started to get used to this situation. Frankly, this way is better to get rid of constructors, getters, and setters.

Secondly, another crucial stuff is about asynchronous processing. In traditional PHP, all are processed synchronously. The disadvantage is that there is no concurrency and that your application or request consumes time awaiting some issues regarding I/O. On the other hand, this case helps your application more clear/readable, and smooth. However, we need to get concurrency for our custom scenarios, and designing the code according to it was compelling for us. We read the blog posts and watched a lot of videos to handle this situation.

Apart from these structs, interfaces, and so on, we refer to the 12-Factor App principles such as “Codebase”, “Dependencies”, “Config” and “Backing Services”. I think this is awesome because we are not entirely familiar with the Go language and we did refer to these principles easily by tackling previous PHP experience. I’d like to give some samples from our project directly. Imagine “Config” principle. We used “github.com/joho/godotenv” package to handle environment variables by creating “.env” file in the root of the project like the following

MYSQL_HOST=127.0.0.1
MYSQL_USER=mert
MYSQL_PASS=1029384
MYSQL_DB=xpress_tes_db

SERVER_HOST=127.0.0.1
SERVER_PORT=8080

To import these variables, we created a “config.go” file.

package config

import (
"github.com/joho/godotenv"
"os"
)

type App struct {
Db
Server
}

type Db struct {
User string
Pass string
Host string
Name string
}

type Server struct {
Host string
Port string
}

func (c App) Load() (conf App, error error) {
err := godotenv.Load()
if err != nil {
return c, err
}

c.Db.User = os.Getenv("MYSQL_USER")
c.Db.Pass = os.Getenv("MYSQL_PASS")
c.Db.Host = os.Getenv("MYSQL_HOST")
c.Db.Name = os.Getenv("MYSQL_DB")
c.Server.Host = os.Getenv("SERVER_HOST")
c.Server.Port = os.Getenv("SERVER_PORT")
return c, nil
}

From now on, we just need to call this method in the “main.go” file. This is cool because I’m loading these parameters once and I’m using them where I need to use them. The following code shows using them to create a database connection.

conf, err := config.App{}.Load()
if err != nil {
return err
}

db, err := database.InitDatabase(conf.Db)

Error Handling

Go doesn’t provide a simple try and catch way to proceed with the errors like PHP, instead, errors are returned as a normal return value. For example, if we need to handle errors by connecting to the database, we would follow this example of pure code.

<?php
$servername = "localhost";
$username = "username";
$password = "password";

try {
$conn = new PDO("mysql:host=$servername;dbname=myDB", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully";
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}

Instead, Go functions and methods return two results such as a result of the method and an error type. If the error is equal to nil type so the function execution is considered to be successful. If the error is not nil, so something went wrong and we can ignore the result. Let’s look at Go example. The following code uses os.Open to open a file. If an error occurs it calls log.Fatal to print the error message and stop.

f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f

Instead, we can create error messages manually as the following. We use “errors.New()” built-in function in Go.

func (u *User) Find(id string) (models.Users, error) {
users := models.User{}
if result := u.DB.Table("Users").Scan(&users); result.Error != nil {
return users, errors.New("the users aren't found!")
}
return users, nil
}func fetchUsers(){
users, err := Find("1")
if err != nil {
log.Fatal(err)
}
log.Println(users)
}

It’s important to note Go’s error syntax doesn’t force you to handle every error your program may throw. Go simply provides a pattern to ensure you think of errors as critical to your program flow, but not much else. I don’t argue PHP error handling is better or not however we struggled in this case because, imagine that, you’re writing if condition for each method result. We just needed to get used to this approach. Usually, returning an error means there’s a problem, and returning nil means there were no errors.

“if …; err != nil” is something you’ll probably type if you write go. I don’t think it’s a plus or a negative. It gets the job done, it’s easy to understand, and it empowers the programmer to do the right thing when the program fails. The rest is up to you.

— Hacker News

To Sum Up

Actually, there are some cases that we have struggled a lot. For example, deployment, receiver methods, pointers/references, and so on. Perhaps they might be another blog post. I try to tell the struggles of our journey from PHP to Go for some parts of our applications and in this way we have obviously gained different views and perspectives. At least, this would be a benefit to write some code of your applications in a different language. We really love to face new challenges and keep them during development processes. I hope this blog post would be helpful for newcomers to the Go language. Thanks for reading and stay tuned!

--

--

Mert Simsek
Beyn Technology

I’m a software developer who wants to learn more. First of all, I’m interested in building, testing, and deploying automatically and autonomously.