- Using
go get
go get github.com/dare-rider/carpark cd $GOPATH/src/github.com/dare-rider/carpark ** update db `dsn` in `config/config.yml` as per your local go build (All dependencies are within repo, under `vendor` dir) ./carpark (Server started at port 3005, check `localhost:3005/ping` to verify)
- Using
git clone
, as we are usinggo mod
we can clone this package anywhere, not necessarily under $GOPATH/srcgit clone https://github.com/dare-rider/carpark.git cd carpark ** update db `dsn` in `config/config.yml` as per your local go build (All dependencies are within repo, under `vendor` dir) ./carpark (Server started at port 3005, check `localhost:3005/ping` to verify)
Make sure to git clone
the repo as mentioned above
cd carpark ** Default `dsn` in `config/config.yml` is as per docker ** If changed? Revert back to `dockeruser:dockeruser@tcp(mysqldb:3306)/cp_dev?parseTime=true` docker-compose up -d --build mysqldb docker-compose up --build app (Server started at port 3005, check `localhost:3005/ping` to verify)
The application architecture is mostly inclined towards The Clean Architecture
,
and used Dependency injection
and MVC - Model (Views)Presentors Controllers
design patterns.
Following is the walkthrough of the application structure.
- All the core business logic resides under
app
package, likecontrollers
,models
,tasks
,presentors
etc appmiddleware
,cofig
,db
,router
packages are used to initialize application.constant
,types
,utils
packages are used to support business logic of application.- All the 3rd Party used plugins are already downloaded under
vendor
directory. go mod
is used for package dependency management.
repo
packages that are used as adapter to 3rd Party, whether it's database or any external http call.usecase
are the wraper aroundrepo
which contains the business logic if any over the raw response fromrepo
and internally exposed to rest of applicationutils/scy21
is the package(copied from some open source repo) that exports the functions to convert GeoLocation coordinates to lat, longutils/geodist
is the package(copied from some open source repo) used to calculate the distance between 2 geo-locations.golang-migrate/migrate
package is used to execute & keep state of the database schema migrations.validator.v9
package is used for request input validations.
main.go
is the entry point of application startup execution. What it does?
generalConfig := config.LoadConfig(configFilePath) constant.InitConstants(generalConfig.MiscConfig) db.InitMysqlDb(generalConfig.DbConfig) db.InitMigrations(generalConfig.DbConfig) routes := router.InitRoutes(generalConfig, db.MysqlConn()) // Starting server. http.ListenAndServe(":3005", routes)
config.LoadConfig
is the function used to load the application config mentioned inconfig/conf.yml
into a global config structgeneralConfig
that can be injected as dependency wherever required.- sample config.yml
db: dsn: root:@tcp(127.0.0.1:3306)/carpark_dev?parseTime=true migration_path: "file://db/migrations/schema" # Relative seed_path: "db/migrations/seed" # Relative gov_sg_service: base_url: "https://api.data.gov.sg/" misc: environment: "development"
constant.InitConstants
initializes the global constants, load environment specific variables and provide corresponding global getters.db.InitMysqlDb
initializes the global db connection, that is then injected to all repos while routes initialization.db.InitMigrations
, takes theDbConfig
which has the migrations path, andgolang-migrate/migrate
executes all the pending migrations insidedb/migrations/schema
(current migrations path).migrate
CLI is used to generate those migrationsup.sql
&down.sql
.schema_migrations
table is used to keep track of last executed migration.router.InitRoutes
initializes thecontroller
routes by injecting dependencies amongst their respective controller structs. Will discuss about it in following section.
- using
chi
router - initialises all the required middlewares default + custom.
- prepare instances of all application dependencies and injecting then as required
app/models
&app/extservices
repo
app/models
&app/extservices
usecase
, by injecting dependencies prepared in Step 1.app/tasks
, by injecting dependencies prepared in Step 2.app/controllers
by injecting dependencies prepared in Step 2 and Step 3.
- Mounting controller routes - each controller has it's separate
Router
function which contains controller specific routes, which is then mounted here under some default pattern, currently used as/
routes ---> controller ---> usecase ---> repo ---> DB | ^ | | | ------> GovSg ----------> tasks
-
GET
/tasks/carpark_upload
, used to upload carparks fromdb/migrations/seed/carpark.csv
, Insert handled oncreate_if_not_exists
elseupdate
metadata.- Query/Path Parameters - None
- Response Success - 200 OK
{ "status": true, "message": "Request Implemented Successfully" }
- Response Failure - 422 Unprocessable Entity
{ "status": false, "message": "open db/migrations/seeds/carpark.csv: no such file or directory" }
-
GET
/tasks/carparkinfo_upload
used to pull recent carpark_infos fromhttps://data.gov.sg/dataset/carpark-availability
and persist locally, Insert handled oncreate_if_not_exists
elseupdate
metadata.- Query/Path Parameters - None
- Response Success - 200 OK
{ "status": true, "message": "Request Implemented Successfully" }
- Response Failure - 422 Unprocessable Entity
{ "status": false, "message": "Get https://api.data.gov.sgs/v1/transport/carpark-availability: dial tcp: lookup api.data.gov.sgs: no such host" }
-
GET
/carparks/nearest
to fetch the carparks sorted based on distance from input location.- Query Parameters
Latitude float64 `schema:"latitude" validate:"required"` Longitude float64 `schema:"longitude" validate:"required"` Page int `schema:"page" validate:"omitempty,min=1"` PerPage int `schema:"per_page" validate:"omitempty,min=1"`
- Response Success - 200 OK
[ { "address": "BLK 301-302,305-308 CLEMENTI AVENUE 4", "latitude": 1.201848090322625, "longitude": 103.88527398855013, "total_slots": 316, "available_slots": 134 }, { "address": "BLK 124/129 BEDOK NORTH STREET 2", "latitude": 1.375709138082525, "longitude": 103.89194725680792, "total_slots": 414, "available_slots": 238 }, { "address": "BLK 909A HOUGANG STREET 91", "latitude": 1.3200501516802696, "longitude": 103.9414272888625, "total_slots": 1052, "available_slots": 751 } ]
- Response Failure - 400 Bad Request
{ "status": false, "message": "Key: 'NearestCarparksRequest.Latitude' Error:Field validation for 'Latitude' failed on the 'required' tag" }
- Default pagination - page: 1, per_page: 50
- used
stretchr/testify
for assertion & generating mocks usingmockery
CLI tool - packages covered
extservices
,models
,tasks
- test case can be found under
test
directory in respective package - Run test case, from application root directory
go test -v ./...
- mysql indexes, wherever required
- golang sync waitgroups to provide parallelism
- avoided (n+1) query
- prepared statements