├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── client └── client.go ├── go.mod ├── go.sum ├── grpc_server.go ├── json_api.go ├── main.go ├── micro ├── proto ├── service.pb.go ├── service.proto └── service_grpc.pb.go ├── service.go └── types └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY go.mod ./ 6 | COPY go.sum ./ 7 | 8 | COPY . ./ 9 | 10 | RUN go build -o /price 11 | 12 | EXPOSE 3000 13 | 14 | CMD [ "/price"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o bin/micro 3 | 4 | run: build 5 | ./bin/micro 6 | 7 | proto: 8 | protoc --go_out=. --go_opt=paths=source_relative \ 9 | --go-grpc_out=. --go-grpc_opt=paths=source_relative \ 10 | proto/service.proto 11 | 12 | .PHONY: proto -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A microservice example written in Golang 2 | 3 | # Installing protobuffer 4 | 5 | ### Linux 6 | 7 | ``` 8 | sudo apt install -y protobuf-compiler 9 | ``` 10 | 11 | ### MacOS 12 | 13 | ``` 14 | brew install protobuff 15 | ``` 16 | 17 | ### GRPC and Protobuffer package dependencies 18 | 19 | go get -u google.golang.org/protobuf/cmd/protoc-gen-go 20 | go install google.golang.org/protobuf/cmd/protoc-gen-go 21 | 22 | go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc 23 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc 24 | 25 | NOTE: You should add the `protoc-gen-go-grpc` to your PATH 26 | 27 | ``` 28 | PATH="${PATH}:${HOME}/go/bin" 29 | 30 | ``` 31 | 32 | ### Running the service 33 | 34 | ``` 35 | make run 36 | ``` 37 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/anthdm/micro/proto" 9 | "github.com/anthdm/micro/types" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func NewGRPCClient(remoteAddr string) (proto.PriceFetcherClient, error) { 14 | conn, err := grpc.Dial(remoteAddr, grpc.WithInsecure()) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | c := proto.NewPriceFetcherClient(conn) 20 | 21 | return c, nil 22 | } 23 | 24 | type Client struct { 25 | endpoint string 26 | } 27 | 28 | func New(endpoint string) *Client { 29 | return &Client{ 30 | endpoint: endpoint, 31 | } 32 | } 33 | 34 | func (c *Client) FetchPrice(ticker string) (*types.PriceResponse, error) { 35 | endpoint := fmt.Sprintf("%s?ticker=%s", c.endpoint, ticker) 36 | req, err := http.NewRequest("GET", endpoint, nil) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | resp, err := http.DefaultClient.Do(req) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if resp.StatusCode != http.StatusOK { 47 | errResp := map[string]any{} 48 | if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil { 49 | return nil, err 50 | } 51 | return nil, fmt.Errorf("service responded with a non 200 status code: %s", errResp["error"]) 52 | } 53 | 54 | priceResp := new(types.PriceResponse) 55 | if err := json.NewDecoder(resp.Body).Decode(priceResp); err != nil { 56 | return nil, err 57 | } 58 | resp.Body.Close() 59 | 60 | return priceResp, nil 61 | } 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anthdm/micro 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 // indirect 7 | github.com/sirupsen/logrus v1.9.0 // indirect 8 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect 9 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 10 | golang.org/x/text v0.3.3 // indirect 11 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 12 | google.golang.org/grpc v1.50.1 // indirect 13 | google.golang.org/protobuf v1.27.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 14 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 15 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 16 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 17 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 18 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 19 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 20 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 21 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 22 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 23 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 24 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 25 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 29 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 30 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 35 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 36 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 37 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 38 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 39 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 40 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 41 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 42 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 45 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 46 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 47 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 50 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 52 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 55 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 57 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 58 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 59 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 60 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 61 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 62 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 63 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 64 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 66 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 67 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 68 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 69 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 70 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 71 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 72 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 73 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 74 | google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= 75 | google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 76 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 77 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 78 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 79 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 80 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 81 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 82 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 83 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 84 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 85 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 86 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 88 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 90 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 91 | -------------------------------------------------------------------------------- /grpc_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "net" 7 | 8 | "github.com/anthdm/micro/proto" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | func makeGRPCServerAndRun(listenAddr string, svc PriceService) error { 13 | grpcPriceFetcher := NewGRPCPriceFetcherServer(svc) 14 | 15 | ln, err := net.Listen("tcp", listenAddr) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | opts := []grpc.ServerOption{} 21 | server := grpc.NewServer(opts...) 22 | proto.RegisterPriceFetcherServer(server, grpcPriceFetcher) 23 | 24 | return server.Serve(ln) 25 | } 26 | 27 | type GRPCPriceFetcherServer struct { 28 | svc PriceService 29 | proto.UnimplementedPriceFetcherServer 30 | } 31 | 32 | func NewGRPCPriceFetcherServer(svc PriceService) *GRPCPriceFetcherServer { 33 | return &GRPCPriceFetcherServer{ 34 | svc: svc, 35 | } 36 | } 37 | 38 | func (s *GRPCPriceFetcherServer) FetchPrice(ctx context.Context, req *proto.PriceRequest) (*proto.PriceResponse, error) { 39 | reqid := rand.Intn(10000) 40 | ctx = context.WithValue(ctx, "requestID", reqid) 41 | price, err := s.svc.FetchPrice(ctx, req.Ticker) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | resp := &proto.PriceResponse{ 47 | Ticker: req.Ticker, 48 | Price: float32(price), 49 | } 50 | 51 | return resp, err 52 | } 53 | -------------------------------------------------------------------------------- /json_api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "math/rand" 8 | "net/http" 9 | 10 | "github.com/anthdm/micro/types" 11 | ) 12 | 13 | type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request) error 14 | 15 | func MakeAPIFunc(fn APIFunc) http.HandlerFunc { 16 | ctx := context.Background() 17 | 18 | return func(w http.ResponseWriter, r *http.Request) { 19 | ctx = context.WithValue(ctx, "requestID", rand.Intn(100000000)) 20 | 21 | if err := fn(ctx, w, r); err != nil { 22 | writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) 23 | } 24 | } 25 | } 26 | 27 | type JSONAPIServer struct { 28 | listenAddr string 29 | svc PriceService 30 | } 31 | 32 | func NewJSONAPIServer(listenAddr string, svc PriceService) *JSONAPIServer { 33 | return &JSONAPIServer{ 34 | svc: svc, 35 | listenAddr: listenAddr, 36 | } 37 | } 38 | 39 | func (s *JSONAPIServer) Run() { 40 | http.HandleFunc("/", MakeAPIFunc(s.HandleFetchPrice)) 41 | http.HandleFunc("/v2", MakeAPIFunc(s.HandleFetchPrice)) 42 | http.ListenAndServe(s.listenAddr, nil) 43 | } 44 | 45 | func (s *JSONAPIServer) HandleFetchPrice(ctx context.Context, w http.ResponseWriter, r *http.Request) error { 46 | ticker := r.URL.Query().Get("ticker") 47 | if len(ticker) == 0 { 48 | return fmt.Errorf("invalid ticker") 49 | } 50 | 51 | // they will put everything here 52 | 53 | price, err := s.svc.FetchPrice(ctx, ticker) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | resp := types.PriceResponse{ 59 | Price: price, 60 | Ticker: ticker, 61 | } 62 | 63 | return writeJSON(w, http.StatusOK, resp) 64 | } 65 | 66 | func writeJSON(w http.ResponseWriter, s int, v any) error { 67 | w.WriteHeader(s) 68 | return json.NewEncoder(w).Encode(v) 69 | } 70 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "github.com/anthdm/micro/client" 11 | "github.com/anthdm/micro/proto" 12 | ) 13 | 14 | func main() { 15 | var ( 16 | jsonAddr = flag.String("json", ":3000", "listen address of the json transport") 17 | grpcAddr = flag.String("grpc", ":4000", "listen address of the grpc transport") 18 | svc = loggingService{&priceService{}} 19 | ctx = context.Background() 20 | ) 21 | flag.Parse() 22 | 23 | grpcClient, err := client.NewGRPCClient(":4000") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | go func() { 29 | for { 30 | time.Sleep(3 * time.Second) 31 | resp, err := grpcClient.FetchPrice(ctx, &proto.PriceRequest{Ticker: "BTC"}) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | fmt.Printf("%+v\n", resp) 37 | } 38 | }() 39 | 40 | go makeGRPCServerAndRun(*grpcAddr, svc) 41 | 42 | jsonServer := NewJSONAPIServer(*jsonAddr, svc) 43 | jsonServer.Run() 44 | } 45 | -------------------------------------------------------------------------------- /micro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthdm/golangmicro/378f5264de9e5a532e31fca535d4369d580352db/micro -------------------------------------------------------------------------------- /proto/service.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: proto/service.proto 3 | 4 | package proto 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type PriceRequest struct { 24 | Ticker string `protobuf:"bytes,1,opt,name=ticker,proto3" json:"ticker,omitempty"` 25 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 26 | XXX_unrecognized []byte `json:"-"` 27 | XXX_sizecache int32 `json:"-"` 28 | } 29 | 30 | func (m *PriceRequest) Reset() { *m = PriceRequest{} } 31 | func (m *PriceRequest) String() string { return proto.CompactTextString(m) } 32 | func (*PriceRequest) ProtoMessage() {} 33 | func (*PriceRequest) Descriptor() ([]byte, []int) { 34 | return fileDescriptor_c33392ef2c1961ba, []int{0} 35 | } 36 | 37 | func (m *PriceRequest) XXX_Unmarshal(b []byte) error { 38 | return xxx_messageInfo_PriceRequest.Unmarshal(m, b) 39 | } 40 | func (m *PriceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 41 | return xxx_messageInfo_PriceRequest.Marshal(b, m, deterministic) 42 | } 43 | func (m *PriceRequest) XXX_Merge(src proto.Message) { 44 | xxx_messageInfo_PriceRequest.Merge(m, src) 45 | } 46 | func (m *PriceRequest) XXX_Size() int { 47 | return xxx_messageInfo_PriceRequest.Size(m) 48 | } 49 | func (m *PriceRequest) XXX_DiscardUnknown() { 50 | xxx_messageInfo_PriceRequest.DiscardUnknown(m) 51 | } 52 | 53 | var xxx_messageInfo_PriceRequest proto.InternalMessageInfo 54 | 55 | func (m *PriceRequest) GetTicker() string { 56 | if m != nil { 57 | return m.Ticker 58 | } 59 | return "" 60 | } 61 | 62 | type PriceResponse struct { 63 | Ticker string `protobuf:"bytes,1,opt,name=ticker,proto3" json:"ticker,omitempty"` 64 | Price float32 `protobuf:"fixed32,2,opt,name=price,proto3" json:"price,omitempty"` 65 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 66 | XXX_unrecognized []byte `json:"-"` 67 | XXX_sizecache int32 `json:"-"` 68 | } 69 | 70 | func (m *PriceResponse) Reset() { *m = PriceResponse{} } 71 | func (m *PriceResponse) String() string { return proto.CompactTextString(m) } 72 | func (*PriceResponse) ProtoMessage() {} 73 | func (*PriceResponse) Descriptor() ([]byte, []int) { 74 | return fileDescriptor_c33392ef2c1961ba, []int{1} 75 | } 76 | 77 | func (m *PriceResponse) XXX_Unmarshal(b []byte) error { 78 | return xxx_messageInfo_PriceResponse.Unmarshal(m, b) 79 | } 80 | func (m *PriceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 81 | return xxx_messageInfo_PriceResponse.Marshal(b, m, deterministic) 82 | } 83 | func (m *PriceResponse) XXX_Merge(src proto.Message) { 84 | xxx_messageInfo_PriceResponse.Merge(m, src) 85 | } 86 | func (m *PriceResponse) XXX_Size() int { 87 | return xxx_messageInfo_PriceResponse.Size(m) 88 | } 89 | func (m *PriceResponse) XXX_DiscardUnknown() { 90 | xxx_messageInfo_PriceResponse.DiscardUnknown(m) 91 | } 92 | 93 | var xxx_messageInfo_PriceResponse proto.InternalMessageInfo 94 | 95 | func (m *PriceResponse) GetTicker() string { 96 | if m != nil { 97 | return m.Ticker 98 | } 99 | return "" 100 | } 101 | 102 | func (m *PriceResponse) GetPrice() float32 { 103 | if m != nil { 104 | return m.Price 105 | } 106 | return 0 107 | } 108 | 109 | func init() { 110 | proto.RegisterType((*PriceRequest)(nil), "PriceRequest") 111 | proto.RegisterType((*PriceResponse)(nil), "PriceResponse") 112 | } 113 | 114 | func init() { proto.RegisterFile("proto/service.proto", fileDescriptor_c33392ef2c1961ba) } 115 | 116 | var fileDescriptor_c33392ef2c1961ba = []byte{ 117 | // 171 bytes of a gzipped FileDescriptorProto 118 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0x28, 0xca, 0x2f, 119 | 0xc9, 0xd7, 0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x03, 0xf3, 0x94, 0xd4, 0xb8, 0x78, 120 | 0x02, 0x8a, 0x32, 0x93, 0x53, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0xc4, 0xb8, 0xd8, 121 | 0x4a, 0x32, 0x93, 0xb3, 0x53, 0x8b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xa0, 0x3c, 0x25, 122 | 0x5b, 0x2e, 0x5e, 0xa8, 0xba, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x5c, 0x0a, 0x85, 0x44, 0xb8, 123 | 0x58, 0x0b, 0x40, 0x0a, 0x25, 0x98, 0x14, 0x18, 0x35, 0x98, 0x82, 0x20, 0x1c, 0x23, 0x6b, 0xa8, 124 | 0x35, 0x6e, 0xa9, 0x25, 0xc9, 0x19, 0xa9, 0x45, 0x42, 0xda, 0x5c, 0x5c, 0x60, 0x26, 0x58, 0x50, 125 | 0x88, 0x57, 0x0f, 0xd9, 0x0d, 0x52, 0x7c, 0x7a, 0x28, 0x56, 0x39, 0xc9, 0x47, 0xc9, 0xa6, 0x67, 126 | 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x27, 0xe6, 0x95, 0x64, 0xa4, 0xe4, 0xea, 127 | 0xe7, 0x66, 0x26, 0x17, 0xe5, 0xeb, 0x83, 0x3d, 0x91, 0xc4, 0x06, 0xa6, 0x8c, 0x01, 0x01, 0x00, 128 | 0x00, 0xff, 0xff, 0xb3, 0xc3, 0x52, 0x94, 0xe2, 0x00, 0x00, 0x00, 129 | } 130 | -------------------------------------------------------------------------------- /proto/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/anthdm/micro/proto"; 4 | 5 | service PriceFetcher { 6 | rpc FetchPrice(PriceRequest) returns (PriceResponse); 7 | } 8 | 9 | message PriceRequest { 10 | string ticker = 1; 11 | } 12 | 13 | message PriceResponse { 14 | string ticker = 1; 15 | float price = 2; 16 | } -------------------------------------------------------------------------------- /proto/service_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.6.1 5 | // source: proto/service.proto 6 | 7 | package proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // PriceFetcherClient is the client API for PriceFetcher service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type PriceFetcherClient interface { 25 | FetchPrice(ctx context.Context, in *PriceRequest, opts ...grpc.CallOption) (*PriceResponse, error) 26 | } 27 | 28 | type priceFetcherClient struct { 29 | cc grpc.ClientConnInterface 30 | } 31 | 32 | func NewPriceFetcherClient(cc grpc.ClientConnInterface) PriceFetcherClient { 33 | return &priceFetcherClient{cc} 34 | } 35 | 36 | func (c *priceFetcherClient) FetchPrice(ctx context.Context, in *PriceRequest, opts ...grpc.CallOption) (*PriceResponse, error) { 37 | out := new(PriceResponse) 38 | err := c.cc.Invoke(ctx, "/PriceFetcher/FetchPrice", in, out, opts...) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return out, nil 43 | } 44 | 45 | // PriceFetcherServer is the server API for PriceFetcher service. 46 | // All implementations must embed UnimplementedPriceFetcherServer 47 | // for forward compatibility 48 | type PriceFetcherServer interface { 49 | FetchPrice(context.Context, *PriceRequest) (*PriceResponse, error) 50 | mustEmbedUnimplementedPriceFetcherServer() 51 | } 52 | 53 | // UnimplementedPriceFetcherServer must be embedded to have forward compatible implementations. 54 | type UnimplementedPriceFetcherServer struct { 55 | } 56 | 57 | func (UnimplementedPriceFetcherServer) FetchPrice(context.Context, *PriceRequest) (*PriceResponse, error) { 58 | return nil, status.Errorf(codes.Unimplemented, "method FetchPrice not implemented") 59 | } 60 | func (UnimplementedPriceFetcherServer) mustEmbedUnimplementedPriceFetcherServer() {} 61 | 62 | // UnsafePriceFetcherServer may be embedded to opt out of forward compatibility for this service. 63 | // Use of this interface is not recommended, as added methods to PriceFetcherServer will 64 | // result in compilation errors. 65 | type UnsafePriceFetcherServer interface { 66 | mustEmbedUnimplementedPriceFetcherServer() 67 | } 68 | 69 | func RegisterPriceFetcherServer(s grpc.ServiceRegistrar, srv PriceFetcherServer) { 70 | s.RegisterService(&PriceFetcher_ServiceDesc, srv) 71 | } 72 | 73 | func _PriceFetcher_FetchPrice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 74 | in := new(PriceRequest) 75 | if err := dec(in); err != nil { 76 | return nil, err 77 | } 78 | if interceptor == nil { 79 | return srv.(PriceFetcherServer).FetchPrice(ctx, in) 80 | } 81 | info := &grpc.UnaryServerInfo{ 82 | Server: srv, 83 | FullMethod: "/PriceFetcher/FetchPrice", 84 | } 85 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 86 | return srv.(PriceFetcherServer).FetchPrice(ctx, req.(*PriceRequest)) 87 | } 88 | return interceptor(ctx, in, info, handler) 89 | } 90 | 91 | // PriceFetcher_ServiceDesc is the grpc.ServiceDesc for PriceFetcher service. 92 | // It's only intended for direct use with grpc.RegisterService, 93 | // and not to be introspected or modified (even as a copy) 94 | var PriceFetcher_ServiceDesc = grpc.ServiceDesc{ 95 | ServiceName: "PriceFetcher", 96 | HandlerType: (*PriceFetcherServer)(nil), 97 | Methods: []grpc.MethodDesc{ 98 | { 99 | MethodName: "FetchPrice", 100 | Handler: _PriceFetcher_FetchPrice_Handler, 101 | }, 102 | }, 103 | Streams: []grpc.StreamDesc{}, 104 | Metadata: "proto/service.proto", 105 | } 106 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var prices = map[string]float64{ 12 | "ETH": 999.99, 13 | "BTC": 20000.0, 14 | "GG": 1000000.0, 15 | } 16 | 17 | // PriceService is an interface that can fetch the price for any given ticker. 18 | type PriceService interface { 19 | FetchPrice(context.Context, string) (float64, error) 20 | } 21 | 22 | type priceService struct{} 23 | 24 | // is the business logic 25 | func (s *priceService) FetchPrice(_ context.Context, ticker string) (float64, error) { 26 | price, ok := prices[ticker] 27 | if !ok { 28 | return 0.0, fmt.Errorf("price for ticker (%s) is not available", ticker) 29 | } 30 | 31 | return price, nil 32 | } 33 | 34 | type loggingService struct { 35 | next PriceService 36 | } 37 | 38 | func (s loggingService) FetchPrice(ctx context.Context, ticker string) (price float64, err error) { 39 | defer func(begin time.Time) { 40 | reqID := ctx.Value("requestID") 41 | 42 | logrus.WithFields(logrus.Fields{ 43 | "requestID": reqID, 44 | "took": time.Since(begin), 45 | "err": err, 46 | "price": price, 47 | "ticker": ticker, 48 | }).Info("FetchPrice") 49 | }(time.Now()) 50 | 51 | return s.next.FetchPrice(ctx, ticker) 52 | } 53 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type PriceResponse struct { 4 | Ticker string `json:"ticker"` 5 | Price float64 `json:"price"` 6 | } 7 | --------------------------------------------------------------------------------