Services Hub API is a API build using gin/gorm. The API can be run as a command. This can be used as a boilerplate template showcasing API capabilities in golang
-
A SQL DB is used to create relations between user/service/versions. This can be further extended to orgs where a user can belong to an org. For the sake of simplicity a user can create a service and a given service has various versions associated to it. The design only permits a user to query service created by that user only. AuthN/AuthZ can be added as middleware if needed.
There are 2 API's created
- /api/v1/users/${user_id}/services
This returns all services for a given user - /api/v1/users/${user_id}/services/${service_id}
This returns all service versions for a given service, provided that service belongs to that user
- /api/v1/users/${user_id}/services
-
Pagination is applied to the first API so client need not query all services at once. The API accepts a list of query params to paginate the request. List of query params:
- limit: integer. Limits the results returned
- page: integer. Page of the results
- search: string. search string to search title or description OffSet is calculated internally.
|____cmd
| |____other-cmd
| |____api
| | |____server.go
| | |____migrate.go
| | |____main.go
| | |____root.go
|____go.mod
|____bin
|____config
| |____config.go
| |____config.dev.yaml
|____Makefile
|____internal
| |____dto
| | |____response.go
| | |____request.go
| |____middleware
| | |____middleware.go
| | |____pagination.go
| |____logger
| | |____logger.go
| | |____level.go
| |____utils
| | |____pagination.go
| |____models
| | |____users.go
| | |____models.go
| | |____services.go
| | |____migrations.go
| | |____service_versions.go
| |____api
| | |____v1
| | | |____app_service.go
| | | |____handlers.go
| |____errors
| | |____middleware_gin.go
| | |____errors.go
| |____services
| | |____user.go
| | |____app_service.go
| | |____app_service_version.go
|____go.sum
|____README.md
|____.gitignore
|____.git
Configuration options are available under config/config.dev.yaml. Configuration Options can also be set as environment variables.
-
Commands Available
- root command: This will provide help with all the available commands
- server: server will run the API on port 8080 by default
- migrate: migrate will migrate the schema needed and populate database with fake data for users/services/versions
-
See all available commands
$ make root
OR
$ go run ./cmd/api
-
Run migrate command to create schema and populate data
$ make migrate
OR
$ go run ./cmd/api migrate
-
Start by running the API. The configuration needed to connect to mysql is under config/config.dev.yaml
$ make run
OR
$ go run ./cmd/api server
Use Makefile to build the project. The binary generated would be added to /bin folder
$ VERSION=1.0.0 ENV=dev make build
I chose to use gorm library(wanted to try it out). The solution seems clean using gorm(preloads dependents in struct), however makes multiple round trips to DB. If roundtrips to DB is an issue. Performance can be obtained by either of the below:
-
use standard golang SQL library. LEFT JOIN users with services and service_versions table
- This provides single round trip to DB. However, will produce more rows because of multiple versions associated with each service.
-
Use SQL functions to generate JSON which can be scanned into a struct. Example query is below. SQL will return aggregated JSON as a single field.
- This would be the most efficient solution(but not so ellegant). This could be difficult to maintain, since any change in structs in golang or SQL may break the code. Also, if you would like to switch to another relational DB, the query may need to be tested.
SELECT JSON_ARRAYAGG(JSON_OBJECT('id', u.id, 'email', u.email, 'services', s.services)) FROM users u LEFT JOIN ( SELECT user_id, id, JSON_ARRAYAGG(JSON_OBJECT('id', id, 'title', title, 'description', description, 'service_versions', sv.json_versions)) services FROM services s LEFT JOIN ( SELECT service_id, JSON_ARRAYAGG(JSON_OBJECT('id', id, 'version', version)) json_versions FROM service_versions GROUP BY service_id ) sv ON sv.service_id = s.id GROUP BY user_id,id ) s ON s.user_id = u.id WHERE u.id = '0be2de59-56e0-47a7-aabc-a82ae064c85f'
-
Index is created on title field for fast lookups using FULLTEXT search
-
Other design considerations for improvements
- Cache data in memory(redis/memcache) for a given user's services for faster lookups and apply pagination. Clear cache on POST/UPDATE for a given record