├── .gitignore ├── Makefile ├── README.md ├── bin ├── build.sh └── clean.sh ├── docker-compose.yml ├── pb └── rate │ ├── rate.pb.go │ └── rate.proto └── services ├── api ├── Dockerfile ├── currencies.go ├── main.go └── main_test.go ├── rate ├── Dockerfile ├── main.go └── rates.json └── www ├── Dockerfile ├── main.go └── static └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | services/api/api 2 | services/www/www 3 | services/rate/rate 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: pb build test deps 2 | 3 | pb: 4 | for f in pb/**/*.proto; do \ 5 | protoc --go_out=plugins=grpc:. $$f; \ 6 | echo compiled: $$f; \ 7 | done 8 | 9 | deps: 10 | go get ./... 11 | 12 | test: 13 | go test ./... 14 | 15 | build: deps test 16 | @./bin/build.sh 17 | 18 | clean: 19 | @./bin/clean.sh 20 | 21 | up: 22 | docker-compose build 23 | docker-compose up -d 24 | 25 | down: 26 | docker-compose down 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Microservices Example using gRPC 2 | 3 | ### Pre-requisites 4 | 5 | Docker https://docs.docker.com/engine/installation 6 | 7 | Protobuf v3 8 | 9 | $ brew install protobuf 10 | 11 | Protoc-gen libraries: 12 | 13 | $ go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 14 | 15 | Clone the repository: 16 | 17 | $ git clone git@github.com:dilipgurung/golang-microservices.git 18 | 19 | ### Protobufs 20 | 21 | To regenerate Protocol Buffer files, run: 22 | 23 | $ make pb 24 | 25 | ### Build the application 26 | 27 | $ make build 28 | 29 | ### Run the application 30 | 31 | $ make up 32 | 33 | Visit the web page in the browser: 34 | 35 | [http://localhost:8000](http://localhost:8000/) 36 | 37 | ### Stop the application 38 | 39 | $ make down 40 | -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | p=`pwd` 4 | for d in $(ls ./services); do 5 | echo "building services/$d" 6 | cd $p/services/$d 7 | CGO_ENABLED=0 GOOS=linux go build -ldflags "-s" -a -installsuffix cgo 8 | done 9 | cd $p 10 | -------------------------------------------------------------------------------- /bin/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | p=`pwd` 4 | for file in services/api/api services/www/www services/rate/rate; do 5 | echo "Removing $file" 6 | rm $p/$file 7 | done 8 | cd $p 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | www: 6 | build: ./services/www 7 | ports: 8 | - "8000:8000" 9 | api: 10 | build: ./services/api 11 | ports: 12 | - "9000:9000" 13 | links: 14 | - rate 15 | rate: 16 | build: ./services/rate 17 | 18 | -------------------------------------------------------------------------------- /pb/rate/rate.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: pb/rate/rate.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package rate is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | pb/rate/rate.proto 10 | 11 | It has these top-level messages: 12 | Request 13 | Result 14 | */ 15 | package rate 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import ( 22 | context "golang.org/x/net/context" 23 | grpc "google.golang.org/grpc" 24 | ) 25 | 26 | // Reference imports to suppress errors if they are not otherwise used. 27 | var _ = proto.Marshal 28 | var _ = fmt.Errorf 29 | var _ = math.Inf 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the proto package it is being compiled against. 33 | // A compilation error at this line likely means your copy of the 34 | // proto package needs to be updated. 35 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 36 | 37 | type Request struct { 38 | SourceCurrency string `protobuf:"bytes,1,opt,name=sourceCurrency" json:"sourceCurrency,omitempty"` 39 | TargetCurrency string `protobuf:"bytes,2,opt,name=targetCurrency" json:"targetCurrency,omitempty"` 40 | } 41 | 42 | func (m *Request) Reset() { *m = Request{} } 43 | func (m *Request) String() string { return proto.CompactTextString(m) } 44 | func (*Request) ProtoMessage() {} 45 | func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 46 | 47 | func (m *Request) GetSourceCurrency() string { 48 | if m != nil { 49 | return m.SourceCurrency 50 | } 51 | return "" 52 | } 53 | 54 | func (m *Request) GetTargetCurrency() string { 55 | if m != nil { 56 | return m.TargetCurrency 57 | } 58 | return "" 59 | } 60 | 61 | type Result struct { 62 | Rate float64 `protobuf:"fixed64,1,opt,name=rate" json:"rate,omitempty"` 63 | } 64 | 65 | func (m *Result) Reset() { *m = Result{} } 66 | func (m *Result) String() string { return proto.CompactTextString(m) } 67 | func (*Result) ProtoMessage() {} 68 | func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 69 | 70 | func (m *Result) GetRate() float64 { 71 | if m != nil { 72 | return m.Rate 73 | } 74 | return 0 75 | } 76 | 77 | func init() { 78 | proto.RegisterType((*Request)(nil), "rate.Request") 79 | proto.RegisterType((*Result)(nil), "rate.Result") 80 | } 81 | 82 | // Reference imports to suppress errors if they are not otherwise used. 83 | var _ context.Context 84 | var _ grpc.ClientConn 85 | 86 | // This is a compile-time assertion to ensure that this generated file 87 | // is compatible with the grpc package it is being compiled against. 88 | const _ = grpc.SupportPackageIsVersion4 89 | 90 | // Client API for Rate service 91 | 92 | type RateClient interface { 93 | // GetRates returns the exchange rate for the given currencies 94 | GetRates(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Result, error) 95 | } 96 | 97 | type rateClient struct { 98 | cc *grpc.ClientConn 99 | } 100 | 101 | func NewRateClient(cc *grpc.ClientConn) RateClient { 102 | return &rateClient{cc} 103 | } 104 | 105 | func (c *rateClient) GetRates(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Result, error) { 106 | out := new(Result) 107 | err := grpc.Invoke(ctx, "/rate.Rate/GetRates", in, out, c.cc, opts...) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return out, nil 112 | } 113 | 114 | // Server API for Rate service 115 | 116 | type RateServer interface { 117 | // GetRates returns the exchange rate for the given currencies 118 | GetRates(context.Context, *Request) (*Result, error) 119 | } 120 | 121 | func RegisterRateServer(s *grpc.Server, srv RateServer) { 122 | s.RegisterService(&_Rate_serviceDesc, srv) 123 | } 124 | 125 | func _Rate_GetRates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 126 | in := new(Request) 127 | if err := dec(in); err != nil { 128 | return nil, err 129 | } 130 | if interceptor == nil { 131 | return srv.(RateServer).GetRates(ctx, in) 132 | } 133 | info := &grpc.UnaryServerInfo{ 134 | Server: srv, 135 | FullMethod: "/rate.Rate/GetRates", 136 | } 137 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 138 | return srv.(RateServer).GetRates(ctx, req.(*Request)) 139 | } 140 | return interceptor(ctx, in, info, handler) 141 | } 142 | 143 | var _Rate_serviceDesc = grpc.ServiceDesc{ 144 | ServiceName: "rate.Rate", 145 | HandlerType: (*RateServer)(nil), 146 | Methods: []grpc.MethodDesc{ 147 | { 148 | MethodName: "GetRates", 149 | Handler: _Rate_GetRates_Handler, 150 | }, 151 | }, 152 | Streams: []grpc.StreamDesc{}, 153 | Metadata: "pb/rate/rate.proto", 154 | } 155 | 156 | func init() { proto.RegisterFile("pb/rate/rate.proto", fileDescriptor0) } 157 | 158 | var fileDescriptor0 = []byte{ 159 | // 155 bytes of a gzipped FileDescriptorProto 160 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x2a, 0x48, 0xd2, 0x2f, 161 | 0x4a, 0x2c, 0x49, 0x05, 0x13, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x2c, 0x20, 0xb6, 0x52, 162 | 0x24, 0x17, 0x7b, 0x50, 0x6a, 0x61, 0x69, 0x6a, 0x71, 0x89, 0x90, 0x1a, 0x17, 0x5f, 0x71, 0x7e, 163 | 0x69, 0x51, 0x72, 0xaa, 0x73, 0x69, 0x51, 0x51, 0x6a, 0x5e, 0x72, 0xa5, 0x04, 0xa3, 0x02, 0xa3, 164 | 0x06, 0x67, 0x10, 0x9a, 0x28, 0x48, 0x5d, 0x49, 0x62, 0x51, 0x7a, 0x6a, 0x09, 0x5c, 0x1d, 0x13, 165 | 0x44, 0x1d, 0xaa, 0xa8, 0x92, 0x0c, 0x17, 0x5b, 0x50, 0x6a, 0x71, 0x69, 0x4e, 0x89, 0x90, 0x10, 166 | 0x17, 0xd8, 0x32, 0xb0, 0x79, 0x8c, 0x41, 0x60, 0xb6, 0x91, 0x3e, 0x17, 0x4b, 0x50, 0x62, 0x49, 167 | 0xaa, 0x90, 0x3a, 0x17, 0x87, 0x7b, 0x6a, 0x09, 0x88, 0x59, 0x2c, 0xc4, 0xab, 0x07, 0x76, 0x1f, 168 | 0xd4, 0x41, 0x52, 0x3c, 0x30, 0x2e, 0xc8, 0x90, 0x24, 0x36, 0xb0, 0xb3, 0x8d, 0x01, 0x01, 0x00, 169 | 0x00, 0xff, 0xff, 0xcc, 0xbf, 0x2f, 0xb0, 0xcc, 0x00, 0x00, 0x00, 170 | } 171 | -------------------------------------------------------------------------------- /pb/rate/rate.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package rate; 4 | 5 | service Rate { 6 | // GetRates returns the exchange rate for the given currencies 7 | rpc GetRates(Request) returns (Result); 8 | } 9 | 10 | message Request { 11 | string sourceCurrency=1; 12 | string targetCurrency=2; 13 | } 14 | 15 | message Result { 16 | double rate=1; 17 | } 18 | -------------------------------------------------------------------------------- /services/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7-alpine 2 | 3 | ADD . /app 4 | WORKDIR /app 5 | 6 | EXPOSE 9000 7 | 8 | CMD ["/app/api"] 9 | -------------------------------------------------------------------------------- /services/api/currencies.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type currencyList struct { 4 | Currencies currencies `json:"currencies"` 5 | } 6 | 7 | type currencies []string 8 | 9 | func getCurList() currencyList { 10 | currencies := currencies{ 11 | "DZD", 12 | "AUD", 13 | "VEF", 14 | "BHD", 15 | "BWP", 16 | "BRL", 17 | "BND", 18 | "CAD", 19 | "CLP", 20 | "CNY", 21 | "COP", 22 | "CZK", 23 | "DKK", 24 | "HUF", 25 | "ISK", 26 | "INR", 27 | "IDR", 28 | "IRR", 29 | "ILS", 30 | "JPY", 31 | "KZT", 32 | "KRW", 33 | "KWD", 34 | "LYD", 35 | "MYR", 36 | "MUR", 37 | "MXN", 38 | "PEN", 39 | "NPR", 40 | "NZD", 41 | "NOK", 42 | "UYU", 43 | "PHP", 44 | "PKR", 45 | "PLN", 46 | "QAR", 47 | "RUB", 48 | "SAR", 49 | "SGD", 50 | "SZAR", 51 | "LKR", 52 | "SEK", 53 | "CHF", 54 | "TND", 55 | "THB", 56 | "TTD", 57 | "AED", 58 | "GBP", 59 | "USD", 60 | "EUR", 61 | "OMR", 62 | } 63 | 64 | return currencyList{currencies} 65 | } 66 | -------------------------------------------------------------------------------- /services/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | context "golang.org/x/net/context" 11 | 12 | "github.com/dilipgurung/golang-microservices/pb/rate" 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | var ( 17 | port = flag.Int("port", 9000, "The server port") 18 | rateSvcAddr = flag.String("rate", "rate:8080", "The Rate service address") 19 | curList currencyList 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | // load the currency list to the memory 26 | curList = getCurList() 27 | 28 | // make a connection to the rate server 29 | // And create a rate client 30 | conn, err := grpc.Dial(*rateSvcAddr, grpc.WithInsecure()) 31 | if err != nil { 32 | log.Fatalf("Failed to dial: %v", err) 33 | } 34 | defer conn.Close() 35 | rc := rate.NewRateClient(conn) 36 | 37 | http.HandleFunc("/api/currencies", getCurrenciesHandler) 38 | http.HandleFunc("/healthcheck", healthCheckHandler) 39 | http.Handle("/api/rates", getRateHandler(rc)) 40 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) 41 | } 42 | 43 | func healthCheckHandler(rw http.ResponseWriter, r *http.Request) { 44 | rw.WriteHeader(http.StatusOK) 45 | } 46 | 47 | func getCurrenciesHandler(rw http.ResponseWriter, r *http.Request) { 48 | rw.Header().Set("Access-Control-Allow-Origin", "*") 49 | rw.Header().Set("Content-Type", "application/json") 50 | 51 | b, err := json.Marshal(curList) 52 | if err != nil { 53 | rw.WriteHeader(http.StatusInternalServerError) 54 | return 55 | } 56 | 57 | rw.WriteHeader(http.StatusOK) 58 | rw.Write(b) 59 | } 60 | 61 | func getRateHandler(rc rate.RateClient) http.Handler { 62 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 63 | req := new(rate.Request) 64 | 65 | rw.Header().Set("Access-Control-Allow-Origin", "*") 66 | rw.Header().Set("Content-Type", "application/json") 67 | 68 | cType, sourceCur, targetCur := r.URL.Query().Get("calculationType"), r.URL.Query().Get("sourceCurrency"), r.URL.Query().Get("targetCurrency") 69 | if cType == "" || sourceCur == "" || targetCur == "" { 70 | http.Error(rw, "Please specify calculationType/sourceCurrency/targetCurrency params", http.StatusBadRequest) 71 | return 72 | } 73 | 74 | switch cType { 75 | case "source": 76 | req.SourceCurrency = sourceCur 77 | req.TargetCurrency = targetCur 78 | case "target": 79 | req.SourceCurrency = targetCur 80 | req.TargetCurrency = sourceCur 81 | default: 82 | http.Error(rw, "Unknown calculationType", http.StatusBadRequest) 83 | return 84 | } 85 | 86 | rates, err := rc.GetRates(context.Background(), req) 87 | 88 | if err != nil { 89 | http.Error(rw, err.Error(), http.StatusInternalServerError) 90 | return 91 | } 92 | 93 | b, err := json.Marshal(rates) 94 | if err != nil { 95 | http.Error(rw, err.Error(), http.StatusInternalServerError) 96 | return 97 | } 98 | rw.WriteHeader(http.StatusOK) 99 | rw.Write(b) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /services/api/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/dilipgurung/golang-microservices/pb/rate" 11 | context "golang.org/x/net/context" 12 | 13 | "fmt" 14 | 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | const ( 19 | rateUsdToGbp = 0.79435 20 | rateGbpToUsd = 1.25889 21 | usd = "USD" 22 | gbp = "GBP" 23 | ) 24 | 25 | // response maps to the json response from the rates api service 26 | type response struct { 27 | Rate float64 `json:"rate"` 28 | } 29 | 30 | func TestHealthCheck(t *testing.T) { 31 | ts := httptest.NewServer(http.HandlerFunc(healthCheckHandler)) 32 | 33 | c := &http.Client{} 34 | res, err := c.Get(ts.URL + "/healthcheck") 35 | if err != nil { 36 | t.Fatalf("Expected error to be nil but got: %v", err) 37 | } 38 | 39 | if res.StatusCode != http.StatusOK { 40 | t.Fatalf("Expected response status to be: %d but got: %d", http.StatusOK, res.StatusCode) 41 | } 42 | 43 | } 44 | 45 | func TestGetCurrencies(t *testing.T) { 46 | curList = currencyList{} 47 | curList.Currencies = []string{"USD", "GBP"} 48 | 49 | ts := httptest.NewServer(http.HandlerFunc(getCurrenciesHandler)) 50 | 51 | b, err := makeRequestTo(ts.URL + "/api/currencies") 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | resCurList := ¤cyList{} 57 | err = json.Unmarshal(b, resCurList) 58 | if err != nil { 59 | t.Fatalf("Expected error to be nil but got: %v", err) 60 | } 61 | 62 | if resCurList.Currencies[0] != usd { 63 | t.Fatalf("Expected result to be: %s but got: %s", usd, resCurList.Currencies[0]) 64 | } 65 | } 66 | 67 | // mockRateClient is an implementation of rate.RateClient 68 | type mockRateClient struct{} 69 | 70 | func (c *mockRateClient) GetRates(ctx context.Context, in *rate.Request, opts ...grpc.CallOption) (*rate.Result, error) { 71 | r := &rate.Result{Rate: rateUsdToGbp} 72 | 73 | if in.GetSourceCurrency() == gbp { 74 | r.Rate = rateGbpToUsd 75 | } 76 | return r, nil 77 | } 78 | 79 | func TestGetRate(t *testing.T) { 80 | resp := new(response) 81 | rc := new(mockRateClient) 82 | ts := httptest.NewServer(getRateHandler(rc)) 83 | 84 | for _, test := range []struct { 85 | sourceCurrency string 86 | targetCurrency string 87 | expectedRate float64 88 | }{ 89 | {usd, gbp, rateUsdToGbp}, 90 | {gbp, usd, rateGbpToUsd}, 91 | } { 92 | url := fmt.Sprintf(ts.URL+"/api/rates?calculationType=source&sourceCurrency=%s&targetCurrency=%s", test.sourceCurrency, test.targetCurrency) 93 | b, err := makeRequestTo(url) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | err = json.Unmarshal(b, resp) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | 103 | if resp.Rate != test.expectedRate { 104 | t.Fatalf("Expected rate to be: %f but got: %f", test.expectedRate, resp.Rate) 105 | } 106 | 107 | } 108 | } 109 | 110 | // helper function to make a request to the given url 111 | // and return the response body as []byte 112 | func makeRequestTo(url string) ([]byte, error) { 113 | c := &http.Client{} 114 | res, err := c.Get(url) 115 | if err != nil { 116 | return nil, fmt.Errorf("Expected error to be nil but got: %v", err) 117 | } 118 | 119 | if res.StatusCode != http.StatusOK { 120 | return nil, fmt.Errorf("Expected response status to be: %d but got: %d", http.StatusOK, res.StatusCode) 121 | } 122 | 123 | b, err := ioutil.ReadAll(res.Body) 124 | if err != nil { 125 | return nil, fmt.Errorf("Expected error to be nil but got: %v", err) 126 | } 127 | 128 | return b, nil 129 | } 130 | -------------------------------------------------------------------------------- /services/rate/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7-alpine 2 | 3 | ADD . /app 4 | WORKDIR /app 5 | 6 | EXPOSE 8080 7 | 8 | CMD ["/app/rate"] 9 | -------------------------------------------------------------------------------- /services/rate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "os" 11 | 12 | context "golang.org/x/net/context" 13 | 14 | "github.com/dilipgurung/golang-microservices/pb/rate" 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | const baseCurrency = "USD" 19 | 20 | type rateServer struct { 21 | rateTable map[string]float64 22 | } 23 | 24 | func (s *rateServer) GetRates(ctx context.Context, r *rate.Request) (*rate.Result, error) { 25 | result := new(rate.Result) 26 | 27 | curSource, ok := s.rateTable[r.SourceCurrency] 28 | if !ok { 29 | return result, fmt.Errorf("The given currency %s was not found", r.SourceCurrency) 30 | } 31 | 32 | curTarget, ok := s.rateTable[r.TargetCurrency] 33 | if !ok { 34 | return result, fmt.Errorf("The given currency %s was not found", r.TargetCurrency) 35 | } 36 | 37 | // if the source currency is USD then we can return the 38 | // base conversion rate for the target currency straight away 39 | if r.SourceCurrency == baseCurrency { 40 | result.Rate = curTarget 41 | return result, nil 42 | } 43 | 44 | result.Rate = curTarget / curSource 45 | 46 | return result, nil 47 | } 48 | 49 | func (s *rateServer) loadRates(path string) error { 50 | 51 | file, err := os.Open(path) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | b, err := ioutil.ReadAll(file) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return json.Unmarshal(b, &s.rateTable) 62 | } 63 | 64 | var port = flag.Int("port", 8080, "The server port") 65 | 66 | func main() { 67 | flag.Parse() 68 | 69 | listen, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 70 | if err != nil { 71 | log.Fatalf("Failed to listen: %v", err) 72 | } 73 | 74 | // crete the grpc server 75 | srv := grpc.NewServer() 76 | rs := new(rateServer) 77 | err = rs.loadRates("./rates.json") 78 | if err != nil { 79 | log.Fatalf("Failed to load rates data: %v", err) 80 | } 81 | rate.RegisterRateServer(srv, rs) 82 | srv.Serve(listen) 83 | } 84 | -------------------------------------------------------------------------------- /services/rate/rates.json: -------------------------------------------------------------------------------- 1 | { 2 | "DZD":111.02, 3 | "ARS":15.985, 4 | "AUD":1.344, 5 | "ATS":13.055, 6 | "BSD":1, 7 | "BHD":0.37641, 8 | "BBD":2, 9 | "BEF":38.273, 10 | "BRL":3.3633, 11 | "GBP":0.79435, 12 | "BGN":1.8525, 13 | "CAD":1.3164, 14 | "CLP":651.68, 15 | "CNY":6.9066, 16 | "COP":3005.5, 17 | "HRK":7.1466, 18 | "CYP":0.55528, 19 | "CZK":25.641, 20 | "DKK":7.0547, 21 | "NLG":2.0908, 22 | "XCD":2.6898, 23 | "EGP":18.097, 24 | "EEK":14.845, 25 | "EUR":0.94876, 26 | "FJD":2.0942, 27 | "FIM":5.6411, 28 | "FRF":6.2234, 29 | "XAF":622.41, 30 | "XPF":113.19, 31 | "DEM":1.8556, 32 | "GHS":4.2098, 33 | "XAU":0.0008557, 34 | "GRD":323.29, 35 | "HNL":22.982, 36 | "HKD":7.7584, 37 | "HUF":298.64, 38 | "ISK":112.32, 39 | "INR":67.508, 40 | "IDR":13297, 41 | "IEP":0.74721, 42 | "ILS":3.8368, 43 | "ITL":1837, 44 | "JMD":129.06, 45 | "JPY":115.17, 46 | "KWD":0.30451, 47 | "LUF":38.273, 48 | "MYR":4.4234, 49 | "MTL":0.4073, 50 | "MXN":20.337, 51 | "MAD":10.095, 52 | "NZD":1.4009, 53 | "NOK":8.5204, 54 | "PKR":104.73, 55 | "PAB":1, 56 | "PEN":3.4068, 57 | "PHP":49.901, 58 | "XPT":0.0010787, 59 | "PLN":4.2274, 60 | "PTE":190.21, 61 | "RON":4.2713, 62 | "RUB":62.33, 63 | "SAR":3.7478, 64 | "RSD":117.12, 65 | "XAG":0.058997, 66 | "SGD":1.4313, 67 | "NPR": 106.3, 68 | "SKK":28.582, 69 | "SIT":227.36, 70 | "ZAR":13.774, 71 | "KRW":1172.2, 72 | "ESP":157.86, 73 | "XDR":0.74039, 74 | "LKR":148.8, 75 | "SEK":9.1992, 76 | "CHF":1.0186, 77 | "TWD":31.805, 78 | "THB":35.694, 79 | "TTD":6.7266, 80 | "TND":2.3192, 81 | "TRY":3.4862, 82 | "AED":3.6678, 83 | "UYU":28.783, 84 | "VEF":10.003, 85 | "VND":22697, 86 | "USD": 1 87 | } 88 | -------------------------------------------------------------------------------- /services/www/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7-alpine 2 | 3 | ADD . /app 4 | WORKDIR /app 5 | 6 | EXPOSE 8000 7 | 8 | CMD ["/app/www"] 9 | -------------------------------------------------------------------------------- /services/www/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | var port = flag.Int("port", 8000, "The server port") 11 | 12 | func main() { 13 | flag.Parse() 14 | 15 | fs := http.FileServer(http.Dir("static")) 16 | http.Handle("/", fs) 17 | http.HandleFunc("/healthcheck", healthCheckHandler) 18 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) 19 | } 20 | 21 | func healthCheckHandler(rw http.ResponseWriter, r *http.Request) { 22 | rw.WriteHeader(http.StatusOK) 23 | } 24 | -------------------------------------------------------------------------------- /services/www/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |