├── services ├── qr │ ├── transport │ │ ├── pb │ │ │ ├── qr.proto │ │ │ └── qr.pb.go │ │ ├── request_response.go │ │ ├── endpoints.go │ │ └── grpc │ │ │ └── service.go │ ├── service.go │ ├── implementation │ │ └── service.go │ └── cmd │ │ └── main.go ├── device │ ├── transport │ │ ├── pb │ │ │ ├── svcdevice.proto │ │ │ └── svcdevice.pb.go │ │ ├── http │ │ │ ├── routes │ │ │ │ └── routes.go │ │ │ └── service.go │ │ ├── request_response.go │ │ ├── endpoints.go │ │ └── grpc │ │ │ └── service.go │ ├── database │ │ ├── sqlite │ │ │ ├── schema.go │ │ │ └── sqlite.go │ │ └── interface.go │ ├── service.go │ ├── implementation │ │ └── service.go │ └── cmd │ │ └── main.go ├── event │ ├── database │ │ ├── sqlite │ │ │ ├── schema.go │ │ │ └── sqlite.go │ │ └── interface.go │ ├── transport │ │ ├── pb │ │ │ ├── event.proto │ │ │ └── event.pb.go │ │ ├── endpoints.go │ │ ├── request_response.go │ │ └── twirp │ │ │ └── service.go │ ├── service.go │ ├── implementation │ │ └── service.go │ └── cmd │ │ └── main.go ├── frontend │ ├── transport │ │ ├── http │ │ │ ├── routes │ │ │ │ └── routes.go │ │ │ └── service.go │ │ ├── endpoints.go │ │ └── request_response.go │ ├── service.go │ ├── implementation │ │ └── service.go │ └── cmd │ │ └── main.go └── elegantmonolith │ └── main.go ├── shared ├── sd │ ├── balancer.go │ ├── factory.go │ ├── round_robin.go │ ├── doc.go │ ├── client_instancer.go │ └── client_cache.go ├── oc │ ├── zipkin.go │ ├── setup.go │ ├── zpages.go │ ├── multi_exporter.go │ └── trace_endpoints.go ├── loggermw │ └── loggermw.go ├── errormw │ └── errormw.go ├── network │ └── ip.go ├── grpcconn │ └── host_mapper.go └── factory │ ├── grpc.go │ └── http.go ├── .gitignore ├── clients ├── event │ ├── client.go │ └── twirp │ │ ├── factory.go │ │ └── client.go ├── device │ ├── http │ │ ├── endpoints.go │ │ └── encode_decode.go │ ├── grpc │ │ ├── endpoints.go │ │ └── encode_decode.go │ └── client.go ├── qr │ ├── client.go │ └── grpc │ │ ├── endpoints.go │ │ └── encode_decode.go ├── frontend │ ├── http │ │ ├── endpoints.go │ │ └── encode_decode.go │ └── client.go └── cli │ └── main.go ├── generate.go ├── README.md └── LICENSE /services/qr/transport/pb/qr.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | 5 | service QR { 6 | rpc Generate (GenerateRequest) returns (GenerateResponse) {} 7 | } 8 | 9 | message GenerateRequest { 10 | string data = 1; 11 | int32 level = 2; 12 | int32 size = 3; 13 | } 14 | 15 | message GenerateResponse { 16 | bytes image = 1; 17 | } 18 | -------------------------------------------------------------------------------- /shared/sd/balancer.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | // stdlib 5 | "errors" 6 | ) 7 | 8 | // Balancer yields client instances according to some heuristic. 9 | type Balancer interface { 10 | Client() (interface{}, error) 11 | } 12 | 13 | // ErrNoClients is returned when no qualifying client instances are available. 14 | var ErrNoClients = errors.New("no client instance available") 15 | -------------------------------------------------------------------------------- /services/device/transport/pb/svcdevice.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | 5 | service Device { 6 | rpc Unlock (UnlockRequest) returns (UnlockResponse) {} 7 | } 8 | 9 | message UnlockRequest { 10 | bytes event_id = 1; 11 | bytes device_id = 2; 12 | string code = 3; 13 | } 14 | 15 | message UnlockResponse { 16 | string event_caption = 1; 17 | string device_caption = 2; 18 | } 19 | -------------------------------------------------------------------------------- /services/device/database/sqlite/schema.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | // external 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | func v1(tx *sqlx.Tx) (err error) { 9 | // add device table 10 | if _, err = tx.Exec(` 11 | CREATE TABLE device ( 12 | id BLOB NOT NULL, event_id BLOB NOT NULL, name TEXT NOT NULL, 13 | hash BLOB NOT NULL, PRIMARY KEY(id) 14 | ) WITHOUT ROWID; 15 | `); err != nil { 16 | return 17 | } 18 | 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # shell scripts 8 | *.sh 9 | 10 | # app databases 11 | *.db* 12 | 13 | # log files 14 | *.log 15 | 16 | # Test binary, build with `go test -c` 17 | *.test 18 | 19 | # Output of the go coverage tool, specifically when used with LiteIDE 20 | *.out 21 | 22 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 23 | .glide/ 24 | 25 | # build directory 26 | build/ 27 | -------------------------------------------------------------------------------- /clients/event/client.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | // stdlib 5 | "net/http" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/sd" 10 | 11 | // project 12 | "github.com/basvanbeek/opencensus-gokit-example/clients/event/twirp" 13 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 14 | ) 15 | 16 | // NewTwirp returns a new event client using the Twirp transport. 17 | func NewTwirp(instancer sd.Instancer, client *http.Client, logger log.Logger) event.Service { 18 | return twirp.NewClient(instancer, client, logger) 19 | } 20 | -------------------------------------------------------------------------------- /services/device/transport/http/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | // external 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | // Endpoints holds all available HTTP endpoints for our service. 9 | type Endpoints struct { 10 | Unlock *mux.Route 11 | } 12 | 13 | // Initialize wires the HTTP endpoints to our Go kit service endpoints. 14 | func Initialize(router *mux.Router) Endpoints { 15 | return Endpoints{ 16 | Unlock: router. 17 | Methods("POST"). 18 | Path("/unlock/{event_id}/{device_id}"). 19 | Queries("code", "{code}"). 20 | Name("unlock"), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /services/event/database/sqlite/schema.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | // external 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | func v1(tx *sqlx.Tx) (err error) { 9 | // add event table 10 | if _, err = tx.Exec(` 11 | CREATE TABLE event ( 12 | id BLOB NOT NULL, tenant_id BLOB NOT NULL, name TEXT NOT NULL, 13 | PRIMARY KEY(id) 14 | ) WITHOUT ROWID;`, 15 | ); err != nil { 16 | return 17 | } 18 | 19 | if _, err = tx.Exec( 20 | `CREATE UNIQUE INDEX uidx_event_name ON event (tenant_id, lower(name));`, 21 | ); err != nil { 22 | return 23 | } 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /shared/sd/factory.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | ) 7 | 8 | // Factory is a function that converts an instance string (e.g. host:port) to a 9 | // specific client implementation. A factory also returns an io.Closer that's 10 | // invoked when the instance goes away and needs to be cleaned up. Factories may 11 | // return nil closers. 12 | // 13 | // Users are expected to provide their own factory functions that assume 14 | // specific transports, or can deduce transports by parsing the instance string. 15 | type Factory func(instance string) (interface{}, io.Closer, error) 16 | -------------------------------------------------------------------------------- /services/device/database/interface.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/kevinburke/go.uuid" 10 | ) 11 | 12 | // Common Errors 13 | var ( 14 | ErrRepository = errors.New("unable to handle request") 15 | ErrNotFound = errors.New("device not found") 16 | ) 17 | 18 | // Repository describes the resource methods needed for this service. 19 | type Repository interface { 20 | GetDevice(ctx context.Context, eventID, deviceID uuid.UUID) (*Session, error) 21 | } 22 | 23 | // Session holds session details 24 | type Session struct { 25 | EventCaption string 26 | DeviceCaption string 27 | UnlockHash []byte 28 | } 29 | -------------------------------------------------------------------------------- /shared/sd/round_robin.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | // stdlib 5 | "sync/atomic" 6 | ) 7 | 8 | // NewRoundRobin returns a load balancer that returns services in sequence. 9 | func NewRoundRobin(s ClientInstancer) Balancer { 10 | return &roundRobin{ 11 | s: s, 12 | c: 0, 13 | } 14 | } 15 | 16 | type roundRobin struct { 17 | s ClientInstancer 18 | c uint64 19 | } 20 | 21 | func (rr *roundRobin) Client() (interface{}, error) { 22 | clients, err := rr.s.Clients() 23 | if err != nil { 24 | return nil, err 25 | } 26 | if len(clients) <= 0 { 27 | return nil, ErrNoClients 28 | } 29 | old := atomic.AddUint64(&rr.c, 1) - 1 30 | idx := old % uint64(len(clients)) 31 | return clients[idx], nil 32 | } 33 | -------------------------------------------------------------------------------- /shared/sd/doc.go: -------------------------------------------------------------------------------- 1 | // Package sd provides utilities related to service discovery. That includes the 2 | // client-side loadbalancer pattern, where a microservice subscribes to a 3 | // service discovery system in order to reach remote instances; as well as the 4 | // registrator pattern, where a microservice registers itself in a service 5 | // discovery system. Implementations are provided for most common systems. 6 | // 7 | // Most of the code in this package is taken from the awesome Go kit library and 8 | // adjusted so generic clients can be created and no dependency on the Go kit 9 | // Endpoint concept is needed. Where possible it uses the Go kit sd package. 10 | // 11 | // See https:/gokit.io for more. 12 | package sd 13 | -------------------------------------------------------------------------------- /services/device/transport/request_response.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // external 5 | "github.com/go-kit/kit/endpoint" 6 | "github.com/kevinburke/go.uuid" 7 | ) 8 | 9 | var ( 10 | _ endpoint.Failer = UnlockResponse{} 11 | ) 12 | 13 | // UnlockRequest holds the request parameters for the Unlock method. 14 | type UnlockRequest struct { 15 | EventID uuid.UUID 16 | DeviceID uuid.UUID 17 | Code string 18 | } 19 | 20 | // UnlockResponse holds the response values for the Unlock method. 21 | type UnlockResponse struct { 22 | EventCaption string 23 | DeviceCaption string 24 | Err error 25 | } 26 | 27 | // Failed implements Failer 28 | func (r UnlockResponse) Failed() error { return r.Err } 29 | -------------------------------------------------------------------------------- /services/qr/transport/request_response.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // external 5 | "github.com/go-kit/kit/endpoint" 6 | 7 | // project 8 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 9 | ) 10 | 11 | var ( 12 | _ endpoint.Failer = GenerateResponse{} 13 | ) 14 | 15 | // GenerateRequest holds the request parameters for the Generate method. 16 | type GenerateRequest struct { 17 | Data string 18 | Level qr.RecoveryLevel 19 | Size int 20 | } 21 | 22 | // GenerateResponse holds the response values for the Generate method. 23 | type GenerateResponse struct { 24 | QR []byte 25 | Err error 26 | } 27 | 28 | // Failed implements Failer. 29 | func (r GenerateResponse) Failed() error { return r.Err } 30 | -------------------------------------------------------------------------------- /shared/oc/zipkin.go: -------------------------------------------------------------------------------- 1 | package oc 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | 7 | // external 8 | zipkin "github.com/openzipkin/zipkin-go" 9 | reporter "github.com/openzipkin/zipkin-go/reporter/http" 10 | oczipkin "go.opencensus.io/exporter/zipkin" 11 | "go.opencensus.io/trace" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 15 | ) 16 | 17 | const ( 18 | zipkinURL = "http://localhost:9411/api/v2/spans" 19 | ) 20 | 21 | func setupZipkin(serviceName string) (trace.Exporter, io.Closer) { 22 | var ( 23 | rep = reporter.NewReporter(zipkinURL) 24 | addr, _ = network.HostIP() 25 | ) 26 | localEndpoint, _ := zipkin.NewEndpoint(serviceName, addr) 27 | 28 | return oczipkin.NewExporter(rep, localEndpoint), rep 29 | } 30 | -------------------------------------------------------------------------------- /shared/oc/setup.go: -------------------------------------------------------------------------------- 1 | package oc 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | "time" 7 | 8 | // external 9 | "go.opencensus.io/stats/view" 10 | "go.opencensus.io/trace" 11 | ) 12 | 13 | // Setup OpenCensus 14 | func Setup(serviceName string) io.Closer { 15 | // Always trace for this demo. 16 | trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) 17 | 18 | // Report stats at every second. 19 | view.SetReportingPeriod(1 * time.Second) 20 | 21 | zipkinExporter, zipkinCloser := setupZipkin(serviceName) 22 | 23 | exporter := &multiExporter{ 24 | t: []trace.Exporter{ 25 | zipkinExporter, 26 | }, 27 | v: []view.Exporter{}, 28 | c: []io.Closer{ 29 | zipkinCloser, 30 | }, 31 | } 32 | 33 | trace.RegisterExporter(exporter) 34 | view.RegisterExporter(exporter) 35 | 36 | return exporter 37 | } 38 | -------------------------------------------------------------------------------- /shared/oc/zpages.go: -------------------------------------------------------------------------------- 1 | package oc 2 | 3 | import ( 4 | // stdlib 5 | "net" 6 | "net/http" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/oklog/run" 12 | "go.opencensus.io/zpages" 13 | 14 | // project 15 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 16 | ) 17 | 18 | // ZPages handling setup 19 | func ZPages(g run.Group, logger log.Logger) { 20 | var ( 21 | bindIP, _ = network.HostIP() 22 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 23 | addr = listener.Addr().String() 24 | ) 25 | 26 | g.Add(func() error { 27 | level.Info(logger).Log("msg", "zpages started", "addr", "http://"+addr) 28 | return http.Serve(listener, zpages.Handler) 29 | }, func(error) { 30 | listener.Close() 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /shared/loggermw/loggermw.go: -------------------------------------------------------------------------------- 1 | package loggermw 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "time" 8 | 9 | // external 10 | "github.com/go-kit/kit/endpoint" 11 | "github.com/go-kit/kit/log" 12 | ) 13 | 14 | // LoggerMiddleware logs our endpoint request response payloads... 15 | func LoggerMiddleware(logger log.Logger) endpoint.Middleware { 16 | return func(next endpoint.Endpoint) endpoint.Endpoint { 17 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 18 | defer func(begin time.Time) { 19 | logger.Log( 20 | "request", fmt.Sprintf("%+v", request), 21 | "response", fmt.Sprintf("%+v", response), 22 | "error", err, 23 | "took", time.Since(begin), 24 | ) 25 | }(time.Now()) 26 | 27 | response, err = next(ctx, request) 28 | return 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package ocgokitexample 2 | 3 | //go:generate protoc -I$GOPATH/src -I. services/device/transport/pb/svcdevice.proto --go_out=plugins=grpc:. 4 | //go:generate protoc -I$GOPATH/src -I. services/qr/transport/pb/qr.proto --go_out=plugins=grpc:. --twirp_out=. 5 | //go:generate protoc -I$GOPATH/src -I. services/event/transport/pb/event.proto --go_out=plugins=grpc:. --twirp_out=. 6 | //go:generate go build -tags sqlite3 -o build/cli clients/cli/main.go 7 | //go:generate go build -tags sqlite3 -o build/ocg-elegantmonolith services/elegantmonolith/main.go 8 | //go:generate go build -tags sqlite3 -o build/ocg-event services/event/cmd/main.go 9 | //go:generate go build -tags sqlite3 -o build/ocg-qrgenerator services/qr/cmd/main.go 10 | //go:generate go build -tags sqlite3 -o build/ocg-device services/device/cmd/main.go 11 | //go:generate go build -tags sqlite3 -o build/ocg-frontend services/frontend/cmd/main.go 12 | -------------------------------------------------------------------------------- /services/qr/transport/endpoints.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | 10 | // project 11 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 12 | ) 13 | 14 | // Endpoints holds all Go kit endpoints for the service. 15 | type Endpoints struct { 16 | Generate endpoint.Endpoint 17 | } 18 | 19 | // MakeEndpoints initializes all Go kit endpoints for the service. 20 | func MakeEndpoints(s qr.Service) Endpoints { 21 | return Endpoints{ 22 | Generate: makeGenerateEndpoint(s), 23 | } 24 | } 25 | 26 | func makeGenerateEndpoint(s qr.Service) endpoint.Endpoint { 27 | return func(ctx context.Context, request interface{}) (interface{}, error) { 28 | req := request.(GenerateRequest) 29 | qr, err := s.Generate(ctx, req.Data, req.Level, req.Size) 30 | return GenerateResponse{QR: qr, Err: err}, nil 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /shared/oc/multi_exporter.go: -------------------------------------------------------------------------------- 1 | package oc 2 | 3 | import ( 4 | // stdlib 5 | "errors" 6 | "io" 7 | "strings" 8 | 9 | // external 10 | "go.opencensus.io/stats/view" 11 | "go.opencensus.io/trace" 12 | ) 13 | 14 | type multiExporter struct { 15 | t []trace.Exporter 16 | v []view.Exporter 17 | c []io.Closer 18 | } 19 | 20 | // ExportSpan implements trace.Exporter 21 | func (m *multiExporter) ExportSpan(s *trace.SpanData) { 22 | for _, t := range m.t { 23 | t.ExportSpan(s) 24 | } 25 | } 26 | 27 | // ExportView implements view.Exporter 28 | func (m *multiExporter) ExportView(d *view.Data) { 29 | for _, v := range m.v { 30 | v.ExportView(d) 31 | } 32 | } 33 | 34 | // Close implements io.Closer 35 | func (m *multiExporter) Close() error { 36 | var e []string 37 | 38 | for _, c := range m.c { 39 | if err := c.Close(); err != nil { 40 | e = append(e, err.Error()) 41 | } 42 | } 43 | 44 | if len(e) > 0 { 45 | return errors.New("ERRORS: " + strings.Join(e, " ; ")) 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /services/device/transport/endpoints.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | 10 | // project 11 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 12 | ) 13 | 14 | // Endpoints holds all Go kit endpoints for the service. 15 | type Endpoints struct { 16 | Unlock endpoint.Endpoint 17 | } 18 | 19 | // MakeEndpoints initializes all Go kit endpoints for the service. 20 | func MakeEndpoints(s device.Service) Endpoints { 21 | return Endpoints{ 22 | Unlock: makeUnlockEndpoint(s), 23 | } 24 | } 25 | 26 | func makeUnlockEndpoint(s device.Service) endpoint.Endpoint { 27 | return func(ctx context.Context, request interface{}) (interface{}, error) { 28 | req := request.(UnlockRequest) 29 | res, err := s.Unlock(ctx, req.EventID, req.DeviceID, req.Code) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return UnlockResponse{EventCaption: res.DeviceCaption, DeviceCaption: res.DeviceCaption}, nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /services/event/database/interface.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/kevinburke/go.uuid" 10 | ) 11 | 12 | // Common Errors 13 | var ( 14 | ErrRepository = errors.New("unable to handle request") 15 | ErrNotFound = errors.New("event not found") 16 | ErrIDExists = errors.New("event id already exists") 17 | ErrNameExists = errors.New("event name already exists") 18 | ) 19 | 20 | // Repository describes the resource methods needed for this service. 21 | type Repository interface { 22 | Create(ctx context.Context, event Event) (*uuid.UUID, error) 23 | Get(ctx context.Context, id uuid.UUID) (*Event, error) 24 | Update(ctx context.Context, event Event) error 25 | Delete(ctx context.Context, tenantID uuid.UUID, id uuid.UUID) error 26 | List(ctx context.Context, tenantID uuid.UUID) ([]*Event, error) 27 | } 28 | 29 | // Event holds event details 30 | type Event struct { 31 | ID uuid.UUID 32 | TenantID uuid.UUID 33 | Name string 34 | } 35 | -------------------------------------------------------------------------------- /services/event/transport/pb/event.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | 5 | service Event { 6 | rpc Create (CreateRequest) returns (CreateResponse) {} 7 | rpc Get (GetRequest) returns (GetResponse) {} 8 | rpc Update (UpdateRequest) returns (UpdateResponse) {} 9 | rpc Delete (DeleteRequest) returns (DeleteResponse) {} 10 | rpc List (ListRequest) returns (ListResponse) {} 11 | } 12 | 13 | message eventObj { 14 | bytes id = 1; 15 | string name = 2; 16 | } 17 | 18 | message CreateRequest { 19 | bytes tenant_id = 1; 20 | eventObj event = 2; 21 | } 22 | 23 | message CreateResponse { 24 | bytes id = 1; 25 | } 26 | 27 | message GetRequest { 28 | bytes tenant_id = 1; 29 | bytes id = 2; 30 | } 31 | 32 | message GetResponse { 33 | eventObj event = 1; 34 | } 35 | 36 | message UpdateRequest { 37 | bytes tenant_id = 1; 38 | eventObj event = 2; 39 | } 40 | 41 | message UpdateResponse { 42 | 43 | } 44 | 45 | message DeleteRequest { 46 | bytes tenant_id = 1; 47 | bytes id = 2; 48 | } 49 | 50 | message DeleteResponse { 51 | 52 | } 53 | 54 | message ListRequest { 55 | bytes tenant_id = 1; 56 | } 57 | 58 | message ListResponse { 59 | repeated eventObj events = 1; 60 | } 61 | -------------------------------------------------------------------------------- /shared/errormw/errormw.go: -------------------------------------------------------------------------------- 1 | package errormw 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/go-kit/kit/log" 10 | ) 11 | 12 | // UnwrapError middleware to move a business error found in the response 13 | // parameter into the error parameter. This function should be called after 14 | // circuit breaking and/or retry logic so a business error will not trigger 15 | // those middlewares. 16 | // It's use is primarily as outer shell middleware at the client end so a client 17 | // method implementation only needs to test for error instead of both error and 18 | // response payload business error. 19 | func UnwrapError(logger log.Logger) endpoint.Middleware { 20 | return func(e endpoint.Endpoint) endpoint.Endpoint { 21 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 22 | response, err = e(ctx, request) 23 | if err != nil { 24 | logger.Log("err", err, "type", "transport") 25 | return nil, err 26 | } 27 | if f, ok := response.(endpoint.Failer); ok { 28 | if f.Failed() != nil { 29 | logger.Log("err", f.Failed(), "type", "business") 30 | return nil, f.Failed() 31 | } 32 | } 33 | return response, nil 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clients/event/twirp/factory.go: -------------------------------------------------------------------------------- 1 | package twirp 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | "net/http" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | kitsd "github.com/go-kit/kit/sd" 11 | 12 | // project 13 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 14 | "github.com/basvanbeek/opencensus-gokit-example/services/event/transport/pb" 15 | "github.com/basvanbeek/opencensus-gokit-example/shared/sd" 16 | ) 17 | 18 | // NewClient returns a new event client using the Twirp transport. 19 | func NewClient(instancer kitsd.Instancer, c *http.Client, logger log.Logger) event.Service { 20 | return &client{ 21 | instancer: factory(instancer, c, logger), 22 | logger: logger, 23 | } 24 | } 25 | 26 | func factory(instancer kitsd.Instancer, client *http.Client, logger log.Logger) func() pb.Event { 27 | factoryFunc := func(instance string) (interface{}, io.Closer, error) { 28 | return pb.NewEventProtobufClient(instance, client), nil, nil 29 | } 30 | clientInstancer := sd.NewClientInstancer(instancer, factoryFunc, logger) 31 | balancer := sd.NewRoundRobin(clientInstancer) 32 | 33 | return func() pb.Event { 34 | client, err := balancer.Client() 35 | if err != nil { 36 | logger.Log("err", err) 37 | return nil 38 | } 39 | return client.(pb.Event) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /services/event/service.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | "github.com/kevinburke/go.uuid" 9 | // external 10 | ) 11 | 12 | // ServiceName of this service. 13 | const ServiceName = "event" 14 | 15 | // Service describes our Event service. 16 | type Service interface { 17 | Create(ctx context.Context, tenantID uuid.UUID, event Event) (*uuid.UUID, error) 18 | Get(ctx context.Context, tenantID, id uuid.UUID) (*Event, error) 19 | Update(ctx context.Context, tenantID uuid.UUID, event Event) error 20 | Delete(ctx context.Context, tenantID uuid.UUID, id uuid.UUID) error 21 | List(ctx context.Context, tenantID uuid.UUID) ([]*Event, error) 22 | } 23 | 24 | // Event Service Error descriptions 25 | const ( 26 | ErrorService = "internal service error" 27 | ErrorUnauthorized = "unauthorized" 28 | ErrorNotFound = "event not found" 29 | ErrorEventExists = "event already exists" 30 | ) 31 | 32 | // Event Service Errors 33 | var ( 34 | ErrService = errors.New(ErrorService) 35 | ErrUnauthorized = errors.New(ErrorUnauthorized) 36 | ErrNotFound = errors.New(ErrorNotFound) 37 | ErrEventExists = errors.New(ErrorEventExists) 38 | ) 39 | 40 | // Event data 41 | type Event struct { 42 | ID uuid.UUID `json:"id"` 43 | Name string `json:"name"` 44 | } 45 | -------------------------------------------------------------------------------- /services/device/service.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/kevinburke/go.uuid" 10 | ) 11 | 12 | // ServiceName of this service. 13 | const ServiceName = "device" 14 | 15 | // Service describes our Device service. 16 | type Service interface { 17 | Unlock(ctx context.Context, eventID, deviceID uuid.UUID, code string) (*Session, error) 18 | } 19 | 20 | // Device Service Error descriptions 21 | const ( 22 | ErrorRequireEventID = "missing required event id" 23 | ErrorRequireDeviceID = "missing required device id" 24 | ErrorRequireUnlockCode = "missing required unlock code" 25 | ErrorRepository = "unable to query repository" 26 | ErrorEventNotFound = "event not found" 27 | ErrorUnlockNotFound = "device / unlock code combination not found" 28 | ) 29 | 30 | // Device Service Errors 31 | var ( 32 | ErrRequireEventID = errors.New(ErrorRequireEventID) 33 | ErrRequireDeviceID = errors.New(ErrorRequireDeviceID) 34 | ErrRequireUnlockCode = errors.New(ErrorRequireUnlockCode) 35 | ErrRepository = errors.New(ErrorRepository) 36 | ErrEventNotFound = errors.New(ErrorEventNotFound) 37 | ErrUnlockNotFound = errors.New(ErrorUnlockNotFound) 38 | ) 39 | 40 | // Session holds session details 41 | type Session struct { 42 | EventCaption string `json:"event_caption,omitempty"` 43 | DeviceCaption string `json:"device_caption,omitempty"` 44 | Token string `json:"token,omitempty"` 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opencensus-gokit-example 2 | Example of using OpenCensus with Go kit 3 | 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/basvanbeek/opencensus-gokit-example)](https://goreportcard.com/report/github.com/basvanbeek/opencensus-gokit-example) 5 | 6 | # notable dependencies 7 | - Go kit 8 | - OpenCensus 9 | - gRPC 10 | - Twirp 11 | - Zipkin 12 | 13 | # build services 14 | 15 | Services can be built by running go generate in the root of the project: 16 | 17 | ```sh 18 | $ go generate ./... 19 | ``` 20 | 21 | # running the services 22 | 23 | Get a Zipkin instance running by following the directions as found 24 | [here](https://zipkin.io/pages/quickstart). This will allow the services 25 | instrumented with OpenCensus to export tracing details to a Zipkin backend. 26 | 27 | Get etcd running for service discovery. Instructions to get it up and 28 | running can be found [here](https://coreos.com/etcd/docs/latest/dl_build.html). 29 | 30 | Now you can start the various services included in this demo. 31 | 32 | Example: 33 | ```sh 34 | #!/bin/sh 35 | nohup ./ocg-qrgenerator &>qrgenerator.log & 36 | nohup ./ocg-device &>device.log & 37 | nohup ./ocg-event &>event.log & 38 | nohup ./ocg-frontend &>frontend.log & 39 | ``` 40 | 41 | Each service will dynamically select available ports to listen on and advertise 42 | these on etcd. It is possible to run multiple instances for each service on a 43 | single machine. The clients can automatically load balance and retry on the 44 | available services. 45 | -------------------------------------------------------------------------------- /shared/network/ip.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | // stdlib 5 | "errors" 6 | "net" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // ErrIFNotFound is returned if we can't find an available network interface. 12 | var ErrIFNotFound = errors.New("network interface not found") 13 | 14 | // HostIP tries to return the IPv4 string from the primary network interface. 15 | func HostIP() (string, error) { 16 | _, ip, err := getPrimaryIPv4Interface() 17 | if err != nil { 18 | // fallback to hostname 19 | hostName, err := os.Hostname() 20 | if err != nil { 21 | return "", err 22 | } 23 | ips, err := net.LookupIP(hostName) 24 | if err != nil { 25 | return "", err 26 | } 27 | for _, ip2 := range ips { 28 | if ip2.To4() != nil { 29 | ip = &ip2 30 | break 31 | } 32 | } 33 | } 34 | if ip == nil { 35 | return "", ErrIFNotFound 36 | } 37 | return ip.String(), nil 38 | } 39 | 40 | func getPrimaryIPv4Interface() (*net.Interface, *net.IP, error) { 41 | interfaces, err := net.Interfaces() 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | for _, i := range interfaces { 46 | if i.Flags&net.FlagUp != net.FlagUp || i.Flags&net.FlagLoopback == net.FlagLoopback { 47 | continue 48 | } 49 | addrs, err := i.Addrs() 50 | if err != nil { 51 | continue 52 | } 53 | for _, a := range addrs { 54 | tokens := strings.Split(a.String(), "/") // ip/netmask 55 | ip := net.ParseIP(tokens[0]) 56 | if ip.To4() != nil { 57 | return &i, &ip, nil 58 | } 59 | } 60 | } 61 | return nil, nil, ErrIFNotFound 62 | } 63 | -------------------------------------------------------------------------------- /clients/device/http/endpoints.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/go-kit/kit/ratelimit" 12 | "github.com/go-kit/kit/sd" 13 | "github.com/gorilla/mux" 14 | "golang.org/x/time/rate" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/http/routes" 19 | "github.com/basvanbeek/opencensus-gokit-example/shared/factory" 20 | "github.com/basvanbeek/opencensus-gokit-example/shared/loggermw" 21 | ) 22 | 23 | // InitEndpoints returns an initialized set of Go kit HTTP endpoints. 24 | func InitEndpoints(instancer sd.Instancer, logger log.Logger) transport.Endpoints { 25 | route := routes.Initialize(mux.NewRouter()) 26 | 27 | // configure client wide rate limiter for all instances and all method 28 | // endpoints 29 | rl := ratelimit.NewErroringLimiter( 30 | rate.NewLimiter(rate.Every(time.Second), 1000), 31 | ) 32 | 33 | // debug logging middleware 34 | lmw := loggermw.LoggerMiddleware(level.Debug(logger)) 35 | 36 | // chain our service wide middlewares 37 | middlewares := endpoint.Chain(lmw, rl) 38 | 39 | // create our client endpoints 40 | return transport.Endpoints{ 41 | Unlock: factory.CreateHTTPEndpoint( 42 | instancer, 43 | middlewares, 44 | "Unlock", 45 | encodeUnlockRequest(route.Unlock), 46 | decodeUnlockResponse, 47 | ), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services/qr/service.go: -------------------------------------------------------------------------------- 1 | package qr 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | ) 8 | 9 | // ServiceName of this service. 10 | const ServiceName = "qr" 11 | 12 | // Service describes our QR service. 13 | type Service interface { 14 | Generate(ctx context.Context, url string, level RecoveryLevel, size int) ([]byte, error) 15 | } 16 | 17 | // QR Service Error descriptions 18 | const ( 19 | ErrorInvalidRecoveryLevel = "invalid recovery level requested" 20 | ErrorInvalidSize = "invalid size requested" 21 | ErrorNoContent = "content can't be empty" 22 | ErrorContentTooLarge = "content size too large" 23 | ErrorGenerate = "unable to generate QR" 24 | ) 25 | 26 | // QR Service Errors 27 | var ( 28 | ErrInvalidRecoveryLevel = errors.New(ErrorInvalidRecoveryLevel) 29 | ErrInvalidSize = errors.New(ErrorInvalidSize) 30 | ErrNoContent = errors.New(ErrorNoContent) 31 | ErrContentTooLarge = errors.New(ErrorContentTooLarge) 32 | ErrGenerate = errors.New(ErrorGenerate) 33 | ) 34 | 35 | // RecoveryLevel : Error detection/recovery capacity. 36 | // See: http://www.qrcode.com/en/about/error_correction.html 37 | type RecoveryLevel int 38 | 39 | // RecoveryLevel enum identifying QR Code Error Correction Capability 40 | const ( 41 | LevelL RecoveryLevel = iota // Level L: 7% error recovery. 42 | LevelM // Level M: 15% error recovery. 43 | LevelQ // Level Q: 25% error recovery. 44 | LevelH // Level H: 30% error recovery. 45 | ) 46 | -------------------------------------------------------------------------------- /services/frontend/transport/http/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | // external 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | // Endpoints holds all available HTTP endpoints for our service. 9 | type Endpoints struct { 10 | Login *mux.Route 11 | EventCreate *mux.Route 12 | EventGet *mux.Route 13 | EventUpdate *mux.Route 14 | EventDelete *mux.Route 15 | EventList *mux.Route 16 | UnlockDevice *mux.Route 17 | GenerateQR *mux.Route 18 | } 19 | 20 | // Initialize wires the HTTP endpoints to our Go kit service endpoints. 21 | func Initialize(router *mux.Router) Endpoints { 22 | return Endpoints{ 23 | Login: router. 24 | Methods("POST"). 25 | Path("/login"). 26 | Name("login"), 27 | EventCreate: router. 28 | Methods("POST"). 29 | Path("/event"). 30 | Name("event_create"), 31 | EventGet: router. 32 | Methods("GET"). 33 | Path("/event/{event_id}"). 34 | Name("event_get"), 35 | EventUpdate: router. 36 | Methods("PUT"). 37 | Path("/event/{event_id}"). 38 | Name("event_update"), 39 | EventDelete: router. 40 | Methods("DELETE"). 41 | Path("/event/{event_id}"). 42 | Name("event_delete"), 43 | EventList: router. 44 | Methods("GET"). 45 | Path("/event"). 46 | Name("event_list"), 47 | UnlockDevice: router. 48 | Methods("POST", "GET"). 49 | Path("/unlock_device/{event_id}/{device_id}"). 50 | Queries("code", "{code}"). 51 | Name("unlock_device"), 52 | GenerateQR: router. 53 | Methods("GET"). 54 | Path("/generate_qr/{event_id}/{device_id}"). 55 | Queries("code", "{code}"). 56 | Name("generate_qr"), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /clients/qr/client.go: -------------------------------------------------------------------------------- 1 | package qr 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/sd" 10 | 11 | //project 12 | "github.com/basvanbeek/opencensus-gokit-example/clients/qr/grpc" 13 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 14 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport" 15 | ) 16 | 17 | // NewGRPCClient returns a new qr client using the gRPC transport. 18 | func NewGRPCClient(instancer sd.Instancer, logger log.Logger) qr.Service { 19 | return &client{ 20 | endpoints: grpc.InitEndpoints(instancer, logger), 21 | logger: logger, 22 | } 23 | } 24 | 25 | // client grpc transport to QR service. 26 | type client struct { 27 | endpoints transport.Endpoints 28 | logger log.Logger 29 | } 30 | 31 | // Generate calls the QR Service Generate method. 32 | func (c client) Generate( 33 | ctx context.Context, data string, recLevel qr.RecoveryLevel, size int, 34 | ) ([]byte, error) { 35 | // we can also validate parameters before sending the request 36 | if len(data) == 0 { 37 | return nil, qr.ErrNoContent 38 | } 39 | if recLevel < qr.LevelL || recLevel > qr.LevelH { 40 | return nil, qr.ErrInvalidRecoveryLevel 41 | } 42 | if size > 4096 { 43 | return nil, qr.ErrInvalidSize 44 | } 45 | 46 | // call our client side go kit endpoint 47 | res, err := c.endpoints.Generate( 48 | ctx, 49 | transport.GenerateRequest{Data: data, Level: recLevel, Size: size}, 50 | ) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | response := res.(transport.GenerateResponse) 56 | return response.QR, nil 57 | } 58 | -------------------------------------------------------------------------------- /clients/qr/grpc/endpoints.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/go-kit/kit/ratelimit" 12 | "github.com/go-kit/kit/sd" 13 | "golang.org/x/time/rate" 14 | "google.golang.org/grpc" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport/pb" 19 | "github.com/basvanbeek/opencensus-gokit-example/shared/factory" 20 | "github.com/basvanbeek/opencensus-gokit-example/shared/grpcconn" 21 | "github.com/basvanbeek/opencensus-gokit-example/shared/loggermw" 22 | ) 23 | 24 | // InitEndpoints returns an initialized set of Go kit gRPC endpoints 25 | func InitEndpoints(instancer sd.Instancer, logger log.Logger) transport.Endpoints { 26 | // initialize our gRPC host mapper helper 27 | hm := grpcconn.NewHostMapper(grpc.WithInsecure()) 28 | 29 | // configure client wide rate limiter for all instances and all method 30 | // endpoints 31 | rl := ratelimit.NewErroringLimiter( 32 | rate.NewLimiter(rate.Every(time.Second), 1000), 33 | ) 34 | 35 | // debug logging middleware 36 | lmw := loggermw.LoggerMiddleware(level.Debug(logger)) 37 | 38 | // chain our service wide middlewares 39 | middlewares := endpoint.Chain(lmw, rl) 40 | 41 | return transport.Endpoints{ 42 | Generate: factory.CreateGRPCEndpoint( 43 | instancer, 44 | hm, 45 | "pb.QR", 46 | middlewares, 47 | "Generate", 48 | pb.GenerateResponse{}, 49 | encodeGenerateRequest, 50 | decodeGenerateResponse, 51 | decodeGenerateError(), 52 | ), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /services/qr/implementation/service.go: -------------------------------------------------------------------------------- 1 | package implementation 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/log/level" 10 | "github.com/skip2/go-qrcode" 11 | 12 | // project 13 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 14 | ) 15 | 16 | // service implements qr.Service 17 | type service struct { 18 | logger log.Logger 19 | } 20 | 21 | // NewService creates and returns a new QR service instance 22 | func NewService(logger log.Logger) qr.Service { 23 | return &service{ 24 | logger: logger, 25 | } 26 | } 27 | 28 | // Generate returns a new QR code image based on provided details 29 | func (s *service) Generate( 30 | ctx context.Context, url string, recLevel qr.RecoveryLevel, size int, 31 | ) ([]byte, error) { 32 | logger := log.With(s.logger, "method", "Generate") 33 | 34 | // test for valid input 35 | if len(url) == 0 { 36 | return nil, qr.ErrNoContent 37 | } 38 | if recLevel < qr.LevelL || recLevel > qr.LevelH { 39 | return nil, qr.ErrInvalidRecoveryLevel 40 | } 41 | if size > 4096 { 42 | return nil, qr.ErrInvalidSize 43 | } 44 | 45 | // do the actual work 46 | b, err := qrcode.Encode(url, qrcode.RecoveryLevel(recLevel), size) 47 | if err != nil { 48 | // actual qrcode lib error... log it... 49 | level.Error(logger).Log("err", err) 50 | 51 | // consumer of this api gets a generic error returned so we don't leak 52 | // implementation details upstream 53 | switch err.Error() { 54 | case "content too long to encode", "length too long to be represented": 55 | err = qr.ErrContentTooLarge 56 | default: 57 | err = qr.ErrGenerate 58 | } 59 | } 60 | 61 | return b, err 62 | } 63 | -------------------------------------------------------------------------------- /clients/device/grpc/endpoints.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/go-kit/kit/ratelimit" 12 | "github.com/go-kit/kit/sd" 13 | "golang.org/x/time/rate" 14 | "google.golang.org/grpc" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/pb" 19 | "github.com/basvanbeek/opencensus-gokit-example/shared/factory" 20 | "github.com/basvanbeek/opencensus-gokit-example/shared/grpcconn" 21 | "github.com/basvanbeek/opencensus-gokit-example/shared/loggermw" 22 | ) 23 | 24 | // InitEndpoints returns an initialized set of Go kit gRPC endpoints 25 | func InitEndpoints(instancer sd.Instancer, logger log.Logger) transport.Endpoints { 26 | // initialize our gRPC host mapper helper 27 | hm := grpcconn.NewHostMapper(grpc.WithInsecure()) 28 | 29 | // configure client wide rate limiter for all instances and all method 30 | // endpoints 31 | rl := ratelimit.NewErroringLimiter( 32 | rate.NewLimiter(rate.Every(time.Second), 1000), 33 | ) 34 | 35 | // debug logging middleware 36 | lmw := loggermw.LoggerMiddleware(level.Debug(logger)) 37 | 38 | // chain our service wide middlewares 39 | middlewares := endpoint.Chain(lmw, rl) 40 | 41 | return transport.Endpoints{ 42 | Unlock: factory.CreateGRPCEndpoint( 43 | instancer, 44 | hm, 45 | "pb.Device", 46 | middlewares, 47 | "Unlock", 48 | pb.UnlockResponse{}, 49 | encodeUnlockRequest, 50 | decodeUnlockResponse, 51 | decodeUnlockError(), 52 | ), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /services/device/implementation/service.go: -------------------------------------------------------------------------------- 1 | package implementation 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "database/sql" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/kevinburke/go.uuid" 12 | "golang.org/x/crypto/bcrypt" 13 | 14 | // project 15 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/device/database" 17 | ) 18 | 19 | // service implements frontend.Service 20 | type service struct { 21 | repository database.Repository 22 | logger log.Logger 23 | } 24 | 25 | // NewService creates and returns a new Device service instance 26 | func NewService(rep database.Repository, logger log.Logger) device.Service { 27 | return &service{ 28 | repository: rep, 29 | logger: logger, 30 | } 31 | } 32 | 33 | // Unlock returns new session data for allowing device to check-in participants. 34 | func (s *service) Unlock( 35 | ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string, 36 | ) (*device.Session, error) { 37 | logger := log.With(s.logger, "method", "Unlock") 38 | 39 | details, err := s.repository.GetDevice(ctx, eventID, deviceID) 40 | if err != nil { 41 | if err != sql.ErrNoRows { 42 | level.Error(logger).Log("err", err) 43 | return nil, device.ErrRepository 44 | } 45 | details = &database.Session{} 46 | } 47 | 48 | if err = bcrypt.CompareHashAndPassword( 49 | details.UnlockHash, []byte(unlockCode), 50 | ); err != nil { 51 | level.Error(logger).Log("err", err) 52 | return nil, device.ErrUnlockNotFound 53 | } 54 | 55 | return &device.Session{ 56 | EventCaption: details.EventCaption, 57 | DeviceCaption: details.DeviceCaption, 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /clients/device/client.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/sd" 10 | "github.com/kevinburke/go.uuid" 11 | 12 | // project 13 | "github.com/basvanbeek/opencensus-gokit-example/clients/device/grpc" 14 | "github.com/basvanbeek/opencensus-gokit-example/clients/device/http" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 17 | ) 18 | 19 | // NewHTTPClient returns a new device client using the HTTP transport. 20 | func NewHTTPClient(instancer sd.Instancer, logger log.Logger) device.Service { 21 | return &client{ 22 | endpoints: http.InitEndpoints(instancer, logger), 23 | logger: logger, 24 | } 25 | } 26 | 27 | // NewGRPCClient returns a new device client using the gRPC transport 28 | func NewGRPCClient(instancer sd.Instancer, logger log.Logger) device.Service { 29 | return &client{ 30 | endpoints: grpc.InitEndpoints(instancer, logger), 31 | logger: logger, 32 | } 33 | 34 | } 35 | 36 | type client struct { 37 | endpoints transport.Endpoints 38 | logger log.Logger 39 | } 40 | 41 | func (c client) Unlock(ctx context.Context, eventID, deviceID uuid.UUID, code string) (*device.Session, error) { 42 | res, err := c.endpoints.Unlock(ctx, transport.UnlockRequest{ 43 | EventID: eventID, 44 | DeviceID: deviceID, 45 | Code: code, 46 | }) 47 | if err != nil { 48 | // transport logic / unknown error 49 | return nil, err 50 | } 51 | 52 | response := res.(transport.UnlockResponse) 53 | if response.Err != nil { 54 | // business logic error 55 | return nil, err 56 | } 57 | 58 | return &device.Session{ 59 | EventCaption: response.EventCaption, 60 | DeviceCaption: response.DeviceCaption, 61 | }, nil 62 | } 63 | -------------------------------------------------------------------------------- /shared/oc/trace_endpoints.go: -------------------------------------------------------------------------------- 1 | package oc 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | kitoc "github.com/go-kit/kit/tracing/opencensus" 10 | "go.opencensus.io/trace" 11 | ) 12 | 13 | // BalancerType used in Retry logic 14 | type BalancerType string 15 | 16 | // BalancerTypes 17 | const ( 18 | Random BalancerType = "random" 19 | RoundRobin BalancerType = "round robin" 20 | ) 21 | 22 | // ClientEndpoint adds our Endpoint Tracing middleware to the existing client 23 | // side endpoint. 24 | func ClientEndpoint(operationName string, attrs ...trace.Attribute) endpoint.Middleware { 25 | attrs = append( 26 | attrs, trace.StringAttribute("gokit.endpoint.type", "client"), 27 | ) 28 | return kitoc.TraceEndpoint( 29 | "gokit/endpoint "+operationName, 30 | kitoc.WithEndpointAttributes(attrs...), 31 | ) 32 | } 33 | 34 | // ServerEndpoint adds our Endpoint Tracing middleware to the existing server 35 | // side endpoint. 36 | func ServerEndpoint(operationName string, attrs ...trace.Attribute) endpoint.Middleware { 37 | attrs = append( 38 | attrs, trace.StringAttribute("gokit.endpoint.type", "server"), 39 | ) 40 | return kitoc.TraceEndpoint( 41 | "gokit/endpoint "+operationName, 42 | kitoc.WithEndpointAttributes(attrs...), 43 | ) 44 | } 45 | 46 | // RetryEndpoint wraps a Go kit lb.Retry endpoint with an annotated span. 47 | func RetryEndpoint( 48 | operationName string, balancer BalancerType, max int, timeout time.Duration, 49 | ) endpoint.Middleware { 50 | return kitoc.TraceEndpoint("gokit/retry "+operationName, 51 | kitoc.WithEndpointAttributes( 52 | trace.StringAttribute("gokit.balancer.type", string(balancer)), 53 | trace.StringAttribute("gokit.retry.timeout", timeout.String()), 54 | trace.Int64Attribute("gokit.retry.max_count", int64(max)), 55 | ), 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /shared/grpcconn/host_mapper.go: -------------------------------------------------------------------------------- 1 | package grpcconn 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | "sync" 7 | 8 | // external 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // HostMapper manages a map of discovered instances with accompanying gRPC 13 | // client connections. 14 | type HostMapper interface { 15 | Get(instance string) (*grpc.ClientConn, io.Closer, error) 16 | } 17 | 18 | // NewHostMapper initializes and returns a HostMapper for a gRPC service. 19 | func NewHostMapper(dialOptions ...grpc.DialOption) HostMapper { 20 | return &hostMapper{ 21 | host: make(map[string]*grpc.ClientConn), 22 | DialOptions: dialOptions, 23 | } 24 | } 25 | 26 | // hostMapper implements HostMapper 27 | type hostMapper struct { 28 | mtx sync.Mutex 29 | host map[string]*grpc.ClientConn 30 | DialOptions []grpc.DialOption 31 | } 32 | 33 | // Get a gRPC client connection for provided instance. 34 | func (h *hostMapper) Get(instance string) (*grpc.ClientConn, io.Closer, error) { 35 | h.mtx.Lock() 36 | defer h.mtx.Unlock() 37 | 38 | if conn := h.host[instance]; conn != nil { 39 | return conn, &closer{h, instance}, nil 40 | } 41 | 42 | conn, err := grpc.Dial(instance, h.DialOptions...) 43 | if err != nil { 44 | return nil, nil, err 45 | } 46 | 47 | h.host[instance] = conn 48 | 49 | return conn, &closer{h, instance}, nil 50 | } 51 | 52 | // remove closes a gRPC client connection and removes it from the map. 53 | func (h *hostMapper) remove(instance string) { 54 | h.mtx.Lock() 55 | if conn := h.host[instance]; conn != nil { 56 | defer conn.Close() 57 | delete(h.host, instance) 58 | } 59 | h.mtx.Unlock() 60 | } 61 | 62 | type closer struct { 63 | hm *hostMapper 64 | instance string 65 | } 66 | 67 | // Close implements io.Closer 68 | func (c *closer) Close() error { 69 | c.hm.remove(c.instance) 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /clients/device/grpc/encode_decode.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/go-kit/kit/endpoint" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/pb" 16 | ) 17 | 18 | func encodeUnlockRequest(_ context.Context, request interface{}) (interface{}, error) { 19 | req := request.(transport.UnlockRequest) 20 | return &pb.UnlockRequest{ 21 | EventId: req.EventID.Bytes(), 22 | }, nil 23 | } 24 | 25 | func decodeUnlockResponse(_ context.Context, response interface{}) (interface{}, error) { 26 | res := response.(*pb.UnlockResponse) 27 | return transport.UnlockResponse{ 28 | DeviceCaption: res.DeviceCaption, 29 | EventCaption: res.EventCaption, 30 | }, nil 31 | } 32 | 33 | func decodeUnlockError() endpoint.Middleware { 34 | return func(e endpoint.Endpoint) endpoint.Endpoint { 35 | return func(ctx context.Context, request interface{}) (interface{}, error) { 36 | // call our gRPC client endpoint 37 | response, err := e(ctx, request) 38 | // check error response 39 | st, _ := status.FromError(err) 40 | switch st.Code() { 41 | case codes.OK: 42 | // no error encountered... proceed with regular response payload 43 | return response, nil 44 | case codes.InvalidArgument, codes.Unauthenticated: 45 | // business logic error which should not retry or trigger 46 | // the circuitbreaker as service is behaving normally. 47 | return transport.UnlockResponse{Err: errors.New(st.Message())}, nil 48 | default: 49 | // error which might invoke a retry or trigger a circuitbreaker 50 | return nil, errors.New(st.Message()) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /services/device/database/sqlite/sqlite.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "database/sql" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/jmoiron/sqlx" 12 | "github.com/kevinburke/go.uuid" 13 | "github.com/openxact/versioning" 14 | 15 | // project 16 | "github.com/basvanbeek/opencensus-gokit-example/services/device/database" 17 | ) 18 | 19 | type sqlite struct { 20 | db *sqlx.DB 21 | logger log.Logger 22 | } 23 | 24 | // New returns a new Repository backed by SQLite 25 | func New(db *sqlx.DB, logger log.Logger) (database.Repository, error) { 26 | // run our embedded database versioning logic 27 | versioner, err := versioning.New( 28 | db, "ocgokitexample.device", level.Debug(logger), false, 29 | ) 30 | if err != nil { 31 | return nil, err 32 | } 33 | versioner.Add(1, v1) 34 | if _, err = versioner.Run(); err != nil { 35 | return nil, err 36 | } 37 | 38 | // return our repository 39 | return &sqlite{ 40 | db: db, 41 | logger: log.With(logger, "rep", "sqlite"), 42 | }, nil 43 | } 44 | 45 | // GetDevice retrieves device information 46 | func (s *sqlite) GetDevice(ctx context.Context, eventID, deviceID uuid.UUID) (*database.Session, error) { 47 | var session = &database.Session{} 48 | 49 | if err := s.db.QueryRowContext( 50 | ctx, 51 | ` 52 | SELECT e.name as event_caption, d.name as device_caption, d.hash 53 | FROM event e INNER JOIN device d ON e.id = d.event_id 54 | WHERE event_id = ?1 AND device_id = ?2; 55 | `, 56 | eventID.Bytes(), deviceID.Bytes(), 57 | ).Scan( 58 | session.EventCaption, session.DeviceCaption, session.UnlockHash, 59 | ); err != nil { 60 | if err == sql.ErrNoRows { 61 | return nil, database.ErrNotFound 62 | } 63 | level.Error(s.logger).Log("err", err.Error()) 64 | return nil, database.ErrRepository 65 | } 66 | 67 | return session, nil 68 | } 69 | 70 | // Close implements io.Closer 71 | func (s *sqlite) Close() error { 72 | return s.db.Close() 73 | } 74 | -------------------------------------------------------------------------------- /clients/device/http/encode_decode.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "io/ioutil" 9 | "net/http" 10 | 11 | // external 12 | kithttp "github.com/go-kit/kit/transport/http" 13 | "github.com/gorilla/mux" 14 | 15 | // project 16 | 17 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 19 | ) 20 | 21 | func encodeUnlockRequest(route *mux.Route) kithttp.EncodeRequestFunc { 22 | return func(_ context.Context, r *http.Request, request interface{}) error { 23 | var ( 24 | err error 25 | req = request.(transport.UnlockRequest) 26 | ) 27 | 28 | if r.URL, err = route.Host(r.URL.Host).URL( 29 | "event_id", req.EventID.String(), 30 | "device_id", req.DeviceID.String(), 31 | "code", req.Code, 32 | ); err != nil { 33 | return err 34 | } 35 | if methods, err := route.GetMethods(); err == nil { 36 | r.Method = methods[0] 37 | } 38 | 39 | return nil 40 | } 41 | } 42 | 43 | func decodeUnlockResponse(_ context.Context, response *http.Response) (interface{}, error) { 44 | var res transport.UnlockResponse 45 | if response.StatusCode != http.StatusOK { 46 | b, err := ioutil.ReadAll(response.Body) 47 | if err != nil { 48 | return nil, err 49 | } 50 | errStr := string(b) 51 | switch errStr { 52 | case device.ErrorRequireEventID: 53 | res.Err = device.ErrRequireEventID 54 | case device.ErrorRequireDeviceID: 55 | res.Err = device.ErrRequireDeviceID 56 | case device.ErrorRequireUnlockCode: 57 | res.Err = device.ErrRequireUnlockCode 58 | case device.ErrorRepository: 59 | res.Err = device.ErrRepository 60 | case device.ErrorEventNotFound: 61 | res.Err = device.ErrEventNotFound 62 | case device.ErrorUnlockNotFound: 63 | res.Err = device.ErrUnlockNotFound 64 | default: 65 | return nil, errors.New(errStr) 66 | } 67 | return res, nil 68 | } 69 | dec := json.NewDecoder(response.Body) 70 | if err := dec.Decode(&res); err != nil { 71 | return nil, err 72 | } 73 | return res, nil 74 | } 75 | -------------------------------------------------------------------------------- /services/event/transport/endpoints.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | 10 | // project 11 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 12 | ) 13 | 14 | // Endpoints holds all Go kit endpoints for the service. 15 | type Endpoints struct { 16 | Create endpoint.Endpoint 17 | Get endpoint.Endpoint 18 | Update endpoint.Endpoint 19 | Delete endpoint.Endpoint 20 | List endpoint.Endpoint 21 | } 22 | 23 | // MakeEndpoints initializes all Go kit endpoints for the service. 24 | func MakeEndpoints(s event.Service) Endpoints { 25 | return Endpoints{ 26 | Create: makeCreateEndpoint(s), 27 | Get: makeGetEndpoint(s), 28 | Update: makeUpdateEndpoint(s), 29 | Delete: makeDeleteEndpoint(s), 30 | List: makeListEndpoint(s), 31 | } 32 | } 33 | 34 | func makeCreateEndpoint(s event.Service) endpoint.Endpoint { 35 | return func(ctx context.Context, request interface{}) (interface{}, error) { 36 | req := request.(CreateRequest) 37 | id, err := s.Create(ctx, req.TenantID, req.Event) 38 | return CreateResponse{ID: id, Err: err}, nil 39 | } 40 | } 41 | 42 | func makeGetEndpoint(s event.Service) endpoint.Endpoint { 43 | return func(ctx context.Context, request interface{}) (interface{}, error) { 44 | req := request.(GetRequest) 45 | event, err := s.Get(ctx, req.TenantID, req.ID) 46 | return GetResponse{Event: event, Err: err}, nil 47 | } 48 | } 49 | 50 | func makeUpdateEndpoint(s event.Service) endpoint.Endpoint { 51 | return func(ctx context.Context, request interface{}) (interface{}, error) { 52 | req := request.(UpdateRequest) 53 | err := s.Update(ctx, req.TenantID, req.Event) 54 | return UpdateResponse{Err: err}, nil 55 | } 56 | } 57 | 58 | func makeDeleteEndpoint(s event.Service) endpoint.Endpoint { 59 | return func(ctx context.Context, request interface{}) (interface{}, error) { 60 | req := request.(DeleteRequest) 61 | err := s.Delete(ctx, req.TenantID, req.ID) 62 | return DeleteResponse{Err: err}, nil 63 | } 64 | } 65 | 66 | func makeListEndpoint(s event.Service) endpoint.Endpoint { 67 | return func(ctx context.Context, request interface{}) (interface{}, error) { 68 | req := request.(ListRequest) 69 | events, err := s.List(ctx, req.TenantID) 70 | return ListResponse{Events: events, Err: err}, nil 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /services/device/transport/http/service.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "encoding/json" 7 | "net/http" 8 | 9 | // external 10 | 11 | "github.com/go-kit/kit/log" 12 | kithttp "github.com/go-kit/kit/transport/http" 13 | "github.com/gorilla/mux" 14 | 15 | // project 16 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 17 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/http/routes" 19 | ) 20 | 21 | // NewService wires our Go kit endpoints to the HTTP transport. 22 | func NewService( 23 | svcEndpoints transport.Endpoints, options []kithttp.ServerOption, 24 | logger log.Logger, 25 | ) http.Handler { 26 | // set-up router and initialize http endpoints 27 | var ( 28 | router = mux.NewRouter() 29 | route = routes.Initialize(router) 30 | errorLogger = kithttp.ServerErrorLogger(logger) 31 | errorEncoder = kithttp.ServerErrorEncoder(encodeErrorResponse) 32 | ) 33 | 34 | options = append(options, errorLogger, errorEncoder) 35 | 36 | // wire our Go kit handlers to the http endpoints 37 | route.Unlock.Handler(kithttp.NewServer( 38 | svcEndpoints.Unlock, decodeUnlockRequest, encodeUnlockResponse, 39 | options..., 40 | )) 41 | 42 | // return our router as http handler 43 | return router 44 | } 45 | 46 | // decode / encode functions for converting between http transport payloads and 47 | // Go kit request_response payloads. 48 | 49 | func decodeUnlockRequest(_ context.Context, r *http.Request) (interface{}, error) { 50 | var req transport.UnlockRequest 51 | err := json.NewDecoder(r.Body).Decode(&req) 52 | return req, err 53 | } 54 | 55 | func encodeUnlockResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 56 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 57 | return json.NewEncoder(w).Encode(response) 58 | } 59 | 60 | func encodeErrorResponse(_ context.Context, err error, w http.ResponseWriter) { 61 | var code int 62 | switch err { 63 | case device.ErrRequireEventID, device.ErrRequireDeviceID, device.ErrRequireUnlockCode: 64 | code = http.StatusBadRequest 65 | case device.ErrEventNotFound, device.ErrUnlockNotFound: 66 | code = http.StatusUnauthorized 67 | default: 68 | code = http.StatusInternalServerError 69 | } 70 | http.Error(w, err.Error(), code) 71 | } 72 | -------------------------------------------------------------------------------- /services/event/transport/request_response.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // external 5 | "github.com/go-kit/kit/endpoint" 6 | "github.com/kevinburke/go.uuid" 7 | 8 | // project 9 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 10 | ) 11 | 12 | var ( 13 | _ endpoint.Failer = CreateResponse{} 14 | _ endpoint.Failer = GetResponse{} 15 | _ endpoint.Failer = UpdateResponse{} 16 | _ endpoint.Failer = DeleteResponse{} 17 | _ endpoint.Failer = ListResponse{} 18 | ) 19 | 20 | // CreateRequest holds the request parameters for the Create method. 21 | type CreateRequest struct { 22 | TenantID uuid.UUID 23 | Event event.Event 24 | } 25 | 26 | // CreateResponse holds the response values for the Create method. 27 | type CreateResponse struct { 28 | ID *uuid.UUID 29 | Err error 30 | } 31 | 32 | // Failed implements Failer 33 | func (r CreateResponse) Failed() error { return r.Err } 34 | 35 | // GetRequest holds the request parameters for the Get method. 36 | type GetRequest struct { 37 | TenantID uuid.UUID 38 | ID uuid.UUID 39 | } 40 | 41 | // GetResponse holds the response values for the Get method. 42 | type GetResponse struct { 43 | Event *event.Event 44 | Err error 45 | } 46 | 47 | // Failed implements Failer 48 | func (r GetResponse) Failed() error { return r.Err } 49 | 50 | // UpdateRequest holds the request parameters for the Update method. 51 | type UpdateRequest struct { 52 | TenantID uuid.UUID 53 | Event event.Event 54 | } 55 | 56 | // UpdateResponse holds the response values for the Update method. 57 | type UpdateResponse struct { 58 | Err error 59 | } 60 | 61 | // Failed implements Failer 62 | func (r UpdateResponse) Failed() error { return r.Err } 63 | 64 | // DeleteRequest holds the request parameters for the Delete method. 65 | type DeleteRequest struct { 66 | TenantID uuid.UUID 67 | ID uuid.UUID 68 | } 69 | 70 | // DeleteResponse holds the response values for the Delete method. 71 | type DeleteResponse struct { 72 | Err error 73 | } 74 | 75 | // Failed implements Failer 76 | func (r DeleteResponse) Failed() error { return r.Err } 77 | 78 | // ListRequest holds the request parameters for the List method. 79 | type ListRequest struct { 80 | TenantID uuid.UUID 81 | } 82 | 83 | // ListResponse holds the response values for the List method. 84 | type ListResponse struct { 85 | Events []*event.Event 86 | Err error 87 | } 88 | 89 | // Failed implements Failer 90 | func (r ListResponse) Failed() error { return r.Err } 91 | -------------------------------------------------------------------------------- /clients/qr/grpc/encode_decode.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/go-kit/kit/endpoint" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport/pb" 17 | ) 18 | 19 | // encodeGenerateRequest encodes the outgoing go kit payload to the grpc payload 20 | func encodeGenerateRequest(_ context.Context, request interface{}) (interface{}, error) { 21 | req := request.(transport.GenerateRequest) 22 | return &pb.GenerateRequest{ 23 | Data: req.Data, 24 | Level: int32(req.Level), 25 | Size: int32(req.Size), 26 | }, nil 27 | } 28 | 29 | // decodeGenerateResponse decodes the incoming grpc payload to go kit payload 30 | func decodeGenerateResponse(_ context.Context, response interface{}) (interface{}, error) { 31 | resp := response.(*pb.GenerateResponse) 32 | return transport.GenerateResponse{QR: resp.Image}, nil 33 | } 34 | 35 | func decodeGenerateError() endpoint.Middleware { 36 | return func(e endpoint.Endpoint) endpoint.Endpoint { 37 | return func(ctx context.Context, request interface{}) (interface{}, error) { 38 | // call our gRPC client endpoint 39 | response, err := e(ctx, request) 40 | // check error response 41 | st, _ := status.FromError(err) 42 | switch st.Code() { 43 | case codes.OK: 44 | // no error encountered... proceed with regular response payload 45 | return response, nil 46 | case codes.InvalidArgument, codes.FailedPrecondition: 47 | // business logic error which should not be retried or trigger 48 | // the circuitbreaker. 49 | switch st.Message() { 50 | case qr.ErrorInvalidRecoveryLevel: 51 | err = qr.ErrInvalidRecoveryLevel 52 | case qr.ErrorInvalidSize: 53 | err = qr.ErrInvalidSize 54 | case qr.ErrorNoContent: 55 | err = qr.ErrNoContent 56 | case qr.ErrorContentTooLarge: 57 | err = qr.ErrContentTooLarge 58 | } 59 | return transport.GenerateResponse{Err: err}, nil 60 | default: 61 | // error which might invoke a retry or trigger a circuitbreaker 62 | switch st.Message() { 63 | case qr.ErrorGenerate: 64 | err = qr.ErrGenerate 65 | default: 66 | err = errors.New(st.Message()) 67 | } 68 | return nil, err 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /services/qr/transport/grpc/service.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | kitgrpc "github.com/go-kit/kit/transport/grpc" 10 | oldcontext "golang.org/x/net/context" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | 14 | // project 15 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport" 17 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport/pb" 18 | ) 19 | 20 | // grpc transport service for QR service. 21 | type grpcServer struct { 22 | generate kitgrpc.Handler 23 | logger log.Logger 24 | } 25 | 26 | // NewGRPCServer returns a new gRPC service for the provided Go kit endpoints 27 | func NewGRPCServer( 28 | endpoints transport.Endpoints, options []kitgrpc.ServerOption, 29 | logger log.Logger, 30 | ) pb.QRServer { 31 | errorLogger := kitgrpc.ServerErrorLogger(logger) 32 | options = append(options, errorLogger) 33 | 34 | return &grpcServer{ 35 | generate: kitgrpc.NewServer( 36 | endpoints.Generate, decodeGenerateRequest, encodeGenerateResponse, options..., 37 | ), 38 | logger: logger, 39 | } 40 | } 41 | 42 | // Generate glues the gRPC method to the Go kit service method 43 | func (s *grpcServer) Generate(ctx oldcontext.Context, req *pb.GenerateRequest) (*pb.GenerateResponse, error) { 44 | _, rep, err := s.generate.ServeGRPC(ctx, req) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return rep.(*pb.GenerateResponse), nil 49 | } 50 | 51 | // decodeGenerateRequest decodes the incoming grpc payload to our go kit payload 52 | func decodeGenerateRequest(_ context.Context, request interface{}) (interface{}, error) { 53 | req := request.(*pb.GenerateRequest) 54 | return transport.GenerateRequest{ 55 | Data: req.Data, 56 | Level: qr.RecoveryLevel(req.Level), 57 | Size: int(req.Size), 58 | }, nil 59 | } 60 | 61 | // encodeGenerateResponse encodes the outgoing go kit payload to the grpc payload 62 | func encodeGenerateResponse(_ context.Context, response interface{}) (interface{}, error) { 63 | res := response.(transport.GenerateResponse) 64 | err := res.Failed() 65 | 66 | switch err { 67 | case nil: 68 | return &pb.GenerateResponse{Image: res.QR}, nil 69 | case qr.ErrInvalidRecoveryLevel, qr.ErrInvalidSize, qr.ErrNoContent: 70 | return nil, status.Error(codes.InvalidArgument, err.Error()) 71 | case qr.ErrContentTooLarge: 72 | return nil, status.Error(codes.FailedPrecondition, err.Error()) 73 | case qr.ErrGenerate: 74 | return nil, status.Error(codes.Internal, err.Error()) 75 | default: 76 | return nil, status.Error(codes.Unknown, err.Error()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /services/device/transport/grpc/service.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | kitgrpc "github.com/go-kit/kit/transport/grpc" 10 | "github.com/kevinburke/go.uuid" 11 | oldcontext "golang.org/x/net/context" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | 15 | // project 16 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 17 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/pb" 19 | ) 20 | 21 | // grpc transport service for QR service. 22 | type grpcServer struct { 23 | unlock kitgrpc.Handler 24 | logger log.Logger 25 | } 26 | 27 | // NewService returns a new gRPC service for the provided Go kit endpoints 28 | func NewService( 29 | endpoints transport.Endpoints, options []kitgrpc.ServerOption, 30 | logger log.Logger, 31 | ) pb.DeviceServer { 32 | var ( 33 | errorLogger = kitgrpc.ServerErrorLogger(logger) 34 | ) 35 | 36 | options = append(options, errorLogger) 37 | 38 | return &grpcServer{ 39 | unlock: kitgrpc.NewServer( 40 | endpoints.Unlock, decodeUnlockRequest, encodeUnlockResponse, options..., 41 | ), 42 | logger: logger, 43 | } 44 | } 45 | 46 | // Generate glues the gRPC method to the Go kit service method 47 | func (s *grpcServer) Unlock(ctx oldcontext.Context, req *pb.UnlockRequest) (*pb.UnlockResponse, error) { 48 | _, rep, err := s.unlock.ServeGRPC(ctx, req) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return rep.(*pb.UnlockResponse), nil 53 | } 54 | 55 | // decodeUnlockRequest decodes the incoming grpc payload to our go kit payload 56 | func decodeUnlockRequest(_ context.Context, request interface{}) (interface{}, error) { 57 | req := request.(*pb.UnlockRequest) 58 | return transport.UnlockRequest{ 59 | EventID: uuid.FromBytesOrNil(req.EventId), 60 | DeviceID: uuid.FromBytesOrNil(req.DeviceId), 61 | Code: req.Code, 62 | }, nil 63 | } 64 | 65 | // encodeUnlockResponse encodes the outgoing go kit payload to the grpc payload 66 | func encodeUnlockResponse(_ context.Context, response interface{}) (interface{}, error) { 67 | res := response.(transport.UnlockResponse) 68 | switch res.Err { 69 | case nil: 70 | return &pb.UnlockResponse{ 71 | EventCaption: res.EventCaption, 72 | DeviceCaption: res.DeviceCaption, 73 | }, nil 74 | case device.ErrRequireEventID, device.ErrRequireDeviceID, device.ErrRequireUnlockCode: 75 | return nil, status.Error(codes.InvalidArgument, res.Err.Error()) 76 | case device.ErrEventNotFound, device.ErrUnlockNotFound: 77 | return nil, status.Error(codes.Unauthenticated, res.Err.Error()) 78 | default: 79 | return nil, status.Error(codes.Unknown, res.Err.Error()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /clients/frontend/http/endpoints.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/go-kit/kit/ratelimit" 12 | "github.com/go-kit/kit/sd" 13 | "github.com/gorilla/mux" 14 | "golang.org/x/time/rate" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport/http/routes" 19 | "github.com/basvanbeek/opencensus-gokit-example/shared/factory" 20 | "github.com/basvanbeek/opencensus-gokit-example/shared/loggermw" 21 | ) 22 | 23 | // InitEndpoints returns an initialized set of Go kit HTTP endpoints. 24 | func InitEndpoints(instancer sd.Instancer, logger log.Logger) transport.Endpoints { 25 | route := routes.Initialize(mux.NewRouter()) 26 | 27 | // configure client wide rate limiter for all instances and all method 28 | // endpoints 29 | rl := ratelimit.NewErroringLimiter( 30 | rate.NewLimiter(rate.Every(time.Second), 1000), 31 | ) 32 | 33 | // debug logging middleware 34 | lmw := loggermw.LoggerMiddleware(level.Debug(logger)) 35 | 36 | // chain our service wide middlewares 37 | middlewares := endpoint.Chain(lmw, rl) 38 | 39 | // create our client endpoints 40 | return transport.Endpoints{ 41 | Login: factory.CreateHTTPEndpoint( 42 | instancer, 43 | middlewares, 44 | "Login", 45 | factory.EncodeGenericRequest(route.Login), 46 | decodeLoginResponse, 47 | ), 48 | EventCreate: factory.CreateHTTPEndpoint( 49 | instancer, 50 | middlewares, 51 | "EventCreate", 52 | factory.EncodeGenericRequest(route.EventCreate), 53 | decodeEventCreateResponse, 54 | ), 55 | EventGet: factory.CreateHTTPEndpoint( 56 | instancer, 57 | middlewares, 58 | "EventGet", 59 | factory.EncodeGenericRequest(route.EventGet), 60 | decodeEventGetResponse, 61 | ), 62 | EventUpdate: factory.CreateHTTPEndpoint( 63 | instancer, 64 | middlewares, 65 | "EventUpdate", 66 | factory.EncodeGenericRequest(route.EventUpdate), 67 | decodeEventUpdateResponse, 68 | ), 69 | EventDelete: factory.CreateHTTPEndpoint( 70 | instancer, 71 | middlewares, 72 | "EventDelete", 73 | factory.EncodeGenericRequest(route.EventDelete), 74 | decodeEventDeleteResponse, 75 | ), 76 | EventList: factory.CreateHTTPEndpoint( 77 | instancer, 78 | middlewares, 79 | "EventList", 80 | factory.EncodeGenericRequest(route.EventList), 81 | decodeEventListResponse, 82 | ), 83 | UnlockDevice: factory.CreateHTTPEndpoint( 84 | instancer, 85 | middlewares, 86 | "UnlockDevice", 87 | factory.EncodeGenericRequest(route.UnlockDevice), 88 | decodeUnlockDeviceResponse, 89 | ), 90 | GenerateQR: factory.CreateHTTPEndpoint( 91 | instancer, 92 | middlewares, 93 | "GenerateQR", 94 | factory.EncodeGenericRequest(route.GenerateQR), 95 | decodeGenerateQRResponse, 96 | ), 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /services/frontend/service.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/kevinburke/go.uuid" 10 | ) 11 | 12 | // ServiceName of this service. 13 | const ServiceName = "frontend" 14 | 15 | // Service describes our Frontend service. 16 | type Service interface { 17 | Login(ctx context.Context, user, pass string) (*Login, error) 18 | 19 | EventCreate(ctx context.Context, tenantID uuid.UUID, event Event) (*uuid.UUID, error) 20 | EventGet(ctx context.Context, tenantID, eventID uuid.UUID) (*Event, error) 21 | EventUpdate(ctx context.Context, tenantID uuid.UUID, event Event) error 22 | EventDelete(ctx context.Context, tenantID, eventID uuid.UUID) error 23 | EventList(ctx context.Context, tenantID uuid.UUID) ([]*Event, error) 24 | 25 | UnlockDevice(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) (*Session, error) 26 | 27 | GenerateQR(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) ([]byte, error) 28 | } 29 | 30 | // Frontend Service Error descriptions 31 | const ( 32 | ErrorService = "internal service error" 33 | ErrorUnauthorized = "unauthorized" 34 | ErrorUserPassRequired = "both user and pass are required" 35 | ErrorUserPassUnknown = "unknown user/pass combination" 36 | ErrorRequireEventID = "missing required event id" 37 | ErrorRequireDeviceID = "missing required device id" 38 | ErrorRequireUnlockCode = "missing required unlock code" 39 | ErrorEventNotFound = "event not found" 40 | ErrorEventExists = "event already exists" 41 | ErrorUnlockNotFound = "device / unlock code combination not found" 42 | ErrorInvalidQRParams = "QR Code can't be generated using provided parameters" 43 | ErrorQRGenerate = "QR Code generator failed" 44 | ) 45 | 46 | // Frontend Service Errors 47 | var ( 48 | ErrService = errors.New(ErrorService) 49 | ErrUnauthorized = errors.New(ErrorUnauthorized) 50 | ErrUserPassRequired = errors.New(ErrorUserPassRequired) 51 | ErrUserPassUnknown = errors.New(ErrorUserPassUnknown) 52 | ErrRequireEventID = errors.New(ErrorRequireEventID) 53 | ErrRequireDeviceID = errors.New(ErrorRequireDeviceID) 54 | ErrRequireUnlockCode = errors.New(ErrorRequireUnlockCode) 55 | ErrEventNotFound = errors.New(ErrorEventNotFound) 56 | ErrEventExists = errors.New(ErrorEventExists) 57 | ErrUnlockNotFound = errors.New(ErrorUnlockNotFound) 58 | 59 | ErrInvalidQRParams = errors.New(ErrorInvalidQRParams) 60 | ErrQRGenerate = errors.New(ErrorQRGenerate) 61 | ) 62 | 63 | // Login holds login details 64 | type Login struct { 65 | ID uuid.UUID 66 | Name string 67 | TenantID uuid.UUID 68 | TenantName string 69 | } 70 | 71 | // Event holds event details 72 | type Event struct { 73 | ID uuid.UUID `json:"id"` 74 | Name string `json:"name"` 75 | } 76 | 77 | // Session holds session details 78 | type Session struct { 79 | EventID uuid.UUID `json:"event_id,omitempty"` 80 | EventCaption string `json:"event_caption,omitempty"` 81 | DeviceID uuid.UUID `json:"device_id,omitempty"` 82 | DeviceCaption string `json:"device_caption,omitempty"` 83 | Token string `json:"token,omitempty"` 84 | } 85 | -------------------------------------------------------------------------------- /services/event/transport/twirp/service.go: -------------------------------------------------------------------------------- 1 | package twirp 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/kevinburke/go.uuid" 10 | "github.com/twitchtv/twirp" 11 | 12 | // project 13 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 14 | "github.com/basvanbeek/opencensus-gokit-example/services/event/transport/pb" 15 | ) 16 | 17 | type server struct { 18 | svc event.Service 19 | logger log.Logger 20 | } 21 | 22 | // NewService returns a new Service backed by Twirp transport. 23 | func NewService(svc event.Service, logger log.Logger) pb.Event { 24 | return &server{ 25 | svc: svc, 26 | logger: logger, 27 | } 28 | } 29 | 30 | func (s *server) Create(ctx context.Context, r *pb.CreateRequest) (*pb.CreateResponse, error) { 31 | id, err := s.svc.Create( 32 | ctx, 33 | uuid.FromBytesOrNil(r.TenantId), 34 | event.Event{ 35 | ID: uuid.FromBytesOrNil(r.Event.Id), 36 | Name: r.Event.Name, 37 | }, 38 | ) 39 | 40 | switch err { 41 | case nil: 42 | return &pb.CreateResponse{Id: id.Bytes()}, nil 43 | case event.ErrEventExists: 44 | return nil, twirp.NewError(twirp.AlreadyExists, err.Error()) 45 | default: 46 | return nil, twirp.InternalErrorWith(err) 47 | } 48 | } 49 | 50 | func (s *server) Get(ctx context.Context, r *pb.GetRequest) (*pb.GetResponse, error) { 51 | evt, err := s.svc.Get( 52 | ctx, 53 | uuid.FromBytesOrNil(r.TenantId), 54 | uuid.FromBytesOrNil(r.Id), 55 | ) 56 | 57 | switch err { 58 | case nil: 59 | return &pb.GetResponse{ 60 | Event: &pb.EventObj{Id: evt.ID.Bytes(), Name: evt.Name}, 61 | }, nil 62 | case event.ErrNotFound: 63 | return nil, twirp.NotFoundError(err.Error()) 64 | default: 65 | return nil, twirp.InternalErrorWith(err) 66 | } 67 | } 68 | 69 | func (s *server) Update(ctx context.Context, r *pb.UpdateRequest) (*pb.UpdateResponse, error) { 70 | err := s.svc.Update( 71 | ctx, 72 | uuid.FromBytesOrNil(r.TenantId), 73 | event.Event{ 74 | ID: uuid.FromBytesOrNil(r.Event.Id), 75 | Name: r.Event.Name, 76 | }, 77 | ) 78 | 79 | switch err { 80 | case nil: 81 | return &pb.UpdateResponse{}, nil 82 | case event.ErrNotFound: 83 | return nil, twirp.NotFoundError(err.Error()) 84 | case event.ErrEventExists: 85 | return nil, twirp.NewError(twirp.AlreadyExists, err.Error()) 86 | default: 87 | return nil, twirp.InternalErrorWith(err) 88 | } 89 | } 90 | 91 | func (s *server) Delete(ctx context.Context, r *pb.DeleteRequest) (*pb.DeleteResponse, error) { 92 | err := s.svc.Delete( 93 | ctx, 94 | uuid.FromBytesOrNil(r.TenantId), 95 | uuid.FromBytesOrNil(r.Id), 96 | ) 97 | 98 | switch err { 99 | case nil: 100 | return &pb.DeleteResponse{}, nil 101 | case event.ErrNotFound: 102 | return nil, twirp.NotFoundError(err.Error()) 103 | default: 104 | return nil, twirp.InternalErrorWith(err) 105 | } 106 | } 107 | 108 | func (s *server) List(ctx context.Context, r *pb.ListRequest) (*pb.ListResponse, error) { 109 | events, err := s.svc.List(ctx, uuid.FromBytesOrNil(r.TenantId)) 110 | if err != nil { 111 | return nil, twirp.InternalErrorWith(err) 112 | } 113 | pbEvents := make([]*pb.EventObj, 0, len(events)) 114 | for _, event := range events { 115 | pbEvent := &pb.EventObj{ 116 | Id: event.ID.Bytes(), 117 | Name: event.Name, 118 | } 119 | pbEvents = append(pbEvents, pbEvent) 120 | } 121 | return &pb.ListResponse{Events: pbEvents}, nil 122 | } 123 | -------------------------------------------------------------------------------- /shared/sd/client_instancer.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | // stdlib 5 | "time" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/sd" 10 | ) 11 | 12 | // ClientInstancer listens to a service discovery system and yields a set of 13 | // identical client instances on demand. An error indicates a problem with 14 | // connectivity to the service discovery system, or within the system itself; 15 | // an ClientInstancer may yield no client instances without error. 16 | type ClientInstancer interface { 17 | Clients() ([]interface{}, error) 18 | } 19 | 20 | // FixedClientInstancer yields a fixed set of client instances. 21 | type FixedClientInstancer []interface{} 22 | 23 | // Clients implements ClientInstancer. 24 | func (s FixedClientInstancer) Clients() ([]interface{}, error) { return s, nil } 25 | 26 | // NewClientInstancer creates a ClientInstancer that subscribes to updates from 27 | // Instancer src and uses factory f to create Client instances. If src notifies 28 | // of an error, the ClientInstancer keeps returning previously created Client 29 | // instances assuming they are still good, unless this behavior is disabled via 30 | // InvalidateOnError option. 31 | func NewClientInstancer( 32 | src sd.Instancer, f Factory, logger log.Logger, options ...Option, 33 | ) *DefaultClientInstancer { 34 | opts := clientInstancerOptions{} 35 | for _, opt := range options { 36 | opt(&opts) 37 | } 38 | se := &DefaultClientInstancer{ 39 | cache: newClientInstancerCache(f, logger, opts), 40 | instancer: src, 41 | ch: make(chan sd.Event), 42 | } 43 | go se.receive() 44 | src.Register(se.ch) 45 | return se 46 | } 47 | 48 | // Option allows control of clientCache behavior. 49 | type Option func(*clientInstancerOptions) 50 | 51 | // InvalidateOnError returns Option that controls how the ClientInstancer 52 | // behaves when then Instancer publishes an Event containing an error. 53 | // Without this option the ClientInstancer continues returning the last known 54 | // client instances. With this option, the ClientInstancer continues returning 55 | // the last known client instances until the timeout elapses, then closes all 56 | // active client instances and starts returning an error. Once the Instancer 57 | // sends a new update with valid resource instances, the normal operation is 58 | // resumed. 59 | func InvalidateOnError(timeout time.Duration) Option { 60 | return func(opts *clientInstancerOptions) { 61 | opts.invalidateOnError = true 62 | opts.invalidateTimeout = timeout 63 | } 64 | } 65 | 66 | type clientInstancerOptions struct { 67 | invalidateOnError bool 68 | invalidateTimeout time.Duration 69 | } 70 | 71 | // DefaultClientInstancer implements an ClientInstancer interface. 72 | // When created with NewClientInstancer function, it automatically registers 73 | // as a subscriber to events from the Instances and maintains a list 74 | // of active client instances. 75 | type DefaultClientInstancer struct { 76 | cache *clientInstancerCache 77 | instancer sd.Instancer 78 | ch chan sd.Event 79 | } 80 | 81 | func (dc *DefaultClientInstancer) receive() { 82 | for event := range dc.ch { 83 | dc.cache.Update(event) 84 | } 85 | } 86 | 87 | // Close deregisters DefaultClientInstancer from the Instancer and stops the 88 | // internal go-routine. 89 | func (dc *DefaultClientInstancer) Close() { 90 | dc.instancer.Deregister(dc.ch) 91 | close(dc.ch) 92 | } 93 | 94 | // Clients implements ClientInstancer. 95 | func (dc *DefaultClientInstancer) Clients() ([]interface{}, error) { 96 | return dc.cache.Clients() 97 | } 98 | -------------------------------------------------------------------------------- /shared/factory/grpc.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | "time" 7 | 8 | // external 9 | "github.com/go-kit/kit/circuitbreaker" 10 | "github.com/go-kit/kit/endpoint" 11 | "github.com/go-kit/kit/log" 12 | "github.com/go-kit/kit/sd" 13 | "github.com/go-kit/kit/sd/lb" 14 | kitoc "github.com/go-kit/kit/tracing/opencensus" 15 | kitgrpc "github.com/go-kit/kit/transport/grpc" 16 | "github.com/sony/gobreaker" 17 | "go.opencensus.io/trace" 18 | 19 | // project 20 | "github.com/basvanbeek/opencensus-gokit-example/shared/errormw" 21 | "github.com/basvanbeek/opencensus-gokit-example/shared/grpcconn" 22 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 23 | ) 24 | 25 | // CreateGRPCEndpoint wires a QR service Go kit method endpoint 26 | func CreateGRPCEndpoint( 27 | instancer sd.Instancer, hm grpcconn.HostMapper, service string, 28 | middleware endpoint.Middleware, method string, reply interface{}, 29 | enc kitgrpc.EncodeRequestFunc, dec kitgrpc.DecodeResponseFunc, 30 | decError endpoint.Middleware, 31 | ) endpoint.Endpoint { 32 | // Set our Go kit gRPC client options 33 | options := []kitgrpc.ClientOption{ 34 | kitoc.GRPCClientTrace(), // OpenCensus Go kit gRPC client tracing 35 | } 36 | 37 | // our method sd.Factory is called when a new QR service is discovered. 38 | factory := func(instance string) (endpoint.Endpoint, io.Closer, error) { 39 | // try to get connection to advertised instance 40 | conn, closer, err := hm.Get(instance) 41 | if err != nil { 42 | // unable to get a connection to instance... can't build endpoint 43 | return nil, nil, err 44 | } 45 | 46 | // set-up our Go kit client endpoint 47 | clientEndpoint := kitgrpc.NewClient( 48 | conn, service, method, enc, dec, reply, options..., 49 | ).Endpoint() 50 | 51 | if decError != nil { 52 | // we have a custom gRPC error router 53 | clientEndpoint = decError(clientEndpoint) 54 | } 55 | 56 | // configure circuit breaker 57 | cb := circuitbreaker.Gobreaker( 58 | gobreaker.NewCircuitBreaker(gobreaker.Settings{ 59 | MaxRequests: 5, 60 | Interval: 10 * time.Second, 61 | Timeout: 10 * time.Second, 62 | ReadyToTrip: func(counts gobreaker.Counts) bool { 63 | return counts.ConsecutiveFailures > 5 64 | }, 65 | }), 66 | ) 67 | 68 | // middleware to trace our client endpoint 69 | tr := oc.ClientEndpoint( 70 | method, trace.StringAttribute("peer.address", instance), 71 | ) 72 | 73 | // chain our middlewares and wrap our endpoint 74 | clientEndpoint = endpoint.Chain(cb, tr, middleware)(clientEndpoint) 75 | 76 | return clientEndpoint, closer, nil 77 | } 78 | 79 | // endpointer manages list of available endpoints servicing our method 80 | endpointer := sd.NewEndpointer(instancer, factory, log.NewNopLogger()) 81 | 82 | // balancer can do a round robin pick from the endpointer list 83 | balancer := lb.NewRoundRobin(endpointer) 84 | 85 | // retry uses balancer for executing a method call with retry and timeout 86 | // logic so client consumer does not have to think about it. 87 | var ( 88 | count = 3 89 | timeout = 5 * time.Second 90 | ) 91 | 92 | // retry uses balancer for executing a method call with retry and 93 | // timeout logic so client consumer does not have to think about it. 94 | endpoint := lb.Retry(count, timeout, balancer) 95 | 96 | // wrap our retries in an annotated parent span 97 | endpoint = oc.RetryEndpoint(method, oc.RoundRobin, count, timeout)(endpoint) 98 | 99 | // unwrap business logic errors 100 | endpoint = errormw.UnwrapError(log.NewNopLogger())(endpoint) 101 | 102 | // return our endpoint 103 | return endpoint 104 | } 105 | -------------------------------------------------------------------------------- /services/frontend/transport/endpoints.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/endpoint" 9 | 10 | // project 11 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 12 | ) 13 | 14 | // Endpoints holds all Go kit endpoints for the service. 15 | type Endpoints struct { 16 | Login endpoint.Endpoint 17 | EventCreate endpoint.Endpoint 18 | EventGet endpoint.Endpoint 19 | EventUpdate endpoint.Endpoint 20 | EventDelete endpoint.Endpoint 21 | EventList endpoint.Endpoint 22 | UnlockDevice endpoint.Endpoint 23 | GenerateQR endpoint.Endpoint 24 | } 25 | 26 | // MakeEndpoints initializes all Go kit endpoints for the service. 27 | func MakeEndpoints(s frontend.Service) Endpoints { 28 | return Endpoints{ 29 | Login: makeLoginEndpoint(s), 30 | EventCreate: makeEventCreateEndpoint(s), 31 | EventGet: makeEventGetEndpoint(s), 32 | EventUpdate: makeEventUpdateEndpoint(s), 33 | EventDelete: makeEventDeleteEndpoint(s), 34 | EventList: makeEventListEndpoint(s), 35 | UnlockDevice: makeUnlockDeviceEndpoint(s), 36 | GenerateQR: makeGenerateQREndpoint(s), 37 | } 38 | } 39 | 40 | func makeLoginEndpoint(s frontend.Service) endpoint.Endpoint { 41 | return func(ctx context.Context, request interface{}) (interface{}, error) { 42 | req := request.(LoginRequest) 43 | login, err := s.Login(ctx, req.User, req.Pass) 44 | if err != nil { 45 | return LoginResponse{Err: err}, nil 46 | } 47 | return LoginResponse{ 48 | ID: login.ID, 49 | Name: login.Name, 50 | TenantID: login.TenantID, 51 | TenantName: login.TenantName, 52 | }, nil 53 | } 54 | } 55 | 56 | func makeEventCreateEndpoint(s frontend.Service) endpoint.Endpoint { 57 | return func(ctx context.Context, request interface{}) (interface{}, error) { 58 | req := request.(EventCreateRequest) 59 | eventID, err := s.EventCreate(ctx, req.TenantID, req.Event) 60 | return EventCreateResponse{EventID: eventID, Err: err}, nil 61 | } 62 | } 63 | 64 | func makeEventGetEndpoint(s frontend.Service) endpoint.Endpoint { 65 | return func(ctx context.Context, request interface{}) (interface{}, error) { 66 | req := request.(EventGetRequest) 67 | event, err := s.EventGet(ctx, req.TenantID, req.EventID) 68 | return EventGetResponse{Event: event, Err: err}, nil 69 | } 70 | } 71 | 72 | func makeEventUpdateEndpoint(s frontend.Service) endpoint.Endpoint { 73 | return func(ctx context.Context, request interface{}) (interface{}, error) { 74 | req := request.(EventUpdateRequest) 75 | err := s.EventUpdate(ctx, req.TenantID, req.Event) 76 | return EventUpdateResponse{Err: err}, nil 77 | } 78 | } 79 | 80 | func makeEventDeleteEndpoint(s frontend.Service) endpoint.Endpoint { 81 | return func(ctx context.Context, request interface{}) (interface{}, error) { 82 | req := request.(EventDeleteRequest) 83 | err := s.EventDelete(ctx, req.TenantID, req.EventID) 84 | return EventDeleteResponse{Err: err}, nil 85 | } 86 | } 87 | 88 | func makeEventListEndpoint(s frontend.Service) endpoint.Endpoint { 89 | return func(ctx context.Context, request interface{}) (interface{}, error) { 90 | req := request.(EventListRequest) 91 | events, err := s.EventList(ctx, req.TenantID) 92 | return EventListResponse{Events: events, Err: err}, nil 93 | } 94 | } 95 | 96 | func makeUnlockDeviceEndpoint(s frontend.Service) endpoint.Endpoint { 97 | return func(ctx context.Context, request interface{}) (interface{}, error) { 98 | req := request.(UnlockDeviceRequest) 99 | session, err := s.UnlockDevice(ctx, req.EventID, req.DeviceID, req.UnlockCode) 100 | return UnlockDeviceResponse{Session: session, Err: err}, nil 101 | } 102 | } 103 | 104 | func makeGenerateQREndpoint(s frontend.Service) endpoint.Endpoint { 105 | return func(ctx context.Context, request interface{}) (interface{}, error) { 106 | req := request.(GenerateQRRequest) 107 | qr, err := s.GenerateQR(ctx, req.EventID, req.DeviceID, req.UnlockCode) 108 | return GenerateQRResponse{QR: qr, Err: err}, nil 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /services/event/implementation/service.go: -------------------------------------------------------------------------------- 1 | package implementation 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/log/level" 10 | "github.com/kevinburke/go.uuid" 11 | 12 | // project 13 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 14 | "github.com/basvanbeek/opencensus-gokit-example/services/event/database" 15 | ) 16 | 17 | // service implements event.Service 18 | type service struct { 19 | i int32 20 | repository database.Repository 21 | logger log.Logger 22 | } 23 | 24 | // NewService creates and returns a new Event service instance 25 | func NewService(rep database.Repository, logger log.Logger) event.Service { 26 | return &service{ 27 | i: 0, 28 | repository: rep, 29 | logger: logger, 30 | } 31 | } 32 | 33 | func (s *service) Create( 34 | ctx context.Context, tenantID uuid.UUID, e event.Event, 35 | ) (*uuid.UUID, error) { 36 | logger := log.With(s.logger, "method", "Create") 37 | 38 | dbEvent := database.Event{ 39 | TenantID: tenantID, 40 | Name: e.Name, 41 | } 42 | id, err := s.repository.Create(ctx, dbEvent) 43 | switch err { 44 | case nil: 45 | return id, nil 46 | case database.ErrRepository: 47 | level.Error(logger).Log("err", err) 48 | return nil, event.ErrService 49 | case database.ErrNameExists: 50 | level.Debug(logger).Log("err", err) 51 | return nil, event.ErrEventExists 52 | default: 53 | level.Error(logger).Log("err", err) 54 | return nil, event.ErrService 55 | } 56 | } 57 | 58 | func (s *service) Get( 59 | ctx context.Context, tenantID, id uuid.UUID, 60 | ) (*event.Event, error) { 61 | logger := log.With(s.logger, "method", "Get") 62 | 63 | dbEvent, err := s.repository.Get(ctx, id) 64 | switch err { 65 | case nil: 66 | if !uuid.Equal(dbEvent.TenantID, tenantID) { 67 | // let's not leak event id's from other tenants. 68 | return nil, event.ErrNotFound 69 | } 70 | return &event.Event{ID: dbEvent.ID, Name: dbEvent.Name}, nil 71 | case database.ErrRepository: 72 | level.Error(logger).Log("err", err) 73 | return nil, event.ErrService 74 | case database.ErrNotFound: 75 | level.Debug(logger).Log("err", err) 76 | return nil, event.ErrNotFound 77 | default: 78 | level.Error(logger).Log("err", err) 79 | return nil, event.ErrService 80 | } 81 | } 82 | 83 | func (s *service) Update(ctx context.Context, tenantID uuid.UUID, e event.Event) error { 84 | logger := log.With(s.logger, "method", "Update") 85 | 86 | err := s.repository.Update( 87 | ctx, 88 | database.Event{ 89 | ID: e.ID, 90 | TenantID: tenantID, 91 | Name: e.Name, 92 | }, 93 | ) 94 | 95 | switch err { 96 | case nil: 97 | return nil 98 | case database.ErrRepository: 99 | level.Error(logger).Log("err", err) 100 | return event.ErrService 101 | case database.ErrNameExists: 102 | level.Debug(logger).Log("err", err) 103 | return event.ErrEventExists 104 | case database.ErrNotFound: 105 | level.Debug(logger).Log("err", err) 106 | return event.ErrNotFound 107 | default: 108 | level.Error(logger).Log("err", err) 109 | return event.ErrService 110 | } 111 | } 112 | 113 | func (s *service) Delete(ctx context.Context, tenantID, id uuid.UUID) error { 114 | logger := log.With(s.logger, "method", "Delete") 115 | 116 | if err := s.repository.Delete(ctx, tenantID, id); err != nil { 117 | level.Error(logger).Log("err", err) 118 | return event.ErrService 119 | } 120 | return nil 121 | } 122 | 123 | func (s *service) List(ctx context.Context, tenantID uuid.UUID) ([]*event.Event, error) { 124 | logger := log.With(s.logger, "method", "List") 125 | 126 | dbEvents, err := s.repository.List(ctx, tenantID) 127 | if err != nil { 128 | level.Error(logger).Log("err", err) 129 | return nil, event.ErrService 130 | } 131 | events := make([]*event.Event, 0, len(dbEvents)) 132 | for _, dbEvent := range dbEvents { 133 | events = append( 134 | events, 135 | &event.Event{ 136 | ID: dbEvent.ID, 137 | Name: dbEvent.Name, 138 | }, 139 | ) 140 | } 141 | return events, nil 142 | } 143 | -------------------------------------------------------------------------------- /shared/factory/http.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | // stdlib 5 | "bytes" 6 | "context" 7 | "encoding/json" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "time" 13 | 14 | // external 15 | "github.com/go-kit/kit/circuitbreaker" 16 | "github.com/go-kit/kit/endpoint" 17 | "github.com/go-kit/kit/log" 18 | "github.com/go-kit/kit/sd" 19 | "github.com/go-kit/kit/sd/lb" 20 | kitoc "github.com/go-kit/kit/tracing/opencensus" 21 | kithttp "github.com/go-kit/kit/transport/http" 22 | "github.com/gorilla/mux" 23 | "github.com/sony/gobreaker" 24 | "go.opencensus.io/trace" 25 | 26 | // project 27 | "github.com/basvanbeek/opencensus-gokit-example/shared/errormw" 28 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 29 | ) 30 | 31 | // CreateHTTPEndpoint creates a Go kit client endpoint 32 | func CreateHTTPEndpoint( 33 | instancer sd.Instancer, middleware endpoint.Middleware, operationName string, 34 | encodeRequest kithttp.EncodeRequestFunc, 35 | decodeResponse kithttp.DecodeResponseFunc, 36 | ) endpoint.Endpoint { 37 | options := []kithttp.ClientOption{ 38 | kitoc.HTTPClientTrace(), // OpenCensus HTTP Client transport tracing 39 | } 40 | 41 | // factory is called each time a new instance is received from service 42 | // discovery. it will create a new Go kit client endpoint which will be 43 | // consumed by the endpointer logic. 44 | factory := func(instance string) (endpoint.Endpoint, io.Closer, error) { 45 | baseURL, err := url.Parse(instance) 46 | if err != nil { 47 | // invalid instance string received... can't build endpoint 48 | return nil, nil, err 49 | } 50 | 51 | // set-up our Go kit client endpoint 52 | // method is not set yet as it will be decided by the provided route 53 | // when encoding the request using our request encoder. 54 | clientEndpoint := kithttp.NewClient( 55 | "", baseURL, encodeRequest, decodeResponse, options..., 56 | ).Endpoint() 57 | 58 | // configure per instance circuit breaker middleware 59 | cb := circuitbreaker.Gobreaker( 60 | gobreaker.NewCircuitBreaker(gobreaker.Settings{ 61 | MaxRequests: 5, 62 | Interval: 10 * time.Second, 63 | Timeout: 10 * time.Second, 64 | ReadyToTrip: func(counts gobreaker.Counts) bool { 65 | return counts.ConsecutiveFailures > 5 66 | }, 67 | }), 68 | ) 69 | 70 | // middleware to trace our client endpoint 71 | tr := oc.ClientEndpoint( 72 | operationName, trace.StringAttribute("peer.address", instance), 73 | ) 74 | 75 | // chain our middlewares and wrap our endpoint 76 | clientEndpoint = endpoint.Chain(cb, tr, middleware)(clientEndpoint) 77 | 78 | return clientEndpoint, nil, nil 79 | } 80 | 81 | // endpoints manages the list of available endpoints servicing our method 82 | endpoints := sd.NewEndpointer(instancer, factory, log.NewNopLogger()) 83 | 84 | // balancer can do a random pick from the endpoint list 85 | balancer := lb.NewRandom(endpoints, time.Now().UnixNano()) 86 | 87 | var ( 88 | count = 3 89 | timeout = 5 * time.Second 90 | ) 91 | 92 | // retry uses balancer for executing a method call with retry and 93 | // timeout logic so client consumer does not have to think about it. 94 | endpoint := lb.Retry(count, timeout, balancer) 95 | 96 | // wrap our retries in an annotated parent span 97 | endpoint = oc.RetryEndpoint(operationName, oc.Random, count, timeout)(endpoint) 98 | 99 | // unwrap business logic errors 100 | endpoint = errormw.UnwrapError(log.NewNopLogger())(endpoint) 101 | 102 | // return our endpoint 103 | return endpoint 104 | } 105 | 106 | // EncodeGenericRequest is a generic request encoder which can be used if we 107 | // don't have to deal with URL parameters. 108 | func EncodeGenericRequest(route *mux.Route) kithttp.EncodeRequestFunc { 109 | return func(_ context.Context, r *http.Request, request interface{}) error { 110 | var err error 111 | 112 | if r.URL, err = route.Host(r.URL.Host).URL(); err != nil { 113 | return err 114 | } 115 | if methods, err := route.GetMethods(); err == nil { 116 | r.Method = methods[0] 117 | } 118 | 119 | if request != nil { 120 | var buf bytes.Buffer 121 | if err := json.NewEncoder(&buf).Encode(request); err != nil { 122 | return err 123 | } 124 | r.Body = ioutil.NopCloser(&buf) 125 | } 126 | return nil 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /clients/cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "os" 8 | 9 | // external 10 | "github.com/davecgh/go-spew/spew" 11 | "github.com/go-kit/kit/log" 12 | "github.com/go-kit/kit/log/level" 13 | "github.com/go-kit/kit/sd" 14 | "github.com/go-kit/kit/sd/etcd" 15 | "github.com/kevinburke/go.uuid" 16 | "go.opencensus.io/trace" 17 | 18 | // project 19 | feclient "github.com/basvanbeek/opencensus-gokit-example/clients/frontend" 20 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 21 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 22 | ) 23 | 24 | const ( 25 | serviceName = "cli" 26 | ) 27 | 28 | func main() { 29 | var ( 30 | err error 31 | instance = uuid.NewV4() 32 | ) 33 | 34 | // initialize our OpenCensus configuration and defer a clean-up 35 | defer oc.Setup(serviceName).Close() 36 | 37 | // initialize our structured logger for the service 38 | var logger log.Logger 39 | { 40 | logger = log.NewLogfmtLogger(os.Stderr) 41 | logger = log.NewSyncLogger(logger) 42 | logger = level.NewFilter(logger, level.AllowDebug()) 43 | logger = log.With(logger, 44 | "svc", serviceName, 45 | "instance", instance, 46 | "ts", log.DefaultTimestampUTC, 47 | "clr", log.DefaultCaller, 48 | ) 49 | } 50 | 51 | ctx, cancel := context.WithCancel(context.Background()) 52 | defer cancel() 53 | 54 | var sdc etcd.Client 55 | { 56 | // create our Go kit etcd client 57 | sdc, err = etcd.NewClient(ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}) 58 | if err != nil { 59 | level.Error(logger).Log("exit", err) 60 | os.Exit(-1) 61 | } 62 | } 63 | 64 | var client frontend.Service 65 | { 66 | var instancer sd.Instancer 67 | instancer, err = etcd.NewInstancer(sdc, "/services/"+frontend.ServiceName+"/http", logger) 68 | if err != nil { 69 | level.Error(logger).Log("exit", err) 70 | } 71 | 72 | client = feclient.NewHTTPClient(instancer, logger) 73 | } 74 | 75 | var tenantID uuid.UUID 76 | { 77 | ctx, span := trace.StartSpan(ctx, "Do Login") 78 | details, err := client.Login(ctx, "john", "doe") 79 | if err != nil { 80 | span.SetStatus(trace.Status{ 81 | Code: trace.StatusCodeUnknown, 82 | Message: err.Error(), 83 | }) 84 | span.End() 85 | level.Error(logger).Log("msg", "login failed", "exit", err) 86 | os.Exit(-1) 87 | } 88 | span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) 89 | span.End() 90 | level.Debug(logger).Log("msg", "login succeeded", "details", fmt.Sprintf("%+v", details)) 91 | tenantID = details.TenantID 92 | } 93 | 94 | { 95 | if err != nil { 96 | level.Error(logger).Log("exit", err) 97 | os.Exit(-1) 98 | } 99 | ctx, span := trace.StartSpan(ctx, "Do EventCreate") 100 | id, err := client.EventCreate(ctx, tenantID, frontend.Event{ 101 | Name: "Marine Corps Marathon", 102 | }) 103 | if err != nil { 104 | span.SetStatus(trace.Status{ 105 | Code: trace.StatusCodeUnknown, 106 | Message: err.Error(), 107 | }) 108 | level.Error(logger).Log("msg", "event create failed", "exit", err) 109 | } else { 110 | span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) 111 | level.Debug(logger).Log("msg", "event create succeeded", "id", id.String()) 112 | } 113 | span.End() 114 | } 115 | 116 | { 117 | if err != nil { 118 | level.Error(logger).Log("exit", err) 119 | os.Exit(-1) 120 | } 121 | ctx, span := trace.StartSpan(ctx, "Do EventList") 122 | events, err := client.EventList(ctx, tenantID) 123 | 124 | // highlight how terribly expensive a call to spew is... 125 | span.Annotate(nil, "spew.dump:start") 126 | fmt.Printf("\nCLIENT EVENT LIST:\nRES:%s\nERR: %+v\n\n", spew.Sdump(events), err) 127 | span.Annotate(nil, "spew.dump:end") 128 | if err != nil { 129 | span.SetStatus(trace.Status{ 130 | Code: trace.StatusCodeUnknown, 131 | Message: err.Error(), 132 | }) 133 | } else { 134 | span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) 135 | } 136 | span.End() 137 | } 138 | 139 | // details, err = client.Login(ctx, "jane", "doe") 140 | // fmt.Printf("\nCLIENT LOGIN:\nRES:%+v\nERR: %+v\n\n", details, err) 141 | // 142 | // details, err = client.Login(ctx, "Anonymous", "Coward") 143 | // fmt.Printf("\nCLIENT LOGIN:\nRES:%+v\nERR: %+v\n\n", details, err) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /clients/frontend/client.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | 7 | // external 8 | 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/sd" 11 | "github.com/kevinburke/go.uuid" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/clients/frontend/http" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 17 | ) 18 | 19 | // NewHTTPClient returns a new frontend client using the HTTP transport. 20 | func NewHTTPClient(instancer sd.Instancer, logger log.Logger) frontend.Service { 21 | return &client{ 22 | endpoints: http.InitEndpoints(instancer, logger), 23 | logger: logger, 24 | } 25 | } 26 | 27 | type client struct { 28 | endpoints transport.Endpoints 29 | logger log.Logger 30 | } 31 | 32 | func (c *client) Login(ctx context.Context, user, pass string) (*frontend.Login, error) { 33 | response, err := c.endpoints.Login( 34 | ctx, 35 | transport.LoginRequest{ 36 | User: user, 37 | Pass: pass, 38 | }, 39 | ) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | res := response.(transport.LoginResponse) 45 | 46 | return &frontend.Login{ 47 | ID: res.ID, 48 | Name: res.Name, 49 | TenantID: res.TenantID, 50 | TenantName: res.TenantName, 51 | }, nil 52 | } 53 | 54 | func (c *client) EventCreate(ctx context.Context, tenantID uuid.UUID, event frontend.Event) (*uuid.UUID, error) { 55 | response, err := c.endpoints.EventCreate( 56 | ctx, 57 | transport.EventCreateRequest{ 58 | TenantID: tenantID, 59 | Event: event, 60 | }, 61 | ) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | res := response.(transport.EventCreateResponse) 67 | return res.EventID, nil 68 | } 69 | 70 | func (c *client) EventGet(ctx context.Context, tenantID, eventID uuid.UUID) (*frontend.Event, error) { 71 | response, err := c.endpoints.EventGet( 72 | ctx, 73 | transport.EventGetRequest{ 74 | TenantID: tenantID, 75 | EventID: eventID, 76 | }, 77 | ) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | res := response.(transport.EventGetResponse) 83 | return res.Event, nil 84 | } 85 | 86 | func (c *client) EventUpdate(ctx context.Context, tenantID uuid.UUID, event frontend.Event) error { 87 | response, err := c.endpoints.EventUpdate( 88 | ctx, 89 | transport.EventUpdateRequest{ 90 | TenantID: tenantID, 91 | Event: event, 92 | }, 93 | ) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | return response.(transport.EventUpdateResponse).Failed() 99 | } 100 | 101 | func (c *client) EventDelete(ctx context.Context, tenantID, eventID uuid.UUID) error { 102 | response, err := c.endpoints.EventDelete( 103 | ctx, 104 | transport.EventDeleteRequest{ 105 | TenantID: tenantID, 106 | EventID: eventID, 107 | }, 108 | ) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | return response.(transport.EventDeleteResponse).Failed() 114 | } 115 | 116 | func (c *client) EventList(ctx context.Context, tenantID uuid.UUID) ([]*frontend.Event, error) { 117 | response, err := c.endpoints.EventList( 118 | ctx, 119 | transport.EventListRequest{ 120 | TenantID: tenantID, 121 | }, 122 | ) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | res := response.(transport.EventListResponse) 128 | 129 | return res.Events, nil 130 | } 131 | 132 | func (c *client) UnlockDevice(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) (*frontend.Session, error) { 133 | response, err := c.endpoints.UnlockDevice( 134 | ctx, 135 | transport.UnlockDeviceRequest{ 136 | EventID: eventID, 137 | DeviceID: deviceID, 138 | UnlockCode: unlockCode, 139 | }, 140 | ) 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | res := response.(transport.UnlockDeviceResponse) 146 | 147 | return res.Session, nil 148 | } 149 | 150 | func (c *client) GenerateQR(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) ([]byte, error) { 151 | response, err := c.endpoints.GenerateQR( 152 | ctx, 153 | transport.GenerateQRRequest{ 154 | EventID: eventID, 155 | DeviceID: deviceID, 156 | UnlockCode: unlockCode, 157 | }, 158 | ) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | res := response.(transport.GenerateQRResponse) 164 | 165 | return res.QR, nil 166 | } 167 | -------------------------------------------------------------------------------- /clients/event/twirp/client.go: -------------------------------------------------------------------------------- 1 | package twirp 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "errors" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/kevinburke/go.uuid" 11 | "github.com/twitchtv/twirp" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/event/transport/pb" 16 | "github.com/basvanbeek/opencensus-gokit-example/shared/sd" 17 | ) 18 | 19 | type client struct { 20 | instancer func() pb.Event 21 | logger log.Logger 22 | } 23 | 24 | func (c client) Create( 25 | ctx context.Context, tenantID uuid.UUID, evt event.Event, 26 | ) (*uuid.UUID, error) { 27 | ci := c.instancer() 28 | if ci == nil { 29 | return nil, sd.ErrNoClients 30 | } 31 | 32 | res, err := ci.Create(ctx, &pb.CreateRequest{ 33 | TenantId: tenantID.Bytes(), 34 | Event: &pb.EventObj{ 35 | Id: evt.ID.Bytes(), 36 | Name: evt.Name, 37 | }, 38 | }) 39 | 40 | if err != nil { 41 | if twErr, ok := err.(twirp.Error); ok { 42 | switch twErr.Msg() { 43 | case event.ErrorService: 44 | return nil, event.ErrService 45 | case event.ErrorUnauthorized: 46 | return nil, event.ErrUnauthorized 47 | case event.ErrorEventExists: 48 | return nil, event.ErrEventExists 49 | default: 50 | return nil, errors.New(twErr.Msg()) 51 | } 52 | } 53 | return nil, err 54 | } 55 | 56 | id, err := uuid.FromBytes(res.Id) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &id, nil 61 | } 62 | 63 | func (c client) Get( 64 | ctx context.Context, tenantID, id uuid.UUID, 65 | ) (*event.Event, error) { 66 | ci := c.instancer() 67 | if ci == nil { 68 | return nil, sd.ErrNoClients 69 | } 70 | 71 | res, err := ci.Get(ctx, &pb.GetRequest{ 72 | TenantId: tenantID.Bytes(), 73 | Id: id.Bytes(), 74 | }) 75 | 76 | if twErr, ok := err.(twirp.Error); ok { 77 | switch twErr.Error() { 78 | case event.ErrorService: 79 | return nil, event.ErrService 80 | case event.ErrorUnauthorized: 81 | return nil, event.ErrUnauthorized 82 | case event.ErrorNotFound: 83 | return nil, event.ErrNotFound 84 | } 85 | } 86 | 87 | return &event.Event{ 88 | ID: uuid.FromBytesOrNil(res.Event.Id), 89 | Name: res.Event.Name, 90 | }, nil 91 | } 92 | 93 | func (c client) Update( 94 | ctx context.Context, tenantID uuid.UUID, evt event.Event, 95 | ) error { 96 | ci := c.instancer() 97 | if ci == nil { 98 | return sd.ErrNoClients 99 | } 100 | 101 | _, err := ci.Update(ctx, &pb.UpdateRequest{ 102 | TenantId: tenantID.Bytes(), 103 | Event: &pb.EventObj{ 104 | Id: evt.ID.Bytes(), 105 | Name: evt.Name, 106 | }, 107 | }) 108 | 109 | if err != nil { 110 | if twErr, ok := err.(twirp.Error); ok { 111 | switch twErr.Error() { 112 | case event.ErrorService: 113 | return event.ErrService 114 | case event.ErrorUnauthorized: 115 | return event.ErrUnauthorized 116 | case event.ErrorNotFound: 117 | return event.ErrNotFound 118 | case event.ErrorEventExists: 119 | return event.ErrEventExists 120 | } 121 | } 122 | } 123 | 124 | return err 125 | } 126 | 127 | func (c client) Delete( 128 | ctx context.Context, tenantID uuid.UUID, id uuid.UUID, 129 | ) error { 130 | ci := c.instancer() 131 | if ci == nil { 132 | return sd.ErrNoClients 133 | } 134 | 135 | _, err := ci.Delete(ctx, &pb.DeleteRequest{ 136 | TenantId: tenantID.Bytes(), 137 | Id: id.Bytes(), 138 | }) 139 | 140 | if err != nil { 141 | if twErr, ok := err.(twirp.Error); ok { 142 | switch twErr.Error() { 143 | case event.ErrorService: 144 | return event.ErrService 145 | case event.ErrorUnauthorized: 146 | return event.ErrUnauthorized 147 | case event.ErrorNotFound: 148 | return event.ErrNotFound 149 | } 150 | } 151 | } 152 | 153 | return err 154 | } 155 | 156 | func (c client) List( 157 | ctx context.Context, tenantID uuid.UUID, 158 | ) ([]*event.Event, error) { 159 | ci := c.instancer() 160 | if ci == nil { 161 | return nil, sd.ErrNoClients 162 | } 163 | 164 | pbListResponse, err := ci.List(ctx, &pb.ListRequest{ 165 | TenantId: tenantID.Bytes(), 166 | }) 167 | 168 | if err != nil { 169 | if twErr, ok := err.(twirp.Error); ok { 170 | switch twErr.Error() { 171 | case event.ErrorService: 172 | return nil, event.ErrService 173 | case event.ErrorUnauthorized: 174 | return nil, event.ErrUnauthorized 175 | } 176 | } 177 | } 178 | 179 | events := make([]*event.Event, 0, len(pbListResponse.Events)) 180 | for _, evt := range pbListResponse.Events { 181 | events = append(events, &event.Event{ 182 | ID: uuid.FromBytesOrNil(evt.Id), 183 | Name: evt.Name, 184 | }) 185 | } 186 | return events, nil 187 | } 188 | -------------------------------------------------------------------------------- /services/frontend/transport/request_response.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | // external 5 | "github.com/go-kit/kit/endpoint" 6 | "github.com/kevinburke/go.uuid" 7 | 8 | // project 9 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 10 | ) 11 | 12 | var ( 13 | _ endpoint.Failer = LoginResponse{} 14 | _ endpoint.Failer = EventCreateResponse{} 15 | _ endpoint.Failer = EventGetResponse{} 16 | _ endpoint.Failer = EventUpdateResponse{} 17 | _ endpoint.Failer = EventDeleteResponse{} 18 | _ endpoint.Failer = EventListResponse{} 19 | _ endpoint.Failer = UnlockDeviceResponse{} 20 | _ endpoint.Failer = GenerateQRResponse{} 21 | ) 22 | 23 | // LoginRequest holds the request parameters for the Login method. 24 | type LoginRequest struct { 25 | User string `json:"user"` 26 | Pass string `json:"pass"` 27 | } 28 | 29 | // LoginResponse holds the response values for the Login method. 30 | type LoginResponse struct { 31 | ID uuid.UUID `json:"id"` 32 | Name string `json:"name"` 33 | TenantID uuid.UUID `json:"tenant_id"` 34 | TenantName string `json:"tenant_name"` 35 | Err error 36 | } 37 | 38 | // Failed implements Failer. 39 | func (r LoginResponse) Failed() error { return r.Err } 40 | 41 | // EventCreateRequest holds the request parameters for the EventCreate method. 42 | type EventCreateRequest struct { 43 | TenantID uuid.UUID `json:"tenant_id"` 44 | Event frontend.Event `json:"event"` 45 | } 46 | 47 | // EventCreateResponse holds the response values for the EventCreate method. 48 | type EventCreateResponse struct { 49 | EventID *uuid.UUID `json:"event_id,omitempty"` 50 | Err error 51 | } 52 | 53 | // Failed implements Failer. 54 | func (r EventCreateResponse) Failed() error { return r.Err } 55 | 56 | // EventGetRequest holds the request parameters for the EventGet method. 57 | type EventGetRequest struct { 58 | TenantID uuid.UUID `json:"tenant_id"` 59 | EventID uuid.UUID `json:"event_id"` 60 | } 61 | 62 | // EventGetResponse holds the response values for the EventGet method. 63 | type EventGetResponse struct { 64 | Event *frontend.Event `json:"event,omitempty"` 65 | Err error 66 | } 67 | 68 | // Failed implements Failer. 69 | func (r EventGetResponse) Failed() error { return r.Err } 70 | 71 | // EventUpdateRequest holds the request parameters for the EventUpdate method. 72 | type EventUpdateRequest struct { 73 | TenantID uuid.UUID `json:"tenant_id"` 74 | Event frontend.Event `json:"event"` 75 | } 76 | 77 | // EventUpdateResponse holds the response values for the EventUpdate method. 78 | type EventUpdateResponse struct { 79 | Err error 80 | } 81 | 82 | // Failed implements Failer. 83 | func (r EventUpdateResponse) Failed() error { return r.Err } 84 | 85 | // EventDeleteRequest holds the request parameters for the EventDelete method. 86 | type EventDeleteRequest struct { 87 | TenantID uuid.UUID `json:"tenant_id"` 88 | EventID uuid.UUID `json:"event_id"` 89 | } 90 | 91 | // EventDeleteResponse holds the response values for the EventDelete method. 92 | type EventDeleteResponse struct { 93 | Err error 94 | } 95 | 96 | // Failed implements Failer. 97 | func (r EventDeleteResponse) Failed() error { return r.Err } 98 | 99 | // EventListRequest holds the request parameters for the EventList method. 100 | type EventListRequest struct { 101 | TenantID uuid.UUID `json:"tenant_id"` 102 | } 103 | 104 | // EventListResponse holds the response values for the EventList method. 105 | type EventListResponse struct { 106 | Events []*frontend.Event `json:"events,omitempty"` 107 | Err error 108 | } 109 | 110 | // Failed implements Failer. 111 | func (r EventListResponse) Failed() error { return r.Err } 112 | 113 | // UnlockDeviceRequest holds the request parameters for the UnlockDevice method. 114 | type UnlockDeviceRequest struct { 115 | EventID uuid.UUID `json:"event_id"` 116 | DeviceID uuid.UUID `json:"device_id"` 117 | UnlockCode string `json:"unlock_code"` 118 | } 119 | 120 | // UnlockDeviceResponse holds the response values for the UnlockDevice method. 121 | type UnlockDeviceResponse struct { 122 | Session *frontend.Session `json:"session,omitempty"` 123 | Err error 124 | } 125 | 126 | // Failed implements Failer. 127 | func (r UnlockDeviceResponse) Failed() error { return r.Err } 128 | 129 | // GenerateQRRequest holds the request parameters for the GenerateQR method. 130 | type GenerateQRRequest struct { 131 | EventID uuid.UUID 132 | DeviceID uuid.UUID 133 | UnlockCode string 134 | } 135 | 136 | // GenerateQRResponse holds the response values for the GenerateQR method. 137 | type GenerateQRResponse struct { 138 | QR []byte 139 | Err error 140 | } 141 | 142 | // Failed implements Failer. 143 | func (r GenerateQRResponse) Failed() error { return r.Err } 144 | -------------------------------------------------------------------------------- /services/qr/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | // external 14 | "github.com/go-kit/kit/log" 15 | "github.com/go-kit/kit/log/level" 16 | "github.com/go-kit/kit/sd/etcd" 17 | kitoc "github.com/go-kit/kit/tracing/opencensus" 18 | kitgrpc "github.com/go-kit/kit/transport/grpc" 19 | "github.com/kevinburke/go.uuid" 20 | "github.com/oklog/run" 21 | "google.golang.org/grpc" 22 | 23 | // project 24 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 25 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/implementation" 26 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport" 27 | grpctransport "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport/grpc" 28 | "github.com/basvanbeek/opencensus-gokit-example/services/qr/transport/pb" 29 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 30 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 31 | ) 32 | 33 | func main() { 34 | var ( 35 | err error 36 | instance = uuid.NewV4() 37 | ) 38 | 39 | // initialize our OpenCensus configuration and defer a clean-up 40 | defer oc.Setup(qr.ServiceName).Close() 41 | 42 | // initialize our structured logger for the service 43 | var logger log.Logger 44 | { 45 | logger = log.NewLogfmtLogger(os.Stderr) 46 | logger = log.NewSyncLogger(logger) 47 | logger = level.NewFilter(logger, level.AllowDebug()) 48 | logger = log.With(logger, 49 | "svc", qr.ServiceName, 50 | "instance", instance, 51 | "ts", log.DefaultTimestampUTC, 52 | "clr", log.DefaultCaller, 53 | ) 54 | } 55 | 56 | level.Info(logger).Log("msg", "service started") 57 | defer level.Info(logger).Log("msg", "service ended") 58 | 59 | ctx, cancel := context.WithCancel(context.Background()) 60 | defer cancel() 61 | 62 | // Create our etcd client for Service Discovery 63 | // 64 | // we could have used the v3 client but then we must vendor or suffer the 65 | // following issue originating from gRPC init: 66 | // panic: http: multiple registrations for /debug/requests 67 | var sdc etcd.Client 68 | { 69 | // create our Go kit etcd client 70 | sdc, err = etcd.NewClient(ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}) 71 | if err != nil { 72 | level.Error(logger).Log("exit", err) 73 | os.Exit(-1) 74 | } 75 | } 76 | 77 | // Create our QR Service 78 | var svc qr.Service 79 | { 80 | svc = implementation.NewService(logger) 81 | // add service level middlewares here 82 | } 83 | 84 | // Create our Go kit endpoints for the QR Service 85 | var endpoints transport.Endpoints 86 | { 87 | endpoints = transport.MakeEndpoints(svc) 88 | // trace our server side endpoints 89 | endpoints = transport.Endpoints{ 90 | Generate: oc.ServerEndpoint("Generate")(endpoints.Generate), 91 | } 92 | } 93 | 94 | // run.Group manages our goroutine lifecycles 95 | // see: https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s 96 | var g run.Group 97 | { 98 | // set-up our ZPages handler 99 | oc.ZPages(g, logger) 100 | } 101 | { 102 | // set-up our grpc transport 103 | var ( 104 | bindIP, _ = network.HostIP() 105 | ocTracing = kitoc.GRPCServerTrace() 106 | serverOptions = []kitgrpc.ServerOption{ocTracing} 107 | qrService = grpctransport.NewGRPCServer(endpoints, serverOptions, logger) 108 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 109 | svcInstance = fmt.Sprintf("/services/%s/grpc/%s/", qr.ServiceName, instance) 110 | addr = listener.Addr().String() 111 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 112 | serviceEntry = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 113 | registrar = etcd.NewRegistrar(sdc, serviceEntry, logger) 114 | grpcServer = grpc.NewServer() 115 | ) 116 | pb.RegisterQRServer(grpcServer, qrService) 117 | 118 | g.Add(func() error { 119 | registrar.Register() 120 | return grpcServer.Serve(listener) 121 | }, func(error) { 122 | registrar.Deregister() 123 | listener.Close() 124 | }) 125 | } 126 | { 127 | // set-up our signal handler 128 | var ( 129 | cancelInterrupt = make(chan struct{}) 130 | c = make(chan os.Signal, 2) 131 | ) 132 | defer close(c) 133 | 134 | g.Add(func() error { 135 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 136 | select { 137 | case sig := <-c: 138 | return fmt.Errorf("received signal %s", sig) 139 | case <-cancelInterrupt: 140 | return nil 141 | } 142 | }, func(error) { 143 | close(cancelInterrupt) 144 | }) 145 | } 146 | 147 | // spawn our goroutines and wait for shutdown 148 | level.Error(logger).Log("exit", g.Run()) 149 | } 150 | -------------------------------------------------------------------------------- /shared/sd/client_cache.go: -------------------------------------------------------------------------------- 1 | package sd 2 | 3 | import ( 4 | // stdlib 5 | "io" 6 | "sort" 7 | "sync" 8 | "time" 9 | 10 | // external 11 | "github.com/go-kit/kit/log" 12 | "github.com/go-kit/kit/sd" 13 | ) 14 | 15 | // clientInstancerCache collects the most recent set of instances from a service 16 | // discovery system, creates clientInstances for them using a factory function, 17 | // and makes them available to consumers. 18 | type clientInstancerCache struct { 19 | options clientInstancerOptions 20 | mtx sync.RWMutex 21 | factory Factory 22 | cache map[string]clientInstanceCloser 23 | err error 24 | clientInstances []interface{} 25 | logger log.Logger 26 | invalidateDeadline time.Time 27 | timeNow func() time.Time 28 | } 29 | 30 | type clientInstanceCloser struct { 31 | ci interface{} 32 | io.Closer 33 | } 34 | 35 | // newClientInstancerCache returns a new, empty clientInstancerCache. 36 | func newClientInstancerCache( 37 | factory Factory, logger log.Logger, options clientInstancerOptions, 38 | ) *clientInstancerCache { 39 | return &clientInstancerCache{ 40 | options: options, 41 | factory: factory, 42 | cache: map[string]clientInstanceCloser{}, 43 | logger: logger, 44 | timeNow: time.Now, 45 | } 46 | } 47 | 48 | // Update should be invoked by clients with a complete set of current instance 49 | // strings whenever that set changes. The cache manufactures new clientInstances 50 | // via the factory, closes old clientInstances when they disappear, and persists 51 | // existing clientInstances if they survive through an update. 52 | func (c *clientInstancerCache) Update(event sd.Event) { 53 | c.mtx.Lock() 54 | defer c.mtx.Unlock() 55 | 56 | // Happy path. 57 | if event.Err == nil { 58 | c.updateCache(event.Instances) 59 | c.err = nil 60 | return 61 | } 62 | 63 | // Sad path. Something's gone wrong in sd. 64 | c.logger.Log("err", event.Err) 65 | if !c.options.invalidateOnError { 66 | return // keep returning the last known clientInstances on error 67 | } 68 | if c.err != nil { 69 | return // already in the error state, do nothing & keep original error 70 | } 71 | c.err = event.Err 72 | // set new deadline to invalidate clientInstances unless non-error Event is 73 | // received 74 | c.invalidateDeadline = c.timeNow().Add(c.options.invalidateTimeout) 75 | } 76 | 77 | func (c *clientInstancerCache) updateCache(instances []string) { 78 | // Deterministic order (for later). 79 | sort.Strings(instances) 80 | 81 | // Produce the current set of services. 82 | cache := make(map[string]clientInstanceCloser, len(instances)) 83 | for _, instance := range instances { 84 | // If it already exists, just copy it over. 85 | if sc, ok := c.cache[instance]; ok { 86 | cache[instance] = sc 87 | delete(c.cache, instance) 88 | continue 89 | } 90 | 91 | // If it doesn't exist, create it. 92 | service, closer, err := c.factory(instance) 93 | if err != nil { 94 | c.logger.Log("instance", instance, "err", err) 95 | continue 96 | } 97 | cache[instance] = clientInstanceCloser{service, closer} 98 | } 99 | 100 | // Close any leftover clientInstances. 101 | for _, sc := range c.cache { 102 | if sc.Closer != nil { 103 | sc.Closer.Close() 104 | } 105 | } 106 | 107 | // Populate the slice of clientInstances. 108 | clientInstances := make([]interface{}, 0, len(cache)) 109 | for _, instance := range instances { 110 | // A bad factory may mean an instance is not present. 111 | if _, ok := cache[instance]; !ok { 112 | continue 113 | } 114 | clientInstances = append(clientInstances, cache[instance].ci) 115 | } 116 | 117 | // Swap and trigger GC for old copies. 118 | c.clientInstances = clientInstances 119 | c.cache = cache 120 | } 121 | 122 | // Clients yields the current set of (presumably identical) clientInstances, 123 | // ordered lexicographically by the corresponding instance string. 124 | func (c *clientInstancerCache) Clients() ([]interface{}, error) { 125 | // in the steady state we're going to have many goroutines calling Clients() 126 | // concurrently, so to minimize contention we use a shared R-lock. 127 | c.mtx.RLock() 128 | 129 | if c.err == nil || c.timeNow().Before(c.invalidateDeadline) { 130 | defer c.mtx.RUnlock() 131 | return c.clientInstances, nil 132 | } 133 | 134 | c.mtx.RUnlock() 135 | 136 | // in case of an error, switch to an exclusive lock. 137 | c.mtx.Lock() 138 | defer c.mtx.Unlock() 139 | 140 | // re-check condition due to a race between RUnlock() and Lock(). 141 | if c.err == nil || c.timeNow().Before(c.invalidateDeadline) { 142 | return c.clientInstances, nil 143 | } 144 | 145 | c.updateCache(nil) // close any remaining active clientInstances 146 | return nil, c.err 147 | } 148 | -------------------------------------------------------------------------------- /services/event/database/sqlite/sqlite.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "database/sql" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/jmoiron/sqlx" 12 | "github.com/kevinburke/go.uuid" 13 | "github.com/mattn/go-sqlite3" 14 | "github.com/openxact/versioning" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/event/database" 18 | ) 19 | 20 | type sqlite struct { 21 | db *sqlx.DB 22 | logger log.Logger 23 | } 24 | 25 | // New returns a new Repository backed by SQLite 26 | func New(db *sqlx.DB, logger log.Logger) (database.Repository, error) { 27 | // run our embedded database versioning logic 28 | versioner, err := versioning.New( 29 | db, "ocgokitexample.event", level.Debug(logger), false, 30 | ) 31 | if err != nil { 32 | return nil, err 33 | } 34 | versioner.Add(1, v1) 35 | if _, err = versioner.Run(); err != nil { 36 | return nil, err 37 | } 38 | 39 | // return our repository 40 | return &sqlite{db: db, logger: logger}, nil 41 | } 42 | 43 | func (s *sqlite) Create( 44 | ctx context.Context, event database.Event, 45 | ) (id *uuid.UUID, err error) { 46 | // check if we need to create a new UUID 47 | if uuid.Equal(event.ID, uuid.Nil) { 48 | event.ID = uuid.NewV4() 49 | } 50 | 51 | if _, err = s.db.ExecContext( 52 | ctx, 53 | `INSERT INTO event (id, tenant_id, name) VALUES (?, ?, ?)`, 54 | event.ID.Bytes(), event.TenantID.Bytes(), event.Name, 55 | ); err != nil { 56 | if sqlErr, ok := err.(sqlite3.Error); ok { 57 | switch sqlErr.ExtendedCode { 58 | case sqlite3.ErrConstraintUnique: 59 | level.Debug(s.logger).Log("err", err) 60 | return nil, database.ErrNameExists 61 | case sqlite3.ErrConstraintPrimaryKey: 62 | level.Debug(s.logger).Log("err", err) 63 | return nil, database.ErrIDExists 64 | } 65 | } 66 | level.Error(s.logger).Log("err", err) 67 | return nil, database.ErrRepository 68 | } 69 | 70 | return &event.ID, nil 71 | } 72 | 73 | func (s *sqlite) Get(ctx context.Context, id uuid.UUID) (*database.Event, error) { 74 | event := database.Event{ID: id} 75 | 76 | if err := s.db.QueryRowContext( 77 | ctx, 78 | `SELECT tenant_id, name FROM event WHERE id = ?`, id.Bytes(), 79 | ).Scan(&event.TenantID, &event.Name); err != nil { 80 | if err == sql.ErrNoRows { 81 | level.Debug(s.logger).Log("err", err) 82 | return nil, database.ErrNotFound 83 | } 84 | level.Error(s.logger).Log("err", err) 85 | return nil, database.ErrRepository 86 | } 87 | 88 | return &event, nil 89 | } 90 | 91 | func (s *sqlite) Update(ctx context.Context, event database.Event) (err error) { 92 | var ( 93 | res sql.Result 94 | cnt int64 95 | ) 96 | 97 | res, err = s.db.ExecContext( 98 | ctx, 99 | `UPDATE event SET name = ? WHERE tenant_id = ? AND id = ?`, 100 | event.Name, event.TenantID.Bytes(), event.ID.Bytes(), 101 | ) 102 | if err != nil { 103 | if sqlErr, ok := err.(sqlite3.Error); ok { 104 | if sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique { 105 | level.Debug(s.logger).Log("err", err) 106 | return database.ErrNameExists 107 | } 108 | } 109 | level.Error(s.logger).Log("err", err) 110 | return database.ErrRepository 111 | } 112 | 113 | cnt, err = res.RowsAffected() 114 | if err != nil { 115 | level.Error(s.logger).Log("err", err) 116 | return database.ErrRepository 117 | } 118 | 119 | if cnt == 0 { 120 | level.Debug(s.logger).Log("err", err) 121 | return database.ErrNotFound 122 | } 123 | 124 | return 125 | } 126 | 127 | func (s *sqlite) Delete( 128 | ctx context.Context, tenantID uuid.UUID, id uuid.UUID, 129 | ) (err error) { 130 | if _, err = s.db.ExecContext( 131 | ctx, 132 | `DELETE FROM event WHERE tenant_id = ? AND id = ?`, 133 | tenantID.Bytes(), id.Bytes(), 134 | ); err != nil { 135 | level.Error(s.logger).Log("err", err) 136 | return database.ErrRepository 137 | } 138 | 139 | return 140 | } 141 | 142 | func (s *sqlite) List( 143 | ctx context.Context, tenantID uuid.UUID, 144 | ) (events []*database.Event, err error) { 145 | var rows *sql.Rows 146 | 147 | if uuid.Equal(tenantID, uuid.Nil) { 148 | // listing all events 149 | rows, err = s.db.QueryContext( 150 | ctx, 151 | `SELECT id, tenant_id, name FROM event ORDER BY tenant_id, name`, 152 | ) 153 | } else { 154 | // listing owned events 155 | rows, err = s.db.QueryContext( 156 | ctx, 157 | `SELECT id, tenant_id, name FROM event WHERE tenant_id = ? ORDER BY name`, 158 | tenantID.Bytes(), 159 | ) 160 | } 161 | if err != nil { 162 | if err == sql.ErrNoRows { 163 | return make([]*database.Event, 0), nil 164 | } 165 | level.Error(s.logger).Log("err", err) 166 | return nil, database.ErrRepository 167 | } 168 | defer rows.Close() 169 | 170 | for rows.Next() { 171 | var event database.Event 172 | if err = rows.Scan( 173 | &event.ID, &event.TenantID, &event.Name, 174 | ); err != nil { 175 | level.Error(s.logger).Log("err", err) 176 | return nil, database.ErrRepository 177 | } 178 | events = append(events, &event) 179 | } 180 | 181 | return events, nil 182 | } 183 | -------------------------------------------------------------------------------- /services/event/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | // external 15 | "github.com/go-kit/kit/log" 16 | "github.com/go-kit/kit/log/level" 17 | "github.com/go-kit/kit/sd/etcd" 18 | "github.com/gorilla/mux" 19 | "github.com/jmoiron/sqlx" 20 | "github.com/kevinburke/go.uuid" 21 | _ "github.com/mattn/go-sqlite3" 22 | "github.com/oklog/run" 23 | "github.com/opencensus-integrations/ocsql" 24 | "go.opencensus.io/plugin/ochttp" 25 | 26 | // project 27 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 28 | "github.com/basvanbeek/opencensus-gokit-example/services/event/database/sqlite" 29 | "github.com/basvanbeek/opencensus-gokit-example/services/event/implementation" 30 | "github.com/basvanbeek/opencensus-gokit-example/services/event/transport/pb" 31 | transporttwirp "github.com/basvanbeek/opencensus-gokit-example/services/event/transport/twirp" 32 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 33 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 34 | ) 35 | 36 | func main() { 37 | var ( 38 | err error 39 | instance = uuid.NewV4() 40 | ) 41 | 42 | // initialize our OpenCensus configuration and defer a clean-up 43 | defer oc.Setup(event.ServiceName).Close() 44 | 45 | // initialize our structured logger for the service 46 | var logger log.Logger 47 | { 48 | logger = log.NewLogfmtLogger(os.Stderr) 49 | logger = log.NewSyncLogger(logger) 50 | logger = level.NewFilter(logger, level.AllowDebug()) 51 | logger = log.With(logger, 52 | "svc", event.ServiceName, 53 | "instance", instance, 54 | "ts", log.DefaultTimestampUTC, 55 | "clr", log.DefaultCaller, 56 | ) 57 | } 58 | 59 | level.Info(logger).Log("msg", "service started") 60 | defer level.Info(logger).Log("msg", "service ended") 61 | 62 | ctx, cancel := context.WithCancel(context.Background()) 63 | defer cancel() 64 | 65 | // Create our etcd client for Service Discovery 66 | // 67 | // we could have used the v3 client but then we must vendor or suffer the 68 | // following issue originating from gRPC init: 69 | // panic: http: multiple registrations for /debug/requests 70 | var sdc etcd.Client 71 | { 72 | // create our Go kit etcd client 73 | sdc, err = etcd.NewClient(ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}) 74 | if err != nil { 75 | level.Error(logger).Log("exit", err) 76 | os.Exit(-1) 77 | } 78 | } 79 | 80 | // Create our DB Connection Driver 81 | var db *sqlx.DB 82 | { 83 | var driverName string 84 | driverName, err = ocsql.Register("sqlite3", ocsql.WithOptions(ocsql.AllTraceOptions)) 85 | if err != nil { 86 | level.Error(logger).Log("exit", err) 87 | os.Exit(-1) 88 | } 89 | db, err = sqlx.Open(driverName, "event.db?_journal_mode=WAL") 90 | if err != nil { 91 | level.Error(logger).Log("exit", err) 92 | os.Exit(-1) 93 | } 94 | } 95 | 96 | // Create our Event Service 97 | var svc event.Service 98 | { 99 | repository, err := sqlite.New(db, logger) 100 | if err != nil { 101 | level.Error(logger).Log("exit", err) 102 | os.Exit(-1) 103 | } 104 | svc = implementation.NewService(repository, logger) 105 | // add service level middlewares here 106 | 107 | } 108 | 109 | // run.Group manages our goroutine lifecycles 110 | // see: https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s 111 | var g run.Group 112 | { 113 | // set-up our ZPages handler 114 | oc.ZPages(g, logger) 115 | } 116 | { 117 | // set-up our twirp transport 118 | var ( 119 | bindIP, _ = network.HostIP() 120 | eventService = transporttwirp.NewService(svc, logger) 121 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 122 | svcInstance = "/services/" + event.ServiceName + "/twirp/" + instance.String() 123 | addr = "http://" + listener.Addr().String() 124 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 125 | service = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 126 | registrar = etcd.NewRegistrar(sdc, service, logger) 127 | twirpHandler = pb.NewEventServer(eventService, nil) 128 | router = mux.NewRouter() 129 | ) 130 | 131 | router.PathPrefix(pb.EventPathPrefix).Handler(twirpHandler) 132 | 133 | // add default ochttp handler for TWIRP 134 | handler := &ochttp.Handler{ 135 | Handler: router, 136 | } 137 | 138 | g.Add(func() error { 139 | registrar.Register() 140 | return http.Serve(listener, handler) 141 | }, func(error) { 142 | registrar.Deregister() 143 | listener.Close() 144 | }) 145 | } 146 | { 147 | // set-up our signal handler 148 | var ( 149 | cancelInterrupt = make(chan struct{}) 150 | c = make(chan os.Signal, 2) 151 | ) 152 | defer close(c) 153 | 154 | g.Add(func() error { 155 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 156 | select { 157 | case sig := <-c: 158 | return fmt.Errorf("received signal %s", sig) 159 | case <-cancelInterrupt: 160 | return nil 161 | } 162 | }, func(error) { 163 | close(cancelInterrupt) 164 | }) 165 | } 166 | 167 | // spawn our goroutines and wait for shutdown 168 | level.Error(logger).Log("exit", g.Run()) 169 | } 170 | -------------------------------------------------------------------------------- /clients/frontend/http/encode_decode.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "bytes" 6 | "context" 7 | "encoding/json" 8 | "errors" 9 | "io/ioutil" 10 | "net/http" 11 | 12 | // project 13 | 14 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 16 | ) 17 | 18 | // decodeLoginResponse decodes the incoming HTTP payload to the Go kit payload 19 | func decodeLoginResponse(_ context.Context, r *http.Response) (interface{}, error) { 20 | var resp transport.LoginResponse 21 | 22 | switch r.StatusCode { 23 | case http.StatusOK: 24 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 25 | return nil, err 26 | } 27 | return resp, nil 28 | case http.StatusBadRequest: 29 | body := decodeErrorResponse(r) 30 | switch body { 31 | case frontend.ErrorUserPassRequired: 32 | resp.Err = frontend.ErrUserPassRequired 33 | case frontend.ErrorUserPassUnknown: 34 | resp.Err = frontend.ErrUserPassUnknown 35 | default: 36 | return nil, errors.New(body) 37 | } 38 | return resp, nil 39 | default: 40 | return nil, errors.New(decodeErrorResponse(r)) 41 | } 42 | } 43 | 44 | // decodeEventCreateResponse decodes the incoming HTTP payload to the Go kit payload 45 | func decodeEventCreateResponse(_ context.Context, r *http.Response) (interface{}, error) { 46 | var resp transport.EventCreateResponse 47 | 48 | switch r.StatusCode { 49 | case http.StatusOK: 50 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 51 | return nil, err 52 | } 53 | return resp, nil 54 | case http.StatusConflict: 55 | body := decodeErrorResponse(r) 56 | switch body { 57 | case frontend.ErrorEventExists: 58 | resp.Err = frontend.ErrEventExists 59 | default: 60 | return nil, errors.New(body) 61 | } 62 | return resp, nil 63 | default: 64 | return nil, errors.New(decodeErrorResponse(r)) 65 | } 66 | } 67 | 68 | // decodeEventGetResponse decodes the incoming HTTP payload to the Go kit payload 69 | func decodeEventGetResponse(_ context.Context, r *http.Response) (interface{}, error) { 70 | var resp transport.EventGetResponse 71 | 72 | switch r.StatusCode { 73 | case http.StatusOK: 74 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 75 | return nil, err 76 | } 77 | return resp, nil 78 | default: 79 | return nil, errors.New(decodeErrorResponse(r)) 80 | } 81 | } 82 | 83 | // decodeEventUpdateResponse decodes the incoming HTTP payload to the Go kit payload 84 | func decodeEventUpdateResponse(_ context.Context, r *http.Response) (interface{}, error) { 85 | var resp transport.EventUpdateResponse 86 | 87 | switch r.StatusCode { 88 | case http.StatusOK: 89 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 90 | return nil, err 91 | } 92 | return resp, nil 93 | default: 94 | return nil, errors.New(decodeErrorResponse(r)) 95 | } 96 | } 97 | 98 | // decodeEventDeleteResponse decodes the incoming HTTP payload to the Go kit payload 99 | func decodeEventDeleteResponse(_ context.Context, r *http.Response) (interface{}, error) { 100 | var resp transport.EventDeleteResponse 101 | 102 | switch r.StatusCode { 103 | case http.StatusOK: 104 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 105 | return nil, err 106 | } 107 | return resp, nil 108 | default: 109 | return nil, errors.New(decodeErrorResponse(r)) 110 | } 111 | } 112 | 113 | // decodeEventListResponse decodes the incoming HTTP payload to the Go kit payload 114 | func decodeEventListResponse(_ context.Context, r *http.Response) (interface{}, error) { 115 | var resp transport.EventListResponse 116 | 117 | switch r.StatusCode { 118 | case http.StatusOK: 119 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 120 | return nil, err 121 | } 122 | return resp, nil 123 | default: 124 | return nil, errors.New(decodeErrorResponse(r)) 125 | } 126 | } 127 | 128 | // decodeUnlockDeviceResponse decodes the incoming HTTP payload to the Go kit payload 129 | func decodeUnlockDeviceResponse(_ context.Context, r *http.Response) (interface{}, error) { 130 | var resp transport.UnlockDeviceResponse 131 | 132 | switch r.StatusCode { 133 | case http.StatusOK: 134 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 135 | return nil, err 136 | } 137 | return resp, nil 138 | default: 139 | return nil, errors.New(decodeErrorResponse(r)) 140 | } 141 | } 142 | 143 | // decodeGenerateQRResponse decodes the incoming HTTP payload to the Go kit payload 144 | func decodeGenerateQRResponse(_ context.Context, r *http.Response) (interface{}, error) { 145 | var resp transport.GenerateQRResponse 146 | 147 | switch r.StatusCode { 148 | case http.StatusOK: 149 | if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { 150 | return nil, err 151 | } 152 | return resp, nil 153 | case http.StatusBadRequest: 154 | body := decodeErrorResponse(r) 155 | switch body { 156 | case frontend.ErrorInvalidQRParams: 157 | resp.Err = frontend.ErrInvalidQRParams 158 | default: 159 | return nil, errors.New(body) 160 | } 161 | return resp, nil 162 | case http.StatusServiceUnavailable: 163 | body := decodeErrorResponse(r) 164 | switch body { 165 | case frontend.ErrorQRGenerate: 166 | return nil, frontend.ErrQRGenerate 167 | default: 168 | return nil, errors.New(body) 169 | } 170 | 171 | default: 172 | return nil, errors.New(decodeErrorResponse(r)) 173 | } 174 | } 175 | 176 | func decodeErrorResponse(r *http.Response) string { 177 | b, err := ioutil.ReadAll(r.Body) 178 | if err != nil { 179 | return err.Error() 180 | } 181 | 182 | return string(bytes.Trim(b, "\r\n\t ")) 183 | } 184 | -------------------------------------------------------------------------------- /services/qr/transport/pb/qr.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: services/qr/transport/pb/qr.proto 3 | 4 | /* 5 | Package pb is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | services/qr/transport/pb/qr.proto 9 | 10 | It has these top-level messages: 11 | GenerateRequest 12 | GenerateResponse 13 | */ 14 | package pb 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import ( 21 | context "golang.org/x/net/context" 22 | grpc "google.golang.org/grpc" 23 | ) 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | type GenerateRequest struct { 37 | Data string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"` 38 | Level int32 `protobuf:"varint,2,opt,name=level" json:"level,omitempty"` 39 | Size int32 `protobuf:"varint,3,opt,name=size" json:"size,omitempty"` 40 | } 41 | 42 | func (m *GenerateRequest) Reset() { *m = GenerateRequest{} } 43 | func (m *GenerateRequest) String() string { return proto.CompactTextString(m) } 44 | func (*GenerateRequest) ProtoMessage() {} 45 | func (*GenerateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 46 | 47 | func (m *GenerateRequest) GetData() string { 48 | if m != nil { 49 | return m.Data 50 | } 51 | return "" 52 | } 53 | 54 | func (m *GenerateRequest) GetLevel() int32 { 55 | if m != nil { 56 | return m.Level 57 | } 58 | return 0 59 | } 60 | 61 | func (m *GenerateRequest) GetSize() int32 { 62 | if m != nil { 63 | return m.Size 64 | } 65 | return 0 66 | } 67 | 68 | type GenerateResponse struct { 69 | Image []byte `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"` 70 | } 71 | 72 | func (m *GenerateResponse) Reset() { *m = GenerateResponse{} } 73 | func (m *GenerateResponse) String() string { return proto.CompactTextString(m) } 74 | func (*GenerateResponse) ProtoMessage() {} 75 | func (*GenerateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 76 | 77 | func (m *GenerateResponse) GetImage() []byte { 78 | if m != nil { 79 | return m.Image 80 | } 81 | return nil 82 | } 83 | 84 | func init() { 85 | proto.RegisterType((*GenerateRequest)(nil), "pb.GenerateRequest") 86 | proto.RegisterType((*GenerateResponse)(nil), "pb.GenerateResponse") 87 | } 88 | 89 | // Reference imports to suppress errors if they are not otherwise used. 90 | var _ context.Context 91 | var _ grpc.ClientConn 92 | 93 | // This is a compile-time assertion to ensure that this generated file 94 | // is compatible with the grpc package it is being compiled against. 95 | const _ = grpc.SupportPackageIsVersion4 96 | 97 | // Client API for QR service 98 | 99 | type QRClient interface { 100 | Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error) 101 | } 102 | 103 | type qRClient struct { 104 | cc *grpc.ClientConn 105 | } 106 | 107 | func NewQRClient(cc *grpc.ClientConn) QRClient { 108 | return &qRClient{cc} 109 | } 110 | 111 | func (c *qRClient) Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (*GenerateResponse, error) { 112 | out := new(GenerateResponse) 113 | err := grpc.Invoke(ctx, "/pb.QR/Generate", in, out, c.cc, opts...) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return out, nil 118 | } 119 | 120 | // Server API for QR service 121 | 122 | type QRServer interface { 123 | Generate(context.Context, *GenerateRequest) (*GenerateResponse, error) 124 | } 125 | 126 | func RegisterQRServer(s *grpc.Server, srv QRServer) { 127 | s.RegisterService(&_QR_serviceDesc, srv) 128 | } 129 | 130 | func _QR_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 131 | in := new(GenerateRequest) 132 | if err := dec(in); err != nil { 133 | return nil, err 134 | } 135 | if interceptor == nil { 136 | return srv.(QRServer).Generate(ctx, in) 137 | } 138 | info := &grpc.UnaryServerInfo{ 139 | Server: srv, 140 | FullMethod: "/pb.QR/Generate", 141 | } 142 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 143 | return srv.(QRServer).Generate(ctx, req.(*GenerateRequest)) 144 | } 145 | return interceptor(ctx, in, info, handler) 146 | } 147 | 148 | var _QR_serviceDesc = grpc.ServiceDesc{ 149 | ServiceName: "pb.QR", 150 | HandlerType: (*QRServer)(nil), 151 | Methods: []grpc.MethodDesc{ 152 | { 153 | MethodName: "Generate", 154 | Handler: _QR_Generate_Handler, 155 | }, 156 | }, 157 | Streams: []grpc.StreamDesc{}, 158 | Metadata: "services/qr/transport/pb/qr.proto", 159 | } 160 | 161 | func init() { proto.RegisterFile("services/qr/transport/pb/qr.proto", fileDescriptor0) } 162 | 163 | var fileDescriptor0 = []byte{ 164 | // 187 bytes of a gzipped FileDescriptorProto 165 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2c, 0x4e, 0x2d, 0x2a, 166 | 0xcb, 0x4c, 0x4e, 0x2d, 0xd6, 0x2f, 0x2c, 0xd2, 0x2f, 0x29, 0x4a, 0xcc, 0x2b, 0x2e, 0xc8, 0x2f, 167 | 0x2a, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x2c, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 168 | 0x48, 0x52, 0xf2, 0xe7, 0xe2, 0x77, 0x4f, 0xcd, 0x4b, 0x2d, 0x4a, 0x2c, 0x49, 0x0d, 0x4a, 0x2d, 169 | 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0x49, 0x49, 0x2c, 0x49, 0x94, 0x60, 0x54, 0x60, 170 | 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x44, 0xb8, 0x58, 0x73, 0x52, 0xcb, 0x52, 0x73, 0x24, 0x98, 171 | 0x14, 0x18, 0x35, 0x58, 0x83, 0x20, 0x1c, 0x90, 0xca, 0xe2, 0xcc, 0xaa, 0x54, 0x09, 0x66, 0xb0, 172 | 0x20, 0x98, 0xad, 0xa4, 0xc1, 0x25, 0x80, 0x30, 0xb0, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0xa4, 173 | 0x3b, 0x33, 0x37, 0x31, 0x3d, 0x15, 0x6c, 0x24, 0x4f, 0x10, 0x84, 0x63, 0x64, 0xcb, 0xc5, 0x14, 174 | 0x18, 0x24, 0x64, 0xce, 0xc5, 0x01, 0x53, 0x2f, 0x24, 0xac, 0x57, 0x90, 0xa4, 0x87, 0xe6, 0x1c, 175 | 0x29, 0x11, 0x54, 0x41, 0x88, 0x91, 0x4a, 0x0c, 0x49, 0x6c, 0x60, 0x4f, 0x18, 0x03, 0x02, 0x00, 176 | 0x00, 0xff, 0xff, 0x3f, 0x8b, 0x1e, 0x2d, 0xe9, 0x00, 0x00, 0x00, 177 | } 178 | -------------------------------------------------------------------------------- /services/frontend/implementation/service.go: -------------------------------------------------------------------------------- 1 | package implementation 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "strings" 7 | 8 | // external 9 | "github.com/go-kit/kit/log" 10 | "github.com/go-kit/kit/log/level" 11 | "github.com/kevinburke/go.uuid" 12 | 13 | // project 14 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 15 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 16 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 17 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 18 | ) 19 | 20 | // service implements frontend.Service 21 | type service struct { 22 | evtClient event.Service 23 | devClient device.Service 24 | qrClient qr.Service 25 | logger log.Logger 26 | } 27 | 28 | // NewService creates and returns a new Frontend service instance 29 | func NewService(evtClient event.Service, devClient device.Service, qrClient qr.Service, logger log.Logger) frontend.Service { 30 | return &service{ 31 | evtClient: evtClient, 32 | devClient: devClient, 33 | qrClient: qrClient, 34 | logger: logger, 35 | } 36 | } 37 | 38 | func (s *service) Login(ctx context.Context, user, pass string) (*frontend.Login, error) { 39 | // NOTE: obviously this is hardcoded due to demo purposes 40 | user = strings.Trim(user, "\r\n\t ") 41 | pass = strings.Trim(pass, "\r\n\t ") 42 | if len(user) == 0 || len(pass) == 0 { 43 | return nil, frontend.ErrUserPassRequired 44 | } 45 | 46 | switch { 47 | case user == "john" && pass == "doe": 48 | return &frontend.Login{ 49 | ID: uuid.NewV5(uuid.NamespaceOID, "user.id.1"), 50 | Name: "John Doe", 51 | TenantID: uuid.NewV5(uuid.NamespaceOID, "tenant.id.1"), 52 | TenantName: "Acme Corp.", 53 | }, nil 54 | case user == "jane" && pass == "doe": 55 | return &frontend.Login{ 56 | ID: uuid.NewV5(uuid.NamespaceOID, "user.id.2"), 57 | Name: "Jane Doe", 58 | TenantID: uuid.NewV5(uuid.NamespaceOID, "tenant.id.2"), 59 | TenantName: "Evil Inc.", 60 | }, nil 61 | } 62 | 63 | return nil, frontend.ErrUserPassUnknown 64 | } 65 | 66 | func (s *service) EventCreate(ctx context.Context, tenantID uuid.UUID, evt frontend.Event) (*uuid.UUID, error) { 67 | id, err := s.evtClient.Create(ctx, tenantID, event.Event(evt)) 68 | 69 | switch err { 70 | case nil: 71 | return id, nil 72 | case event.ErrEventExists: 73 | return nil, frontend.ErrEventExists 74 | default: 75 | return nil, frontend.ErrService 76 | } 77 | } 78 | 79 | func (s *service) EventGet(ctx context.Context, tenantID, id uuid.UUID) (*frontend.Event, error) { 80 | evt, err := s.evtClient.Get(ctx, tenantID, id) 81 | 82 | switch err { 83 | case nil: 84 | return (*frontend.Event)(evt), nil 85 | case event.ErrNotFound: 86 | return nil, frontend.ErrEventNotFound 87 | default: 88 | return nil, frontend.ErrService 89 | } 90 | } 91 | 92 | func (s *service) EventUpdate(ctx context.Context, tenantID uuid.UUID, evt frontend.Event) error { 93 | err := s.evtClient.Update(ctx, tenantID, event.Event(evt)) 94 | 95 | switch err { 96 | case nil: 97 | return nil 98 | case event.ErrEventExists: 99 | return frontend.ErrEventExists 100 | case event.ErrNotFound: 101 | return frontend.ErrEventNotFound 102 | default: 103 | return frontend.ErrService 104 | } 105 | } 106 | 107 | func (s *service) EventDelete(ctx context.Context, tenantID, id uuid.UUID) error { 108 | if err := s.evtClient.Delete(ctx, tenantID, id); err != nil { 109 | return frontend.ErrService 110 | } 111 | return nil 112 | } 113 | 114 | func (s *service) EventList(ctx context.Context, tenantID uuid.UUID) ([]*frontend.Event, error) { 115 | evts, err := s.evtClient.List(ctx, tenantID) 116 | if err != nil { 117 | return nil, frontend.ErrService 118 | } 119 | events := make([]*frontend.Event, 0, len(evts)) 120 | for _, e := range evts { 121 | events = append(events, (*frontend.Event)(e)) 122 | } 123 | return events, nil 124 | } 125 | 126 | // Unlockdevice returns a new session for allowing device to check-in participants. 127 | func (s *service) UnlockDevice(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) (*frontend.Session, error) { 128 | logger := log.With(s.logger, "method", "UnlockDevice") 129 | 130 | if eventID == uuid.Nil { 131 | level.Warn(logger).Log("err", frontend.ErrRequireEventID) 132 | return nil, frontend.ErrRequireEventID 133 | } 134 | if deviceID == uuid.Nil { 135 | level.Warn(logger).Log("err", frontend.ErrRequireDeviceID) 136 | return nil, frontend.ErrRequireDeviceID 137 | } 138 | 139 | unlockCode = strings.Trim(unlockCode, "\r\n\t ") 140 | if unlockCode == "" { 141 | level.Warn(logger).Log("err", frontend.ErrRequireUnlockCode) 142 | return nil, frontend.ErrRequireUnlockCode 143 | } 144 | 145 | session, err := s.devClient.Unlock(ctx, eventID, deviceID, unlockCode) 146 | 147 | switch err { 148 | case nil: 149 | return &frontend.Session{ 150 | EventID: eventID, 151 | EventCaption: session.EventCaption, 152 | DeviceID: deviceID, 153 | DeviceCaption: session.DeviceCaption, 154 | Token: "TOKEN", 155 | }, nil 156 | case device.ErrUnlockNotFound: 157 | return nil, frontend.ErrUnlockNotFound 158 | default: 159 | return nil, frontend.ErrService 160 | } 161 | } 162 | 163 | // Generate returns a new QR code device unlock image based on the provided details. 164 | func (s *service) GenerateQR(ctx context.Context, eventID, deviceID uuid.UUID, unlockCode string) ([]byte, error) { 165 | logger := log.With(s.logger, "method", "GenerateQR") 166 | 167 | if eventID == uuid.Nil { 168 | level.Warn(logger).Log("err", frontend.ErrRequireEventID) 169 | return nil, frontend.ErrRequireEventID 170 | } 171 | if deviceID == uuid.Nil { 172 | level.Warn(logger).Log("err", frontend.ErrRequireDeviceID) 173 | return nil, frontend.ErrRequireDeviceID 174 | } 175 | 176 | unlockCode = strings.Trim(unlockCode, "\r\n\t ") 177 | if unlockCode == "" { 178 | level.Warn(logger).Log("err", frontend.ErrRequireUnlockCode) 179 | return nil, frontend.ErrRequireUnlockCode 180 | } 181 | 182 | data, err := s.qrClient.Generate( 183 | ctx, eventID.String()+":"+deviceID.String()+":"+unlockCode, qr.LevelM, 256, 184 | ) 185 | 186 | switch err { 187 | case nil: 188 | return data, nil 189 | case qr.ErrNoContent, qr.ErrInvalidSize, qr.ErrInvalidRecoveryLevel, 190 | qr.ErrContentTooLarge: 191 | return nil, frontend.ErrInvalidQRParams 192 | case qr.ErrGenerate: 193 | return nil, frontend.ErrQRGenerate 194 | default: 195 | return nil, frontend.ErrService 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /services/frontend/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | // external 15 | "github.com/go-kit/kit/log" 16 | "github.com/go-kit/kit/log/level" 17 | "github.com/go-kit/kit/sd/etcd" 18 | kitoc "github.com/go-kit/kit/tracing/opencensus" 19 | kithttp "github.com/go-kit/kit/transport/http" 20 | "github.com/kevinburke/go.uuid" 21 | "github.com/oklog/run" 22 | "go.opencensus.io/plugin/ochttp" 23 | 24 | // project 25 | devclient "github.com/basvanbeek/opencensus-gokit-example/clients/device" 26 | evtclient "github.com/basvanbeek/opencensus-gokit-example/clients/event" 27 | qrclient "github.com/basvanbeek/opencensus-gokit-example/clients/qr" 28 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 29 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 30 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 31 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/implementation" 32 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 33 | httptransport "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport/http" 34 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 35 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 36 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 37 | ) 38 | 39 | func main() { 40 | var ( 41 | err error 42 | instance = uuid.NewV4() 43 | ) 44 | 45 | // initialize our OpenCensus configuration and defer a clean-up 46 | defer oc.Setup(frontend.ServiceName).Close() 47 | 48 | // initialize our structured logger for the service 49 | var logger log.Logger 50 | { 51 | logger = log.NewLogfmtLogger(os.Stderr) 52 | logger = log.NewSyncLogger(logger) 53 | logger = level.NewFilter(logger, level.AllowDebug()) 54 | logger = log.With(logger, 55 | "svc", frontend.ServiceName, 56 | "instance", instance, 57 | "ts", log.DefaultTimestampUTC, 58 | "clr", log.DefaultCaller, 59 | ) 60 | } 61 | 62 | level.Info(logger).Log("msg", "service started") 63 | defer level.Info(logger).Log("msg", "service ended") 64 | 65 | ctx, cancel := context.WithCancel(context.Background()) 66 | defer cancel() 67 | 68 | // Create our etcd client for Service Discovery 69 | // 70 | // we could have used the v3 client but then we must vendor or suffer the 71 | // following issue originating from gRPC init: 72 | // panic: http: multiple registrations for /debug/requests 73 | var sdc etcd.Client 74 | { 75 | // create our Go kit etcd client 76 | sdc, err = etcd.NewClient(ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}) 77 | if err != nil { 78 | level.Error(logger).Log("exit", err) 79 | os.Exit(-1) 80 | } 81 | } 82 | 83 | var svc frontend.Service 84 | { 85 | // create an instancer for the event client 86 | evtInstancer, err := etcd.NewInstancer(sdc, "/services/"+event.ServiceName+"/twirp", logger) 87 | if err != nil { 88 | level.Error(logger).Log("exit", err) 89 | } 90 | httpClient := &http.Client{Transport: &ochttp.Transport{}} 91 | evtClient := evtclient.NewTwirp(evtInstancer, httpClient, logger) 92 | 93 | // create an instancer for the device client 94 | devInstancer, err := etcd.NewInstancer(sdc, "/services/"+device.ServiceName+"/http", logger) 95 | if err != nil { 96 | level.Error(logger).Log("exit", err) 97 | } 98 | 99 | // initialize our Device client using http transport 100 | devClient := devclient.NewHTTPClient(devInstancer, logger) 101 | 102 | // create an instancer for the QR client 103 | qrInstancer, err := etcd.NewInstancer(sdc, "/services/"+qr.ServiceName+"/grpc", logger) 104 | if err != nil { 105 | level.Error(logger).Log("exit", err) 106 | } 107 | // initialize QR client 108 | qrClient := qrclient.NewGRPCClient(qrInstancer, logger) 109 | 110 | // create our frontend service 111 | svc = implementation.NewService(evtClient, devClient, qrClient, logger) 112 | // add service level middlewares here 113 | } 114 | 115 | var endpoints transport.Endpoints 116 | { 117 | endpoints = transport.MakeEndpoints(svc) 118 | 119 | // trace our server side endpoints 120 | endpoints = transport.Endpoints{ 121 | Login: oc.ServerEndpoint("Login")(endpoints.Login), 122 | EventCreate: oc.ServerEndpoint("EventCreate")(endpoints.EventCreate), 123 | EventGet: oc.ServerEndpoint("EventGet")(endpoints.EventGet), 124 | EventUpdate: oc.ServerEndpoint("EventUpdate")(endpoints.EventUpdate), 125 | EventDelete: oc.ServerEndpoint("EventDelete")(endpoints.EventDelete), 126 | EventList: oc.ServerEndpoint("EventList")(endpoints.EventList), 127 | UnlockDevice: oc.ServerEndpoint("UnlockDevice")(endpoints.UnlockDevice), 128 | GenerateQR: oc.ServerEndpoint("GenerateQR")(endpoints.GenerateQR), 129 | } 130 | } 131 | 132 | // run.Group manages our goroutine lifecycles 133 | // see: https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s 134 | var g run.Group 135 | { 136 | // set-up our ZPages handler 137 | oc.ZPages(g, logger) 138 | } 139 | { 140 | // set-up our http transport 141 | var ( 142 | bindIP, _ = network.HostIP() 143 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 144 | svcInstance = fmt.Sprintf("/services/%s/http/%s/", frontend.ServiceName, instance) 145 | addr = "http://" + listener.Addr().String() 146 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 147 | service = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 148 | registrar = etcd.NewRegistrar(sdc, service, logger) 149 | ocTracing = kitoc.HTTPServerTrace() 150 | serverOptions = []kithttp.ServerOption{ocTracing} 151 | feService = httptransport.NewService(endpoints, serverOptions, logger) 152 | ) 153 | 154 | g.Add(func() error { 155 | registrar.Register() 156 | return http.Serve(listener, feService) 157 | }, func(error) { 158 | registrar.Deregister() 159 | listener.Close() 160 | }) 161 | } 162 | { 163 | // set-up our signal handler 164 | var ( 165 | cancelInterrupt = make(chan struct{}) 166 | c = make(chan os.Signal, 2) 167 | ) 168 | defer close(c) 169 | 170 | g.Add(func() error { 171 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 172 | select { 173 | case sig := <-c: 174 | return fmt.Errorf("received signal %s", sig) 175 | case <-cancelInterrupt: 176 | return nil 177 | } 178 | }, func(error) { 179 | close(cancelInterrupt) 180 | }) 181 | } 182 | 183 | // spawn our goroutines and wait for shutdown 184 | level.Error(logger).Log("exit", g.Run()) 185 | } 186 | -------------------------------------------------------------------------------- /services/device/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | // external 15 | "github.com/go-kit/kit/log" 16 | "github.com/go-kit/kit/log/level" 17 | "github.com/go-kit/kit/sd/etcd" 18 | kitoc "github.com/go-kit/kit/tracing/opencensus" 19 | kitgrpc "github.com/go-kit/kit/transport/grpc" 20 | kithttp "github.com/go-kit/kit/transport/http" 21 | "github.com/jmoiron/sqlx" 22 | "github.com/kevinburke/go.uuid" 23 | _ "github.com/mattn/go-sqlite3" 24 | "github.com/oklog/run" 25 | "github.com/opencensus-integrations/ocsql" 26 | "google.golang.org/grpc" 27 | 28 | // project 29 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 30 | "github.com/basvanbeek/opencensus-gokit-example/services/device/database/sqlite" 31 | "github.com/basvanbeek/opencensus-gokit-example/services/device/implementation" 32 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport" 33 | grpctransport "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/grpc" 34 | httptransport "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/http" 35 | "github.com/basvanbeek/opencensus-gokit-example/services/device/transport/pb" 36 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 37 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 38 | ) 39 | 40 | func main() { 41 | var ( 42 | err error 43 | instance = uuid.NewV4() 44 | ) 45 | 46 | // initialize our OpenCensus configuration and defer a clean-up 47 | defer oc.Setup(device.ServiceName).Close() 48 | 49 | // initialize our structured logger for the service 50 | var logger log.Logger 51 | { 52 | logger = log.NewLogfmtLogger(os.Stderr) 53 | logger = log.NewSyncLogger(logger) 54 | logger = level.NewFilter(logger, level.AllowDebug()) 55 | logger = log.With(logger, 56 | "svc", device.ServiceName, 57 | "instance", instance, 58 | "ts", log.DefaultTimestampUTC, 59 | "clr", log.DefaultCaller, 60 | ) 61 | } 62 | 63 | level.Info(logger).Log("msg", "service started") 64 | defer level.Info(logger).Log("msg", "service ended") 65 | 66 | // find our host IP to advertise 67 | var bindIP string 68 | { 69 | if bindIP, err = network.HostIP(); err != nil { 70 | level.Error(logger).Log("exit", err) 71 | os.Exit(-1) 72 | } 73 | } 74 | 75 | ctx, cancel := context.WithCancel(context.Background()) 76 | defer cancel() 77 | 78 | // Create our etcd client for Service Discovery 79 | // 80 | // we could have used the v3 client but then we must vendor or suffer the 81 | // following issue originating from gRPC init: 82 | // panic: http: multiple registrations for /debug/requests 83 | var sdc etcd.Client 84 | { 85 | // create our Go kit etcd client 86 | sdc, err = etcd.NewClient( 87 | ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}, 88 | ) 89 | if err != nil { 90 | level.Error(logger).Log("exit", err) 91 | os.Exit(-1) 92 | } 93 | } 94 | 95 | // Create our DB Connection Driver 96 | var db *sqlx.DB 97 | { 98 | // create our ocsql instrumented sqlite3 driver 99 | var driverName string 100 | driverName, err = ocsql.Register("sqlite3", ocsql.WithOptions(ocsql.AllTraceOptions)) 101 | if err != nil { 102 | level.Error(logger).Log("exit", err) 103 | os.Exit(-1) 104 | } 105 | db, err = sqlx.Open(driverName, "device.db?_journal_mode=WAL") 106 | if err != nil { 107 | level.Error(logger).Log("exit", err) 108 | os.Exit(-1) 109 | } 110 | 111 | // make sure the DB is in WAL mode 112 | if _, err = db.Exec(`PRAGMA journal_mode=wal`); err != nil { 113 | level.Error(logger).Log("exit", err) 114 | os.Exit(-1) 115 | } 116 | } 117 | 118 | // Create our Device Service 119 | var svc device.Service 120 | { 121 | repository, err := sqlite.New(db, logger) 122 | if err != nil { 123 | level.Error(logger).Log("exit", err) 124 | os.Exit(-1) 125 | } 126 | svc = implementation.NewService(repository, logger) 127 | // add service level middlewares here 128 | } 129 | 130 | // Create our Go kit endpoints for the Device Service 131 | var endpoints transport.Endpoints 132 | { 133 | endpoints = transport.MakeEndpoints(svc) 134 | // add endpoint level middlewares here 135 | endpoints.Unlock = oc.ServerEndpoint("UnlockEndpoint")(endpoints.Unlock) 136 | } 137 | 138 | // run.Group manages our goroutine lifecycles 139 | // see: https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s 140 | var g run.Group 141 | { 142 | // set-up our ZPages handler 143 | oc.ZPages(g, logger) 144 | } 145 | { 146 | // set-up our grpc transport 147 | var ( 148 | ocTracing = kitoc.GRPCServerTrace() 149 | serverOptions = []kitgrpc.ServerOption{ocTracing} 150 | service = grpctransport.NewService(endpoints, serverOptions, logger) 151 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 152 | svcInstance = fmt.Sprintf("/services/%s/grpc/%s/", device.ServiceName, instance) 153 | addr = listener.Addr().String() 154 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 155 | serviceEntry = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 156 | registrar = etcd.NewRegistrar(sdc, serviceEntry, logger) 157 | grpcServer = grpc.NewServer() 158 | ) 159 | pb.RegisterDeviceServer(grpcServer, service) 160 | 161 | g.Add(func() error { 162 | registrar.Register() 163 | return grpcServer.Serve(listener) 164 | }, func(error) { 165 | registrar.Deregister() 166 | listener.Close() 167 | }) 168 | } 169 | { 170 | // set-up our http transport 171 | var ( 172 | ocTracing = kitoc.HTTPServerTrace() 173 | serverOptions = []kithttp.ServerOption{ocTracing} 174 | service = httptransport.NewService(endpoints, serverOptions, logger) 175 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 176 | svcInstance = fmt.Sprintf("/services/%s/http/%s/", device.ServiceName, instance) 177 | addr = "http://" + listener.Addr().String() 178 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 179 | serviceEntry = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 180 | registrar = etcd.NewRegistrar(sdc, serviceEntry, logger) 181 | ) 182 | 183 | g.Add(func() error { 184 | registrar.Register() 185 | return http.Serve(listener, service) 186 | }, func(error) { 187 | registrar.Deregister() 188 | listener.Close() 189 | }) 190 | } 191 | { 192 | // set-up our signal handler 193 | var ( 194 | cancelInterrupt = make(chan struct{}) 195 | c = make(chan os.Signal, 2) 196 | ) 197 | defer close(c) 198 | 199 | g.Add(func() error { 200 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 201 | select { 202 | case sig := <-c: 203 | return fmt.Errorf("received signal %s", sig) 204 | case <-cancelInterrupt: 205 | return nil 206 | } 207 | }, func(error) { 208 | close(cancelInterrupt) 209 | }) 210 | } 211 | 212 | // spawn our goroutines and wait for shutdown 213 | level.Error(logger).Log("exit", g.Run()) 214 | } 215 | -------------------------------------------------------------------------------- /services/device/transport/pb/svcdevice.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: services/device/transport/pb/svcdevice.proto 3 | 4 | /* 5 | Package pb is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | services/device/transport/pb/svcdevice.proto 9 | 10 | It has these top-level messages: 11 | UnlockRequest 12 | UnlockResponse 13 | */ 14 | package pb 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import ( 21 | context "golang.org/x/net/context" 22 | grpc "google.golang.org/grpc" 23 | ) 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | type UnlockRequest struct { 37 | EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` 38 | DeviceId []byte `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` 39 | Code string `protobuf:"bytes,3,opt,name=code" json:"code,omitempty"` 40 | } 41 | 42 | func (m *UnlockRequest) Reset() { *m = UnlockRequest{} } 43 | func (m *UnlockRequest) String() string { return proto.CompactTextString(m) } 44 | func (*UnlockRequest) ProtoMessage() {} 45 | func (*UnlockRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 46 | 47 | func (m *UnlockRequest) GetEventId() []byte { 48 | if m != nil { 49 | return m.EventId 50 | } 51 | return nil 52 | } 53 | 54 | func (m *UnlockRequest) GetDeviceId() []byte { 55 | if m != nil { 56 | return m.DeviceId 57 | } 58 | return nil 59 | } 60 | 61 | func (m *UnlockRequest) GetCode() string { 62 | if m != nil { 63 | return m.Code 64 | } 65 | return "" 66 | } 67 | 68 | type UnlockResponse struct { 69 | EventCaption string `protobuf:"bytes,1,opt,name=event_caption,json=eventCaption" json:"event_caption,omitempty"` 70 | DeviceCaption string `protobuf:"bytes,2,opt,name=device_caption,json=deviceCaption" json:"device_caption,omitempty"` 71 | } 72 | 73 | func (m *UnlockResponse) Reset() { *m = UnlockResponse{} } 74 | func (m *UnlockResponse) String() string { return proto.CompactTextString(m) } 75 | func (*UnlockResponse) ProtoMessage() {} 76 | func (*UnlockResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 77 | 78 | func (m *UnlockResponse) GetEventCaption() string { 79 | if m != nil { 80 | return m.EventCaption 81 | } 82 | return "" 83 | } 84 | 85 | func (m *UnlockResponse) GetDeviceCaption() string { 86 | if m != nil { 87 | return m.DeviceCaption 88 | } 89 | return "" 90 | } 91 | 92 | func init() { 93 | proto.RegisterType((*UnlockRequest)(nil), "pb.UnlockRequest") 94 | proto.RegisterType((*UnlockResponse)(nil), "pb.UnlockResponse") 95 | } 96 | 97 | // Reference imports to suppress errors if they are not otherwise used. 98 | var _ context.Context 99 | var _ grpc.ClientConn 100 | 101 | // This is a compile-time assertion to ensure that this generated file 102 | // is compatible with the grpc package it is being compiled against. 103 | const _ = grpc.SupportPackageIsVersion4 104 | 105 | // Client API for Device service 106 | 107 | type DeviceClient interface { 108 | Unlock(ctx context.Context, in *UnlockRequest, opts ...grpc.CallOption) (*UnlockResponse, error) 109 | } 110 | 111 | type deviceClient struct { 112 | cc *grpc.ClientConn 113 | } 114 | 115 | func NewDeviceClient(cc *grpc.ClientConn) DeviceClient { 116 | return &deviceClient{cc} 117 | } 118 | 119 | func (c *deviceClient) Unlock(ctx context.Context, in *UnlockRequest, opts ...grpc.CallOption) (*UnlockResponse, error) { 120 | out := new(UnlockResponse) 121 | err := grpc.Invoke(ctx, "/pb.Device/Unlock", in, out, c.cc, opts...) 122 | if err != nil { 123 | return nil, err 124 | } 125 | return out, nil 126 | } 127 | 128 | // Server API for Device service 129 | 130 | type DeviceServer interface { 131 | Unlock(context.Context, *UnlockRequest) (*UnlockResponse, error) 132 | } 133 | 134 | func RegisterDeviceServer(s *grpc.Server, srv DeviceServer) { 135 | s.RegisterService(&_Device_serviceDesc, srv) 136 | } 137 | 138 | func _Device_Unlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 139 | in := new(UnlockRequest) 140 | if err := dec(in); err != nil { 141 | return nil, err 142 | } 143 | if interceptor == nil { 144 | return srv.(DeviceServer).Unlock(ctx, in) 145 | } 146 | info := &grpc.UnaryServerInfo{ 147 | Server: srv, 148 | FullMethod: "/pb.Device/Unlock", 149 | } 150 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 151 | return srv.(DeviceServer).Unlock(ctx, req.(*UnlockRequest)) 152 | } 153 | return interceptor(ctx, in, info, handler) 154 | } 155 | 156 | var _Device_serviceDesc = grpc.ServiceDesc{ 157 | ServiceName: "pb.Device", 158 | HandlerType: (*DeviceServer)(nil), 159 | Methods: []grpc.MethodDesc{ 160 | { 161 | MethodName: "Unlock", 162 | Handler: _Device_Unlock_Handler, 163 | }, 164 | }, 165 | Streams: []grpc.StreamDesc{}, 166 | Metadata: "services/device/transport/pb/svcdevice.proto", 167 | } 168 | 169 | func init() { proto.RegisterFile("services/device/transport/pb/svcdevice.proto", fileDescriptor0) } 170 | 171 | var fileDescriptor0 = []byte{ 172 | // 222 bytes of a gzipped FileDescriptorProto 173 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0x31, 0x4b, 0x04, 0x31, 174 | 0x10, 0x85, 0xdd, 0x55, 0xd6, 0xdb, 0xe1, 0xf6, 0xc0, 0xa9, 0x4e, 0x6d, 0x8e, 0x15, 0xe1, 0x0a, 175 | 0xd9, 0xa0, 0x96, 0x96, 0xda, 0x5c, 0x1b, 0xb0, 0x52, 0x90, 0x4b, 0x32, 0xc5, 0xa2, 0x24, 0x31, 176 | 0x89, 0xfb, 0xfb, 0xe5, 0x66, 0x6e, 0x41, 0xab, 0x24, 0xdf, 0x7b, 0x79, 0x2f, 0x19, 0xb8, 0xcb, 177 | 0x94, 0xa6, 0xd1, 0x52, 0x56, 0x8e, 0x0e, 0xab, 0x2a, 0x69, 0xef, 0x73, 0x0c, 0xa9, 0xa8, 0x68, 178 | 0x54, 0x9e, 0xac, 0xe0, 0x21, 0xa6, 0x50, 0x02, 0xd6, 0xd1, 0xf4, 0x6f, 0xd0, 0xbd, 0xfa, 0xaf, 179 | 0x60, 0x3f, 0x35, 0x7d, 0xff, 0x50, 0x2e, 0x78, 0x09, 0x0b, 0x9a, 0xc8, 0x97, 0x8f, 0xd1, 0xad, 180 | 0xab, 0x4d, 0xb5, 0x5d, 0xea, 0x73, 0x3e, 0xef, 0x1c, 0x5e, 0x43, 0x2b, 0xf7, 0x0f, 0x5a, 0xcd, 181 | 0xda, 0x42, 0xc0, 0xce, 0x21, 0xc2, 0x99, 0x0d, 0x8e, 0xd6, 0xa7, 0x9b, 0x6a, 0xdb, 0x6a, 0xde, 182 | 0xf7, 0xef, 0xb0, 0x9a, 0xc3, 0x73, 0x0c, 0x3e, 0x13, 0xde, 0x40, 0x27, 0xe9, 0x76, 0x1f, 0xcb, 183 | 0x18, 0x3c, 0x57, 0xb4, 0x7a, 0xc9, 0xf0, 0x59, 0x18, 0xde, 0xc2, 0xea, 0xd8, 0x33, 0xbb, 0x6a, 184 | 0x76, 0x75, 0x42, 0x8f, 0xb6, 0x87, 0x27, 0x68, 0x5e, 0x18, 0xe0, 0x3d, 0x34, 0xd2, 0x83, 0x17, 185 | 0x43, 0x34, 0xc3, 0xbf, 0x0f, 0x5d, 0xe1, 0x5f, 0x24, 0xcf, 0xe8, 0x4f, 0x4c, 0xc3, 0x23, 0x78, 186 | 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x91, 0x4b, 0xd9, 0x32, 0x01, 0x00, 0x00, 187 | } 188 | -------------------------------------------------------------------------------- /services/elegantmonolith/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | // external 15 | "github.com/go-kit/kit/log" 16 | "github.com/go-kit/kit/log/level" 17 | "github.com/go-kit/kit/sd/etcd" 18 | kitoc "github.com/go-kit/kit/tracing/opencensus" 19 | kithttp "github.com/go-kit/kit/transport/http" 20 | "github.com/jmoiron/sqlx" 21 | "github.com/kevinburke/go.uuid" 22 | _ "github.com/mattn/go-sqlite3" 23 | "github.com/oklog/run" 24 | 25 | // project 26 | "github.com/basvanbeek/opencensus-gokit-example/services/device" 27 | devsql "github.com/basvanbeek/opencensus-gokit-example/services/device/database/sqlite" 28 | devimplementation "github.com/basvanbeek/opencensus-gokit-example/services/device/implementation" 29 | "github.com/basvanbeek/opencensus-gokit-example/services/event" 30 | evtsql "github.com/basvanbeek/opencensus-gokit-example/services/event/database/sqlite" 31 | evtimplementation "github.com/basvanbeek/opencensus-gokit-example/services/event/implementation" 32 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 33 | feimplementation "github.com/basvanbeek/opencensus-gokit-example/services/frontend/implementation" 34 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 35 | httptransport "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport/http" 36 | "github.com/basvanbeek/opencensus-gokit-example/services/qr" 37 | qrimplementation "github.com/basvanbeek/opencensus-gokit-example/services/qr/implementation" 38 | "github.com/basvanbeek/opencensus-gokit-example/shared/network" 39 | "github.com/basvanbeek/opencensus-gokit-example/shared/oc" 40 | ) 41 | 42 | // ServiceName of this service. 43 | const serviceName = "ElegantMonolith" 44 | 45 | func main() { 46 | var ( 47 | err error 48 | instance = uuid.NewV4() 49 | ) 50 | 51 | // initialize our OpenCensus configuration and defer a clean-up 52 | defer oc.Setup(serviceName).Close() 53 | 54 | // initialize our structured logger for the service 55 | var logger log.Logger 56 | { 57 | logger = log.NewLogfmtLogger(os.Stderr) 58 | logger = log.NewSyncLogger(logger) 59 | logger = level.NewFilter(logger, level.AllowDebug()) 60 | logger = log.With(logger, 61 | "svc", serviceName, 62 | "instance", instance, 63 | "ts", log.DefaultTimestampUTC, 64 | "clr", log.DefaultCaller, 65 | ) 66 | } 67 | 68 | level.Info(logger).Log("msg", "service started") 69 | defer level.Info(logger).Log("msg", "service ended") 70 | 71 | ctx, cancel := context.WithCancel(context.Background()) 72 | defer cancel() 73 | 74 | // Create our etcd client for Service Discovery 75 | // 76 | // we could have used the v3 client but then we must vendor or suffer the 77 | // following issue originating from gRPC init: 78 | // panic: http: multiple registrations for /debug/requests 79 | var sdc etcd.Client 80 | { 81 | // create our Go kit etcd client 82 | sdc, err = etcd.NewClient(ctx, []string{"http://localhost:2379"}, etcd.ClientOptions{}) 83 | if err != nil { 84 | level.Error(logger).Log("exit", err) 85 | os.Exit(-1) 86 | } 87 | } 88 | 89 | // Create our DB Connection Driver 90 | var db *sqlx.DB 91 | { 92 | db, err = sqlx.Open("sqlite3", "monolith.db?_journal_mode=WAL") 93 | if err != nil { 94 | level.Error(logger).Log("exit", err) 95 | os.Exit(-1) 96 | } 97 | 98 | // make sure the DB is in WAL mode 99 | if _, err = db.Exec(`PRAGMA journal_mode=wal`); err != nil { 100 | level.Error(logger).Log("exit", err) 101 | os.Exit(-1) 102 | } 103 | } 104 | 105 | // Create our Event service component 106 | var eventService event.Service 107 | { 108 | var logger = log.With(logger, "component", event.ServiceName) 109 | 110 | repository, err := evtsql.New(db, logger) 111 | if err != nil { 112 | level.Error(logger).Log("exit", err) 113 | os.Exit(-1) 114 | } 115 | eventService = evtimplementation.NewService(repository, logger) 116 | // add service level middlewares here 117 | 118 | } 119 | 120 | // Create our Device service component 121 | var deviceService device.Service 122 | { 123 | var logger = log.With(logger, "component", device.ServiceName) 124 | 125 | repository, err := devsql.New(db, logger) 126 | if err != nil { 127 | level.Error(logger).Log("exit", err) 128 | os.Exit(-1) 129 | } 130 | deviceService = devimplementation.NewService(repository, logger) 131 | // add service level middlewares here 132 | } 133 | 134 | // Create our QR service component 135 | var qrService qr.Service 136 | { 137 | var logger = log.With(logger, "component", qr.ServiceName) 138 | 139 | qrService = qrimplementation.NewService(logger) 140 | // add service level middlewares here 141 | } 142 | 143 | // Create our frontend service component 144 | var frontendService frontend.Service 145 | { 146 | var logger = log.With(logger, "component", frontend.ServiceName) 147 | 148 | frontendService = feimplementation.NewService( 149 | eventService, deviceService, qrService, logger, 150 | ) 151 | // add service level middlewares here 152 | } 153 | 154 | var endpoints transport.Endpoints 155 | { 156 | endpoints = transport.MakeEndpoints(frontendService) 157 | // trace our server side endpoints 158 | endpoints = transport.Endpoints{ 159 | Login: oc.ServerEndpoint("Login")(endpoints.Login), 160 | EventCreate: oc.ServerEndpoint("EventCreate")(endpoints.EventCreate), 161 | EventGet: oc.ServerEndpoint("EventGet")(endpoints.EventGet), 162 | EventUpdate: oc.ServerEndpoint("EventUpdate")(endpoints.EventUpdate), 163 | EventDelete: oc.ServerEndpoint("EventDelete")(endpoints.EventDelete), 164 | EventList: oc.ServerEndpoint("EventList")(endpoints.EventList), 165 | UnlockDevice: oc.ServerEndpoint("UnlockDevice")(endpoints.UnlockDevice), 166 | GenerateQR: oc.ServerEndpoint("GenerateQR")(endpoints.GenerateQR), 167 | } 168 | } 169 | 170 | // run.Group manages our goroutine lifecycles 171 | // see: https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s 172 | var g run.Group 173 | { 174 | // set-up our ZPages handler 175 | oc.ZPages(g, logger) 176 | } 177 | { 178 | // set-up our http transport 179 | var ( 180 | bindIP, _ = network.HostIP() 181 | listener, _ = net.Listen("tcp", bindIP+":0") // dynamic port assignment 182 | svcInstance = fmt.Sprintf("/services/%s/http/%s/", frontend.ServiceName, instance) // monolith is basically frontend service but with all micro service backend logic embedded 183 | addr = "http://" + listener.Addr().String() 184 | ttl = etcd.NewTTLOption(3*time.Second, 10*time.Second) 185 | service = etcd.Service{Key: svcInstance, Value: addr, TTL: ttl} 186 | registrar = etcd.NewRegistrar(sdc, service, logger) 187 | ocTracing = kitoc.HTTPServerTrace() 188 | serverOptions = []kithttp.ServerOption{ocTracing} 189 | feService = httptransport.NewService(endpoints, serverOptions, logger) 190 | ) 191 | 192 | g.Add(func() error { 193 | registrar.Register() 194 | return http.Serve(listener, feService) 195 | }, func(error) { 196 | registrar.Deregister() 197 | listener.Close() 198 | }) 199 | } 200 | { 201 | // set-up our signal handler 202 | var ( 203 | cancelInterrupt = make(chan struct{}) 204 | c = make(chan os.Signal, 2) 205 | ) 206 | defer close(c) 207 | 208 | g.Add(func() error { 209 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 210 | select { 211 | case sig := <-c: 212 | return fmt.Errorf("received signal %s", sig) 213 | case <-cancelInterrupt: 214 | return nil 215 | } 216 | }, func(error) { 217 | close(cancelInterrupt) 218 | }) 219 | } 220 | 221 | // spawn our goroutines and wait for shutdown 222 | level.Error(logger).Log("exit", g.Run()) 223 | } 224 | -------------------------------------------------------------------------------- /services/frontend/transport/http/service.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | // stdlib 5 | "context" 6 | "encoding/json" 7 | "net/http" 8 | 9 | // external 10 | "github.com/go-kit/kit/endpoint" 11 | "github.com/go-kit/kit/log" 12 | kithttp "github.com/go-kit/kit/transport/http" 13 | "github.com/gorilla/mux" 14 | "github.com/kevinburke/go.uuid" 15 | 16 | // project 17 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend" 18 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport" 19 | "github.com/basvanbeek/opencensus-gokit-example/services/frontend/transport/http/routes" 20 | ) 21 | 22 | // NewService wires our Go kit endpoints to the HTTP transport. 23 | func NewService( 24 | svcEndpoints transport.Endpoints, options []kithttp.ServerOption, 25 | logger log.Logger, 26 | ) http.Handler { 27 | // set-up router and initialize http endpoints 28 | var ( 29 | router = mux.NewRouter() 30 | route = routes.Initialize(router) 31 | errorLogger = kithttp.ServerErrorLogger(logger) 32 | errorEncoder = kithttp.ServerErrorEncoder(encodeErrorResponse) 33 | ) 34 | 35 | options = append(options, errorLogger, errorEncoder) 36 | 37 | // wire our Go kit handlers to the http endpoints 38 | route.Login.Handler(kithttp.NewServer( 39 | svcEndpoints.Login, decodeLoginRequest, encodeLoginResponse, 40 | options..., 41 | )) 42 | 43 | route.EventCreate.Handler(kithttp.NewServer( 44 | svcEndpoints.EventCreate, decodeEventCreateRequest, encodeEventCreateResponse, 45 | options..., 46 | )) 47 | 48 | route.EventGet.Handler(kithttp.NewServer( 49 | svcEndpoints.EventGet, decodeEventGetRequest, encodeEventGetResponse, 50 | options..., 51 | )) 52 | 53 | route.EventUpdate.Handler(kithttp.NewServer( 54 | svcEndpoints.EventUpdate, decodeEventUpdateRequest, encodeEventUpdateResponse, 55 | options..., 56 | )) 57 | 58 | route.EventDelete.Handler(kithttp.NewServer( 59 | svcEndpoints.EventDelete, decodeEventDeleteRequest, encodeEventDeleteResponse, 60 | options..., 61 | )) 62 | 63 | route.EventList.Handler(kithttp.NewServer( 64 | svcEndpoints.EventList, decodeEventListRequest, encodeEventListResponse, 65 | options..., 66 | )) 67 | 68 | route.UnlockDevice.Handler(kithttp.NewServer( 69 | svcEndpoints.UnlockDevice, decodeUnlockDeviceRequest, encodeUnlockDeviceResponse, 70 | options..., 71 | )) 72 | 73 | route.GenerateQR.Handler(kithttp.NewServer( 74 | svcEndpoints.GenerateQR, decodeGenerateQRRequest, encodeGenerateQRResponse, 75 | options..., 76 | )) 77 | 78 | // return our router as http handler 79 | return router 80 | } 81 | 82 | // decode / encode functions for converting between http transport payloads and 83 | // Go kit request_response payloads. 84 | 85 | func decodeLoginRequest(_ context.Context, r *http.Request) (interface{}, error) { 86 | var req transport.LoginRequest 87 | err := json.NewDecoder(r.Body).Decode(&req) 88 | return req, err 89 | } 90 | 91 | func encodeLoginResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 92 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 93 | if err := response.(endpoint.Failer).Failed(); err != nil { 94 | return err 95 | } 96 | return json.NewEncoder(w).Encode(response) 97 | } 98 | 99 | func decodeEventCreateRequest(_ context.Context, r *http.Request) (interface{}, error) { 100 | var req transport.EventCreateRequest 101 | err := json.NewDecoder(r.Body).Decode(&req) 102 | return req, err 103 | } 104 | 105 | func encodeEventCreateResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 106 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 107 | if err := response.(endpoint.Failer).Failed(); err != nil { 108 | return err 109 | } 110 | return json.NewEncoder(w).Encode(response) 111 | } 112 | func decodeEventGetRequest(_ context.Context, r *http.Request) (interface{}, error) { 113 | var req transport.EventGetRequest 114 | err := json.NewDecoder(r.Body).Decode(&req) 115 | return req, err 116 | } 117 | 118 | func encodeEventGetResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 119 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 120 | if err := response.(endpoint.Failer).Failed(); err != nil { 121 | return err 122 | } 123 | return json.NewEncoder(w).Encode(response) 124 | } 125 | func decodeEventUpdateRequest(_ context.Context, r *http.Request) (interface{}, error) { 126 | var req transport.EventUpdateRequest 127 | err := json.NewDecoder(r.Body).Decode(&req) 128 | return req, err 129 | } 130 | 131 | func encodeEventUpdateResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 132 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 133 | if err := response.(endpoint.Failer).Failed(); err != nil { 134 | return err 135 | } 136 | return json.NewEncoder(w).Encode(response) 137 | } 138 | func decodeEventDeleteRequest(_ context.Context, r *http.Request) (interface{}, error) { 139 | var req transport.EventDeleteRequest 140 | err := json.NewDecoder(r.Body).Decode(&req) 141 | return req, err 142 | } 143 | 144 | func encodeEventDeleteResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 145 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 146 | if err := response.(endpoint.Failer).Failed(); err != nil { 147 | return err 148 | } 149 | return json.NewEncoder(w).Encode(response) 150 | } 151 | func decodeEventListRequest(_ context.Context, r *http.Request) (interface{}, error) { 152 | var req transport.EventListRequest 153 | err := json.NewDecoder(r.Body).Decode(&req) 154 | return req, err 155 | } 156 | 157 | func encodeEventListResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 158 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 159 | if err := response.(endpoint.Failer).Failed(); err != nil { 160 | return err 161 | } 162 | return json.NewEncoder(w).Encode(response) 163 | } 164 | 165 | func decodeUnlockDeviceRequest(_ context.Context, r *http.Request) (interface{}, error) { 166 | var req transport.UnlockDeviceRequest 167 | err := json.NewDecoder(r.Body).Decode(&req) 168 | return req, err 169 | } 170 | 171 | func encodeUnlockDeviceResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 172 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 173 | if err := response.(endpoint.Failer).Failed(); err != nil { 174 | return err 175 | } 176 | return json.NewEncoder(w).Encode(response) 177 | } 178 | 179 | func decodeGenerateQRRequest(_ context.Context, r *http.Request) (interface{}, error) { 180 | var ( 181 | err error 182 | req transport.GenerateQRRequest 183 | ) 184 | v := mux.Vars(r) 185 | if req.EventID, err = uuid.FromString(v["event_id"]); err != nil { 186 | return nil, err 187 | } 188 | 189 | if req.DeviceID, err = uuid.FromString(v["device_id"]); err != nil { 190 | return nil, err 191 | } 192 | req.UnlockCode = v["code"] 193 | return req, nil 194 | } 195 | 196 | func encodeGenerateQRResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 197 | res := response.(transport.GenerateQRResponse) 198 | if res.Failed() != nil { 199 | // TODO: add logic ex. auth 200 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 201 | w.WriteHeader(http.StatusInternalServerError) 202 | b, err := json.Marshal(res.Failed().Error()) 203 | if err != nil { 204 | return err 205 | } 206 | w.Write(b) 207 | return nil 208 | } 209 | w.Header().Set("Content-Type", "image/png") 210 | w.Write(res.QR) 211 | return nil 212 | } 213 | 214 | func encodeErrorResponse(_ context.Context, err error, w http.ResponseWriter) { 215 | var code int 216 | switch err { 217 | case frontend.ErrUserPassRequired, frontend.ErrRequireEventID, 218 | frontend.ErrRequireDeviceID, frontend.ErrRequireUnlockCode, 219 | frontend.ErrInvalidQRParams: 220 | code = http.StatusBadRequest 221 | case frontend.ErrEventExists: 222 | code = http.StatusConflict 223 | case frontend.ErrEventNotFound: 224 | code = http.StatusNotFound 225 | case frontend.ErrUserPassUnknown, frontend.ErrUnlockNotFound, 226 | frontend.ErrUnauthorized: 227 | code = http.StatusUnauthorized 228 | case frontend.ErrQRGenerate: 229 | code = http.StatusServiceUnavailable 230 | default: 231 | code = http.StatusInternalServerError 232 | } 233 | http.Error(w, err.Error(), code) 234 | } 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /services/event/transport/pb/event.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: services/event/transport/pb/event.proto 3 | 4 | /* 5 | Package pb is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | services/event/transport/pb/event.proto 9 | 10 | It has these top-level messages: 11 | EventObj 12 | CreateRequest 13 | CreateResponse 14 | GetRequest 15 | GetResponse 16 | UpdateRequest 17 | UpdateResponse 18 | DeleteRequest 19 | DeleteResponse 20 | ListRequest 21 | ListResponse 22 | */ 23 | package pb 24 | 25 | import proto "github.com/golang/protobuf/proto" 26 | import fmt "fmt" 27 | import math "math" 28 | 29 | import ( 30 | context "golang.org/x/net/context" 31 | grpc "google.golang.org/grpc" 32 | ) 33 | 34 | // Reference imports to suppress errors if they are not otherwise used. 35 | var _ = proto.Marshal 36 | var _ = fmt.Errorf 37 | var _ = math.Inf 38 | 39 | // This is a compile-time assertion to ensure that this generated file 40 | // is compatible with the proto package it is being compiled against. 41 | // A compilation error at this line likely means your copy of the 42 | // proto package needs to be updated. 43 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 44 | 45 | type EventObj struct { 46 | Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 47 | Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 48 | } 49 | 50 | func (m *EventObj) Reset() { *m = EventObj{} } 51 | func (m *EventObj) String() string { return proto.CompactTextString(m) } 52 | func (*EventObj) ProtoMessage() {} 53 | func (*EventObj) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 54 | 55 | func (m *EventObj) GetId() []byte { 56 | if m != nil { 57 | return m.Id 58 | } 59 | return nil 60 | } 61 | 62 | func (m *EventObj) GetName() string { 63 | if m != nil { 64 | return m.Name 65 | } 66 | return "" 67 | } 68 | 69 | type CreateRequest struct { 70 | TenantId []byte `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` 71 | Event *EventObj `protobuf:"bytes,2,opt,name=event" json:"event,omitempty"` 72 | } 73 | 74 | func (m *CreateRequest) Reset() { *m = CreateRequest{} } 75 | func (m *CreateRequest) String() string { return proto.CompactTextString(m) } 76 | func (*CreateRequest) ProtoMessage() {} 77 | func (*CreateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 78 | 79 | func (m *CreateRequest) GetTenantId() []byte { 80 | if m != nil { 81 | return m.TenantId 82 | } 83 | return nil 84 | } 85 | 86 | func (m *CreateRequest) GetEvent() *EventObj { 87 | if m != nil { 88 | return m.Event 89 | } 90 | return nil 91 | } 92 | 93 | type CreateResponse struct { 94 | Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 95 | } 96 | 97 | func (m *CreateResponse) Reset() { *m = CreateResponse{} } 98 | func (m *CreateResponse) String() string { return proto.CompactTextString(m) } 99 | func (*CreateResponse) ProtoMessage() {} 100 | func (*CreateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 101 | 102 | func (m *CreateResponse) GetId() []byte { 103 | if m != nil { 104 | return m.Id 105 | } 106 | return nil 107 | } 108 | 109 | type GetRequest struct { 110 | TenantId []byte `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` 111 | Id []byte `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` 112 | } 113 | 114 | func (m *GetRequest) Reset() { *m = GetRequest{} } 115 | func (m *GetRequest) String() string { return proto.CompactTextString(m) } 116 | func (*GetRequest) ProtoMessage() {} 117 | func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 118 | 119 | func (m *GetRequest) GetTenantId() []byte { 120 | if m != nil { 121 | return m.TenantId 122 | } 123 | return nil 124 | } 125 | 126 | func (m *GetRequest) GetId() []byte { 127 | if m != nil { 128 | return m.Id 129 | } 130 | return nil 131 | } 132 | 133 | type GetResponse struct { 134 | Event *EventObj `protobuf:"bytes,1,opt,name=event" json:"event,omitempty"` 135 | } 136 | 137 | func (m *GetResponse) Reset() { *m = GetResponse{} } 138 | func (m *GetResponse) String() string { return proto.CompactTextString(m) } 139 | func (*GetResponse) ProtoMessage() {} 140 | func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 141 | 142 | func (m *GetResponse) GetEvent() *EventObj { 143 | if m != nil { 144 | return m.Event 145 | } 146 | return nil 147 | } 148 | 149 | type UpdateRequest struct { 150 | TenantId []byte `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` 151 | Event *EventObj `protobuf:"bytes,2,opt,name=event" json:"event,omitempty"` 152 | } 153 | 154 | func (m *UpdateRequest) Reset() { *m = UpdateRequest{} } 155 | func (m *UpdateRequest) String() string { return proto.CompactTextString(m) } 156 | func (*UpdateRequest) ProtoMessage() {} 157 | func (*UpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 158 | 159 | func (m *UpdateRequest) GetTenantId() []byte { 160 | if m != nil { 161 | return m.TenantId 162 | } 163 | return nil 164 | } 165 | 166 | func (m *UpdateRequest) GetEvent() *EventObj { 167 | if m != nil { 168 | return m.Event 169 | } 170 | return nil 171 | } 172 | 173 | type UpdateResponse struct { 174 | } 175 | 176 | func (m *UpdateResponse) Reset() { *m = UpdateResponse{} } 177 | func (m *UpdateResponse) String() string { return proto.CompactTextString(m) } 178 | func (*UpdateResponse) ProtoMessage() {} 179 | func (*UpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } 180 | 181 | type DeleteRequest struct { 182 | TenantId []byte `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` 183 | Id []byte `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` 184 | } 185 | 186 | func (m *DeleteRequest) Reset() { *m = DeleteRequest{} } 187 | func (m *DeleteRequest) String() string { return proto.CompactTextString(m) } 188 | func (*DeleteRequest) ProtoMessage() {} 189 | func (*DeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } 190 | 191 | func (m *DeleteRequest) GetTenantId() []byte { 192 | if m != nil { 193 | return m.TenantId 194 | } 195 | return nil 196 | } 197 | 198 | func (m *DeleteRequest) GetId() []byte { 199 | if m != nil { 200 | return m.Id 201 | } 202 | return nil 203 | } 204 | 205 | type DeleteResponse struct { 206 | } 207 | 208 | func (m *DeleteResponse) Reset() { *m = DeleteResponse{} } 209 | func (m *DeleteResponse) String() string { return proto.CompactTextString(m) } 210 | func (*DeleteResponse) ProtoMessage() {} 211 | func (*DeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } 212 | 213 | type ListRequest struct { 214 | TenantId []byte `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` 215 | } 216 | 217 | func (m *ListRequest) Reset() { *m = ListRequest{} } 218 | func (m *ListRequest) String() string { return proto.CompactTextString(m) } 219 | func (*ListRequest) ProtoMessage() {} 220 | func (*ListRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } 221 | 222 | func (m *ListRequest) GetTenantId() []byte { 223 | if m != nil { 224 | return m.TenantId 225 | } 226 | return nil 227 | } 228 | 229 | type ListResponse struct { 230 | Events []*EventObj `protobuf:"bytes,1,rep,name=events" json:"events,omitempty"` 231 | } 232 | 233 | func (m *ListResponse) Reset() { *m = ListResponse{} } 234 | func (m *ListResponse) String() string { return proto.CompactTextString(m) } 235 | func (*ListResponse) ProtoMessage() {} 236 | func (*ListResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } 237 | 238 | func (m *ListResponse) GetEvents() []*EventObj { 239 | if m != nil { 240 | return m.Events 241 | } 242 | return nil 243 | } 244 | 245 | func init() { 246 | proto.RegisterType((*EventObj)(nil), "pb.eventObj") 247 | proto.RegisterType((*CreateRequest)(nil), "pb.CreateRequest") 248 | proto.RegisterType((*CreateResponse)(nil), "pb.CreateResponse") 249 | proto.RegisterType((*GetRequest)(nil), "pb.GetRequest") 250 | proto.RegisterType((*GetResponse)(nil), "pb.GetResponse") 251 | proto.RegisterType((*UpdateRequest)(nil), "pb.UpdateRequest") 252 | proto.RegisterType((*UpdateResponse)(nil), "pb.UpdateResponse") 253 | proto.RegisterType((*DeleteRequest)(nil), "pb.DeleteRequest") 254 | proto.RegisterType((*DeleteResponse)(nil), "pb.DeleteResponse") 255 | proto.RegisterType((*ListRequest)(nil), "pb.ListRequest") 256 | proto.RegisterType((*ListResponse)(nil), "pb.ListResponse") 257 | } 258 | 259 | // Reference imports to suppress errors if they are not otherwise used. 260 | var _ context.Context 261 | var _ grpc.ClientConn 262 | 263 | // This is a compile-time assertion to ensure that this generated file 264 | // is compatible with the grpc package it is being compiled against. 265 | const _ = grpc.SupportPackageIsVersion4 266 | 267 | // Client API for Event service 268 | 269 | type EventClient interface { 270 | Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) 271 | Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) 272 | Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) 273 | Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) 274 | List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) 275 | } 276 | 277 | type eventClient struct { 278 | cc *grpc.ClientConn 279 | } 280 | 281 | func NewEventClient(cc *grpc.ClientConn) EventClient { 282 | return &eventClient{cc} 283 | } 284 | 285 | func (c *eventClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { 286 | out := new(CreateResponse) 287 | err := grpc.Invoke(ctx, "/pb.Event/Create", in, out, c.cc, opts...) 288 | if err != nil { 289 | return nil, err 290 | } 291 | return out, nil 292 | } 293 | 294 | func (c *eventClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { 295 | out := new(GetResponse) 296 | err := grpc.Invoke(ctx, "/pb.Event/Get", in, out, c.cc, opts...) 297 | if err != nil { 298 | return nil, err 299 | } 300 | return out, nil 301 | } 302 | 303 | func (c *eventClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) { 304 | out := new(UpdateResponse) 305 | err := grpc.Invoke(ctx, "/pb.Event/Update", in, out, c.cc, opts...) 306 | if err != nil { 307 | return nil, err 308 | } 309 | return out, nil 310 | } 311 | 312 | func (c *eventClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { 313 | out := new(DeleteResponse) 314 | err := grpc.Invoke(ctx, "/pb.Event/Delete", in, out, c.cc, opts...) 315 | if err != nil { 316 | return nil, err 317 | } 318 | return out, nil 319 | } 320 | 321 | func (c *eventClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { 322 | out := new(ListResponse) 323 | err := grpc.Invoke(ctx, "/pb.Event/List", in, out, c.cc, opts...) 324 | if err != nil { 325 | return nil, err 326 | } 327 | return out, nil 328 | } 329 | 330 | // Server API for Event service 331 | 332 | type EventServer interface { 333 | Create(context.Context, *CreateRequest) (*CreateResponse, error) 334 | Get(context.Context, *GetRequest) (*GetResponse, error) 335 | Update(context.Context, *UpdateRequest) (*UpdateResponse, error) 336 | Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) 337 | List(context.Context, *ListRequest) (*ListResponse, error) 338 | } 339 | 340 | func RegisterEventServer(s *grpc.Server, srv EventServer) { 341 | s.RegisterService(&_Event_serviceDesc, srv) 342 | } 343 | 344 | func _Event_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 345 | in := new(CreateRequest) 346 | if err := dec(in); err != nil { 347 | return nil, err 348 | } 349 | if interceptor == nil { 350 | return srv.(EventServer).Create(ctx, in) 351 | } 352 | info := &grpc.UnaryServerInfo{ 353 | Server: srv, 354 | FullMethod: "/pb.Event/Create", 355 | } 356 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 357 | return srv.(EventServer).Create(ctx, req.(*CreateRequest)) 358 | } 359 | return interceptor(ctx, in, info, handler) 360 | } 361 | 362 | func _Event_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 363 | in := new(GetRequest) 364 | if err := dec(in); err != nil { 365 | return nil, err 366 | } 367 | if interceptor == nil { 368 | return srv.(EventServer).Get(ctx, in) 369 | } 370 | info := &grpc.UnaryServerInfo{ 371 | Server: srv, 372 | FullMethod: "/pb.Event/Get", 373 | } 374 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 375 | return srv.(EventServer).Get(ctx, req.(*GetRequest)) 376 | } 377 | return interceptor(ctx, in, info, handler) 378 | } 379 | 380 | func _Event_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 381 | in := new(UpdateRequest) 382 | if err := dec(in); err != nil { 383 | return nil, err 384 | } 385 | if interceptor == nil { 386 | return srv.(EventServer).Update(ctx, in) 387 | } 388 | info := &grpc.UnaryServerInfo{ 389 | Server: srv, 390 | FullMethod: "/pb.Event/Update", 391 | } 392 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 393 | return srv.(EventServer).Update(ctx, req.(*UpdateRequest)) 394 | } 395 | return interceptor(ctx, in, info, handler) 396 | } 397 | 398 | func _Event_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 399 | in := new(DeleteRequest) 400 | if err := dec(in); err != nil { 401 | return nil, err 402 | } 403 | if interceptor == nil { 404 | return srv.(EventServer).Delete(ctx, in) 405 | } 406 | info := &grpc.UnaryServerInfo{ 407 | Server: srv, 408 | FullMethod: "/pb.Event/Delete", 409 | } 410 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 411 | return srv.(EventServer).Delete(ctx, req.(*DeleteRequest)) 412 | } 413 | return interceptor(ctx, in, info, handler) 414 | } 415 | 416 | func _Event_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 417 | in := new(ListRequest) 418 | if err := dec(in); err != nil { 419 | return nil, err 420 | } 421 | if interceptor == nil { 422 | return srv.(EventServer).List(ctx, in) 423 | } 424 | info := &grpc.UnaryServerInfo{ 425 | Server: srv, 426 | FullMethod: "/pb.Event/List", 427 | } 428 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 429 | return srv.(EventServer).List(ctx, req.(*ListRequest)) 430 | } 431 | return interceptor(ctx, in, info, handler) 432 | } 433 | 434 | var _Event_serviceDesc = grpc.ServiceDesc{ 435 | ServiceName: "pb.Event", 436 | HandlerType: (*EventServer)(nil), 437 | Methods: []grpc.MethodDesc{ 438 | { 439 | MethodName: "Create", 440 | Handler: _Event_Create_Handler, 441 | }, 442 | { 443 | MethodName: "Get", 444 | Handler: _Event_Get_Handler, 445 | }, 446 | { 447 | MethodName: "Update", 448 | Handler: _Event_Update_Handler, 449 | }, 450 | { 451 | MethodName: "Delete", 452 | Handler: _Event_Delete_Handler, 453 | }, 454 | { 455 | MethodName: "List", 456 | Handler: _Event_List_Handler, 457 | }, 458 | }, 459 | Streams: []grpc.StreamDesc{}, 460 | Metadata: "services/event/transport/pb/event.proto", 461 | } 462 | 463 | func init() { proto.RegisterFile("services/event/transport/pb/event.proto", fileDescriptor0) } 464 | 465 | var fileDescriptor0 = []byte{ 466 | // 349 bytes of a gzipped FileDescriptorProto 467 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xc1, 0x4a, 0xc3, 0x40, 468 | 0x10, 0x6d, 0xd2, 0x36, 0xb4, 0xd3, 0x36, 0xd6, 0x39, 0x95, 0x7a, 0x09, 0x8b, 0x60, 0x51, 0x48, 469 | 0x69, 0xf5, 0x22, 0x78, 0x53, 0x29, 0x82, 0x60, 0x09, 0x78, 0x96, 0xc4, 0xcc, 0x21, 0xa2, 0xc9, 470 | 0x9a, 0x5d, 0xfb, 0xeb, 0x5e, 0x25, 0x3b, 0x49, 0xda, 0x46, 0x84, 0x1e, 0xbc, 0x6d, 0xdf, 0xcc, 471 | 0x7b, 0xf3, 0xe6, 0x4d, 0x03, 0x67, 0x8a, 0xf2, 0x4d, 0xf2, 0x4a, 0x6a, 0x4e, 0x1b, 0x4a, 0xf5, 472 | 0x5c, 0xe7, 0x61, 0xaa, 0x64, 0x96, 0xeb, 0xb9, 0x8c, 0x18, 0xf2, 0x65, 0x9e, 0xe9, 0x0c, 0x6d, 473 | 0x19, 0x09, 0x1f, 0x7a, 0x06, 0x7a, 0x8a, 0xde, 0xd0, 0x05, 0x3b, 0x89, 0x27, 0x96, 0x67, 0xcd, 474 | 0x86, 0x81, 0x9d, 0xc4, 0x88, 0xd0, 0x49, 0xc3, 0x0f, 0x9a, 0xd8, 0x9e, 0x35, 0xeb, 0x07, 0xe6, 475 | 0x2d, 0xd6, 0x30, 0xba, 0xcd, 0x29, 0xd4, 0x14, 0xd0, 0xe7, 0x17, 0x29, 0x8d, 0x27, 0xd0, 0xd7, 476 | 0x94, 0x86, 0xa9, 0x7e, 0xa9, 0xb9, 0x3d, 0x06, 0x1e, 0x62, 0x14, 0xd0, 0x35, 0xea, 0x46, 0x62, 477 | 0xb0, 0x1c, 0xfa, 0x32, 0xf2, 0xab, 0x71, 0x01, 0x97, 0x84, 0x07, 0x6e, 0xa5, 0xa8, 0x64, 0x96, 478 | 0x2a, 0x6a, 0xfa, 0x10, 0xd7, 0x00, 0x2b, 0xd2, 0x07, 0x0d, 0x64, 0xaa, 0x5d, 0x53, 0x17, 0x30, 479 | 0x30, 0xd4, 0x52, 0xb9, 0xf6, 0x63, 0xfd, 0xed, 0x67, 0x0d, 0xa3, 0x67, 0x19, 0xff, 0xe7, 0x86, 480 | 0x63, 0x70, 0x2b, 0x45, 0xf6, 0x21, 0x6e, 0x60, 0x74, 0x47, 0xef, 0x74, 0xe0, 0x8c, 0xe6, 0x52, 481 | 0x63, 0x70, 0x2b, 0x76, 0xa9, 0x77, 0x0e, 0x83, 0xc7, 0x44, 0x1d, 0x14, 0x91, 0xb8, 0x82, 0x21, 482 | 0xf7, 0x96, 0x99, 0x9c, 0x82, 0x63, 0x6c, 0xaa, 0x89, 0xe5, 0xb5, 0x7f, 0xad, 0x50, 0xd6, 0x96, 483 | 0xdf, 0x16, 0x74, 0xef, 0x8b, 0x27, 0x2e, 0xc0, 0xe1, 0x7b, 0xe1, 0x71, 0xd1, 0xb9, 0xf7, 0x6f, 484 | 0x98, 0xe2, 0x2e, 0x54, 0x9a, 0x6b, 0xe1, 0x0c, 0xda, 0x2b, 0xd2, 0xe8, 0x16, 0xc5, 0xed, 0x25, 485 | 0xa7, 0x47, 0xf5, 0xef, 0xba, 0x73, 0x01, 0x0e, 0x47, 0xc5, 0xe2, 0x7b, 0x87, 0x60, 0xf1, 0x46, 486 | 0x92, 0x86, 0xc2, 0x69, 0x30, 0x65, 0x2f, 0x57, 0xa6, 0x34, 0xc2, 0x6a, 0xe1, 0x05, 0x74, 0x8a, 487 | 0x08, 0xd0, 0x18, 0xd8, 0x09, 0x6e, 0x3a, 0xde, 0x02, 0x55, 0x73, 0xe4, 0x98, 0x8f, 0xe5, 0xf2, 488 | 0x27, 0x00, 0x00, 0xff, 0xff, 0x25, 0x75, 0x29, 0x7e, 0x57, 0x03, 0x00, 0x00, 489 | } 490 | --------------------------------------------------------------------------------