This is an example for Go project.
The motivation behind this project is to learn and broaden my limited knowledge about programming, project design, concepts, algorithm, and especially, Go itself. In this project, I will try to implement business logic/flow into Go program for various use-cases.
What is industrial programming? Peter Bourgon explain the terms, as:
- In a startup or corporate environment.
- Within a team where engineers come and go.
- On code that outlives any single engineer.
- Serving highly mutable business requirements.
The project theme is Property
. I will try to build a Property application, where people able to search and book the property.
- Users were able to register and log in.
- Users were able to register their Properties.
- Register the property detail
- Upload the property image
- Users were able to book a Property.
- Users were able to receive notifications and have a notification inbox.
This project is using:
- PostgreSQL for the main database
- Redis for k/v, user session management, caching.
This is guide to get started with this project and installing dependencies required for running this project locally
- Make
- Docker
- Soda CLI from gobuffalo. You can install this by using
make install-deps
To create and migrate the database, we will use soda CLI
created by gobuffalo
. The command is wrapper by this script.
Use this command to fully create and migrate the databse schema:
make dbup
The following flags is avaiable to help the project configuration and debug parameters.
--config_file
define where the configuration file is located--env_file
define where environment variable file is located, this is a helper file to set environment variable.--debug
define the debug configuration for the project. With flag key:-debug=server=1
to turn on the debug server.-debug=testconfig=1
to test the configuration of the project.- example:
--debug=-debugserver=1-testconfig=1
For applicatoin configuration, config-file is used and located on the root project directory. The configuration-file is written in toml
grammar.
The configuration value in the configuration-file
is embed as environment-variable value. The value will be replaced by the environment-variable value in runtime or when program started. To help the process, the project use the help of environment-variable file. The environment-variable
files is choosen because it is simpler as we can have multiple files and we can hide/ignore the file for specific use-case, for example, secret value of client_id
and client_secret
of some cloud vendor.
The mixed of configuration-variable
and environment-variable
is used to help people in project to see what configuration structure is exists within the project, and able to dynamically changed depends on the environment variables value.
Configuration Structure:
-
Servers
[object]
:- Main
[object]
:- Address: address of the main server, for example
localhost:8000
- Address: address of the main server, for example
- Admin
[object]
:- Address: adress of the admin server, for example
localhost:5726
- Address: adress of the admin server, for example
- Debug
[object]
:- Address
[string]
: address of debug server, for examplelocalhost:9000
- Address
- Main
-
Log
- level: level of the log,
debug|info|warn|error|fatal
- file: file location to store the log
- color: print with color on the terminal
- level: level of the log,
-
Resources
- Object Storage
[array]
- [Object Storage Object]
- name
[string]
: name of the object storage, for exampleimage
- name
- [Object Storage Object]
- Database
[object]
- Connect
[array]
- [Connect Object]
- name: name of the database, for example
user
- driver: the driver of database,
mysql|postgres
- leader
object
- dsn: the dsn of database leader, for example
- replica
object
- dsn: the dsn of database replica, for example
- name: name of the database, for example
- [Connect Object]
- Connect
- Redis
[object]
:- Connect
[array]
:- [Connect Object]
- Name
[string]
: name of redis, for examplesession
- Address
[string]
: address of redis server, for examplelocalhost:6379
- Name
- [Connect Object]
- Connect
- Object Storage
Resources configuration allows the project to easily add and remove resources. Because, as the project grow, we might need to add more connection to more postgres, redis or other type of database. Instead of handling the connection manually inside the code, a wrappeer is added to hold all the connection to resources
The project have no environment state. Different flags and configuration value is used in different environment.
Environment state like dev
, staging
, and production
is usually used to check in what environment the program/application is running. From experience, this considered harmful for the program itself, as developer tempted to abuse the state for many things. Developer tempted to abuse the state because the function is available, and sometimes it is the easiest way to accomplish some goals. By using the state, people in the project are cutting edges and create conditional expression for various use-cases. This leads to broken mental model, bugs, and edge-cases to the product which make life harder for the maintainers.
For example, in code:
if env.IsDevelopment() {
// do something only in dev
}
if env.IsStaging() {
// do something only in staging
}
if env.IsProduction() {
// do something only in production
}
This environment state, sometimes also used for configuration directive. When the configuration directive is gated by the environment state, another problem occur. Because, configuration for each environment might have different variables and value, and different configuration file can mean different things.
For example, in configuration with spec project.{environment_name}.config.toml
:
- project.dev.config.toml
- project.staging.config.toml
- project.production.config.toml
Or, imagine if you have many different configurations(with various reasons/decision) using this kind of directive:
- project.dev.config1.toml
- project.staging.config1.toml
- project.production.config1.toml
- project.dev.config2.toml
- project.staging.config2.toml
- project.production.config2.toml
Multiple configurations with environment state directive, usually used to address different configurations in each environments. For example, when a database is pointing to one instance in dev
but not in staging
, which completely different. Or, when doing some migration we want to get rid of some configuration variables in some environment. This all are valid use-cases, and the given solution by using the environment state for configuration directive works. Usually, until the configuration is become too long and different for each environments, then turning into problems for the maintainers.
As sometimes we need to run with some special configuration in non-production or in production environment, this might be able to achieved by using the combination of flags and configuration-file. Variables from flags and configuration is more clear and straightforward than IsEnvrionment
, and can be used to checked the design choices, do we have too many hacks? Why? For whatever reason, the flags/configuration variables between environments should stay the same, to maintain consistency.
But, in the end, it depends/back to each project policies and governance.
All Go main programs is located in go-project-example/cmd/*
folder.
To be added
As stated at the top of this document, the design contains several layers and components and very much similar to onion ring or clean architecture attempt.
But, let's talk about the components first.
- Server
- Usecase
- Repository
- Entity
Is where all the http
handler exists. This layer is responsible to hold all the http
handlers and request validation.
Main Server
Main server for serving main application. All business use-case handler exists within this server.
Admin Server
Admin server is a server for administrational purpose. On by default and should not open to public.
Use-case for admin server:
/metrics
endpoint/resource/status
endpoint- pprof endpoint
- check current configuration value
Debug Server
Debug server is a server for experimental purpose, and should be enabled with a spesific flag. This server should not be triggered in production environment.
Use-case for debug server:
- Login bypass
- Serve fileserver for local object storage
To be added
To be added
To be added
To be added
When a company become larger and the number of people and teams increase rapidly, it is make sense to adopt microservice architecture for the company... to be added
NOTE
Microservice itself is not a silver bullet to begin with, it solves organizational scale problem, teams become more independent and responsible of their own products, etc. But, it also introduces a lot of problem, for example network, state, and other distributed system problems. Before taking this path, its best to understand what problems that we want to solve with microservice gain more knowledge around microservice, and how we want to tackle the problems inside the organization.
In Go, errors
are value. This is one of the of the Go's proverbs. So, instead of throwing error, Go program will treat the error
as value and the error
should be checked or returned to the caller.
Don't just check errors, handle them gracefully - Go's proverbs
Check the error or return, but not both. - Dave Channey
While handling error
in Go is very easy and straightforward, the error
itself is sometimes lacking of context.
To be continued.
To set default timezone in go programs, we can set an environment variable named TZ
when running or in our programs before time
package get called. The value of TZ
is the location of Timezone. In my case, it is Asia/Jakarta
.
For example, in code:
os.SetEnv("TZ", "Asia/Jakarta")
or, during execution:
TZ=Asia/Jakarta ./yourbin
What about more than one timezone in a single application?
You can always use time
package function called time.LoadLocation
to create spesific timezone/location time object.
For example:
loc, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
// handle the error
}
// this function produce time.Now specified to loc or Asia/Jakarta
nowInLoc := time.Now().In(loc)
Articles
- https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
- https://blog.golang.org/go1.13-errors
Videos
Code
- Upspin errors package