go-forex-rates - is an HTTP server provides REST API Gateway for multiple currency rates providers.
It aggregates various types of currency rates source (such as plain html page or third-party API) in a single REST API with common endpoints.
Now application supports two endpoints: historical
for historical currency rates and latest
for real-time currency rates.
You can write your own Provider, implemented RatesProvider interface for custom third-party API or another data source.
This server uses two level cache (in-memory for temporary rates and persistent for historical rates) to reduce number of API calls to third-party providers. You can tune cache settings in configuration file. Package includes docker image and systemd-unit for drop-in integration with your current infrastructure.
- Currency rates microservice
- âś… Fetch historical currency exchange rates
- âś… Fetch latest (real-time) currency exchange rates
- âś… Automatic preload historical exchange rates (integrated cron service)
- âś… Dependency injection supported
- âś… Multi-level cache for rates
- âś… Rates providers included
- Fixer
- Emirates
- âś… Your custom rates provider supporting
- âś… Swagger UI
- âś… Clear API Request and Response
- âś… Docker image & service health check
- Fixer
- Emirates
- Memory
- MySQL
There are two API endpoints.
The historical endpoint provides historical currency rates for given base currency and quoted currencies. These rates persist in L2 cache.
Request example:
curl -X GET "http://localhost:9090/api/v1/historical/fixer/2021-08-02?base=EUR&symbols=AED%2CUSD" -H "accept: application/json"
Response example:
{
"success":true,
"historical":true,
"date":"2021-08-02",
"timestamp":1627948799,
"base":"EUR",
"rates":{
"AED":4.361358,
"USD":1.187345
}
}
The Latest endpoint provides real-time currency rates from provider (with help of force=true request parameter) or
cacheable (in L1 cache) rates with ttl defined in l1_cache.default_expiration
parameter in config.yml
(or ENV variable).
The Latest rates has never cached in L2 cache.
curl -X GET "http://localhost:9090/api/v1/latest/fixer?base=AED&symbols=EUR%2CUSD" -H "accept: application/json"
Response example:
{
"success": true,
"historical": false,
"date": "2021-08-05",
"timestamp": 1628151663,
"base": "AED",
"rates": {
"EUR": 0.230008,
"USD": 0.272242
}
}
go-forex-rates supports historical currency rates automatic fetch with help of integrated cron subsystem. You can enable it for selected provider(if it supports it) this way. Edit config.yml, set these parameters:
historical_preload: true
rates_generated_time: 23:00:00
historical_start_date: "2018-11-01"
- historical_preload - enables automatic rates preload
- historical_start_date - preload rates from this date
- rates_generated_time - after this time today historical rates exists at provider side. Service can fetch them.
After first run go-forex-rates makes initial rates preload for such providers to fill L2 persistent cache.
Screenshots can be found in ./docs/screenshots
This microservice based on these parts:
- Dependency injection container: dig
- Web framework: gin
- ORM: gorm
- Scrapping framework: colly v2
- Cache: go-cache
- Package name: go-forex-rates
- Database name: go_forex_rates
- Table name: currency_rate
- Docker service name: goforexrates
- Systemd service name: goforexrates
Service uses multi-level cache. There are 3 levels of abstraction:
- L1: very fast temporary in-memory cache patrickmn/go-cache
- L2: fast persistent cache gorm-mysql
- L3: slow API-request to third-party currency rates provider.
- Chained cache pattern populate L1 & L2 cache when fetching data from L2; populate L1 cache when fetching data from L2.
- Persistent caching L2 enables only for immutable (historical) currency rates.
- L1 caching enable for all rates.
Sample configurations located in ./configs/config.yml.dist.
Almost all configuration properties can be overwritten by ENV variables. YAML-ENV mapping you can find in ./internal/model.ApplicationConfig.go
System requirements:
> go version
go version go1.16.7
Install package and dependencies:
go get -u github.com/netandreus/go-forex-rates
cp ./configs/config.yml.dist ./configs/config.yml
Feel free to edit config.yml with your settings. Microservice needs MySQL database server for store L2 cache immutable (historical) values.
Build program by:
go get -d ./...
go get -u github.com/swaggo/swag/cmd/swag
go build -o main
Run program by:
go run .
For you patient we added historical currency rates for "emirates" provider in to this distributive. If you are on docker host, and your MySQL container exported 3306 port to host you can do something like this.
gunzip < ./assets/go_forex_rates.sql.gz | mysql -h 127.0.0.1 -P 3306 --ssl-mode=disabled -u go_forex_rates -p go_forex_rates
You can export saved historical currency rates from database back to file.
mysqldump -h 127.0.0.1 -P 3306 --ssl-mode=disabled --column-statistics=0 -u go_forex_rates -p go_forex_rates | gzip -v9 > ./assets/go_forex_rates.sql.gz
To changing http port:
- please edit it in
./configs/config.yml
inengine.port
section. - change port in docker-compose.yml in ports section and in services.goforexrates.healthcheck.test section
If you want to swagger playground works correct you should do either:
- Change port in
main.go
docblock comment, rebuild manual(./api/*)sh $HOME/go/bin/swag init --output=./api
, rebuild and restart container after it.
// @host localhost:9090
OR
- Manually change port in
./api/swagger.json
and./api.swagger.yaml
You can write your own provider implementing RatesProvider interface (./internal/pkg/provider/RatesProvider.go).
In Provider's constructor you can use any service, registered in DIC. Just add it as an argument of constructor function.
package custom_provider
const Code = "custom_provider_code"
func New(db *gorm.DB, config *model.ApplicationConfig) *Provider {
provider := &Provider{
code: Code,
db: db,
config: config.Providers[Code],
}
return provider
}
Next you can add some config parameters for your provider in ./configs/config.yml in providers section with the key you chosen in Code constant.
providers:
custom_provider_code:
location: Europe/Moscow
rates_generated_time: 23:00:00
supported_currencies: ["AED", "ARS"]
historical_preload: true
historical_start_date: "2018-11-01"
Registration processed in init
function like this:
// init initialize server
func init() {
...
// Add rates providers
srv.ContainerInvoke(func(registry *provider.Registry, db *gorm.DB, config *model.ApplicationConfig) {
...
registry.AddProvider(custom_provider_code.New(db, config))
...
})
...
}
Change currency_rate.provider enum with new custom provider code.
Generate API docs in folder "api"
$HOME/go/bin/swag init --output=./api
You can test API here: http://localhost:9090/swagger/index.html
You can run Go Documentation Server
with actual version of documentation with this command:
$HOME/go/bin/godoc -http=:8080
Documentation will be available by this url:
http://localhost:8080/pkg/github.com/netandreus/go-forex-rates/pkg/server/
Service try to search MySQL server in attached networks. If you don't have common_network
docker network - you should create it
docker network create common_network
or comment this network in docer-compose.yml
file.
If you does not have mysql user on database host - you should create it. Connect to MySQL as root user and run:
CREATE DATABASE go_forex_rates CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE USER 'go_forex_rates'@'%' IDENTIFIED BY 'xxxxxxx';
GRANT ALL PRIVILEGES ON go_forex_rates. * TO 'go_forex_rates'@'%';
FLUSH PRIVILEGES;
docker run --network=host -e "L2_HOSTNAME=host.docker.internal" go-forex-rates_goforexrates
Command for build container:
export HOSTNAME; sudo docker-compose build
Command for run container:
export HOSTNAME; docker-compose up
Docker-host's HOSTNAME will be used as Swagger hostname later.
You can find systemd service file in ./init/goforexrates.service
Default install path and working dir is: /home/admin/goforexrates/current
You can change it for your needs.
Please feel free to contribute on this library and do not hesitate to open an issue if you want to discuss a feature.