├── .DS_Store ├── .deepsource.toml ├── .github └── workflows │ └── codesee-arch-diagram.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── api └── proto │ ├── details.pb.go │ ├── details.proto │ ├── ratings.pb.go │ ├── ratings.proto │ ├── reviews.pb.go │ └── reviews.proto ├── build ├── details │ └── Dockerfile ├── products │ └── Dockerfile ├── ratings │ └── Dockerfile └── reviews │ └── Dockerfile ├── cmd ├── details │ ├── main.go │ ├── wire.go │ └── wire_gen.go ├── products │ ├── main.go │ ├── wire.go │ └── wire_gen.go ├── ratings │ ├── main.go │ ├── wire.go │ └── wire_gen.go └── reviews │ ├── main.go │ ├── wire.go │ └── wire_gen.go ├── configs ├── details.yml ├── grafana │ ├── dashboards-api │ │ ├── details-api.json │ │ ├── products-api.json │ │ ├── ratings-api.json │ │ └── reviews-api.json │ ├── dashboards │ │ ├── details.json │ │ ├── products.json │ │ ├── ratings.json │ │ └── reviews.json │ └── provisioning │ │ ├── dashboards │ │ └── dashboards.yaml │ │ └── datasources │ │ └── prometheus.yaml ├── products.yml ├── prometheus │ ├── alertmanager.yml │ ├── prometheus.yml │ └── rules │ │ ├── details.yml │ │ ├── products.bak │ │ ├── products.yml │ │ ├── ratings.yml │ │ └── reviews.yml ├── ratings.yml └── reviews.yml ├── deployments └── docker-compose.yml ├── doc └── images │ ├── alert.jpg │ ├── alert1.jpg │ ├── grafana_dashboard.jpg │ ├── grafana_dashboard1.jpg │ ├── jaeger.jpg │ └── jaeger1.jpg ├── go.mod ├── go.sum ├── internal ├── app │ ├── details │ │ ├── app.go │ │ ├── controllers │ │ │ ├── controllers.go │ │ │ ├── details.go │ │ │ ├── details_test.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ ├── grpcservers │ │ │ ├── details.go │ │ │ ├── details_test.go │ │ │ ├── servers.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ ├── repositories │ │ │ ├── cover.out │ │ │ ├── details.go │ │ │ ├── details_test.go │ │ │ ├── repositories.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ └── services │ │ │ ├── details.go │ │ │ ├── details_test.go │ │ │ ├── services.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ ├── products │ │ ├── app.go │ │ ├── controllers │ │ │ ├── controllers.go │ │ │ └── products.go │ │ ├── grpcclients │ │ │ ├── clients.go │ │ │ ├── details.go │ │ │ ├── ratings.go │ │ │ └── reviews.go │ │ └── services │ │ │ ├── products.go │ │ │ ├── products_test.go │ │ │ ├── services.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ ├── ratings │ │ ├── app.go │ │ ├── controllers │ │ │ ├── controllers.go │ │ │ ├── ratings.go │ │ │ ├── ratings_test.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ ├── grpcservers │ │ │ ├── rating.go │ │ │ ├── rating_test.go │ │ │ ├── servers.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ ├── repositories │ │ │ ├── ratings.go │ │ │ ├── ratings_test.go │ │ │ ├── repositories.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ │ └── services │ │ │ ├── ratings.go │ │ │ ├── ratings_test.go │ │ │ ├── services.go │ │ │ ├── wire.go │ │ │ └── wire_gen.go │ └── reviews │ │ ├── app.go │ │ ├── controllers │ │ ├── controllers.go │ │ ├── reviews.go │ │ ├── reviews_test.go │ │ ├── wire.go │ │ └── wire_gen.go │ │ ├── grpcservers │ │ ├── reviews.go │ │ ├── reviews_test.go │ │ ├── servers.go │ │ ├── wire.go │ │ └── wire_gen.go │ │ ├── repositories │ │ ├── repositories.go │ │ ├── reviews.go │ │ ├── reviews_test.go │ │ ├── wire.go │ │ └── wire_gen.go │ │ └── services │ │ ├── reviews.go │ │ ├── reviews_test.go │ │ ├── services.go │ │ ├── wire.go │ │ └── wire_gen.go └── pkg │ ├── app │ └── app.go │ ├── config │ └── config.go │ ├── consul │ └── consul.go │ ├── database │ └── database.go │ ├── jaeger │ └── jaeger.go │ ├── log │ └── log.go │ ├── models │ ├── detail.go │ ├── product.go │ ├── rating.go │ └── review.go │ ├── transports │ ├── grpc │ │ ├── client.go │ │ ├── grpc.go │ │ └── server.go │ └── http │ │ ├── http.go │ │ └── middlewares │ │ └── ginprom │ │ └── ginprom.go │ └── utils │ └── netutil │ ├── ip.go │ └── port.go ├── mocks ├── DetailsClient.go ├── DetailsRepository.go ├── DetailsServer.go ├── DetailsService.go ├── RatingsClient.go ├── RatingsRepository.go ├── RatingsServer.go ├── RatingsService.go ├── ReviewsClient.go ├── ReviewsRepository.go ├── ReviewsServer.go └── ReviewsService.go └── scripts ├── grafana ├── dashboard-api.jsonnet ├── dashboard.jsonnet └── grpc.json ├── products.sql ├── prometheus └── rules.jsonnet ├── wait-for └── wait-for-it.sh /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/.DS_Store -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "shell" 5 | 6 | [[analyzers]] 7 | name = "go" 8 | 9 | [analyzers.meta] 10 | import_root = "github.com/sdgmf/go-project-sample" -------------------------------------------------------------------------------- /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | # This workflow was added by CodeSee. Learn more at https://codesee.io/ 2 | # This is v2.0 of this workflow file 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request_target: 8 | types: [opened, synchronize, reopened] 9 | 10 | name: CodeSee 11 | 12 | permissions: read-all 13 | 14 | jobs: 15 | codesee: 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | name: Analyze the repo with CodeSee 19 | steps: 20 | - uses: Codesee-io/codesee-action@v2 21 | with: 22 | codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 23 | codesee-url: https://app.codesee.io 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | dist 3 | .idea -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scripts/grafana/grafonnet-lib"] 2 | path = scripts/grafana/grafonnet-lib 3 | url = https://github.com/grafana/grafonnet-lib.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | apps = 'products' 'details' 'ratings' 'reviews' 2 | .PHONY: run 3 | run: proto wire 4 | for app in $(apps) ;\ 5 | do \ 6 | go run ./cmd/$$app -f configs/$$app.yml & \ 7 | done 8 | .PHONY: wire 9 | wire: 10 | wire ./... 11 | .PHONY: test 12 | test: mock 13 | for app in $(apps) ;\ 14 | do \ 15 | go test -v ./internal/app/$$app/... -f `pwd`/configs/$$app.yml -covermode=count -coverprofile=dist/cover-$$app.out ;\ 16 | done 17 | .PHONY: build 18 | build: 19 | for app in $(apps) ;\ 20 | do \ 21 | GOOS=linux GOARCH="amd64" go build -o dist/$$app-linux-amd64 ./cmd/$$app/; \ 22 | GOOS=darwin GOARCH="amd64" go build -o dist/$$app-darwin-amd64 ./cmd/$$app/; \ 23 | done 24 | .PHONY: cover 25 | cover: test 26 | for app in $(apps) ;\ 27 | do \ 28 | go tool cover -html=dist/cover-$$app.out; \ 29 | done 30 | .PHONY: mock 31 | mock: 32 | mockery --all 33 | .PHONY: lint 34 | lint: 35 | golint ./... 36 | .PHONY: proto 37 | proto: 38 | protoc -I api/proto ./api/proto/* --go_out=plugins=grpc:api/proto 39 | .PHONY: dash 40 | dash: # create grafana dashboard 41 | for app in $(apps) ;\ 42 | do \ 43 | jsonnet -J ./grafana/grafonnet-lib -o ./configs/grafana/dashboards/$$app.json --ext-str app=$$app ./scripts/grafana/dashboard.jsonnet ;\ 44 | done 45 | .PHONY: pubdash 46 | pubdash: 47 | for app in $(apps) ;\ 48 | do \ 49 | jsonnet -J ./grafana/grafonnet-lib -o ./configs/grafana/dashboards-api/$$app-api.json --ext-str app=$$app ./scripts/grafana/dashboard-api.jsonnet ; \ 50 | curl -X DELETE --user admin:admin -H "Content-Type: application/json" 'http://localhost:3000/api/dashboards/db/$$app'; \ 51 | curl -x POST --user admin:admin -H "Content-Type: application/json" --data-binary "@./configs/grafana/dashboards-api/$$app-api.json" http://localhost:3000/api/dashboards/db ; \ 52 | done 53 | .PHONY: rules 54 | rules: 55 | for app in $(apps) ;\ 56 | do \ 57 | jsonnet -o ./configs/prometheus/rules/$$app.yml --ext-str app=$$app ./scripts/prometheus/rules.jsonnet ; \ 58 | done 59 | .PHONY: docker 60 | docker-compose: build dash rules 61 | docker-compose -f deployments/docker-compose.yml up --build -d 62 | all: lint cover docker 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 通过一个完整的项目的示例,从项目的结构、分层思想、依赖注入、错误处理、单元测试、服务治理、框架选择等方面介绍Go语言项目的最佳实践经验. 3 | 项目分为products、details、ratings、reviews四个微服务,依赖关系如下. 4 | 5 | ![dependency](https://sdgmf.github.io/images/goproject_dep.jpg) 6 | 7 | ## 准备 8 | 9 | 安装docker,go,[jsonnet](https://jsonnet.org/) 10 | 11 | ## 快速开始 12 | 下载项目 13 | ```bash 14 | git clone https://github.com/sdgmf/go-project-sample.git 15 | cd go-project-sample 16 | git submodule init 17 | git submodule update 18 | make docker-compose 19 | ``` 20 | 21 | * **访问接口**: http://localhost:8080/product/1 22 | * **consul**: http://localhost:8500/ 23 | * **grafana**: http://localhost:3000/ 24 | * **jaeger**: http://localhost:16686/search 25 | * **Prometheus**: http://localhost:9090/graph 26 | * **AlertManager**: http://localhost:9093 27 | 28 | 29 | ## 截图 30 | 31 | Grafana Dashboard,可以自动生成! 32 | 33 | ![dashboard](./doc/images/grafana_dashboard.jpg) 34 | 35 | ![dashboard1](./doc/images/grafana_dashboard1.jpg) 36 | 37 | Prometheus Alert 监控告警,自动生成! 38 | 39 | ![alert](./doc/images/alert.jpg) 40 | 41 | ![alert](./doc/images/alert1.jpg) 42 | 43 | 调用链跟踪 44 | 45 | ![jaeger](./doc/images/jaeger.jpg) 46 | 47 | ![jaeger](./doc/images/jaeger1.jpg) 48 | 49 | 50 | 更新更新更新 51 | ## [中文文档](https://sdgmf.github.io/goproject/) 52 | 53 | 54 | 更新更新更新 55 | -------------------------------------------------------------------------------- /api/proto/details.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: details.proto 3 | 4 | package proto 5 | 6 | import ( 7 | context "context" 8 | fmt "fmt" 9 | proto "github.com/golang/protobuf/proto" 10 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | math "math" 15 | ) 16 | 17 | // Reference imports to suppress errors if they are not otherwise used. 18 | var _ = proto.Marshal 19 | var _ = fmt.Errorf 20 | var _ = math.Inf 21 | 22 | // This is a compile-time assertion to ensure that this generated file 23 | // is compatible with the proto package it is being compiled against. 24 | // A compilation error at this line likely means your copy of the 25 | // proto package needs to be updated. 26 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 27 | 28 | type GetDetailRequest struct { 29 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *GetDetailRequest) Reset() { *m = GetDetailRequest{} } 36 | func (m *GetDetailRequest) String() string { return proto.CompactTextString(m) } 37 | func (*GetDetailRequest) ProtoMessage() {} 38 | func (*GetDetailRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_c457112169d4fb2c, []int{0} 40 | } 41 | 42 | func (m *GetDetailRequest) XXX_Unmarshal(b []byte) error { 43 | return xxx_messageInfo_GetDetailRequest.Unmarshal(m, b) 44 | } 45 | func (m *GetDetailRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 46 | return xxx_messageInfo_GetDetailRequest.Marshal(b, m, deterministic) 47 | } 48 | func (m *GetDetailRequest) XXX_Merge(src proto.Message) { 49 | xxx_messageInfo_GetDetailRequest.Merge(m, src) 50 | } 51 | func (m *GetDetailRequest) XXX_Size() int { 52 | return xxx_messageInfo_GetDetailRequest.Size(m) 53 | } 54 | func (m *GetDetailRequest) XXX_DiscardUnknown() { 55 | xxx_messageInfo_GetDetailRequest.DiscardUnknown(m) 56 | } 57 | 58 | var xxx_messageInfo_GetDetailRequest proto.InternalMessageInfo 59 | 60 | func (m *GetDetailRequest) GetId() uint64 { 61 | if m != nil { 62 | return m.Id 63 | } 64 | return 0 65 | } 66 | 67 | type Detail struct { 68 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 69 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 70 | Price float32 `protobuf:"fixed32,3,opt,name=price,proto3" json:"price,omitempty"` 71 | CreatedTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=createdTime,proto3" json:"createdTime,omitempty"` 72 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 73 | XXX_unrecognized []byte `json:"-"` 74 | XXX_sizecache int32 `json:"-"` 75 | } 76 | 77 | func (m *Detail) Reset() { *m = Detail{} } 78 | func (m *Detail) String() string { return proto.CompactTextString(m) } 79 | func (*Detail) ProtoMessage() {} 80 | func (*Detail) Descriptor() ([]byte, []int) { 81 | return fileDescriptor_c457112169d4fb2c, []int{1} 82 | } 83 | 84 | func (m *Detail) XXX_Unmarshal(b []byte) error { 85 | return xxx_messageInfo_Detail.Unmarshal(m, b) 86 | } 87 | func (m *Detail) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 88 | return xxx_messageInfo_Detail.Marshal(b, m, deterministic) 89 | } 90 | func (m *Detail) XXX_Merge(src proto.Message) { 91 | xxx_messageInfo_Detail.Merge(m, src) 92 | } 93 | func (m *Detail) XXX_Size() int { 94 | return xxx_messageInfo_Detail.Size(m) 95 | } 96 | func (m *Detail) XXX_DiscardUnknown() { 97 | xxx_messageInfo_Detail.DiscardUnknown(m) 98 | } 99 | 100 | var xxx_messageInfo_Detail proto.InternalMessageInfo 101 | 102 | func (m *Detail) GetId() uint64 { 103 | if m != nil { 104 | return m.Id 105 | } 106 | return 0 107 | } 108 | 109 | func (m *Detail) GetName() string { 110 | if m != nil { 111 | return m.Name 112 | } 113 | return "" 114 | } 115 | 116 | func (m *Detail) GetPrice() float32 { 117 | if m != nil { 118 | return m.Price 119 | } 120 | return 0 121 | } 122 | 123 | func (m *Detail) GetCreatedTime() *timestamp.Timestamp { 124 | if m != nil { 125 | return m.CreatedTime 126 | } 127 | return nil 128 | } 129 | 130 | func init() { 131 | proto.RegisterType((*GetDetailRequest)(nil), "GetDetailRequest") 132 | proto.RegisterType((*Detail)(nil), "Detail") 133 | } 134 | 135 | func init() { proto.RegisterFile("details.proto", fileDescriptor_c457112169d4fb2c) } 136 | 137 | var fileDescriptor_c457112169d4fb2c = []byte{ 138 | // 208 bytes of a gzipped FileDescriptorProto 139 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x8e, 0x31, 0x4b, 0x03, 0x31, 140 | 0x14, 0x80, 0x49, 0x7a, 0xed, 0xe1, 0x2b, 0x8a, 0x06, 0x87, 0x70, 0x8b, 0xf1, 0xa6, 0x0c, 0x92, 141 | 0x42, 0x5d, 0x9d, 0x44, 0xe8, 0x1e, 0x3a, 0xb9, 0xc5, 0xe6, 0x59, 0x02, 0x8d, 0x39, 0x2f, 0xef, 142 | 0x76, 0x7f, 0xba, 0x90, 0x70, 0x50, 0x6e, 0x4a, 0x3e, 0xde, 0xf7, 0x1e, 0x1f, 0xdc, 0x7a, 0x24, 143 | 0x17, 0x2e, 0xd9, 0x0c, 0x63, 0xa2, 0xd4, 0x3d, 0x9d, 0x53, 0x3a, 0x5f, 0x70, 0x57, 0xe8, 0x6b, 144 | 0xfa, 0xde, 0x51, 0x88, 0x98, 0xc9, 0xc5, 0xa1, 0x0a, 0x7d, 0x0f, 0xf7, 0x07, 0xa4, 0x8f, 0xb2, 145 | 0x64, 0xf1, 0x77, 0xc2, 0x4c, 0xe2, 0x0e, 0x78, 0xf0, 0x92, 0x29, 0xa6, 0x1b, 0xcb, 0x83, 0xef, 146 | 0xff, 0x18, 0x6c, 0xaa, 0xb1, 0x1c, 0x09, 0x01, 0xcd, 0x8f, 0x8b, 0x28, 0xb9, 0x62, 0xfa, 0xc6, 147 | 0x96, 0xbf, 0x78, 0x84, 0xf5, 0x30, 0x86, 0x13, 0xca, 0x95, 0x62, 0x9a, 0xdb, 0x0a, 0xe2, 0x0d, 148 | 0xb6, 0xa7, 0x11, 0x1d, 0xa1, 0x3f, 0x86, 0x88, 0xb2, 0x51, 0x4c, 0x6f, 0xf7, 0x9d, 0xa9, 0x7d, 149 | 0x66, 0xee, 0x33, 0xc7, 0xb9, 0xcf, 0x5e, 0xeb, 0xfb, 0x17, 0x68, 0x6b, 0x41, 0x16, 0xcf, 0xb0, 150 | 0x3a, 0x20, 0x89, 0x07, 0xb3, 0xec, 0xee, 0x5a, 0x53, 0xf9, 0xbd, 0xfd, 0x5c, 0xd7, 0x83, 0x9b, 151 | 0xf2, 0xbc, 0xfe, 0x07, 0x00, 0x00, 0xff, 0xff, 0x92, 0x33, 0x0b, 0x56, 0x16, 0x01, 0x00, 0x00, 152 | } 153 | 154 | // Reference imports to suppress errors if they are not otherwise used. 155 | var _ context.Context 156 | var _ grpc.ClientConn 157 | 158 | // This is a compile-time assertion to ensure that this generated file 159 | // is compatible with the grpc package it is being compiled against. 160 | const _ = grpc.SupportPackageIsVersion4 161 | 162 | // DetailsClient is the client API for Details service. 163 | // 164 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 165 | type DetailsClient interface { 166 | Get(ctx context.Context, in *GetDetailRequest, opts ...grpc.CallOption) (*Detail, error) 167 | } 168 | 169 | type detailsClient struct { 170 | cc *grpc.ClientConn 171 | } 172 | 173 | func NewDetailsClient(cc *grpc.ClientConn) DetailsClient { 174 | return &detailsClient{cc} 175 | } 176 | 177 | func (c *detailsClient) Get(ctx context.Context, in *GetDetailRequest, opts ...grpc.CallOption) (*Detail, error) { 178 | out := new(Detail) 179 | err := c.cc.Invoke(ctx, "/Details/Get", in, out, opts...) 180 | if err != nil { 181 | return nil, err 182 | } 183 | return out, nil 184 | } 185 | 186 | // DetailsServer is the server API for Details service. 187 | type DetailsServer interface { 188 | Get(context.Context, *GetDetailRequest) (*Detail, error) 189 | } 190 | 191 | // UnimplementedDetailsServer can be embedded to have forward compatible implementations. 192 | type UnimplementedDetailsServer struct { 193 | } 194 | 195 | func (*UnimplementedDetailsServer) Get(ctx context.Context, req *GetDetailRequest) (*Detail, error) { 196 | return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") 197 | } 198 | 199 | func RegisterDetailsServer(s *grpc.Server, srv DetailsServer) { 200 | s.RegisterService(&_Details_serviceDesc, srv) 201 | } 202 | 203 | func _Details_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 204 | in := new(GetDetailRequest) 205 | if err := dec(in); err != nil { 206 | return nil, err 207 | } 208 | if interceptor == nil { 209 | return srv.(DetailsServer).Get(ctx, in) 210 | } 211 | info := &grpc.UnaryServerInfo{ 212 | Server: srv, 213 | FullMethod: "/Details/Get", 214 | } 215 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 216 | return srv.(DetailsServer).Get(ctx, req.(*GetDetailRequest)) 217 | } 218 | return interceptor(ctx, in, info, handler) 219 | } 220 | 221 | var _Details_serviceDesc = grpc.ServiceDesc{ 222 | ServiceName: "Details", 223 | HandlerType: (*DetailsServer)(nil), 224 | Methods: []grpc.MethodDesc{ 225 | { 226 | MethodName: "Get", 227 | Handler: _Details_Get_Handler, 228 | }, 229 | }, 230 | Streams: []grpc.StreamDesc{}, 231 | Metadata: "details.proto", 232 | } 233 | -------------------------------------------------------------------------------- /api/proto/details.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/timestamp.proto"; 3 | option go_package = "proto"; 4 | 5 | 6 | service Details { 7 | rpc Get (GetDetailRequest) returns (Detail); 8 | } 9 | 10 | message GetDetailRequest { 11 | uint64 id = 1; 12 | } 13 | 14 | message Detail { 15 | uint64 id = 1; 16 | string name = 2; 17 | float price = 3; 18 | google.protobuf.Timestamp createdTime = 4; 19 | } 20 | -------------------------------------------------------------------------------- /api/proto/ratings.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: ratings.proto 3 | 4 | package proto 5 | 6 | import ( 7 | context "context" 8 | fmt "fmt" 9 | proto "github.com/golang/protobuf/proto" 10 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | math "math" 15 | ) 16 | 17 | // Reference imports to suppress errors if they are not otherwise used. 18 | var _ = proto.Marshal 19 | var _ = fmt.Errorf 20 | var _ = math.Inf 21 | 22 | // This is a compile-time assertion to ensure that this generated file 23 | // is compatible with the proto package it is being compiled against. 24 | // A compilation error at this line likely means your copy of the 25 | // proto package needs to be updated. 26 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 27 | 28 | type GetRatingRequest struct { 29 | ProductID uint64 `protobuf:"varint,1,opt,name=productID,proto3" json:"productID,omitempty"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *GetRatingRequest) Reset() { *m = GetRatingRequest{} } 36 | func (m *GetRatingRequest) String() string { return proto.CompactTextString(m) } 37 | func (*GetRatingRequest) ProtoMessage() {} 38 | func (*GetRatingRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_bf07e2fe2aafa921, []int{0} 40 | } 41 | 42 | func (m *GetRatingRequest) XXX_Unmarshal(b []byte) error { 43 | return xxx_messageInfo_GetRatingRequest.Unmarshal(m, b) 44 | } 45 | func (m *GetRatingRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 46 | return xxx_messageInfo_GetRatingRequest.Marshal(b, m, deterministic) 47 | } 48 | func (m *GetRatingRequest) XXX_Merge(src proto.Message) { 49 | xxx_messageInfo_GetRatingRequest.Merge(m, src) 50 | } 51 | func (m *GetRatingRequest) XXX_Size() int { 52 | return xxx_messageInfo_GetRatingRequest.Size(m) 53 | } 54 | func (m *GetRatingRequest) XXX_DiscardUnknown() { 55 | xxx_messageInfo_GetRatingRequest.DiscardUnknown(m) 56 | } 57 | 58 | var xxx_messageInfo_GetRatingRequest proto.InternalMessageInfo 59 | 60 | func (m *GetRatingRequest) GetProductID() uint64 { 61 | if m != nil { 62 | return m.ProductID 63 | } 64 | return 0 65 | } 66 | 67 | type Rating struct { 68 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 69 | ProductID uint64 `protobuf:"varint,2,opt,name=productID,proto3" json:"productID,omitempty"` 70 | Score uint32 `protobuf:"varint,3,opt,name=score,proto3" json:"score,omitempty"` 71 | UpdatedTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=updatedTime,proto3" json:"updatedTime,omitempty"` 72 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 73 | XXX_unrecognized []byte `json:"-"` 74 | XXX_sizecache int32 `json:"-"` 75 | } 76 | 77 | func (m *Rating) Reset() { *m = Rating{} } 78 | func (m *Rating) String() string { return proto.CompactTextString(m) } 79 | func (*Rating) ProtoMessage() {} 80 | func (*Rating) Descriptor() ([]byte, []int) { 81 | return fileDescriptor_bf07e2fe2aafa921, []int{1} 82 | } 83 | 84 | func (m *Rating) XXX_Unmarshal(b []byte) error { 85 | return xxx_messageInfo_Rating.Unmarshal(m, b) 86 | } 87 | func (m *Rating) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 88 | return xxx_messageInfo_Rating.Marshal(b, m, deterministic) 89 | } 90 | func (m *Rating) XXX_Merge(src proto.Message) { 91 | xxx_messageInfo_Rating.Merge(m, src) 92 | } 93 | func (m *Rating) XXX_Size() int { 94 | return xxx_messageInfo_Rating.Size(m) 95 | } 96 | func (m *Rating) XXX_DiscardUnknown() { 97 | xxx_messageInfo_Rating.DiscardUnknown(m) 98 | } 99 | 100 | var xxx_messageInfo_Rating proto.InternalMessageInfo 101 | 102 | func (m *Rating) GetId() uint64 { 103 | if m != nil { 104 | return m.Id 105 | } 106 | return 0 107 | } 108 | 109 | func (m *Rating) GetProductID() uint64 { 110 | if m != nil { 111 | return m.ProductID 112 | } 113 | return 0 114 | } 115 | 116 | func (m *Rating) GetScore() uint32 { 117 | if m != nil { 118 | return m.Score 119 | } 120 | return 0 121 | } 122 | 123 | func (m *Rating) GetUpdatedTime() *timestamp.Timestamp { 124 | if m != nil { 125 | return m.UpdatedTime 126 | } 127 | return nil 128 | } 129 | 130 | func init() { 131 | proto.RegisterType((*GetRatingRequest)(nil), "GetRatingRequest") 132 | proto.RegisterType((*Rating)(nil), "Rating") 133 | } 134 | 135 | func init() { proto.RegisterFile("ratings.proto", fileDescriptor_bf07e2fe2aafa921) } 136 | 137 | var fileDescriptor_bf07e2fe2aafa921 = []byte{ 138 | // 216 bytes of a gzipped FileDescriptorProto 139 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x8f, 0x4d, 0x4b, 0xc4, 0x30, 140 | 0x10, 0x86, 0x49, 0xf7, 0xa3, 0x38, 0xcb, 0x8a, 0x06, 0x0f, 0xa5, 0x08, 0xd6, 0x9e, 0x7a, 0x90, 141 | 0x54, 0xea, 0xd5, 0x93, 0x08, 0xc5, 0x6b, 0xe8, 0xc9, 0x5b, 0x6d, 0xc6, 0x12, 0xb0, 0x26, 0x26, 142 | 0x93, 0x3f, 0xe1, 0xaf, 0x16, 0x1a, 0x8b, 0x6e, 0x4f, 0xc3, 0x3b, 0x3c, 0xef, 0xf0, 0x0c, 0x1c, 143 | 0x5d, 0x4f, 0xfa, 0x73, 0xf4, 0xc2, 0x3a, 0x43, 0x26, 0xbf, 0x19, 0x8d, 0x19, 0x3f, 0xb0, 0x9e, 144 | 0xd3, 0x5b, 0x78, 0xaf, 0x49, 0x4f, 0xe8, 0xa9, 0x9f, 0x6c, 0x04, 0xca, 0x7b, 0xb8, 0x68, 0x91, 145 | 0xe4, 0x5c, 0x92, 0xf8, 0x15, 0xd0, 0x13, 0xbf, 0x86, 0x33, 0xeb, 0x8c, 0x0a, 0x03, 0xbd, 0x3c, 146 | 0x67, 0xac, 0x60, 0xd5, 0x56, 0xfe, 0x2d, 0xca, 0x6f, 0x06, 0xfb, 0xc8, 0xf3, 0x73, 0x48, 0xb4, 147 | 0xfa, 0x25, 0x12, 0xad, 0x4e, 0x8b, 0xc9, 0xaa, 0xc8, 0xaf, 0x60, 0xe7, 0x07, 0xe3, 0x30, 0xdb, 148 | 0x14, 0xac, 0x3a, 0xca, 0x18, 0xf8, 0x23, 0x1c, 0x82, 0x55, 0x3d, 0xa1, 0xea, 0xf4, 0x84, 0xd9, 149 | 0xb6, 0x60, 0xd5, 0xa1, 0xc9, 0x45, 0xf4, 0x16, 0x8b, 0xb7, 0xe8, 0x16, 0x6f, 0xf9, 0x1f, 0x6f, 150 | 0xee, 0x20, 0x8d, 0x2e, 0x9e, 0xdf, 0xc2, 0xa6, 0x45, 0xe2, 0x97, 0x62, 0xfd, 0x4f, 0x9e, 0x8a, 151 | 0x98, 0x9f, 0xd2, 0xd7, 0x5d, 0x3c, 0xb8, 0x9f, 0xc7, 0xc3, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 152 | 0xa6, 0xb2, 0x40, 0x95, 0x2e, 0x01, 0x00, 0x00, 153 | } 154 | 155 | // Reference imports to suppress errors if they are not otherwise used. 156 | var _ context.Context 157 | var _ grpc.ClientConn 158 | 159 | // This is a compile-time assertion to ensure that this generated file 160 | // is compatible with the grpc package it is being compiled against. 161 | const _ = grpc.SupportPackageIsVersion4 162 | 163 | // RatingsClient is the client API for Ratings service. 164 | // 165 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 166 | type RatingsClient interface { 167 | Get(ctx context.Context, in *GetRatingRequest, opts ...grpc.CallOption) (*Rating, error) 168 | } 169 | 170 | type ratingsClient struct { 171 | cc *grpc.ClientConn 172 | } 173 | 174 | func NewRatingsClient(cc *grpc.ClientConn) RatingsClient { 175 | return &ratingsClient{cc} 176 | } 177 | 178 | func (c *ratingsClient) Get(ctx context.Context, in *GetRatingRequest, opts ...grpc.CallOption) (*Rating, error) { 179 | out := new(Rating) 180 | err := c.cc.Invoke(ctx, "/Ratings/Get", in, out, opts...) 181 | if err != nil { 182 | return nil, err 183 | } 184 | return out, nil 185 | } 186 | 187 | // RatingsServer is the server API for Ratings service. 188 | type RatingsServer interface { 189 | Get(context.Context, *GetRatingRequest) (*Rating, error) 190 | } 191 | 192 | // UnimplementedRatingsServer can be embedded to have forward compatible implementations. 193 | type UnimplementedRatingsServer struct { 194 | } 195 | 196 | func (*UnimplementedRatingsServer) Get(ctx context.Context, req *GetRatingRequest) (*Rating, error) { 197 | return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") 198 | } 199 | 200 | func RegisterRatingsServer(s *grpc.Server, srv RatingsServer) { 201 | s.RegisterService(&_Ratings_serviceDesc, srv) 202 | } 203 | 204 | func _Ratings_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 205 | in := new(GetRatingRequest) 206 | if err := dec(in); err != nil { 207 | return nil, err 208 | } 209 | if interceptor == nil { 210 | return srv.(RatingsServer).Get(ctx, in) 211 | } 212 | info := &grpc.UnaryServerInfo{ 213 | Server: srv, 214 | FullMethod: "/Ratings/Get", 215 | } 216 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 217 | return srv.(RatingsServer).Get(ctx, req.(*GetRatingRequest)) 218 | } 219 | return interceptor(ctx, in, info, handler) 220 | } 221 | 222 | var _Ratings_serviceDesc = grpc.ServiceDesc{ 223 | ServiceName: "Ratings", 224 | HandlerType: (*RatingsServer)(nil), 225 | Methods: []grpc.MethodDesc{ 226 | { 227 | MethodName: "Get", 228 | Handler: _Ratings_Get_Handler, 229 | }, 230 | }, 231 | Streams: []grpc.StreamDesc{}, 232 | Metadata: "ratings.proto", 233 | } 234 | -------------------------------------------------------------------------------- /api/proto/ratings.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/timestamp.proto"; 3 | option go_package = "proto"; 4 | 5 | 6 | service Ratings { 7 | rpc Get (GetRatingRequest) returns (Rating); 8 | } 9 | 10 | message GetRatingRequest { 11 | uint64 productID = 1; 12 | } 13 | 14 | message Rating { 15 | uint64 id = 1; 16 | uint64 productID = 2; 17 | uint32 score = 3; 18 | google.protobuf.Timestamp updatedTime = 4; 19 | } 20 | -------------------------------------------------------------------------------- /api/proto/reviews.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/timestamp.proto"; 3 | option go_package = "proto"; 4 | 5 | 6 | service Reviews { 7 | rpc Query (QueryReviewsRequest) returns (QueryReviewsResponse); 8 | } 9 | 10 | message QueryReviewsRequest { 11 | uint64 productID = 1; 12 | } 13 | 14 | message Review { 15 | uint64 id = 1; 16 | uint64 productID = 2; 17 | string message = 3; 18 | google.protobuf.Timestamp createdTime = 4; 19 | } 20 | 21 | message QueryReviewsResponse { 22 | repeated Review reviews = 1; 23 | } -------------------------------------------------------------------------------- /build/details/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk update && apk add bash 4 | 5 | ADD ./dist/details-linux-amd64 app 6 | ADD ./configs/details.yml . 7 | ADD ./scripts/wait-for . 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/products/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk update && apk add bash 4 | 5 | ADD ./dist/products-linux-amd64 app 6 | ADD ./configs/products.yml . 7 | ADD ./scripts/wait-for . 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/ratings/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk update && apk add bash 4 | 5 | ADD ./dist/ratings-linux-amd64 app 6 | ADD ./configs/ratings.yml . 7 | ADD ./scripts/wait-for . 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/reviews/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk update && apk add bash 4 | 5 | ADD ./dist/reviews-linux-amd64 app 6 | ADD ./configs/reviews.yml . 7 | ADD ./scripts/wait-for . 8 | -------------------------------------------------------------------------------- /cmd/details/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | var configFile = flag.String("f", "details.yml", "set config file which viper will loading.") 8 | 9 | func main() { 10 | flag.Parse() 11 | 12 | app, err := CreateApp(*configFile) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | if err := app.Start(); err != nil { 18 | panic(err) 19 | } 20 | 21 | app.AwaitSignal() 22 | } 23 | -------------------------------------------------------------------------------- /cmd/details/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/app/details/controllers" 8 | "github.com/sdgmf/go-project-sample/internal/app/details/grpcservers" 9 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/details" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 20 | ) 21 | 22 | var providerSet = wire.NewSet( 23 | log.ProviderSet, 24 | config.ProviderSet, 25 | database.ProviderSet, 26 | services.ProviderSet, 27 | repositories.ProviderSet, 28 | consul.ProviderSet, 29 | jaeger.ProviderSet, 30 | http.ProviderSet, 31 | grpc.ProviderSet, 32 | details.ProviderSet, 33 | controllers.ProviderSet, 34 | grpcservers.ProviderSet, 35 | ) 36 | 37 | func CreateApp(cf string) (*app.Application, error) { 38 | panic(wire.Build(providerSet)) 39 | } 40 | -------------------------------------------------------------------------------- /cmd/details/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/details" 11 | "github.com/sdgmf/go-project-sample/internal/app/details/controllers" 12 | "github.com/sdgmf/go-project-sample/internal/app/details/grpcservers" 13 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 14 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 20 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 21 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 22 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 23 | ) 24 | 25 | // Injectors from wire.go: 26 | 27 | func CreateApp(cf string) (*app.Application, error) { 28 | viper, err := config.New(cf) 29 | if err != nil { 30 | return nil, err 31 | } 32 | options, err := log.NewOptions(viper) 33 | if err != nil { 34 | return nil, err 35 | } 36 | logger, err := log.New(options) 37 | if err != nil { 38 | return nil, err 39 | } 40 | detailsOptions, err := details.NewOptions(viper, logger) 41 | if err != nil { 42 | return nil, err 43 | } 44 | httpOptions, err := http.NewOptions(viper) 45 | if err != nil { 46 | return nil, err 47 | } 48 | databaseOptions, err := database.NewOptions(viper, logger) 49 | if err != nil { 50 | return nil, err 51 | } 52 | db, err := database.New(databaseOptions) 53 | if err != nil { 54 | return nil, err 55 | } 56 | detailsRepository := repositories.NewMysqlDetailsRepository(logger, db) 57 | detailsService := services.NewDetailService(logger, detailsRepository) 58 | detailsController := controllers.NewDetailsController(logger, detailsService) 59 | initControllers := controllers.CreateInitControllersFn(detailsController) 60 | configuration, err := jaeger.NewConfiguration(viper, logger) 61 | if err != nil { 62 | return nil, err 63 | } 64 | tracer, err := jaeger.New(configuration) 65 | if err != nil { 66 | return nil, err 67 | } 68 | engine := http.NewRouter(httpOptions, logger, initControllers, tracer) 69 | consulOptions, err := consul.NewOptions(viper) 70 | if err != nil { 71 | return nil, err 72 | } 73 | client, err := consul.New(consulOptions) 74 | if err != nil { 75 | return nil, err 76 | } 77 | server, err := http.New(httpOptions, logger, engine, client) 78 | if err != nil { 79 | return nil, err 80 | } 81 | serverOptions, err := grpc.NewServerOptions(viper) 82 | if err != nil { 83 | return nil, err 84 | } 85 | detailsServer, err := grpcservers.NewDetailsServer(logger, detailsService) 86 | if err != nil { 87 | return nil, err 88 | } 89 | initServers := grpcservers.CreateInitServersFn(detailsServer) 90 | grpcServer, err := grpc.NewServer(serverOptions, logger, initServers, client, tracer) 91 | if err != nil { 92 | return nil, err 93 | } 94 | application, err := details.NewApp(detailsOptions, logger, server, grpcServer) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return application, nil 99 | } 100 | 101 | // wire.go: 102 | 103 | var providerSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, repositories.ProviderSet, consul.ProviderSet, jaeger.ProviderSet, http.ProviderSet, grpc.ProviderSet, details.ProviderSet, controllers.ProviderSet, grpcservers.ProviderSet) 104 | -------------------------------------------------------------------------------- /cmd/products/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | var configFile = flag.String("f", "products.yml", "set config file which viper will loading.") 8 | 9 | func main() { 10 | flag.Parse() 11 | 12 | 13 | app, err := CreateApp(*configFile) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | if err := app.Start(); err != nil { 19 | panic(err) 20 | } 21 | 22 | app.AwaitSignal() 23 | } 24 | -------------------------------------------------------------------------------- /cmd/products/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/app/products" 8 | "github.com/sdgmf/go-project-sample/internal/app/products/controllers" 9 | "github.com/sdgmf/go-project-sample/internal/app/products/services" 10 | "github.com/sdgmf/go-project-sample/internal/app/products/grpcclients" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 18 | ) 19 | 20 | var providerSet = wire.NewSet( 21 | log.ProviderSet, 22 | config.ProviderSet, 23 | consul.ProviderSet, 24 | jaeger.ProviderSet, 25 | http.ProviderSet, 26 | grpc.ProviderSet, 27 | grpcclients.ProviderSet, 28 | controllers.ProviderSet, 29 | services.ProviderSet, 30 | products.ProviderSet, 31 | ) 32 | 33 | 34 | func CreateApp(cf string) (*app.Application, error) { 35 | panic(wire.Build(providerSet)) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/products/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/products" 11 | "github.com/sdgmf/go-project-sample/internal/app/products/controllers" 12 | "github.com/sdgmf/go-project-sample/internal/app/products/grpcclients" 13 | "github.com/sdgmf/go-project-sample/internal/app/products/services" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 20 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 21 | ) 22 | 23 | // Injectors from wire.go: 24 | 25 | func CreateApp(cf string) (*app.Application, error) { 26 | viper, err := config.New(cf) 27 | if err != nil { 28 | return nil, err 29 | } 30 | options, err := log.NewOptions(viper) 31 | if err != nil { 32 | return nil, err 33 | } 34 | logger, err := log.New(options) 35 | if err != nil { 36 | return nil, err 37 | } 38 | productsOptions, err := products.NewOptions(viper, logger) 39 | if err != nil { 40 | return nil, err 41 | } 42 | httpOptions, err := http.NewOptions(viper) 43 | if err != nil { 44 | return nil, err 45 | } 46 | consulOptions, err := consul.NewOptions(viper) 47 | if err != nil { 48 | return nil, err 49 | } 50 | configuration, err := jaeger.NewConfiguration(viper, logger) 51 | if err != nil { 52 | return nil, err 53 | } 54 | tracer, err := jaeger.New(configuration) 55 | if err != nil { 56 | return nil, err 57 | } 58 | clientOptions, err := grpc.NewClientOptions(viper, tracer) 59 | if err != nil { 60 | return nil, err 61 | } 62 | client, err := grpc.NewClient(consulOptions, clientOptions) 63 | if err != nil { 64 | return nil, err 65 | } 66 | detailsClient, err := grpcclients.NewDetailsClient(client) 67 | if err != nil { 68 | return nil, err 69 | } 70 | ratingsClient, err := grpcclients.NewRatingsClient(client) 71 | if err != nil { 72 | return nil, err 73 | } 74 | reviewsClient, err := grpcclients.NewReviewsClient(client) 75 | if err != nil { 76 | return nil, err 77 | } 78 | productsService := services.NewProductService(logger, detailsClient, ratingsClient, reviewsClient) 79 | productsController := controllers.NewProductsController(logger, productsService) 80 | initControllers := controllers.CreateInitControllersFn(productsController) 81 | engine := http.NewRouter(httpOptions, logger, initControllers, tracer) 82 | apiClient, err := consul.New(consulOptions) 83 | if err != nil { 84 | return nil, err 85 | } 86 | server, err := http.New(httpOptions, logger, engine, apiClient) 87 | if err != nil { 88 | return nil, err 89 | } 90 | application, err := products.NewApp(productsOptions, logger, server) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return application, nil 95 | } 96 | 97 | // wire.go: 98 | 99 | var providerSet = wire.NewSet(log.ProviderSet, config.ProviderSet, consul.ProviderSet, jaeger.ProviderSet, http.ProviderSet, grpc.ProviderSet, grpcclients.ProviderSet, controllers.ProviderSet, services.ProviderSet, products.ProviderSet) 100 | -------------------------------------------------------------------------------- /cmd/ratings/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | var configFile = flag.String("f", "ratings.yml", "set config file which viper will loading.") 8 | 9 | func main() { 10 | flag.Parse() 11 | 12 | app, err := CreateApp(*configFile) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | if err := app.Start(); err != nil { 18 | panic(err) 19 | } 20 | 21 | app.AwaitSignal() 22 | } 23 | -------------------------------------------------------------------------------- /cmd/ratings/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/app/ratings" 8 | "github.com/sdgmf/go-project-sample/internal/app/ratings/controllers" 9 | "github.com/sdgmf/go-project-sample/internal/app/ratings/grpcservers" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 16 | 17 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 20 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 21 | ) 22 | 23 | var providerSet = wire.NewSet( 24 | log.ProviderSet, 25 | config.ProviderSet, 26 | database.ProviderSet, 27 | services.ProviderSet, 28 | consul.ProviderSet, 29 | jaeger.ProviderSet, 30 | http.ProviderSet, 31 | grpc.ProviderSet, 32 | ratings.ProviderSet, 33 | repositories.ProviderSet, 34 | controllers.ProviderSet, 35 | grpcservers.ProviderSet, 36 | ) 37 | 38 | 39 | func CreateApp(cf string) (*app.Application, error) { 40 | panic(wire.Build(providerSet)) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/ratings/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings" 11 | "github.com/sdgmf/go-project-sample/internal/app/ratings/controllers" 12 | "github.com/sdgmf/go-project-sample/internal/app/ratings/grpcservers" 13 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 14 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 20 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 21 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 22 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 23 | ) 24 | 25 | // Injectors from wire.go: 26 | 27 | func CreateApp(cf string) (*app.Application, error) { 28 | viper, err := config.New(cf) 29 | if err != nil { 30 | return nil, err 31 | } 32 | options, err := log.NewOptions(viper) 33 | if err != nil { 34 | return nil, err 35 | } 36 | logger, err := log.New(options) 37 | if err != nil { 38 | return nil, err 39 | } 40 | ratingsOptions, err := ratings.NewOptions(viper, logger) 41 | if err != nil { 42 | return nil, err 43 | } 44 | httpOptions, err := http.NewOptions(viper) 45 | if err != nil { 46 | return nil, err 47 | } 48 | databaseOptions, err := database.NewOptions(viper, logger) 49 | if err != nil { 50 | return nil, err 51 | } 52 | db, err := database.New(databaseOptions) 53 | if err != nil { 54 | return nil, err 55 | } 56 | ratingsRepository := repositories.NewMysqlRatingsRepository(logger, db) 57 | ratingsService := services.NewRatingService(logger, ratingsRepository) 58 | ratingsController := controllers.NewRatingsController(logger, ratingsService) 59 | initControllers := controllers.CreateInitControllersFn(ratingsController) 60 | configuration, err := jaeger.NewConfiguration(viper, logger) 61 | if err != nil { 62 | return nil, err 63 | } 64 | tracer, err := jaeger.New(configuration) 65 | if err != nil { 66 | return nil, err 67 | } 68 | engine := http.NewRouter(httpOptions, logger, initControllers, tracer) 69 | consulOptions, err := consul.NewOptions(viper) 70 | if err != nil { 71 | return nil, err 72 | } 73 | client, err := consul.New(consulOptions) 74 | if err != nil { 75 | return nil, err 76 | } 77 | server, err := http.New(httpOptions, logger, engine, client) 78 | if err != nil { 79 | return nil, err 80 | } 81 | serverOptions, err := grpc.NewServerOptions(viper) 82 | if err != nil { 83 | return nil, err 84 | } 85 | ratingsServer, err := grpcservers.NewRatingsServer(logger, ratingsService) 86 | if err != nil { 87 | return nil, err 88 | } 89 | initServers := grpcservers.CreateInitServersFn(ratingsServer) 90 | grpcServer, err := grpc.NewServer(serverOptions, logger, initServers, client, tracer) 91 | if err != nil { 92 | return nil, err 93 | } 94 | application, err := ratings.NewApp(ratingsOptions, logger, server, grpcServer) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return application, nil 99 | } 100 | 101 | // wire.go: 102 | 103 | var providerSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, consul.ProviderSet, jaeger.ProviderSet, http.ProviderSet, grpc.ProviderSet, ratings.ProviderSet, repositories.ProviderSet, controllers.ProviderSet, grpcservers.ProviderSet) 104 | -------------------------------------------------------------------------------- /cmd/reviews/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | ) 7 | 8 | var configFile = flag.String("f", "reviews.yml", "set config file which viper will loading.") 9 | 10 | func main() { 11 | flag.Parse() 12 | 13 | app, err := CreateApp(*configFile) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | if err := app.Start(); err != nil { 19 | panic(err) 20 | } 21 | 22 | app.AwaitSignal() 23 | } 24 | -------------------------------------------------------------------------------- /cmd/reviews/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/app/reviews" 8 | "github.com/sdgmf/go-project-sample/internal/app/reviews/controllers" 9 | "github.com/sdgmf/go-project-sample/internal/app/reviews/grpcservers" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 20 | ) 21 | 22 | var providerSet = wire.NewSet( 23 | log.ProviderSet, 24 | config.ProviderSet, 25 | database.ProviderSet, 26 | services.ProviderSet, 27 | consul.ProviderSet, 28 | jaeger.ProviderSet, 29 | http.ProviderSet, 30 | grpc.ProviderSet, 31 | reviews.ProviderSet, 32 | repositories.ProviderSet, 33 | controllers.ProviderSet, 34 | grpcservers.ProviderSet, 35 | ) 36 | 37 | 38 | func CreateApp(cf string) (*app.Application, error) { 39 | panic(wire.Build(providerSet)) 40 | } 41 | -------------------------------------------------------------------------------- /cmd/reviews/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews" 11 | "github.com/sdgmf/go-project-sample/internal/app/reviews/controllers" 12 | "github.com/sdgmf/go-project-sample/internal/app/reviews/grpcservers" 13 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 14 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 17 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 18 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 19 | "github.com/sdgmf/go-project-sample/internal/pkg/jaeger" 20 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 21 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 22 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 23 | ) 24 | 25 | // Injectors from wire.go: 26 | 27 | func CreateApp(cf string) (*app.Application, error) { 28 | viper, err := config.New(cf) 29 | if err != nil { 30 | return nil, err 31 | } 32 | options, err := log.NewOptions(viper) 33 | if err != nil { 34 | return nil, err 35 | } 36 | logger, err := log.New(options) 37 | if err != nil { 38 | return nil, err 39 | } 40 | reviewsOptions, err := reviews.NewOptions(viper, logger) 41 | if err != nil { 42 | return nil, err 43 | } 44 | httpOptions, err := http.NewOptions(viper) 45 | if err != nil { 46 | return nil, err 47 | } 48 | databaseOptions, err := database.NewOptions(viper, logger) 49 | if err != nil { 50 | return nil, err 51 | } 52 | db, err := database.New(databaseOptions) 53 | if err != nil { 54 | return nil, err 55 | } 56 | reviewsRepository := repositories.NewMysqlReviewsRepository(logger, db) 57 | reviewsService := services.NewReviewService(logger, reviewsRepository) 58 | reviewsController := controllers.NewReviewsController(logger, reviewsService) 59 | initControllers := controllers.CreateInitControllersFn(reviewsController) 60 | configuration, err := jaeger.NewConfiguration(viper, logger) 61 | if err != nil { 62 | return nil, err 63 | } 64 | tracer, err := jaeger.New(configuration) 65 | if err != nil { 66 | return nil, err 67 | } 68 | engine := http.NewRouter(httpOptions, logger, initControllers, tracer) 69 | consulOptions, err := consul.NewOptions(viper) 70 | if err != nil { 71 | return nil, err 72 | } 73 | client, err := consul.New(consulOptions) 74 | if err != nil { 75 | return nil, err 76 | } 77 | server, err := http.New(httpOptions, logger, engine, client) 78 | if err != nil { 79 | return nil, err 80 | } 81 | serverOptions, err := grpc.NewServerOptions(viper) 82 | if err != nil { 83 | return nil, err 84 | } 85 | reviewsServer, err := grpcservers.NewReviewsServer(logger, reviewsService) 86 | if err != nil { 87 | return nil, err 88 | } 89 | initServers := grpcservers.CreateInitServersFn(reviewsServer) 90 | grpcServer, err := grpc.NewServer(serverOptions, logger, initServers, client, tracer) 91 | if err != nil { 92 | return nil, err 93 | } 94 | application, err := reviews.NewApp(reviewsOptions, logger, server, grpcServer) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return application, nil 99 | } 100 | 101 | // wire.go: 102 | 103 | var providerSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, consul.ProviderSet, jaeger.ProviderSet, http.ProviderSet, grpc.ProviderSet, reviews.ProviderSet, repositories.ProviderSet, controllers.ProviderSet, grpcservers.ProviderSet) 104 | -------------------------------------------------------------------------------- /configs/details.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: details 3 | http: 4 | mode: release 5 | port: 8081 6 | db: 7 | url: products:123456@tcp(db:3306)/products?charset=utf8&parseTime=True&loc=Local 8 | debug: false 9 | log: 10 | filename: /tmp/details.log 11 | maxSize: 500 12 | maxBackups: 3 13 | maxAge: 3 14 | level: "debug" 15 | stdout: false 16 | consul: 17 | addr: consul:8500 18 | grpc: 19 | port: 0 20 | jaeger: 21 | serviceName: details 22 | reporter: 23 | localAgentHostPort: "jaeger-agent:6831" 24 | sampler: 25 | type: const 26 | param: 1 27 | -------------------------------------------------------------------------------- /configs/grafana/provisioning/dashboards/dashboards.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | providers: 3 | # an unique provider name 4 | - name: 'default' 5 | # org id. will default to orgId 1 if not specified 6 | orgId: 1 7 | # name of the dashboard folder. Required 8 | folder: '' 9 | # folder UID. will be automatically generated if not specified 10 | folderUid: '' 11 | # provider type. Required 12 | type: file 13 | # disable dashboard deletion 14 | disableDeletion: false 15 | # enable dashboard editing 16 | editable: true 17 | # how often Grafana will scan for changed dashboards 18 | updateIntervalSeconds: 10 19 | options: 20 | # path to dashboard files on disk. Required 21 | path: /tmp/dashboards -------------------------------------------------------------------------------- /configs/grafana/provisioning/datasources/prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | access: proxy 7 | url: http://prometheus:9090 8 | isDefault: true 9 | editable: true -------------------------------------------------------------------------------- /configs/products.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: products 3 | http: 4 | mode: release 5 | port: 8080 6 | log: 7 | filename: /tmp/products.log 8 | maxSize: 500 9 | maxBackups: 3 10 | maxAge: 3 11 | level: "debug" 12 | stdout: false 13 | consul: 14 | addr: consul:8500 15 | grpc: 16 | port: 0 17 | jaeger: 18 | serviceName: products 19 | reporter: 20 | localAgentHostPort: "jaeger-agent:6831" 21 | sampler: 22 | type: const 23 | param: 1 -------------------------------------------------------------------------------- /configs/prometheus/alertmanager.yml: -------------------------------------------------------------------------------- 1 | global: 2 | resolve_timeout: 10s 3 | smtp_from: xxx@xxx 4 | smtp_smarthost: ip:port 5 | smtp_auth_username: xxx@xxx 6 | smtp_auth_password: xxx 7 | smtp_auth_identity: xxx@xxx 8 | route: 9 | group_by: ['email'] 10 | group_wait: 10s 11 | group_interval: 10s 12 | repeat_interval: 1h 13 | receiver: 'email' 14 | receivers: 15 | - name: 'email' 16 | email_configs: 17 | - to: "{{.GroupLabels.email}}" 18 | send_resolved: true 19 | #inhibit_rules: 20 | # - source_match: 21 | # severity: 'critical' 22 | # target_match: 23 | # severity: 'warning' 24 | # equal: ['alertname'] -------------------------------------------------------------------------------- /configs/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 10s 4 | rule_files: 5 | - /rules/*.yml 6 | scrape_configs: 7 | - job_name: consul 8 | consul_sd_configs: 9 | - server: consul:8500 10 | datacenter: dc1 11 | tags: 12 | - http 13 | relabel_configs: 14 | - source_labels: [__meta_consul_service] 15 | target_label: app 16 | - job_name: grafana 17 | static_configs: 18 | - targets: 19 | - "grafana:3000" 20 | - job_name: prometheus 21 | static_configs: 22 | - targets: 23 | - "localhost:9090" 24 | alerting: 25 | alertmanagers: 26 | - static_configs: 27 | - targets: 28 | - "alertmanager:9093" 29 | -------------------------------------------------------------------------------- /configs/prometheus/rules/details.yml: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "details", 5 | "rules": [ 6 | { 7 | "alert": "ServiceDown[details]", 8 | "annotations": { 9 | "description": "{{ $labels.app}} has been down for more than 10 seconds." 10 | }, 11 | "expr": "absent(up{app=\"details\"}) == 1", 12 | "for": "10s", 13 | "labels": { 14 | "email": "xxx@xxx" 15 | } 16 | }, 17 | { 18 | "alert": "GRPCServerErrorThrottlingHigh[details]", 19 | "annotations": { 20 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 21 | }, 22 | "expr": "sum(rate(grpc_server_handled_total{app=\"details\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 23 | "for": "10s", 24 | "labels": { 25 | "email": "xxx@xxx" 26 | } 27 | }, 28 | { 29 | "alert": "GRPServerCLatencyThrottlingHigh[details]", 30 | "annotations": { 31 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 32 | }, 33 | "expr": "histogram_quantile(0.99,sum(rate(grpc_server_handling_seconds_bucket{app=\"details\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 34 | "for": "10s", 35 | "labels": { 36 | "email": "xxx@xxx" 37 | } 38 | }, 39 | { 40 | "alert": "GRPCClientErrorThrottlingHigh[details]", 41 | "annotations": { 42 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 43 | }, 44 | "expr": "sum(rate(grpc_client_handled_total{app=\"details\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 45 | "for": "10s", 46 | "labels": { 47 | "email": "xxx@xxx" 48 | } 49 | }, 50 | { 51 | "alert": "GRPCClientLatencyThrottlingHigh[details]", 52 | "annotations": { 53 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 54 | }, 55 | "expr": "histogram_quantile(0.99,sum(rate(grpc_client_handling_seconds_bucket{app=\"details\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 56 | "for": "10s", 57 | "labels": { 58 | "email": "xxx@xxx" 59 | } 60 | }, 61 | { 62 | "alert": "HTTPErrorThrottlingHigh[details]", 63 | "annotations": { 64 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 65 | }, 66 | "expr": "sum(rate(http_server_requests_seconds_count{app=\"details\",code!=\"200\"}[1m])) by (instance) > 0", 67 | "for": "10s", 68 | "labels": { 69 | "email": "xxx@xxx" 70 | } 71 | }, 72 | { 73 | "alert": "HTTPLatencyThrottlingHigh[details]", 74 | "annotations": { 75 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 76 | }, 77 | "expr": "histogram_quantile(0.99,sum(rate(http_server_requests_seconds_bucket{app=\"details\"}[1m])) by (instance,le)) > 0.2", 78 | "for": "10s", 79 | "labels": { 80 | "email": "xxx@xxx" 81 | } 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /configs/prometheus/rules/products.bak: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: products 3 | rules: 4 | # heartbeat alert 5 | - alert: Heartbeat 6 | expr: vector(1) 7 | labels: 8 | severity: informational 9 | # service availability alert 10 | - alert: service_down 11 | expr: up == 0 12 | - alert: service_up 13 | expr: up == 1 14 | 15 | -------------------------------------------------------------------------------- /configs/prometheus/rules/products.yml: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "products", 5 | "rules": [ 6 | { 7 | "alert": "ServiceDown[products]", 8 | "annotations": { 9 | "description": "{{ $labels.app}} has been down for more than 10 seconds." 10 | }, 11 | "expr": "absent(up{app=\"products\"}) == 1", 12 | "for": "10s", 13 | "labels": { 14 | "email": "xxx@xxx" 15 | } 16 | }, 17 | { 18 | "alert": "GRPCServerErrorThrottlingHigh[products]", 19 | "annotations": { 20 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 21 | }, 22 | "expr": "sum(rate(grpc_server_handled_total{app=\"products\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 23 | "for": "10s", 24 | "labels": { 25 | "email": "xxx@xxx" 26 | } 27 | }, 28 | { 29 | "alert": "GRPServerCLatencyThrottlingHigh[products]", 30 | "annotations": { 31 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 32 | }, 33 | "expr": "histogram_quantile(0.99,sum(rate(grpc_server_handling_seconds_bucket{app=\"products\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 34 | "for": "10s", 35 | "labels": { 36 | "email": "xxx@xxx" 37 | } 38 | }, 39 | { 40 | "alert": "GRPCClientErrorThrottlingHigh[products]", 41 | "annotations": { 42 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 43 | }, 44 | "expr": "sum(rate(grpc_client_handled_total{app=\"products\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 45 | "for": "10s", 46 | "labels": { 47 | "email": "xxx@xxx" 48 | } 49 | }, 50 | { 51 | "alert": "GRPCClientLatencyThrottlingHigh[products]", 52 | "annotations": { 53 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 54 | }, 55 | "expr": "histogram_quantile(0.99,sum(rate(grpc_client_handling_seconds_bucket{app=\"products\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 56 | "for": "10s", 57 | "labels": { 58 | "email": "xxx@xxx" 59 | } 60 | }, 61 | { 62 | "alert": "HTTPErrorThrottlingHigh[products]", 63 | "annotations": { 64 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 65 | }, 66 | "expr": "sum(rate(http_server_requests_seconds_count{app=\"products\",code!=\"200\"}[1m])) by (instance) > 0", 67 | "for": "10s", 68 | "labels": { 69 | "email": "xxx@xxx" 70 | } 71 | }, 72 | { 73 | "alert": "HTTPLatencyThrottlingHigh[products]", 74 | "annotations": { 75 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 76 | }, 77 | "expr": "histogram_quantile(0.99,sum(rate(http_server_requests_seconds_bucket{app=\"products\"}[1m])) by (instance,le)) > 0.2", 78 | "for": "10s", 79 | "labels": { 80 | "email": "xxx@xxx" 81 | } 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /configs/prometheus/rules/ratings.yml: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "ratings", 5 | "rules": [ 6 | { 7 | "alert": "ServiceDown[ratings]", 8 | "annotations": { 9 | "description": "{{ $labels.app}} has been down for more than 10 seconds." 10 | }, 11 | "expr": "absent(up{app=\"ratings\"}) == 1", 12 | "for": "10s", 13 | "labels": { 14 | "email": "xxx@xxx" 15 | } 16 | }, 17 | { 18 | "alert": "GRPCServerErrorThrottlingHigh[ratings]", 19 | "annotations": { 20 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 21 | }, 22 | "expr": "sum(rate(grpc_server_handled_total{app=\"ratings\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 23 | "for": "10s", 24 | "labels": { 25 | "email": "xxx@xxx" 26 | } 27 | }, 28 | { 29 | "alert": "GRPServerCLatencyThrottlingHigh[ratings]", 30 | "annotations": { 31 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 32 | }, 33 | "expr": "histogram_quantile(0.99,sum(rate(grpc_server_handling_seconds_bucket{app=\"ratings\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 34 | "for": "10s", 35 | "labels": { 36 | "email": "xxx@xxx" 37 | } 38 | }, 39 | { 40 | "alert": "GRPCClientErrorThrottlingHigh[ratings]", 41 | "annotations": { 42 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 43 | }, 44 | "expr": "sum(rate(grpc_client_handled_total{app=\"ratings\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 45 | "for": "10s", 46 | "labels": { 47 | "email": "xxx@xxx" 48 | } 49 | }, 50 | { 51 | "alert": "GRPCClientLatencyThrottlingHigh[ratings]", 52 | "annotations": { 53 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 54 | }, 55 | "expr": "histogram_quantile(0.99,sum(rate(grpc_client_handling_seconds_bucket{app=\"ratings\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 56 | "for": "10s", 57 | "labels": { 58 | "email": "xxx@xxx" 59 | } 60 | }, 61 | { 62 | "alert": "HTTPErrorThrottlingHigh[ratings]", 63 | "annotations": { 64 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 65 | }, 66 | "expr": "sum(rate(http_server_requests_seconds_count{app=\"ratings\",code!=\"200\"}[1m])) by (instance) > 0", 67 | "for": "10s", 68 | "labels": { 69 | "email": "xxx@xxx" 70 | } 71 | }, 72 | { 73 | "alert": "HTTPLatencyThrottlingHigh[ratings]", 74 | "annotations": { 75 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 76 | }, 77 | "expr": "histogram_quantile(0.99,sum(rate(http_server_requests_seconds_bucket{app=\"ratings\"}[1m])) by (instance,le)) > 0.2", 78 | "for": "10s", 79 | "labels": { 80 | "email": "xxx@xxx" 81 | } 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /configs/prometheus/rules/reviews.yml: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "reviews", 5 | "rules": [ 6 | { 7 | "alert": "ServiceDown[reviews]", 8 | "annotations": { 9 | "description": "{{ $labels.app}} has been down for more than 10 seconds." 10 | }, 11 | "expr": "absent(up{app=\"reviews\"}) == 1", 12 | "for": "10s", 13 | "labels": { 14 | "email": "xxx@xxx" 15 | } 16 | }, 17 | { 18 | "alert": "GRPCServerErrorThrottlingHigh[reviews]", 19 | "annotations": { 20 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 21 | }, 22 | "expr": "sum(rate(grpc_server_handled_total{app=\"reviews\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 23 | "for": "10s", 24 | "labels": { 25 | "email": "xxx@xxx" 26 | } 27 | }, 28 | { 29 | "alert": "GRPServerCLatencyThrottlingHigh[reviews]", 30 | "annotations": { 31 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 32 | }, 33 | "expr": "histogram_quantile(0.99,sum(rate(grpc_server_handling_seconds_bucket{app=\"reviews\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 34 | "for": "10s", 35 | "labels": { 36 | "email": "xxx@xxx" 37 | } 38 | }, 39 | { 40 | "alert": "GRPCClientErrorThrottlingHigh[reviews]", 41 | "annotations": { 42 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 43 | }, 44 | "expr": "sum(rate(grpc_client_handled_total{app=\"reviews\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 45 | "for": "10s", 46 | "labels": { 47 | "email": "xxx@xxx" 48 | } 49 | }, 50 | { 51 | "alert": "GRPCClientLatencyThrottlingHigh[reviews]", 52 | "annotations": { 53 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 54 | }, 55 | "expr": "histogram_quantile(0.99,sum(rate(grpc_client_handling_seconds_bucket{app=\"reviews\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 56 | "for": "10s", 57 | "labels": { 58 | "email": "xxx@xxx" 59 | } 60 | }, 61 | { 62 | "alert": "HTTPErrorThrottlingHigh[reviews]", 63 | "annotations": { 64 | "description": "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 65 | }, 66 | "expr": "sum(rate(http_server_requests_seconds_count{app=\"reviews\",code!=\"200\"}[1m])) by (instance) > 0", 67 | "for": "10s", 68 | "labels": { 69 | "email": "xxx@xxx" 70 | } 71 | }, 72 | { 73 | "alert": "HTTPLatencyThrottlingHigh[reviews]", 74 | "annotations": { 75 | "description": "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 76 | }, 77 | "expr": "histogram_quantile(0.99,sum(rate(http_server_requests_seconds_bucket{app=\"reviews\"}[1m])) by (instance,le)) > 0.2", 78 | "for": "10s", 79 | "labels": { 80 | "email": "xxx@xxx" 81 | } 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /configs/ratings.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: ratings 3 | http: 4 | mode: release 5 | port: 0 6 | db: 7 | url: products:123456@tcp(db:3306)/products?charset=utf8&parseTime=True&loc=Local 8 | debug : false 9 | log: 10 | filename: /tmp/ratings.log 11 | maxSize: 500 12 | maxBackups: 3 13 | maxAge: 3 14 | level: "debug" 15 | stdout: false 16 | consul: 17 | addr: consul:8500 18 | grpc: 19 | port: 0 20 | jaeger: 21 | serviceName: rating 22 | reporter: 23 | localAgentHostPort: "jaeger-agent:6831" 24 | sampler: 25 | type: const 26 | param: 1 -------------------------------------------------------------------------------- /configs/reviews.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: reviews 3 | http: 4 | mode: release 5 | port: 0 6 | db: 7 | url: products:123456@tcp(db:3306)/products?charset=utf8&parseTime=True&loc=Local 8 | debug : false 9 | log: 10 | filename: /tmp/review.log 11 | maxSize: 500 12 | maxBackups: 3 13 | maxAge: 3 14 | level: "debug" 15 | stdout: false 16 | consul: 17 | addr: consul:8500 18 | grpc: 19 | port: 0 20 | jaeger: 21 | serviceName: reviews 22 | reporter: 23 | localAgentHostPort: "jaeger-agent:6831" 24 | sampler: 25 | type: const 26 | param: 1 -------------------------------------------------------------------------------- /deployments/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | details: 4 | build: 5 | context: .. 6 | dockerfile: "build/details/Dockerfile" 7 | command: ./wait-for db:3306 -t 30 -- ./app 8 | depends_on: 9 | - db 10 | - consul 11 | - jaeger-agent 12 | ports: 13 | - "8081:8081" 14 | ratings: 15 | build: 16 | context: .. 17 | dockerfile: "build/ratings/Dockerfile" 18 | command: ./wait-for db:3306 -t 30 -- ./app 19 | depends_on: 20 | - db 21 | - consul 22 | - jaeger-agent 23 | reviews: 24 | build: 25 | context: .. 26 | dockerfile: "build/reviews/Dockerfile" 27 | command: ./wait-for db:3306 -t 30 -- ./app 28 | depends_on: 29 | - db 30 | - consul 31 | - jaeger-agent 32 | products: 33 | build: 34 | context: .. 35 | dockerfile: "build/products/Dockerfile" 36 | command: ./wait-for db:3306 -t 30 -- ./app 37 | depends_on: 38 | - db 39 | - consul 40 | - jaeger-agent 41 | ports: 42 | - "8080:8080" 43 | db: 44 | image: mariadb:latest 45 | environment: 46 | MYSQL_ROOT_PASSWORD: 123456 47 | MYSQL_DATABASE: products 48 | MYSQL_USER: products 49 | MYSQL_PASSWORD: 123456 50 | volumes: 51 | - ../scripts/products.sql:/docker-entrypoint-initdb.d/products.sql 52 | consul: 53 | image: consul:1.4 54 | environment: 55 | - "CONSUL_BIND_INTERFACE=eth0" 56 | - "CONSUL_ALLOW_PRIVILEGED_PORTS=" 57 | - "CONSUL_HTTP_ADDR=0.0.0.0" 58 | command: 59 | - consul 60 | - agent 61 | - -dev 62 | - -ui 63 | - -bind={{ GetInterfaceIP "eth0" }} 64 | - -client={{ GetInterfaceIP "eth0" }} 65 | ports: 66 | - "8500:8500" 67 | expose: 68 | - 53/udp 69 | jaeger-collector: 70 | image: jaegertracing/jaeger-collector 71 | command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra", "--collector.zipkin.http-port=9411"] 72 | ports: 73 | - "14269" 74 | - "14268:14268" 75 | - "14267" 76 | - "14250" 77 | - "9411:9411" 78 | restart: on-failure 79 | depends_on: 80 | - cassandra-schema 81 | 82 | jaeger-query: 83 | image: jaegertracing/jaeger-query 84 | command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra"] 85 | ports: 86 | - "16686:16686" 87 | - "16687" 88 | restart: on-failure 89 | depends_on: 90 | - cassandra-schema 91 | jaeger-agent: 92 | image: jaegertracing/jaeger-agent 93 | command: ["--reporter.grpc.host-port=jaeger-collector:14250"] 94 | ports: 95 | - "5775:5775/udp" 96 | - "6831:6831/udp" 97 | - "6832:6832/udp" 98 | - "5778:5778" 99 | restart: on-failure 100 | depends_on: 101 | - jaeger-collector 102 | cassandra: 103 | image: cassandra:3.9 104 | cassandra-schema: 105 | image: jaegertracing/jaeger-cassandra-schema 106 | depends_on: 107 | - cassandra 108 | prometheus: 109 | image: prom/prometheus 110 | ports: 111 | - 9090:9090 112 | command: 113 | - '--config.file=/prometheus/prometheus.yml' 114 | - "--web.external-url=http://localhost:9090" 115 | volumes: 116 | - "../configs/prometheus/prometheus.yml:/prometheus/prometheus.yml" 117 | - "../configs/prometheus/rules:/rules" 118 | alertmanager: 119 | image: prom/alertmanager 120 | volumes: 121 | - "../configs/prometheus/alertmanager.yml:/prometheus/alertmanager.yml" 122 | - "/tmp/alermanager:/data" 123 | command: 124 | - '--config.file=/prometheus/alertmanager.yml' 125 | - '--storage.path=/data' 126 | ports: 127 | - 9093:9093 128 | grafana-db: 129 | image: mariadb:latest 130 | environment: 131 | MYSQL_ROOT_PASSWORD: rootpass 132 | MYSQL_DATABASE: grafana 133 | MYSQL_USER: grafana 134 | MYSQL_PASSWORD: password 135 | ports: 136 | - 3306 137 | grafana: 138 | image: grafana/grafana:latest 139 | volumes: 140 | - ../configs/grafana/provisioning/:/etc/grafana/provisioning/ 141 | - ../configs/grafana/dashboards/:/tmp/dashboards/ 142 | environment: 143 | # - VIRTUAL_HOST=grafana.loc 144 | - GF_SERVER_ROOT_URL=http://localhost:3000 145 | - GF_DATABASE_NAME=grafana 146 | - GF_DATABASE_USER=grafana 147 | - GF_DATABASE_PASSWORD=password 148 | - GF_DATABASE_TYPE=mysql 149 | - GF_DATABASE_HOST=grafana-db:3306 150 | - GF_DATABASE_MAX_OPEN_CONN=300 151 | - GF_SESSION_PROVIDER=mysql 152 | - GF_SESSION_PROVIDER_CONFIG=grafana:password@tcp(db:3306)/grafana?allowNativePasswords=true 153 | # - GF_DATABASE_TYPE=postgres 154 | # - GF_DATABASE_HOST=db:5432 155 | # - GF_DATABASE_SSL_MODE=disable 156 | # - GF_SESSION_PROVIDER=postgres 157 | # - GF_SESSION_PROVIDER_CONFIG=user=grafana password=password host=db port=5432 dbname=grafana sslmode=disable 158 | - GF_SERVER_ROUTER_LOGGING=true 159 | - GF_LOG_CONSOLE_FORMAT=json 160 | - GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug,auth:debug 161 | - GF_AUTH_TOKEN_ROTATION_INTERVAL_MINUTES=2 162 | ports: 163 | - "3000:3000" 164 | depends_on: 165 | - grafana-db 166 | restart: always 167 | -------------------------------------------------------------------------------- /doc/images/alert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/alert.jpg -------------------------------------------------------------------------------- /doc/images/alert1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/alert1.jpg -------------------------------------------------------------------------------- /doc/images/grafana_dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/grafana_dashboard.jpg -------------------------------------------------------------------------------- /doc/images/grafana_dashboard1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/grafana_dashboard1.jpg -------------------------------------------------------------------------------- /doc/images/jaeger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/jaeger.jpg -------------------------------------------------------------------------------- /doc/images/jaeger1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdgmf/go-project-sample/ce62dce5e013606727a635b3052317c2a85607cd/doc/images/jaeger1.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sdgmf/go-project-sample 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect 7 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect 8 | github.com/gin-contrib/pprof v1.2.0 9 | github.com/gin-contrib/zap v0.0.0-20190528085758-3cc18cd8fce3 10 | github.com/gin-gonic/gin v1.4.0 11 | github.com/golang/protobuf v1.3.2 12 | github.com/google/wire v0.3.0 13 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 14 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 15 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 16 | github.com/hashicorp/consul v1.2.3 17 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 18 | github.com/hashicorp/go-uuid v1.0.1 // indirect 19 | github.com/hashicorp/memberlist v0.1.4 // indirect 20 | github.com/jinzhu/gorm v1.9.10 21 | github.com/mbobakov/grpc-consul-resolver v1.3.0 22 | github.com/mitchellh/go-testing-interface v1.0.0 // indirect 23 | github.com/opentracing-contrib/go-gin v0.0.0-20190301172248-2e18f8b9c7d4 24 | github.com/opentracing/opentracing-go v1.1.0 25 | github.com/pkg/errors v0.8.1 26 | github.com/prometheus/client_golang v0.9.3 27 | github.com/spf13/viper v1.4.0 28 | github.com/stretchr/testify v1.3.0 29 | github.com/uber-go/atomic v1.4.0 // indirect 30 | github.com/uber/jaeger-client-go v2.16.0+incompatible 31 | github.com/uber/jaeger-lib v2.0.0+incompatible 32 | github.com/zsais/go-gin-prometheus v0.1.0 33 | go.uber.org/zap v1.10.0 34 | google.golang.org/grpc v1.22.1 35 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 36 | ) 37 | 38 | replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go v0.0.0-20190204201341-e444a5086c43 39 | -------------------------------------------------------------------------------- /internal/app/details/app.go: -------------------------------------------------------------------------------- 1 | package details 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 9 | "github.com/spf13/viper" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type Options struct { 14 | Name string 15 | } 16 | 17 | func NewOptions(v *viper.Viper, logger *zap.Logger) (*Options, error) { 18 | var err error 19 | o := new(Options) 20 | if err = v.UnmarshalKey("app", o); err != nil { 21 | return nil, errors.Wrap(err, "unmarshal app option error") 22 | } 23 | 24 | logger.Info("load application options success") 25 | 26 | return o, err 27 | } 28 | 29 | func NewApp(o *Options, logger *zap.Logger, hs *http.Server, gs *grpc.Server) (*app.Application, error) { 30 | 31 | a, err := app.New(o.Name, logger, app.GrpcServerOption(gs), app.HttpServerOption(hs)) 32 | 33 | if err != nil { 34 | return nil, errors.Wrap(err, "new app error") 35 | } 36 | 37 | return a, nil 38 | } 39 | 40 | var ProviderSet = wire.NewSet(NewApp, NewOptions) 41 | -------------------------------------------------------------------------------- /internal/app/details/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/google/wire" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 7 | ) 8 | 9 | func CreateInitControllersFn( 10 | pc *DetailsController, 11 | ) http.InitControllers { 12 | return func(r *gin.Engine) { 13 | r.GET("/detail/:id", pc.Get) 14 | } 15 | } 16 | 17 | var ProviderSet = wire.NewSet(NewDetailsController, CreateInitControllersFn) 18 | -------------------------------------------------------------------------------- /internal/app/details/controllers/details.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "go.uber.org/zap" 6 | "net/http" 7 | "strconv" 8 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 9 | ) 10 | 11 | type DetailsController struct { 12 | logger *zap.Logger 13 | service services.DetailsService 14 | } 15 | 16 | func NewDetailsController(logger *zap.Logger, s services.DetailsService) *DetailsController { 17 | return &DetailsController{ 18 | logger: logger, 19 | service: s, 20 | } 21 | } 22 | 23 | func (pc *DetailsController) Get(c *gin.Context) { 24 | ID, err := strconv.ParseUint(c.Param("id"), 10, 64) 25 | if err != nil { 26 | _ = c.AbortWithError(http.StatusBadRequest, err) 27 | return 28 | } 29 | 30 | p, err := pc.service.Get(ID) 31 | if err != nil { 32 | pc.logger.Error("get product by id error", zap.Error(err)) 33 | c.String(http.StatusInternalServerError,"%+v", err) 34 | return 35 | } 36 | 37 | c.JSON(http.StatusOK, p) 38 | } 39 | -------------------------------------------------------------------------------- /internal/app/details/controllers/details_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 9 | "github.com/sdgmf/go-project-sample/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/mock" 12 | "io/ioutil" 13 | "net/http/httptest" 14 | "testing" 15 | ) 16 | 17 | var r *gin.Engine 18 | var configFile = flag.String("f", "details.yml", "set config file which viper will loading.") 19 | 20 | func setup() { 21 | r = gin.New() 22 | } 23 | 24 | func TestDetailsController_Get(t *testing.T) { 25 | flag.Parse() 26 | setup() 27 | 28 | sto := new(mocks.DetailsRepository) 29 | 30 | sto.On("Get", mock.AnythingOfType("uint64")).Return(func(ID uint64) (p *models.Detail) { 31 | return &models.Detail{ 32 | ID: ID, 33 | } 34 | }, func(ID uint64) error { 35 | return nil 36 | }) 37 | 38 | c, err := CreateDetailsController(*configFile, sto) 39 | if err != nil { 40 | t.Fatalf("create product serviceerror,%+v", err) 41 | } 42 | 43 | r.GET("/proto/:id", c.Get) 44 | 45 | tests := []struct { 46 | name string 47 | id uint64 48 | expected uint64 49 | }{ 50 | {"1", 1, 1}, 51 | {"2", 2, 2}, 52 | {"3", 3, 3}, 53 | } 54 | 55 | for _, test := range tests { 56 | t.Run(test.name, func(t *testing.T) { 57 | uri := fmt.Sprintf("/proto/%d", test.id) 58 | // 构造get请求 59 | req := httptest.NewRequest("GET", uri, nil) 60 | // 初始化响应 61 | w := httptest.NewRecorder() 62 | 63 | // 调用相应的controller接口 64 | r.ServeHTTP(w, req) 65 | 66 | // 提取响应 67 | rs := w.Result() 68 | defer func() { 69 | _ = rs.Body.Close() 70 | }() 71 | 72 | // 读取响应body 73 | body, _ := ioutil.ReadAll(rs.Body) 74 | p := new(models.Detail) 75 | err := json.Unmarshal(body, p) 76 | if err != nil { 77 | t.Errorf("unmarshal response body error:%v", err) 78 | } 79 | 80 | assert.Equal(t, test.expected, p.ID) 81 | }) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /internal/app/details/controllers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package controllers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 12 | ) 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | services.ProviderSet, 19 | //repositories.ProviderSet, 20 | ProviderSet, 21 | ) 22 | 23 | 24 | func CreateDetailsController(cf string, sto repositories.DetailsRepository) (*DetailsController, error) { 25 | panic(wire.Build(testProviderSet)) 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/details/controllers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package controllers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 15 | ) 16 | 17 | // Injectors from wire.go: 18 | 19 | func CreateDetailsController(cf string, sto repositories.DetailsRepository) (*DetailsController, error) { 20 | viper, err := config.New(cf) 21 | if err != nil { 22 | return nil, err 23 | } 24 | options, err := log.NewOptions(viper) 25 | if err != nil { 26 | return nil, err 27 | } 28 | logger, err := log.New(options) 29 | if err != nil { 30 | return nil, err 31 | } 32 | detailsService := services.NewDetailService(logger, sto) 33 | detailsController := NewDetailsController(logger, detailsService) 34 | return detailsController, nil 35 | } 36 | 37 | // wire.go: 38 | 39 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, ProviderSet) 40 | -------------------------------------------------------------------------------- /internal/app/details/grpcservers/details.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes" 6 | "github.com/pkg/errors" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type DetailsServer struct { 13 | logger *zap.Logger 14 | service services.DetailsService 15 | } 16 | 17 | func NewDetailsServer(logger *zap.Logger, ps services.DetailsService) (*DetailsServer, error) { 18 | return &DetailsServer{ 19 | logger: logger, 20 | service: ps, 21 | }, nil 22 | } 23 | 24 | func (s *DetailsServer) Get(ctx context.Context, req *proto.GetDetailRequest) (*proto.Detail, error) { 25 | p, err := s.service.Get(req.Id) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "details grpc service get detail error") 28 | } 29 | ct, err := ptypes.TimestampProto(p.CreatedTime) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "convert create time error") 32 | } 33 | 34 | resp := &proto.Detail{ 35 | Id: uint64(p.ID), 36 | Name: p.Name, 37 | Price: p.Price, 38 | CreatedTime: ct, 39 | } 40 | 41 | return resp, nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/app/details/grpcservers/details_test.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/sdgmf/go-project-sample/api/proto" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 8 | "github.com/sdgmf/go-project-sample/mocks" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/mock" 11 | "testing" 12 | ) 13 | 14 | var configFile = flag.String("f", "details.yml", "set config file which viper will loading.") 15 | 16 | func TestDetailsService_Get(t *testing.T) { 17 | flag.Parse() 18 | 19 | service := new(mocks.DetailsService) 20 | 21 | service.On("Get", mock.AnythingOfType("uint64")).Return(func(ID uint64) (p *models.Detail) { 22 | return &models.Detail{ 23 | ID: ID, 24 | } 25 | }, func(ID uint64) error { 26 | return nil 27 | }) 28 | 29 | server, err := CreateDetailsServer(*configFile, service) 30 | if err != nil { 31 | t.Fatalf("create product server error,%+v", err) 32 | } 33 | 34 | // 表格驱动测试 35 | tests := []struct { 36 | name string 37 | id uint64 38 | expected uint64 39 | }{ 40 | {"1+1", 1, 1}, 41 | {"2+3", 2, 2}, 42 | {"4+5", 3, 3}, 43 | } 44 | 45 | for _, test := range tests { 46 | t.Run(test.name, func(t *testing.T) { 47 | req := &proto.GetDetailRequest{ 48 | Id: test.id, 49 | } 50 | p, err := server.Get(context.Background(), req) 51 | if err != nil { 52 | t.Fatalf("product service get proudct error,%+v", err) 53 | } 54 | 55 | assert.Equal(t, test.expected, p.Id) 56 | }) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /internal/app/details/grpcservers/servers.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | stdgrpc "google.golang.org/grpc" 8 | ) 9 | 10 | 11 | 12 | func CreateInitServersFn( 13 | ps *DetailsServer, 14 | ) grpc.InitServers { 15 | return func(s *stdgrpc.Server) { 16 | proto.RegisterDetailsServer(s, ps) 17 | } 18 | } 19 | 20 | var ProviderSet = wire.NewSet(NewDetailsServer, CreateInitServersFn) -------------------------------------------------------------------------------- /internal/app/details/grpcservers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package grpcservers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateDetailsServer(cf string, service services.DetailsService) (*DetailsServer, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } -------------------------------------------------------------------------------- /internal/app/details/grpcservers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package grpcservers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/services" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateDetailsServer(cf string, service services.DetailsService) (*DetailsServer, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | detailsServer, err := NewDetailsServer(logger, service) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return detailsServer, nil 36 | } 37 | 38 | // wire.go: 39 | 40 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 41 | -------------------------------------------------------------------------------- /internal/app/details/repositories/cover.out: -------------------------------------------------------------------------------- 1 | mode: count 2 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:17.54,20.16 3 1 3 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:23.2,24.16 2 1 4 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:27.2,28.16 2 1 5 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:31.2,32.16 2 1 6 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:35.2,36.16 2 1 7 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:39.2,40.29 2 1 8 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:20.16,22.3 1 0 9 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:24.16,26.3 1 0 10 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:28.16,30.3 1 0 11 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:32.16,34.3 1 0 12 | github.com/sdgmf/go-project-sample/pkg/Repositorys/wire_gen.go:36.16,38.3 1 0 13 | github.com/sdgmf/go-project-sample/pkg/Repositorys/products.go:19.79,24.2 1 1 14 | github.com/sdgmf/go-project-sample/pkg/Repositorys/products.go:26.76,28.72 2 3 15 | github.com/sdgmf/go-project-sample/pkg/Repositorys/products.go:31.2,31.8 1 1 16 | github.com/sdgmf/go-project-sample/pkg/Repositorys/products.go:28.72,30.3 1 2 17 | -------------------------------------------------------------------------------- /internal/app/details/repositories/details.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/pkg/errors" 6 | "go.uber.org/zap" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 8 | ) 9 | 10 | type DetailsRepository interface { 11 | Get(ID uint64) (p *models.Detail, err error) 12 | } 13 | 14 | type MysqlDetailsRepository struct { 15 | logger *zap.Logger 16 | db *gorm.DB 17 | } 18 | 19 | func NewMysqlDetailsRepository(logger *zap.Logger, db *gorm.DB) DetailsRepository { 20 | return &MysqlDetailsRepository{ 21 | logger: logger.With(zap.String("type", "DetailsRepository")), 22 | db: db, 23 | } 24 | } 25 | 26 | func (s *MysqlDetailsRepository) Get(ID uint64) (p *models.Detail, err error) { 27 | p = new(models.Detail) 28 | if err = s.db.Model(p).Where("id = ?", ID).First(p).Error; err != nil { 29 | return nil, errors.Wrapf(err, "get product error[id=%d]", ID) 30 | } 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/details/repositories/details_test.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "flag" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var configFile = flag.String("f", "details.yml", "set config file which viper will loading.") 10 | 11 | func TestDetailsRepository_Get(t *testing.T) { 12 | flag.Parse() 13 | 14 | sto, err := CreateDetailRepository(*configFile) 15 | if err != nil { 16 | t.Fatalf("create product Repository error,%+v", err) 17 | } 18 | 19 | tests := []struct { 20 | name string 21 | id uint64 22 | expected bool 23 | }{ 24 | {"id=1", 1, true}, 25 | {"id=2", 2, true}, 26 | {"id=3", 3, true}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | _, err := sto.Get(test.id) 32 | 33 | if test.expected { 34 | assert.NoError(t, err ) 35 | }else { 36 | assert.Error(t, err) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/app/details/repositories/repositories.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/google/wire" 5 | ) 6 | 7 | var ProviderSet = wire.NewSet(NewMysqlDetailsRepository) 8 | //var MockProviderSet = wire.NewSet(wire.InterfaceValue(new(DetailsRepository),new(MockDetailsRepository))) 9 | -------------------------------------------------------------------------------- /internal/app/details/repositories/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package repositories 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | ) 11 | 12 | 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | ProviderSet, 19 | ) 20 | 21 | func CreateDetailRepository(f string) (DetailsRepository, error) { 22 | panic(wire.Build(testProviderSet)) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /internal/app/details/repositories/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package repositories 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 13 | ) 14 | 15 | // Injectors from wire.go: 16 | 17 | func CreateDetailRepository(f string) (DetailsRepository, error) { 18 | viper, err := config.New(f) 19 | if err != nil { 20 | return nil, err 21 | } 22 | options, err := log.NewOptions(viper) 23 | if err != nil { 24 | return nil, err 25 | } 26 | logger, err := log.New(options) 27 | if err != nil { 28 | return nil, err 29 | } 30 | databaseOptions, err := database.NewOptions(viper, logger) 31 | if err != nil { 32 | return nil, err 33 | } 34 | db, err := database.New(databaseOptions) 35 | if err != nil { 36 | return nil, err 37 | } 38 | detailsRepository := NewMysqlDetailsRepository(logger, db) 39 | return detailsRepository, nil 40 | } 41 | 42 | // wire.go: 43 | 44 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 45 | -------------------------------------------------------------------------------- /internal/app/details/services/details.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go.uber.org/zap" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 8 | ) 9 | 10 | type DetailsService interface { 11 | Get(ID uint64) (*models.Detail, error) 12 | } 13 | 14 | type DefaultDetailsService struct { 15 | logger *zap.Logger 16 | Repository repositories.DetailsRepository 17 | } 18 | 19 | func NewDetailService(logger *zap.Logger, Repository repositories.DetailsRepository) DetailsService { 20 | return &DefaultDetailsService{ 21 | logger: logger.With(zap.String("type","DefaultDetailsService")), 22 | Repository: Repository, 23 | } 24 | } 25 | 26 | func (s *DefaultDetailsService) Get(ID uint64) (p *models.Detail, err error) { 27 | if p, err = s.Repository.Get(ID); err != nil { 28 | return nil, errors.Wrap(err, "detail service get detail error") 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/details/services/details_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "flag" 5 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 6 | "github.com/sdgmf/go-project-sample/mocks" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/mock" 9 | "testing" 10 | ) 11 | 12 | var configFile = flag.String("f", "details.yml", "set config file which viper will loading.") 13 | 14 | func TestDetailsRepository_Get(t *testing.T) { 15 | flag.Parse() 16 | 17 | sto := new(mocks.DetailsRepository) 18 | 19 | sto.On("Get", mock.AnythingOfType("uint64")).Return(func(ID uint64) (p *models.Detail) { 20 | return &models.Detail{ 21 | ID: ID, 22 | } 23 | }, func(ID uint64) error { 24 | return nil 25 | }) 26 | 27 | svc, err := CreateDetailsService(*configFile, sto) 28 | if err != nil { 29 | t.Fatalf("create product serviceerror,%+v", err) 30 | } 31 | 32 | // 表格驱动测试 33 | tests := []struct { 34 | name string 35 | id uint64 36 | expected uint64 37 | }{ 38 | {"1+1", 1, 1}, 39 | {"2+3", 2, 2}, 40 | {"4+5", 3, 3}, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | p, err := svc.Get(test.id) 46 | if err != nil { 47 | t.Fatalf("product service get proudct error,%+v", err) 48 | } 49 | 50 | assert.Equal(t, test.expected, p.ID) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/details/services/services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewDetailService) 6 | -------------------------------------------------------------------------------- /internal/app/details/services/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package services 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateDetailsService(cf string, sto repositories.DetailsRepository) (DetailsService, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } 23 | -------------------------------------------------------------------------------- /internal/app/details/services/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package services 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/details/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateDetailsService(cf string, sto repositories.DetailsRepository) (DetailsService, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | detailsService := NewDetailService(logger, sto) 32 | return detailsService, nil 33 | } 34 | 35 | // wire.go: 36 | 37 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 38 | -------------------------------------------------------------------------------- /internal/app/products/app.go: -------------------------------------------------------------------------------- 1 | package products 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 8 | "github.com/spf13/viper" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type Options struct { 13 | Name string 14 | } 15 | 16 | func NewOptions(v *viper.Viper, logger *zap.Logger) (*Options, error) { 17 | var err error 18 | o := new(Options) 19 | if err = v.UnmarshalKey("app", o); err != nil { 20 | return nil, errors.Wrap(err, "unmarshal app option error") 21 | } 22 | 23 | logger.Info("load application options success") 24 | 25 | return o, err 26 | } 27 | 28 | func NewApp(o *Options, logger *zap.Logger, hs *http.Server) (*app.Application, error) { 29 | a, err := app.New(o.Name, logger, app.HttpServerOption(hs)) 30 | 31 | if err != nil { 32 | return nil, errors.Wrap(err, "new app error") 33 | } 34 | 35 | return a, nil 36 | } 37 | 38 | var ProviderSet = wire.NewSet(NewApp, NewOptions) 39 | -------------------------------------------------------------------------------- /internal/app/products/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/google/wire" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 7 | ) 8 | 9 | func CreateInitControllersFn( 10 | pc *ProductsController, 11 | ) http.InitControllers { 12 | return func(r *gin.Engine) { 13 | r.GET("/product/:id", pc.Get) 14 | } 15 | } 16 | 17 | var ProviderSet = wire.NewSet(NewProductsController, CreateInitControllersFn) 18 | -------------------------------------------------------------------------------- /internal/app/products/controllers/products.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/sdgmf/go-project-sample/internal/app/products/services" 6 | "go.uber.org/zap" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | type ProductsController struct { 12 | logger *zap.Logger 13 | service services.ProductsService 14 | } 15 | 16 | func NewProductsController(logger *zap.Logger, s services.ProductsService) *ProductsController { 17 | return &ProductsController{ 18 | logger: logger, 19 | service: s, 20 | } 21 | } 22 | 23 | func (pc *ProductsController) Get(c *gin.Context) { 24 | ID, err := strconv.ParseUint(c.Param("id"), 10, 64) 25 | if err != nil { 26 | _ = c.AbortWithError(http.StatusBadRequest, err) 27 | return 28 | } 29 | 30 | p, err := pc.service.Get(c.Request.Context(), ID) 31 | if err != nil { 32 | pc.logger.Error("get product by id error", zap.Error(err)) 33 | c.String(http.StatusInternalServerError, "%+v", err) 34 | return 35 | } 36 | 37 | c.JSON(http.StatusOK, p) 38 | } 39 | -------------------------------------------------------------------------------- /internal/app/products/grpcclients/clients.go: -------------------------------------------------------------------------------- 1 | package grpcclients 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewRatingsClient, NewDetailsClient, NewReviewsClient) 6 | -------------------------------------------------------------------------------- /internal/app/products/grpcclients/details.go: -------------------------------------------------------------------------------- 1 | package grpcclients 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | ) 8 | 9 | func NewDetailsClient(client *grpc.Client) (proto.DetailsClient, error) { 10 | conn,err := client.Dial("Details") 11 | if err != nil { 12 | return nil,errors.Wrap(err,"detail client dial error") 13 | } 14 | c := proto.NewDetailsClient(conn) 15 | 16 | return c, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/app/products/grpcclients/ratings.go: -------------------------------------------------------------------------------- 1 | package grpcclients 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | ) 8 | 9 | func NewRatingsClient(client *grpc.Client) (proto.RatingsClient, error) { 10 | conn, err := client.Dial("Ratings") 11 | if err != nil { 12 | return nil, errors.Wrap(err, "detail client dial error") 13 | } 14 | c := proto.NewRatingsClient(conn) 15 | 16 | return c, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/app/products/grpcclients/reviews.go: -------------------------------------------------------------------------------- 1 | package grpcclients 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | ) 8 | 9 | func NewReviewsClient(client *grpc.Client) (proto.ReviewsClient, error) { 10 | conn, err := client.Dial("Reviews") 11 | if err != nil { 12 | return nil, errors.Wrap(err, "detail client dial error") 13 | } 14 | c := proto.NewReviewsClient(conn) 15 | 16 | return c, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/app/products/services/products.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes" 6 | "github.com/pkg/errors" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type ProductsService interface { 13 | Get(c context.Context, ID uint64) (*models.Product, error) 14 | } 15 | 16 | type DefaultProductsService struct { 17 | logger *zap.Logger 18 | detailsSvc proto.DetailsClient 19 | ratingsSvc proto.RatingsClient 20 | reviewsSvc proto.ReviewsClient 21 | } 22 | 23 | func NewProductService(logger *zap.Logger, 24 | detailsSvc proto.DetailsClient, 25 | ratingsSvc proto.RatingsClient, 26 | reviewsSvc proto.ReviewsClient) ProductsService { 27 | return &DefaultProductsService{ 28 | logger: logger.With(zap.String("type", "DefaultProductsService")), 29 | detailsSvc: detailsSvc, 30 | ratingsSvc: ratingsSvc, 31 | reviewsSvc: reviewsSvc, 32 | } 33 | } 34 | 35 | func (s *DefaultProductsService) Get(c context.Context, productID uint64) (p *models.Product, err error) { 36 | var ( 37 | detail *models.Detail 38 | rating *models.Rating 39 | reviews []*models.Review 40 | ) 41 | 42 | // get detail 43 | { 44 | req := &proto.GetDetailRequest{ 45 | Id: productID, 46 | } 47 | 48 | pd, err := s.detailsSvc.Get(c, req) 49 | if err != nil { 50 | return nil, errors.Wrap(err, "get rating error") 51 | } 52 | ct, err := ptypes.Timestamp(pd.CreatedTime) 53 | 54 | detail = &models.Detail{ 55 | ID: pd.Id, 56 | Price: pd.Price, 57 | CreatedTime: ct, 58 | } 59 | } 60 | 61 | // get rating 62 | { 63 | req := &proto.GetRatingRequest{ 64 | ProductID: productID, 65 | } 66 | 67 | pr, err := s.ratingsSvc.Get(c, req) 68 | if err != nil { 69 | return nil, errors.Wrap(err, "get rating error") 70 | } 71 | ut, err := ptypes.Timestamp(pr.UpdatedTime) 72 | 73 | rating = &models.Rating{ 74 | ID: pr.Id, 75 | Score: pr.Score, 76 | UpdatedTime: ut, 77 | } 78 | } 79 | 80 | // query reviews 81 | { 82 | req := &proto.QueryReviewsRequest{ 83 | ProductID: productID, 84 | } 85 | 86 | resp, err := s.reviewsSvc.Query(c, req) 87 | if err != nil { 88 | return nil, errors.Wrap(err, "get rating error") 89 | } 90 | 91 | reviews = make([]*models.Review, 0, len(resp.Reviews)) 92 | 93 | for _, pr := range resp.Reviews { 94 | ct, err := ptypes.Timestamp(pr.CreatedTime) 95 | if err != nil { 96 | return nil, errors.Wrapf(err, "convert create time error") 97 | } 98 | 99 | r := &models.Review{ 100 | ID: pr.Id, 101 | ProductID: pr.ProductID, 102 | Message: pr.Message, 103 | CreatedTime: ct, 104 | } 105 | 106 | reviews = append(reviews, r) 107 | } 108 | 109 | } 110 | 111 | return &models.Product{ 112 | Detail: detail, 113 | Rating: rating, 114 | Reviews: reviews, 115 | }, nil 116 | } 117 | -------------------------------------------------------------------------------- /internal/app/products/services/products_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/golang/protobuf/ptypes" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/mocks" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/mock" 11 | "google.golang.org/grpc" 12 | "testing" 13 | ) 14 | 15 | var configFile = flag.String("f", "products.bak", "set config file which viper will loading.") 16 | 17 | func TestDefaultProductsService_Get(t *testing.T) { 18 | flag.Parse() 19 | 20 | detailsCli := new(mocks.DetailsClient) 21 | detailsCli.On("Get", mock.Anything, mock.Anything). 22 | Return(func(ctx context.Context, req *proto.GetDetailRequest, cos ...grpc.CallOption) *proto.Detail { 23 | return &proto.Detail{ 24 | Id: req.Id, 25 | CreatedTime: ptypes.TimestampNow(), 26 | } 27 | }, func(ctx context.Context, req *proto.GetDetailRequest, cos ...grpc.CallOption) error { 28 | return nil 29 | }) 30 | 31 | ratingsCli := new(mocks.RatingsClient) 32 | 33 | ratingsCli.On("Get", context.Background(), mock.AnythingOfType("*proto.GetRatingRequest")). 34 | Return(func(ctx context.Context, req *proto.GetRatingRequest, cos ...grpc.CallOption) *proto.Rating { 35 | return &proto.Rating{ 36 | Id: req.ProductID, 37 | UpdatedTime: ptypes.TimestampNow(), 38 | } 39 | }, func(ctx context.Context, req *proto.GetRatingRequest, cos ...grpc.CallOption) error { 40 | return nil 41 | }) 42 | 43 | reviewsCli := new(mocks.ReviewsClient) 44 | 45 | reviewsCli.On("Query", context.Background(), mock.AnythingOfType("*proto.QueryReviewsRequest")). 46 | Return(func(ctx context.Context, req *proto.QueryReviewsRequest, cos ...grpc.CallOption) *proto.QueryReviewsResponse { 47 | return &proto.QueryReviewsResponse{ 48 | Reviews: []*proto.Review{ 49 | &proto.Review{ 50 | Id: req.ProductID, 51 | CreatedTime: ptypes.TimestampNow(), 52 | }, 53 | }, 54 | } 55 | }, func(ctx context.Context, req *proto.QueryReviewsRequest, cos ...grpc.CallOption) error { 56 | return nil 57 | }) 58 | 59 | svc, err := CreateProductsService(*configFile, detailsCli, ratingsCli, reviewsCli) 60 | if err != nil { 61 | t.Fatalf("create product service error,%+v", err) 62 | } 63 | 64 | // 表格驱动测试 65 | tests := []struct { 66 | name string 67 | id uint64 68 | expected bool 69 | }{ 70 | {"id=1", 1, true}, 71 | } 72 | 73 | for _, test := range tests { 74 | t.Run(test.name, func(t *testing.T) { 75 | _, err := svc.Get(context.Background(), test.id) 76 | 77 | if test.expected { 78 | assert.NoError(t, err) 79 | } else { 80 | assert.Error(t, err) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /internal/app/products/services/services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewProductService) 6 | -------------------------------------------------------------------------------- /internal/app/products/services/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package services 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | ) 11 | 12 | var testProviderSet = wire.NewSet( 13 | log.ProviderSet, 14 | config.ProviderSet, 15 | ProviderSet, 16 | ) 17 | 18 | func CreateProductsService(cf string, 19 | detailsSvc proto.DetailsClient, 20 | ratingsSvc proto.RatingsClient, 21 | reviewsSvc proto.ReviewsClient) (ProductsService, error) { 22 | panic(wire.Build(testProviderSet)) 23 | } 24 | -------------------------------------------------------------------------------- /internal/app/products/services/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package services 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/api/proto" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 13 | ) 14 | 15 | // Injectors from wire.go: 16 | 17 | func CreateProductsService(cf string, detailsSvc proto.DetailsClient, ratingsSvc proto.RatingsClient, reviewsSvc proto.ReviewsClient) (ProductsService, error) { 18 | viper, err := config.New(cf) 19 | if err != nil { 20 | return nil, err 21 | } 22 | options, err := log.NewOptions(viper) 23 | if err != nil { 24 | return nil, err 25 | } 26 | logger, err := log.New(options) 27 | if err != nil { 28 | return nil, err 29 | } 30 | productsService := NewProductService(logger, detailsSvc, ratingsSvc, reviewsSvc) 31 | return productsService, nil 32 | } 33 | 34 | // wire.go: 35 | 36 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, ProviderSet) 37 | -------------------------------------------------------------------------------- /internal/app/ratings/app.go: -------------------------------------------------------------------------------- 1 | package ratings 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 9 | "github.com/spf13/viper" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type Options struct { 14 | Name string 15 | } 16 | 17 | func NewOptions(v *viper.Viper, logger *zap.Logger) (*Options, error) { 18 | var err error 19 | o := new(Options) 20 | if err = v.UnmarshalKey("app", o); err != nil { 21 | return nil, errors.Wrap(err, "unmarshal app option error") 22 | } 23 | 24 | logger.Info("load application options success") 25 | 26 | return o, err 27 | } 28 | 29 | func NewApp(o *Options, logger *zap.Logger, hs *http.Server, gs *grpc.Server) (*app.Application, error) { 30 | a, err := app.New(o.Name, logger, app.GrpcServerOption(gs), app.HttpServerOption(hs)) 31 | 32 | if err != nil { 33 | return nil, errors.Wrap(err, "new app error") 34 | } 35 | 36 | return a, nil 37 | } 38 | 39 | var ProviderSet = wire.NewSet(NewApp, NewOptions) 40 | -------------------------------------------------------------------------------- /internal/app/ratings/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/google/wire" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 7 | ) 8 | 9 | func CreateInitControllersFn( 10 | pc *RatingsController, 11 | ) http.InitControllers { 12 | return func(r *gin.Engine) { 13 | r.GET("/rating/:productID", pc.Get) 14 | } 15 | } 16 | 17 | var ProviderSet = wire.NewSet(NewRatingsController, CreateInitControllersFn) 18 | -------------------------------------------------------------------------------- /internal/app/ratings/controllers/ratings.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "go.uber.org/zap" 6 | "net/http" 7 | "strconv" 8 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 9 | ) 10 | 11 | type RatingsController struct { 12 | logger *zap.Logger 13 | service services.RatingsService 14 | } 15 | 16 | func NewRatingsController(logger *zap.Logger, s services.RatingsService) *RatingsController { 17 | return &RatingsController{ 18 | logger: logger, 19 | service: s, 20 | } 21 | } 22 | 23 | func (pc *RatingsController) Get(c *gin.Context) { 24 | ID, err := strconv.ParseUint(c.Param("productID"), 10, 64) 25 | if err != nil { 26 | _ = c.AbortWithError(http.StatusBadRequest, err) 27 | return 28 | } 29 | 30 | p, err := pc.service.Get(ID) 31 | if err != nil { 32 | pc.logger.Error("get rating by productID error", zap.Error(err)) 33 | c.String(http.StatusInternalServerError,"%+v", err) 34 | return 35 | } 36 | 37 | c.JSON(http.StatusOK, p) 38 | } 39 | -------------------------------------------------------------------------------- /internal/app/ratings/controllers/ratings_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 9 | "github.com/sdgmf/go-project-sample/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/mock" 12 | "io/ioutil" 13 | "net/http/httptest" 14 | "testing" 15 | ) 16 | 17 | var r *gin.Engine 18 | var configFile = flag.String("f", "ratings.yml", "set config file which viper will loading.") 19 | 20 | func setup() { 21 | r = gin.New() 22 | } 23 | 24 | func TestRatingsController_Get(t *testing.T) { 25 | flag.Parse() 26 | setup() 27 | 28 | sto := new(mocks.RatingsRepository) 29 | 30 | sto.On("Get", mock.AnythingOfType("uint64")).Return(func(productID uint64) (p *models.Rating) { 31 | return &models.Rating{ 32 | ProductID: productID, 33 | } 34 | }, func(ID uint64) error { 35 | return nil 36 | }) 37 | 38 | c, err := CreateRatingsController(*configFile, sto) 39 | if err != nil { 40 | t.Fatalf("create product serviceerror,%+v", err) 41 | } 42 | 43 | r.GET("/rating/:productID", c.Get) 44 | 45 | tests := []struct { 46 | name string 47 | id uint64 48 | expected uint64 49 | }{ 50 | {"id=1", 1, 1}, 51 | {"id=2", 2, 2}, 52 | {"id=3", 3, 3}, 53 | } 54 | 55 | for _, test := range tests { 56 | t.Run(test.name, func(t *testing.T) { 57 | uri := fmt.Sprintf("/rating/%d", test.id) 58 | // 构造get请求 59 | req := httptest.NewRequest("GET", uri, nil) 60 | // 初始化响应 61 | w := httptest.NewRecorder() 62 | 63 | // 调用相应的controller接口 64 | r.ServeHTTP(w, req) 65 | 66 | // 提取响应 67 | rs := w.Result() 68 | defer func() { 69 | _ = rs.Body.Close() 70 | }() 71 | 72 | // 读取响应body 73 | body, _ := ioutil.ReadAll(rs.Body) 74 | r := new(models.Rating) 75 | err := json.Unmarshal(body, r) 76 | if err != nil { 77 | t.Errorf("unmarshal response body error:%v", err) 78 | } 79 | 80 | assert.Equal(t, test.expected, r.ProductID) 81 | }) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /internal/app/ratings/controllers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package controllers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 12 | ) 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | services.ProviderSet, 19 | //repositories.ProviderSet, 20 | ProviderSet, 21 | ) 22 | 23 | 24 | func CreateRatingsController(cf string, sto repositories.RatingsRepository) (*RatingsController, error) { 25 | panic(wire.Build(testProviderSet)) 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/ratings/controllers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package controllers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 15 | ) 16 | 17 | // Injectors from wire.go: 18 | 19 | func CreateRatingsController(cf string, sto repositories.RatingsRepository) (*RatingsController, error) { 20 | viper, err := config.New(cf) 21 | if err != nil { 22 | return nil, err 23 | } 24 | options, err := log.NewOptions(viper) 25 | if err != nil { 26 | return nil, err 27 | } 28 | logger, err := log.New(options) 29 | if err != nil { 30 | return nil, err 31 | } 32 | ratingsService := services.NewRatingService(logger, sto) 33 | ratingsController := NewRatingsController(logger, ratingsService) 34 | return ratingsController, nil 35 | } 36 | 37 | // wire.go: 38 | 39 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, ProviderSet) 40 | -------------------------------------------------------------------------------- /internal/app/ratings/grpcservers/rating.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes" 6 | "github.com/pkg/errors" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type RatingsServer struct { 13 | logger *zap.Logger 14 | service services.RatingsService 15 | } 16 | 17 | func NewRatingsServer(logger *zap.Logger, ps services.RatingsService) (*RatingsServer, error) { 18 | return &RatingsServer{ 19 | logger: logger, 20 | service: ps, 21 | }, nil 22 | } 23 | 24 | func (s *RatingsServer) Get(ctx context.Context, req *proto.GetRatingRequest) (*proto.Rating, error) { 25 | r, err := s.service.Get(req.ProductID) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "product grpc service get rating error") 28 | } 29 | ut, err := ptypes.TimestampProto(r.UpdatedTime) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "convert create time error") 32 | } 33 | 34 | resp := &proto.Rating{ 35 | Id: uint64(r.ID), 36 | ProductID: r.ProductID, 37 | Score: r.Score, 38 | UpdatedTime: ut, 39 | } 40 | 41 | return resp, nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/app/ratings/grpcservers/rating_test.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/sdgmf/go-project-sample/api/proto" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 8 | "github.com/sdgmf/go-project-sample/mocks" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/mock" 11 | "testing" 12 | ) 13 | 14 | var configFile = flag.String("f", "ratings.yml", "set config file which viper will loading.") 15 | 16 | func TestRatingsServer_Get(t *testing.T) { 17 | flag.Parse() 18 | 19 | service := new(mocks.RatingsService) 20 | 21 | service.On("Get", mock.AnythingOfType("uint64")).Return(func(productID uint64) (p *models.Rating) { 22 | return &models.Rating{ 23 | ProductID: productID, 24 | } 25 | }, func(ID uint64) error { 26 | return nil 27 | }) 28 | 29 | server, err := CreateRatingsServer(*configFile, service) 30 | if err != nil { 31 | t.Fatalf("create product server error,%+v", err) 32 | } 33 | 34 | // 表格驱动测试 35 | tests := []struct { 36 | name string 37 | id uint64 38 | expected uint64 39 | }{ 40 | {"id=1", 1, 1}, 41 | {"id=2", 2, 2}, 42 | {"id=3", 3, 3}, 43 | } 44 | 45 | for _, test := range tests { 46 | t.Run(test.name, func(t *testing.T) { 47 | req := &proto.GetRatingRequest{ 48 | ProductID: test.id, 49 | } 50 | r, err := server.Get(context.Background(), req) 51 | if err != nil { 52 | t.Fatalf("get detail error,%+v", err) 53 | } 54 | 55 | assert.Equal(t, test.expected, r.ProductID) 56 | }) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /internal/app/ratings/grpcservers/servers.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | stdgrpc "google.golang.org/grpc" 8 | ) 9 | 10 | 11 | 12 | func CreateInitServersFn( 13 | ps *RatingsServer, 14 | ) grpc.InitServers { 15 | return func(s *stdgrpc.Server) { 16 | proto.RegisterRatingsServer(s, ps) 17 | } 18 | } 19 | 20 | var ProviderSet = wire.NewSet(NewRatingsServer, CreateInitServersFn) -------------------------------------------------------------------------------- /internal/app/ratings/grpcservers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package grpcservers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateRatingsServer(cf string, service services.RatingsService) (*RatingsServer, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } -------------------------------------------------------------------------------- /internal/app/ratings/grpcservers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package grpcservers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/services" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateRatingsServer(cf string, service services.RatingsService) (*RatingsServer, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | ratingsServer, err := NewRatingsServer(logger, service) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return ratingsServer, nil 36 | } 37 | 38 | // wire.go: 39 | 40 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 41 | -------------------------------------------------------------------------------- /internal/app/ratings/repositories/ratings.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type RatingsRepository interface { 11 | Get(productID uint64) (p *models.Rating, err error) 12 | } 13 | 14 | type MysqlRatingsRepository struct { 15 | logger *zap.Logger 16 | db *gorm.DB 17 | } 18 | 19 | func NewMysqlRatingsRepository(logger *zap.Logger, db *gorm.DB) RatingsRepository { 20 | return &MysqlRatingsRepository{ 21 | logger: logger.With(zap.String("type", "RatingsRepository")), 22 | db: db, 23 | } 24 | } 25 | 26 | func (s *MysqlRatingsRepository) Get(productID uint64) (p *models.Rating, err error) { 27 | p = new(models.Rating) 28 | if err = s.db.Model(p).Where("product_id = ?", productID).First(p).Error; err != nil { 29 | return nil, errors.Wrapf(err, "get rating error[productID=%d]", productID) 30 | } 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/ratings/repositories/ratings_test.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "flag" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var configFile = flag.String("f", "ratings.yml", "set config file which viper will loading.") 10 | 11 | func TestRatingsRepository_Get(t *testing.T) { 12 | flag.Parse() 13 | 14 | sto, err := CreateRatingRepository(*configFile) 15 | if err != nil { 16 | t.Fatalf("create product Repository error,%+v", err) 17 | } 18 | 19 | tests := []struct { 20 | name string 21 | id uint64 22 | expected bool 23 | }{ 24 | {"1+1", 1, true}, 25 | {"2+3", 2, true}, 26 | {"4+5", 3, true}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | _, err := sto.Get(test.id) 32 | 33 | if test.expected { 34 | assert.NoError(t, err ) 35 | }else { 36 | assert.Error(t, err) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/app/ratings/repositories/repositories.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/google/wire" 5 | ) 6 | 7 | var ProviderSet = wire.NewSet(NewMysqlRatingsRepository) 8 | //var MockProviderSet = wire.NewSet(wire.InterfaceValue(new(RatingsRepository),new(MockRatingsRepository))) 9 | -------------------------------------------------------------------------------- /internal/app/ratings/repositories/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package repositories 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | ) 11 | 12 | 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | ProviderSet, 19 | ) 20 | 21 | func CreateRatingRepository(f string) (RatingsRepository, error) { 22 | panic(wire.Build(testProviderSet)) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /internal/app/ratings/repositories/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package repositories 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 13 | ) 14 | 15 | // Injectors from wire.go: 16 | 17 | func CreateRatingRepository(f string) (RatingsRepository, error) { 18 | viper, err := config.New(f) 19 | if err != nil { 20 | return nil, err 21 | } 22 | options, err := log.NewOptions(viper) 23 | if err != nil { 24 | return nil, err 25 | } 26 | logger, err := log.New(options) 27 | if err != nil { 28 | return nil, err 29 | } 30 | databaseOptions, err := database.NewOptions(viper, logger) 31 | if err != nil { 32 | return nil, err 33 | } 34 | db, err := database.New(databaseOptions) 35 | if err != nil { 36 | return nil, err 37 | } 38 | ratingsRepository := NewMysqlRatingsRepository(logger, db) 39 | return ratingsRepository, nil 40 | } 41 | 42 | // wire.go: 43 | 44 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 45 | -------------------------------------------------------------------------------- /internal/app/ratings/services/ratings.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type RatingsService interface { 11 | Get(ID uint64) (*models.Rating, error) 12 | } 13 | 14 | type DefaultRatingsService struct { 15 | logger *zap.Logger 16 | Repository repositories.RatingsRepository 17 | } 18 | 19 | func NewRatingService(logger *zap.Logger, Repository repositories.RatingsRepository) RatingsService { 20 | return &DefaultRatingsService{ 21 | logger: logger.With(zap.String("type", "DefaultRatingsService")), 22 | Repository: Repository, 23 | } 24 | } 25 | 26 | func (s *DefaultRatingsService) Get(productID uint64) (p *models.Rating, err error) { 27 | if p, err = s.Repository.Get(productID); err != nil { 28 | return nil, errors.Wrap(err, "get rating error") 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/ratings/services/ratings_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "flag" 5 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 6 | "github.com/sdgmf/go-project-sample/mocks" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/mock" 9 | "testing" 10 | ) 11 | 12 | var configFile = flag.String("f", "ratings.yml", "set config file which viper will loading.") 13 | 14 | func TestDefaultRatingsService_Get(t *testing.T) { 15 | flag.Parse() 16 | 17 | sto := new(mocks.RatingsRepository) 18 | 19 | sto.On("Get", mock.AnythingOfType("uint64")).Return(func(ID uint64) (p *models.Rating) { 20 | return &models.Rating{ 21 | ID: ID, 22 | } 23 | }, func(ID uint64) error { 24 | return nil 25 | }) 26 | 27 | svc, err := CreateRatingsService(*configFile, sto) 28 | if err != nil { 29 | t.Fatalf("create product serviceerror,%+v", err) 30 | } 31 | 32 | // 表格驱动测试 33 | tests := []struct { 34 | name string 35 | id uint64 36 | expected uint64 37 | }{ 38 | {"1+1", 1, 1}, 39 | {"2+3", 2, 2}, 40 | {"4+5", 3, 3}, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | p, err := svc.Get(test.id) 46 | if err != nil { 47 | t.Fatalf("product service get proudct error,%+v", err) 48 | } 49 | 50 | assert.Equal(t, test.expected, p.ID) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/ratings/services/services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewRatingService) 6 | -------------------------------------------------------------------------------- /internal/app/ratings/services/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package services 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateRatingsService(cf string, sto repositories.RatingsRepository) (RatingsService, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } 23 | -------------------------------------------------------------------------------- /internal/app/ratings/services/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package services 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/ratings/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateRatingsService(cf string, sto repositories.RatingsRepository) (RatingsService, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | ratingsService := NewRatingService(logger, sto) 32 | return ratingsService, nil 33 | } 34 | 35 | // wire.go: 36 | 37 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 38 | -------------------------------------------------------------------------------- /internal/app/reviews/app.go: -------------------------------------------------------------------------------- 1 | package reviews 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/app" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 9 | "github.com/spf13/viper" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type Options struct { 14 | Name string 15 | } 16 | 17 | func NewOptions(v *viper.Viper, logger *zap.Logger) (*Options, error) { 18 | var err error 19 | o := new(Options) 20 | if err = v.UnmarshalKey("app", o); err != nil { 21 | return nil, errors.Wrap(err, "unmarshal app option error") 22 | } 23 | 24 | logger.Info("load application options success") 25 | 26 | return o, err 27 | } 28 | 29 | func NewApp(o *Options, logger *zap.Logger, hs *http.Server, gs *grpc.Server) (*app.Application, error) { 30 | a, err := app.New(o.Name, logger, app.GrpcServerOption(gs), app.HttpServerOption(hs)) 31 | 32 | if err != nil { 33 | return nil, errors.Wrap(err, "new app error") 34 | } 35 | 36 | return a, nil 37 | } 38 | 39 | var ProviderSet = wire.NewSet(NewApp, NewOptions) 40 | -------------------------------------------------------------------------------- /internal/app/reviews/controllers/controllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/google/wire" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 7 | ) 8 | 9 | func CreateInitControllersFn( 10 | pc *ReviewsController, 11 | ) http.InitControllers { 12 | return func(r *gin.Engine) { 13 | r.GET("/reviews", pc.Query) 14 | } 15 | } 16 | 17 | var ProviderSet = wire.NewSet(NewReviewsController, CreateInitControllersFn) 18 | -------------------------------------------------------------------------------- /internal/app/reviews/controllers/reviews.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "go.uber.org/zap" 6 | "net/http" 7 | "strconv" 8 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 9 | ) 10 | 11 | type ReviewsController struct { 12 | logger *zap.Logger 13 | service services.ReviewsService 14 | } 15 | 16 | func NewReviewsController(logger *zap.Logger, s services.ReviewsService) *ReviewsController { 17 | return &ReviewsController{ 18 | logger: logger, 19 | service: s, 20 | } 21 | } 22 | 23 | func (pc *ReviewsController) Query(c *gin.Context) { 24 | ID, err := strconv.ParseUint(c.Query("productID"), 10, 64) 25 | if err != nil { 26 | _ = c.AbortWithError(http.StatusBadRequest, err) 27 | return 28 | } 29 | 30 | rs, err := pc.service.Query(ID) 31 | if err != nil { 32 | pc.logger.Error("get review by productID error", zap.Error(err)) 33 | c.String(http.StatusInternalServerError,"%+v", err) 34 | return 35 | } 36 | 37 | c.JSON(http.StatusOK, rs) 38 | } 39 | -------------------------------------------------------------------------------- /internal/app/reviews/controllers/reviews_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 9 | "github.com/sdgmf/go-project-sample/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/mock" 12 | "io/ioutil" 13 | "net/http/httptest" 14 | "testing" 15 | ) 16 | 17 | var r *gin.Engine 18 | var configFile = flag.String("f", "reviews.yml", "set config file which viper will loading.") 19 | 20 | func setup() { 21 | r = gin.New() 22 | } 23 | 24 | func TestReviewsController_Get(t *testing.T) { 25 | flag.Parse() 26 | setup() 27 | 28 | sto := new(mocks.ReviewsRepository) 29 | 30 | sto.On("Query", mock.AnythingOfType("uint64")).Return(func(ID uint64) (p []*models.Review) { 31 | return []*models.Review{&models.Review{ 32 | ID: ID, 33 | }} 34 | }, func(ID uint64) error { 35 | return nil 36 | }) 37 | 38 | c, err := CreateReviewsController(*configFile, sto) 39 | if err != nil { 40 | t.Fatalf("create reviews service error,%+v", err) 41 | } 42 | 43 | r.GET("/reviews", c.Query) 44 | 45 | tests := []struct { 46 | name string 47 | id uint64 48 | expected int 49 | }{ 50 | {"1", 1, 1}, 51 | {"2", 2, 1}, 52 | {"3", 3, 1}, 53 | } 54 | 55 | for _, test := range tests { 56 | t.Run(test.name, func(t *testing.T) { 57 | uri := fmt.Sprintf("/reviews?productID=%d", test.id) 58 | // 构造get请求 59 | req := httptest.NewRequest("GET", uri, nil) 60 | // 初始化响应 61 | w := httptest.NewRecorder() 62 | 63 | // 调用相应的controller接口 64 | r.ServeHTTP(w, req) 65 | 66 | // 提取响应 67 | result := w.Result() 68 | defer func() { 69 | _ = result.Body.Close() 70 | }() 71 | 72 | // 读取响应body 73 | body, _ := ioutil.ReadAll(result.Body) 74 | var rs []*models.Review 75 | err := json.Unmarshal(body, &rs) 76 | if err != nil { 77 | t.Errorf("unmarshal response body error:%v", err) 78 | } 79 | 80 | assert.Equal(t, test.expected, len(rs)) 81 | }) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /internal/app/reviews/controllers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package controllers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 11 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 12 | ) 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | services.ProviderSet, 19 | //repositories.ProviderSet, 20 | ProviderSet, 21 | ) 22 | 23 | 24 | func CreateReviewsController(cf string, sto repositories.ReviewsRepository) (*ReviewsController, error) { 25 | panic(wire.Build(testProviderSet)) 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/reviews/controllers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package controllers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 15 | ) 16 | 17 | // Injectors from wire.go: 18 | 19 | func CreateReviewsController(cf string, sto repositories.ReviewsRepository) (*ReviewsController, error) { 20 | viper, err := config.New(cf) 21 | if err != nil { 22 | return nil, err 23 | } 24 | options, err := log.NewOptions(viper) 25 | if err != nil { 26 | return nil, err 27 | } 28 | logger, err := log.New(options) 29 | if err != nil { 30 | return nil, err 31 | } 32 | reviewsService := services.NewReviewService(logger, sto) 33 | reviewsController := NewReviewsController(logger, reviewsService) 34 | return reviewsController, nil 35 | } 36 | 37 | // wire.go: 38 | 39 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, services.ProviderSet, ProviderSet) 40 | -------------------------------------------------------------------------------- /internal/app/reviews/grpcservers/reviews.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes" 6 | "github.com/pkg/errors" 7 | "github.com/sdgmf/go-project-sample/api/proto" 8 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type ReviewsServer struct { 13 | logger *zap.Logger 14 | service services.ReviewsService 15 | } 16 | 17 | func NewReviewsServer(logger *zap.Logger, ps services.ReviewsService) (*ReviewsServer, error) { 18 | return &ReviewsServer{ 19 | logger: logger, 20 | service: ps, 21 | }, nil 22 | } 23 | 24 | func (s *ReviewsServer) Query(ctx context.Context, req *proto.QueryReviewsRequest) (*proto.QueryReviewsResponse, error) { 25 | rs, err := s.service.Query(req.ProductID) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "reviews grpc service get reviews error") 28 | } 29 | 30 | resp := &proto.QueryReviewsResponse{ 31 | Reviews: make([]*proto.Review, 0, len(rs)), 32 | } 33 | for _, r := range rs { 34 | ct, err := ptypes.TimestampProto(r.CreatedTime) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "convert create time error") 37 | } 38 | 39 | pr := &proto.Review{ 40 | Id: uint64(r.ID), 41 | ProductID: r.ProductID, 42 | Message: r.Message, 43 | CreatedTime: ct, 44 | } 45 | 46 | resp.Reviews = append(resp.Reviews, pr) 47 | } 48 | 49 | return resp, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/app/reviews/grpcservers/reviews_test.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/sdgmf/go-project-sample/api/proto" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 8 | "github.com/sdgmf/go-project-sample/mocks" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/mock" 11 | "testing" 12 | ) 13 | 14 | var configFile = flag.String("f", "reviews.yml", "set config file which viper will loading.") 15 | 16 | func TestReviewsServer_Query(t *testing.T) { 17 | flag.Parse() 18 | 19 | service := new(mocks.ReviewsService) 20 | 21 | service.On("Query", mock.AnythingOfType("uint64")).Return(func(proudctID uint64) (p []*models.Review) { 22 | return []*models.Review{&models.Review{ 23 | ProductID: proudctID, 24 | }} 25 | }, func(ID uint64) error { 26 | return nil 27 | }) 28 | 29 | server, err := CreateReviewsServer(*configFile, service) 30 | if err != nil { 31 | t.Fatalf("create product server error,%+v", err) 32 | } 33 | 34 | // 表格驱动测试 35 | tests := []struct { 36 | name string 37 | id uint64 38 | expected int 39 | }{ 40 | {"productID=1", 1, 1}, 41 | {"productID=2", 2, 1}, 42 | {"productID=3", 3, 1}, 43 | } 44 | 45 | for _, test := range tests { 46 | t.Run(test.name, func(t *testing.T) { 47 | req := &proto.QueryReviewsRequest{ 48 | ProductID: test.id, 49 | } 50 | rs, err := server.Query(context.Background(), req) 51 | if err != nil { 52 | t.Fatalf("product service get proudct error,%+v", err) 53 | } 54 | 55 | assert.Equal(t, test.expected, len(rs.Reviews)) 56 | }) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /internal/app/reviews/grpcservers/servers.go: -------------------------------------------------------------------------------- 1 | package grpcservers 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/sdgmf/go-project-sample/api/proto" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 7 | stdgrpc "google.golang.org/grpc" 8 | ) 9 | 10 | 11 | 12 | func CreateInitServersFn( 13 | ps *ReviewsServer, 14 | ) grpc.InitServers { 15 | return func(s *stdgrpc.Server) { 16 | proto.RegisterReviewsServer(s, ps) 17 | } 18 | } 19 | 20 | var ProviderSet = wire.NewSet(NewReviewsServer, CreateInitServersFn) -------------------------------------------------------------------------------- /internal/app/reviews/grpcservers/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package grpcservers 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateReviewsServer(cf string, service services.ReviewsService) (*ReviewsServer, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } -------------------------------------------------------------------------------- /internal/app/reviews/grpcservers/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package grpcservers 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/services" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateReviewsServer(cf string, service services.ReviewsService) (*ReviewsServer, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | reviewsServer, err := NewReviewsServer(logger, service) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return reviewsServer, nil 36 | } 37 | 38 | // wire.go: 39 | 40 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 41 | -------------------------------------------------------------------------------- /internal/app/reviews/repositories/repositories.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/google/wire" 5 | ) 6 | 7 | var ProviderSet = wire.NewSet(NewMysqlReviewsRepository) 8 | //var MockProviderSet = wire.NewSet(wire.InterfaceValue(new(ReviewsRepository),new(MockReviewsRepository))) 9 | -------------------------------------------------------------------------------- /internal/app/reviews/repositories/reviews.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | "github.com/pkg/errors" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type ReviewsRepository interface { 11 | Query(productID uint64) (p []*models.Review, err error) 12 | } 13 | 14 | type MysqlReviewsRepository struct { 15 | logger *zap.Logger 16 | db *gorm.DB 17 | } 18 | 19 | func NewMysqlReviewsRepository(logger *zap.Logger, db *gorm.DB) ReviewsRepository { 20 | return &MysqlReviewsRepository{ 21 | logger: logger.With(zap.String("type", "ReviewsRepository")), 22 | db: db, 23 | } 24 | } 25 | 26 | func (s *MysqlReviewsRepository) Query(productID uint64) (rs []*models.Review, err error) { 27 | if err = s.db.Table("reviews").Where("product_id = ?", productID).Find(&rs).Error; err != nil { 28 | return nil, errors.Wrapf(err, "get review error[productID=%d]", productID) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /internal/app/reviews/repositories/reviews_test.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "flag" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | var configFile = flag.String("f", "reviews.yml", "set config file which viper will loading.") 10 | 11 | func TestReviewsRepository_Get(t *testing.T) { 12 | flag.Parse() 13 | 14 | sto, err := CreateReviewRepository(*configFile) 15 | if err != nil { 16 | t.Fatalf("create reviews Repository error,%+v", err) 17 | } 18 | 19 | tests := []struct { 20 | name string 21 | id uint64 22 | expected int 23 | }{ 24 | {"id=1", 1, 1}, 25 | {"id=2", 2, 1}, 26 | {"id=3", 3, 1}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | rs, err := sto.Query(test.id) 32 | if err != nil { 33 | t.Errorf("query reviews error:%+v", err) 34 | } 35 | 36 | assert.Equal(t, test.expected, len(rs)) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/app/reviews/repositories/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package repositories 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | ) 11 | 12 | 13 | 14 | var testProviderSet = wire.NewSet( 15 | log.ProviderSet, 16 | config.ProviderSet, 17 | database.ProviderSet, 18 | ProviderSet, 19 | ) 20 | 21 | func CreateReviewRepository(f string) (ReviewsRepository, error) { 22 | panic(wire.Build(testProviderSet)) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /internal/app/reviews/repositories/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package repositories 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 13 | ) 14 | 15 | // Injectors from wire.go: 16 | 17 | func CreateReviewRepository(f string) (ReviewsRepository, error) { 18 | viper, err := config.New(f) 19 | if err != nil { 20 | return nil, err 21 | } 22 | options, err := log.NewOptions(viper) 23 | if err != nil { 24 | return nil, err 25 | } 26 | logger, err := log.New(options) 27 | if err != nil { 28 | return nil, err 29 | } 30 | databaseOptions, err := database.NewOptions(viper, logger) 31 | if err != nil { 32 | return nil, err 33 | } 34 | db, err := database.New(databaseOptions) 35 | if err != nil { 36 | return nil, err 37 | } 38 | reviewsRepository := NewMysqlReviewsRepository(logger, db) 39 | return reviewsRepository, nil 40 | } 41 | 42 | // wire.go: 43 | 44 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 45 | -------------------------------------------------------------------------------- /internal/app/reviews/services/reviews.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 6 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type ReviewsService interface { 11 | Query(productID uint64) ([]*models.Review, error) 12 | } 13 | 14 | type DefaultReviewsService struct { 15 | logger *zap.Logger 16 | Repository repositories.ReviewsRepository 17 | } 18 | 19 | func NewReviewService(logger *zap.Logger, Repository repositories.ReviewsRepository) ReviewsService { 20 | return &DefaultReviewsService{ 21 | logger: logger.With(zap.String("type", "DefaultReviewsService")), 22 | Repository: Repository, 23 | } 24 | } 25 | 26 | func (s *DefaultReviewsService) Query(productID uint64) (rs []*models.Review, err error) { 27 | if rs, err = s.Repository.Query(productID); err != nil { 28 | return nil, errors.Wrap(err, "get review error") 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/reviews/services/reviews_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "flag" 5 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 6 | "github.com/sdgmf/go-project-sample/mocks" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/mock" 9 | "testing" 10 | ) 11 | 12 | var configFile = flag.String("f", "reviews.yml", "set config file which viper will loading.") 13 | 14 | func TestReviewsService_Query(t *testing.T) { 15 | flag.Parse() 16 | 17 | sto := new(mocks.ReviewsRepository) 18 | 19 | sto.On("Query", mock.AnythingOfType("uint64")).Return(func(productID uint64) (p []*models.Review) { 20 | return []*models.Review{&models.Review{ 21 | ProductID: productID, 22 | }} 23 | }, func(ID uint64) error { 24 | return nil 25 | }) 26 | 27 | svc, err := CreateReviewsService(*configFile, sto) 28 | if err != nil { 29 | t.Fatalf("create reviews service error,%+v", err) 30 | } 31 | 32 | // 表格驱动测试 33 | tests := []struct { 34 | name string 35 | id uint64 36 | expected int 37 | }{ 38 | {"id=1", 1, 1}, 39 | {"id=2", 2, 1}, 40 | {"id=3", 3, 1}, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | rs, err := svc.Query(test.id) 46 | if err != nil { 47 | t.Fatalf("product service get proudct error,%+v", err) 48 | } 49 | 50 | assert.Equal(t, test.expected, len(rs)) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/reviews/services/services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import "github.com/google/wire" 4 | 5 | var ProviderSet = wire.NewSet(NewReviewService) 6 | -------------------------------------------------------------------------------- /internal/app/reviews/services/wire.go: -------------------------------------------------------------------------------- 1 | // +build wireinject 2 | 3 | package services 4 | 5 | import ( 6 | "github.com/google/wire" 7 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 8 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 9 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 11 | ) 12 | 13 | var testProviderSet = wire.NewSet( 14 | log.ProviderSet, 15 | config.ProviderSet, 16 | database.ProviderSet, 17 | ProviderSet, 18 | ) 19 | 20 | func CreateReviewsService(cf string, sto repositories.ReviewsRepository) (ReviewsService, error) { 21 | panic(wire.Build(testProviderSet)) 22 | } 23 | -------------------------------------------------------------------------------- /internal/app/reviews/services/wire_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by Wire. DO NOT EDIT. 2 | 3 | //go:generate wire 4 | //+build !wireinject 5 | 6 | package services 7 | 8 | import ( 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/app/reviews/repositories" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/config" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/database" 13 | "github.com/sdgmf/go-project-sample/internal/pkg/log" 14 | ) 15 | 16 | // Injectors from wire.go: 17 | 18 | func CreateReviewsService(cf string, sto repositories.ReviewsRepository) (ReviewsService, error) { 19 | viper, err := config.New(cf) 20 | if err != nil { 21 | return nil, err 22 | } 23 | options, err := log.NewOptions(viper) 24 | if err != nil { 25 | return nil, err 26 | } 27 | logger, err := log.New(options) 28 | if err != nil { 29 | return nil, err 30 | } 31 | reviewsService := NewReviewService(logger, sto) 32 | return reviewsService, nil 33 | } 34 | 35 | // wire.go: 36 | 37 | var testProviderSet = wire.NewSet(log.ProviderSet, config.ProviderSet, database.ProviderSet, ProviderSet) 38 | -------------------------------------------------------------------------------- /internal/pkg/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/google/wire" 10 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/grpc" 11 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | type Application struct { 16 | name string 17 | logger *zap.Logger 18 | httpServer *http.Server 19 | grpcServer *grpc.Server 20 | } 21 | 22 | type Option func(app *Application) error 23 | 24 | func HttpServerOption(svr *http.Server) Option { 25 | return func(app *Application) error { 26 | svr.Application(app.name) 27 | app.httpServer = svr 28 | 29 | return nil 30 | } 31 | } 32 | 33 | func GrpcServerOption(svr *grpc.Server) Option { 34 | return func(app *Application) error { 35 | svr.Application(app.name) 36 | app.grpcServer = svr 37 | return nil 38 | } 39 | } 40 | 41 | 42 | func New(name string, logger *zap.Logger, options ...Option) (*Application, error) { 43 | app := &Application{ 44 | name: name, 45 | logger: logger.With(zap.String("type", "Application")), 46 | } 47 | 48 | for _, option := range options { 49 | if err := option(app); err != nil { 50 | return nil, err 51 | } 52 | } 53 | 54 | return app, nil 55 | } 56 | 57 | func (a *Application) Start() error { 58 | if a.httpServer != nil { 59 | if err := a.httpServer.Start(); err != nil { 60 | return errors.Wrap(err, "http server start error") 61 | } 62 | } 63 | 64 | if a.grpcServer != nil { 65 | if err := a.grpcServer.Start(); err != nil { 66 | return errors.Wrap(err, "grpc server start error") 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (a *Application) AwaitSignal() { 74 | c := make(chan os.Signal, 1) 75 | signal.Reset(syscall.SIGTERM, syscall.SIGINT) 76 | signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) 77 | select { 78 | case s := <-c: 79 | a.logger.Info("receive a signal", zap.String("signal", s.String())) 80 | if a.httpServer != nil { 81 | if err := a.httpServer.Stop(); err != nil { 82 | a.logger.Warn("stop http server error", zap.Error(err)) 83 | } 84 | } 85 | 86 | if a.grpcServer != nil { 87 | if err := a.grpcServer.Stop(); err != nil { 88 | a.logger.Warn("stop grpc server error", zap.Error(err)) 89 | } 90 | } 91 | 92 | os.Exit(0) 93 | } 94 | } 95 | 96 | var ProviderSet = wire.NewSet(New) 97 | -------------------------------------------------------------------------------- /internal/pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/wire" 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | // Init 初始化viper 10 | func New(path string) (*viper.Viper, error) { 11 | var ( 12 | err error 13 | v = viper.New() 14 | ) 15 | v.AddConfigPath(".") 16 | v.SetConfigFile(string(path)) 17 | 18 | if err := v.ReadInConfig(); err == nil { 19 | fmt.Printf("use config file -> %s\n", v.ConfigFileUsed()) 20 | } else { 21 | return nil, err 22 | } 23 | 24 | return v, err 25 | } 26 | var ProviderSet = wire.NewSet(New) 27 | -------------------------------------------------------------------------------- /internal/pkg/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "github.com/google/wire" 5 | consulApi "github.com/hashicorp/consul/api" 6 | "github.com/pkg/errors" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | type Options struct { 11 | Addr string 12 | } 13 | 14 | func NewOptions(v *viper.Viper) (*Options, error) { 15 | var ( 16 | err error 17 | o = new(Options) 18 | ) 19 | if err = v.UnmarshalKey("consul", o); err != nil { 20 | return nil, errors.Wrapf(err, "viper unmarshal consul options error") 21 | } 22 | 23 | return o, nil 24 | } 25 | 26 | func New(o *Options) (*consulApi.Client, error) { 27 | 28 | // initialize consul 29 | var ( 30 | consulCli *consulApi.Client 31 | err error 32 | ) 33 | 34 | consulCli, err = consulApi.NewClient(&consulApi.Config{ 35 | Address: o.Addr, 36 | }) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "create consul client error") 39 | } 40 | 41 | return consulCli, nil 42 | } 43 | 44 | var ProviderSet = wire.NewSet(New, NewOptions) 45 | -------------------------------------------------------------------------------- /internal/pkg/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/mysql" 7 | "github.com/pkg/errors" 8 | "github.com/spf13/viper" 9 | "go.uber.org/zap" 10 | "github.com/sdgmf/go-project-sample/internal/pkg/models" 11 | ) 12 | 13 | // Options is configuration of database 14 | type Options struct { 15 | URL string `yaml:"url"` 16 | Debug bool 17 | } 18 | 19 | func NewOptions(v *viper.Viper, logger *zap.Logger) (*Options, error) { 20 | var err error 21 | o := new(Options) 22 | if err = v.UnmarshalKey("db", o); err != nil { 23 | return nil, errors.Wrap(err, "unmarshal db option error") 24 | } 25 | 26 | logger.Info("load database options success", zap.String("url", o.URL)) 27 | 28 | return o, err 29 | } 30 | 31 | // Init 初始化数据库 32 | func New(o *Options) (*gorm.DB, error) { 33 | var err error 34 | db, err := gorm.Open("mysql", o.URL) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "gorm open database connection error") 37 | } 38 | 39 | if o.Debug { 40 | db = db.Debug() 41 | } 42 | 43 | db.AutoMigrate(&models.Detail{}) 44 | db.AutoMigrate(&models.Rating{}) 45 | db.AutoMigrate(&models.Review{}) 46 | 47 | return db, nil 48 | } 49 | 50 | var ProviderSet = wire.NewSet(New, NewOptions) 51 | -------------------------------------------------------------------------------- /internal/pkg/jaeger/jaeger.go: -------------------------------------------------------------------------------- 1 | package jaeger 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/opentracing/opentracing-go" 6 | "github.com/pkg/errors" 7 | "github.com/spf13/viper" 8 | "github.com/uber/jaeger-client-go/config" 9 | "github.com/uber/jaeger-lib/metrics/prometheus" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | func NewConfiguration(v *viper.Viper, logger *zap.Logger) (*config.Configuration, error) { 14 | var ( 15 | err error 16 | c = new(config.Configuration) 17 | ) 18 | 19 | if err = v.UnmarshalKey("jaeger", c); err != nil { 20 | return nil, errors.Wrap(err, "unmarshal jaeger configuration error") 21 | } 22 | 23 | logger.Info("load jaeger configuration success") 24 | 25 | return c, nil 26 | } 27 | 28 | func New(c *config.Configuration) (opentracing.Tracer, error) { 29 | 30 | metricsFactory := prometheus.New() 31 | tracer, _, err := c.NewTracer(config.Metrics(metricsFactory)) 32 | 33 | if err != nil { 34 | return nil, errors.Wrap(err, "create jaeger tracer error") 35 | } 36 | 37 | return tracer, nil 38 | } 39 | 40 | var ProviderSet = wire.NewSet(New, NewConfiguration) 41 | -------------------------------------------------------------------------------- /internal/pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/google/wire" 5 | "github.com/spf13/viper" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | "gopkg.in/natefinch/lumberjack.v2" 9 | "os" 10 | ) 11 | 12 | // Options is log configuration struct 13 | type Options struct { 14 | Filename string 15 | MaxSize int 16 | MaxBackups int 17 | MaxAge int 18 | Level string 19 | Stdout bool 20 | } 21 | 22 | func NewOptions(v *viper.Viper) (*Options, error) { 23 | var ( 24 | err error 25 | o = new(Options) 26 | ) 27 | if err = v.UnmarshalKey("log", o); err != nil { 28 | return nil, err 29 | } 30 | 31 | return o, err 32 | } 33 | 34 | // New for init zap log library 35 | func New(o *Options) (*zap.Logger, error) { 36 | var ( 37 | err error 38 | level = zap.NewAtomicLevel() 39 | logger *zap.Logger 40 | ) 41 | 42 | err = level.UnmarshalText([]byte(o.Level)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | fw := zapcore.AddSync(&lumberjack.Logger{ 48 | Filename: o.Filename, 49 | MaxSize: o.MaxSize, // megabytes 50 | MaxBackups: o.MaxBackups, 51 | MaxAge: o.MaxAge, // days 52 | }) 53 | 54 | cw := zapcore.Lock(os.Stdout) 55 | 56 | // file core 采用jsonEncoder 57 | cores := make([]zapcore.Core, 0, 2) 58 | je := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) 59 | cores = append(cores, zapcore.NewCore(je, fw, level)) 60 | 61 | // stdout core 采用 ConsoleEncoder 62 | if o.Stdout { 63 | ce := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) 64 | cores = append(cores, zapcore.NewCore(ce, cw, level)) 65 | } 66 | 67 | core := zapcore.NewTee(cores...) 68 | logger = zap.New(core) 69 | 70 | zap.ReplaceGlobals(logger) 71 | 72 | return logger, err 73 | } 74 | 75 | var ProviderSet = wire.NewSet(New, NewOptions) 76 | -------------------------------------------------------------------------------- /internal/pkg/models/detail.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Detail struct { 6 | ID uint64 `json:"id"` 7 | Name string `json:"name"` 8 | Price float32 `json:"price"` 9 | CreatedTime time.Time `json:"created_time"` 10 | } 11 | -------------------------------------------------------------------------------- /internal/pkg/models/product.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Product struct { 4 | Detail *Detail `json:"detail"` 5 | Rating *Rating `json:"rating"` 6 | Reviews []*Review `json:"reviews"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/pkg/models/rating.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Rating struct { 6 | ID uint64 `json:"id"` 7 | ProductID uint64 `json:"product_id"` 8 | Score uint32 `json:"score"` 9 | UpdatedTime time.Time `json:"updated_time"` 10 | } 11 | -------------------------------------------------------------------------------- /internal/pkg/models/review.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Review struct { 6 | ID uint64 `json:"id"` 7 | ProductID uint64 `json:"product_id"` 8 | Message string `json:"message"` 9 | CreatedTime time.Time `json:"created_time"` 10 | } 11 | -------------------------------------------------------------------------------- /internal/pkg/transports/grpc/client.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/grpc-ecosystem/go-grpc-middleware" 7 | "github.com/grpc-ecosystem/go-grpc-prometheus" 8 | "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" 9 | _ "github.com/mbobakov/grpc-consul-resolver" // It's important 10 | "github.com/opentracing/opentracing-go" 11 | "github.com/pkg/errors" 12 | "github.com/sdgmf/go-project-sample/internal/pkg/consul" 13 | "github.com/spf13/viper" 14 | "google.golang.org/grpc" 15 | "time" 16 | ) 17 | 18 | type ClientOptions struct { 19 | Wait time.Duration 20 | Tag string 21 | GrpcDialOptions []grpc.DialOption 22 | } 23 | 24 | func NewClientOptions(v *viper.Viper, tracer opentracing.Tracer) (*ClientOptions, error) { 25 | var ( 26 | err error 27 | o = new(ClientOptions) 28 | ) 29 | if err = v.UnmarshalKey("grpc.client", o); err != nil { 30 | return nil, err 31 | } 32 | grpc_prometheus.EnableClientHandlingTimeHistogram() 33 | o.GrpcDialOptions = append(o.GrpcDialOptions, 34 | grpc.WithInsecure(), 35 | grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient( 36 | grpc_prometheus.UnaryClientInterceptor, 37 | otgrpc.OpenTracingClientInterceptor(tracer)), 38 | ), 39 | grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient( 40 | grpc_prometheus.StreamClientInterceptor, 41 | otgrpc.OpenTracingStreamClientInterceptor(tracer)), 42 | ), 43 | ) 44 | 45 | return o, nil 46 | } 47 | 48 | type ClientOptional func(o *ClientOptions) 49 | 50 | func WithTimeout(d time.Duration) ClientOptional { 51 | return func(o *ClientOptions) { 52 | o.Wait = d 53 | } 54 | } 55 | 56 | func WithTag(tag string) ClientOptional { 57 | return func(o *ClientOptions) { 58 | o.Tag = tag 59 | } 60 | } 61 | 62 | func WithGrpcDialOptions(options ...grpc.DialOption) ClientOptional { 63 | return func(o *ClientOptions) { 64 | o.GrpcDialOptions = append(o.GrpcDialOptions, options...) 65 | } 66 | } 67 | 68 | type Client struct { 69 | consulOptions *consul.Options 70 | o *ClientOptions 71 | } 72 | 73 | func NewClient(consulOptions *consul.Options, o *ClientOptions) (*Client, error) { 74 | return &Client{ 75 | consulOptions: consulOptions, 76 | o: o, 77 | }, nil 78 | } 79 | 80 | func (c *Client) Dial(service string, options ...ClientOptional) (*grpc.ClientConn, error) { 81 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 82 | defer cancel() 83 | 84 | o := &ClientOptions{ 85 | Wait: c.o.Wait, 86 | Tag: c.o.Tag, 87 | GrpcDialOptions: c.o.GrpcDialOptions, 88 | } 89 | for _, option := range options { 90 | option(o) 91 | } 92 | 93 | target := fmt.Sprintf("consul://%s/%s?wait=%s&tag=%s", c.consulOptions.Addr, service, o.Wait, o.Tag) 94 | 95 | conn, err := grpc.DialContext(ctx, target, o.GrpcDialOptions...) 96 | if err != nil { 97 | return nil, errors.Wrap(err, "grpc dial error") 98 | } 99 | 100 | return conn, nil 101 | } 102 | -------------------------------------------------------------------------------- /internal/pkg/transports/grpc/grpc.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "github.com/google/wire" 5 | ) 6 | 7 | var ProviderSet = wire.NewSet(NewServerOptions, NewServer,NewClientOptions,NewClient) 8 | 9 | -------------------------------------------------------------------------------- /internal/pkg/transports/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/grpc-ecosystem/go-grpc-middleware" 6 | "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" 7 | "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 8 | "github.com/grpc-ecosystem/go-grpc-middleware/tags" 9 | "github.com/grpc-ecosystem/go-grpc-prometheus" 10 | "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" 11 | consulApi "github.com/hashicorp/consul/api" 12 | "github.com/opentracing/opentracing-go" 13 | "github.com/pkg/errors" 14 | "github.com/sdgmf/go-project-sample/internal/pkg/utils/netutil" 15 | "github.com/spf13/viper" 16 | "go.uber.org/zap" 17 | "google.golang.org/grpc" 18 | "log" 19 | "net" 20 | ) 21 | 22 | type ServerOptions struct { 23 | Port int 24 | } 25 | 26 | func NewServerOptions(v *viper.Viper) (*ServerOptions, error) { 27 | var ( 28 | err error 29 | o = new(ServerOptions) 30 | ) 31 | if err = v.UnmarshalKey("grpc", o); err != nil { 32 | return nil, err 33 | } 34 | 35 | return o, nil 36 | } 37 | 38 | type Server struct { 39 | o *ServerOptions 40 | app string 41 | host string 42 | port int 43 | logger *zap.Logger 44 | server *grpc.Server 45 | consulCli *consulApi.Client 46 | } 47 | 48 | 49 | type InitServers func(s *grpc.Server) 50 | 51 | func NewServer(o *ServerOptions, logger *zap.Logger, init InitServers, consulCli *consulApi.Client, tracer opentracing.Tracer) (*Server, error) { 52 | // initialize grpc server 53 | var gs *grpc.Server 54 | logger = logger.With(zap.String("type", "grpc")) 55 | { 56 | grpc_prometheus.EnableHandlingTimeHistogram() 57 | gs = grpc.NewServer( 58 | grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( 59 | grpc_ctxtags.StreamServerInterceptor(), 60 | grpc_prometheus.StreamServerInterceptor, 61 | grpc_zap.StreamServerInterceptor(logger), 62 | grpc_recovery.StreamServerInterceptor(), 63 | otgrpc.OpenTracingStreamServerInterceptor(tracer), 64 | )), 65 | grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( 66 | grpc_ctxtags.UnaryServerInterceptor(), 67 | grpc_prometheus.UnaryServerInterceptor, 68 | grpc_zap.UnaryServerInterceptor(logger), 69 | grpc_recovery.UnaryServerInterceptor(), 70 | otgrpc.OpenTracingServerInterceptor(tracer), 71 | )), 72 | ) 73 | init(gs) 74 | } 75 | 76 | return &Server{ 77 | o: o, 78 | logger: logger.With(zap.String("type", "grpc.Server")), 79 | server: gs, 80 | consulCli: consulCli, 81 | }, nil 82 | } 83 | 84 | func (s *Server) Application(name string) { 85 | s.app = name 86 | } 87 | 88 | func (s *Server) Start() error { 89 | s.port = s.o.Port 90 | if s.port == 0 { 91 | s.port = netutil.GetAvailablePort() 92 | } 93 | 94 | s.host = netutil.GetLocalIP4() 95 | 96 | if s.host == "" { 97 | return errors.New("get local ipv4 error") 98 | } 99 | 100 | addr := fmt.Sprintf("%s:%d", s.host, s.port) 101 | 102 | s.logger.Info("grpc server starting ...", zap.String("addr", addr)) 103 | go func() { 104 | lis, err := net.Listen("tcp", addr) 105 | if err != nil { 106 | log.Fatalf("failed to listen: %v", err) 107 | } 108 | 109 | if err := s.server.Serve(lis); err != nil { 110 | s.logger.Fatal("failed to serve: %v", zap.Error(err)) 111 | } 112 | }() 113 | 114 | if err := s.register(); err != nil { 115 | return errors.Wrap(err, "register grpc server error") 116 | } 117 | 118 | return nil 119 | } 120 | 121 | func (s *Server) register() error { 122 | addr := fmt.Sprintf("%s:%d", s.host, s.port) 123 | 124 | for key, _ := range s.server.GetServiceInfo() { 125 | check := &consulApi.AgentServiceCheck{ 126 | Interval: "10s", 127 | DeregisterCriticalServiceAfter: "60m", 128 | TCP: addr, 129 | } 130 | 131 | id := fmt.Sprintf("%s[%s:%d]", key, s.host, s.port) 132 | 133 | svcReg := &consulApi.AgentServiceRegistration{ 134 | ID: id, 135 | Name: key, 136 | Tags: []string{"grpc"}, 137 | Port: s.port, 138 | Address: s.host, 139 | EnableTagOverride: true, 140 | Check: check, 141 | Checks: nil, 142 | } 143 | 144 | err := s.consulCli.Agent().ServiceRegister(svcReg) 145 | if err != nil { 146 | return errors.Wrap(err, "register service error") 147 | } 148 | s.logger.Info("register grpc service success", zap.String("id", id)) 149 | } 150 | 151 | return nil 152 | } 153 | 154 | func (s *Server) deRegister() error { 155 | for key, _ := range s.server.GetServiceInfo() { 156 | id := fmt.Sprintf("%s[%s:%d]", key, s.host, s.port) 157 | 158 | err := s.consulCli.Agent().ServiceDeregister(id) 159 | if err != nil { 160 | return errors.Wrapf(err, "deregister service error[id=%s]", id) 161 | } 162 | s.logger.Info("deregister service success ", zap.String("id", id)) 163 | } 164 | 165 | return nil 166 | } 167 | 168 | func (s *Server) Stop() error { 169 | s.logger.Info("grpc server stopping ...") 170 | if err := s.deRegister(); err != nil { 171 | return errors.Wrap(err, "deregister grpc server error") 172 | } 173 | s.server.GracefulStop() 174 | return nil 175 | } 176 | -------------------------------------------------------------------------------- /internal/pkg/transports/http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gin-contrib/pprof" 7 | "github.com/gin-contrib/zap" 8 | "github.com/gin-gonic/gin" 9 | "github.com/google/wire" 10 | consulApi "github.com/hashicorp/consul/api" 11 | "github.com/opentracing-contrib/go-gin/ginhttp" 12 | "github.com/opentracing/opentracing-go" 13 | "github.com/pkg/errors" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/sdgmf/go-project-sample/internal/pkg/transports/http/middlewares/ginprom" 16 | "github.com/sdgmf/go-project-sample/internal/pkg/utils/netutil" 17 | "github.com/spf13/viper" 18 | "go.uber.org/zap" 19 | "net/http" 20 | "time" 21 | ) 22 | 23 | type Options struct { 24 | Port int 25 | Mode string 26 | } 27 | 28 | type Server struct { 29 | o *Options 30 | app string 31 | host string 32 | port int 33 | logger *zap.Logger 34 | router *gin.Engine 35 | httpServer http.Server 36 | consulCli *consulApi.Client 37 | } 38 | 39 | func NewOptions(v *viper.Viper) (*Options, error) { 40 | var ( 41 | err error 42 | o = new(Options) 43 | ) 44 | 45 | if err = v.UnmarshalKey("http", o); err != nil { 46 | return nil, err 47 | } 48 | 49 | return o, err 50 | } 51 | 52 | type InitControllers func(r *gin.Engine) 53 | 54 | func NewRouter(o *Options, logger *zap.Logger, init InitControllers, tracer opentracing.Tracer) *gin.Engine { 55 | 56 | // 配置gin 57 | gin.SetMode(o.Mode) 58 | r := gin.New() 59 | 60 | r.Use(gin.Recovery()) // panic之后自动恢复 61 | r.Use(ginzap.Ginzap(logger, time.RFC3339, true)) 62 | r.Use(ginzap.RecoveryWithZap(logger, true)) 63 | r.Use(ginprom.New(r).Middleware()) // 添加prometheus 监控 64 | r.Use(ginhttp.Middleware(tracer)) 65 | 66 | r.GET("/metrics", gin.WrapH(promhttp.Handler())) 67 | pprof.Register(r) 68 | 69 | init(r) 70 | 71 | return r 72 | } 73 | 74 | func New(o *Options, logger *zap.Logger, router *gin.Engine, consulCli *consulApi.Client) (*Server, error) { 75 | var s = &Server{ 76 | logger: logger.With(zap.String("type", "http.Server")), 77 | router: router, 78 | consulCli: consulCli, 79 | o: o, 80 | } 81 | 82 | return s, nil 83 | } 84 | 85 | func (s *Server) Application(name string) { 86 | s.app = name 87 | } 88 | 89 | func (s *Server) Start() error { 90 | s.port = s.o.Port 91 | if s.port == 0 { 92 | s.port = netutil.GetAvailablePort() 93 | } 94 | 95 | s.host = netutil.GetLocalIP4() 96 | 97 | if s.host == "" { 98 | return errors.New("get local ipv4 error") 99 | } 100 | 101 | addr := fmt.Sprintf("%s:%d", s.host, s.port) 102 | 103 | s.httpServer = http.Server{Addr: addr, Handler: s.router} 104 | 105 | s.logger.Info("http server starting ...", zap.String("addr", addr)) 106 | go func() { 107 | if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { 108 | s.logger.Fatal("start http server err", zap.Error(err)) 109 | return 110 | } 111 | }() 112 | 113 | if err := s.register(); err != nil { 114 | return errors.Wrap(err, "register http server error") 115 | } 116 | return nil 117 | } 118 | 119 | func (s *Server) register() error { 120 | addr := fmt.Sprintf("%s:%d", s.host, s.port) 121 | 122 | check := &consulApi.AgentServiceCheck{ 123 | Interval: "10s", 124 | DeregisterCriticalServiceAfter: "60m", 125 | TCP: addr, 126 | } 127 | 128 | id := fmt.Sprintf("%s[%s:%d]", s.app, s.host, s.port) 129 | 130 | svcReg := &consulApi.AgentServiceRegistration{ 131 | ID: id, 132 | Name: string(s.app), 133 | Tags: []string{"http"}, 134 | Port: s.port, 135 | Address: s.host, 136 | EnableTagOverride: true, 137 | Check: check, 138 | Checks: nil, 139 | } 140 | 141 | err := s.consulCli.Agent().ServiceRegister(svcReg) 142 | if err != nil { 143 | return errors.Wrap(err, "register service error") 144 | } 145 | s.logger.Info("register http server success", zap.String("id", id)) 146 | 147 | return nil 148 | } 149 | 150 | func (s *Server) deRegister() error { 151 | id := fmt.Sprintf("%s[%s:%d]", s.app, s.host, s.port) 152 | 153 | err := s.consulCli.Agent().ServiceDeregister(id) 154 | if err != nil { 155 | return errors.Wrapf(err, "deregister service error[key=%s]", id) 156 | } 157 | s.logger.Info("deregister service success ", zap.String("service", id)) 158 | 159 | return nil 160 | } 161 | 162 | func (s *Server) Stop() error { 163 | s.logger.Info("stopping http server") 164 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) // 平滑关闭,等待5秒钟处理 165 | defer cancel() 166 | 167 | if err := s.deRegister(); err != nil { 168 | return errors.Wrap(err, "deregister http server error") 169 | } 170 | 171 | if err := s.httpServer.Shutdown(ctx); err != nil { 172 | return errors.Wrap(err, "shutdown http server error") 173 | } 174 | 175 | return nil 176 | } 177 | 178 | var ProviderSet = wire.NewSet(New, NewRouter, NewOptions) 179 | -------------------------------------------------------------------------------- /internal/pkg/transports/http/middlewares/ginprom/ginprom.go: -------------------------------------------------------------------------------- 1 | package ginprom 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | const ( 13 | metricsPath = "/metrics" 14 | faviconPath = "/favicon.ico" 15 | ) 16 | 17 | var ( 18 | httpHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 19 | Namespace: "http_server", 20 | Name: "requests_seconds", 21 | Help: "Histogram of response latency (seconds) of http handlers.", 22 | }, []string{"method", "code", "uri"}) 23 | ) 24 | 25 | func init() { 26 | prometheus.MustRegister(httpHistogram) 27 | } 28 | 29 | type handlerPath struct { 30 | sync.Map 31 | } 32 | 33 | func (hp *handlerPath) get(handler string) string { 34 | v, ok := hp.Load(handler) 35 | if !ok { 36 | return "" 37 | } 38 | return v.(string) 39 | } 40 | 41 | func (hp *handlerPath) set(ri gin.RouteInfo) { 42 | hp.Store(ri.Handler, ri.Path) 43 | } 44 | 45 | // GinPrometheus struct 46 | type GinPrometheus struct { 47 | engine *gin.Engine 48 | ignored map[string]bool 49 | pathMap *handlerPath 50 | updated bool 51 | } 52 | 53 | // Option 可配置参数 54 | type Option func(*GinPrometheus) 55 | 56 | // Ignore 添加忽略的路径 57 | func Ignore(path ...string) Option { 58 | return func(gp *GinPrometheus) { 59 | for _, p := range path { 60 | gp.ignored[p] = true 61 | } 62 | } 63 | } 64 | 65 | // New 构造器 66 | func New(e *gin.Engine, options ...Option) *GinPrometheus { 67 | // 参数验证 68 | if e == nil { 69 | return nil 70 | } 71 | gp := &GinPrometheus{ 72 | engine: e, 73 | ignored: map[string]bool{ 74 | metricsPath: true, 75 | faviconPath: true, 76 | }, 77 | pathMap: &handlerPath{}, 78 | } 79 | for _, o := range options { 80 | o(gp) 81 | } 82 | return gp 83 | } 84 | 85 | func (gp *GinPrometheus) updatePath() { 86 | gp.updated = true 87 | for _, ri := range gp.engine.Routes() { 88 | gp.pathMap.set(ri) 89 | } 90 | } 91 | 92 | // Middleware 返回中间件 93 | func (gp *GinPrometheus) Middleware() gin.HandlerFunc { 94 | return func(c *gin.Context) { 95 | if !gp.updated { 96 | gp.updatePath() 97 | } 98 | // 把不需要的过滤掉 99 | if gp.ignored[c.Request.URL.String()] == true { 100 | c.Next() 101 | return 102 | } 103 | start := time.Now() 104 | 105 | c.Next() 106 | 107 | httpHistogram.WithLabelValues( 108 | c.Request.Method, 109 | strconv.Itoa(c.Writer.Status()), 110 | gp.pathMap.get(c.HandlerName()), 111 | ).Observe(time.Since(start).Seconds()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internal/pkg/utils/netutil/ip.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | // GetLocalIP4 gets local ip address. 9 | func GetLocalIP4() (ip string) { 10 | interfaces, err := net.Interfaces() 11 | net.InterfaceAddrs() 12 | if err != nil { 13 | return 14 | } 15 | if len(interfaces) == 2 { 16 | for _, face := range interfaces { 17 | if strings.Contains(face.Name, "lo") { 18 | continue 19 | } 20 | addrs, err := face.Addrs() 21 | if err != nil { 22 | return 23 | } 24 | for _, addr := range addrs { 25 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 26 | if ipnet.IP.To4() != nil { 27 | currIP := ipnet.IP.String() 28 | if !strings.Contains(currIP, ":") && currIP != "127.0.0.1" { 29 | ip = currIP 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | for _, face := range interfaces { 37 | if strings.Contains(face.Name, "lo") { 38 | continue 39 | } 40 | addrs, err := face.Addrs() 41 | if err != nil { 42 | return 43 | } 44 | for _, addr := range addrs { 45 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 46 | if ipnet.IP.To4() != nil { 47 | currIP := ipnet.IP.String() 48 | if !strings.Contains(currIP, ":") && currIP != "127.0.0.1" && isIntranetIpv4(currIP) { 49 | ip = currIP 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | return 57 | } 58 | 59 | func isIntranetIpv4(ip string) bool { 60 | if strings.HasPrefix(ip, "192.168.") || 61 | strings.HasPrefix(ip, "169.254.") || 62 | strings.HasPrefix(ip, "172.") || 63 | strings.HasPrefix(ip, "10.30.") || 64 | strings.HasPrefix(ip, "10.31.") { 65 | return true 66 | } 67 | return false 68 | } 69 | -------------------------------------------------------------------------------- /internal/pkg/utils/netutil/port.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import "net" 4 | 5 | // GetAvailablePort returns a port at random 6 | func GetAvailablePort() int { 7 | l, _ := net.Listen("tcp", ":0") // listen on localhost 8 | defer l.Close() 9 | port := l.Addr().(*net.TCPAddr).Port 10 | 11 | return port 12 | } 13 | 14 | -------------------------------------------------------------------------------- /mocks/DetailsClient.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import grpc "google.golang.org/grpc" 7 | import mock "github.com/stretchr/testify/mock" 8 | import proto "github.com/sdgmf/go-project-sample/api/proto" 9 | 10 | // DetailsClient is an autogenerated mock type for the DetailsClient type 11 | type DetailsClient struct { 12 | mock.Mock 13 | } 14 | 15 | // Get provides a mock function with given fields: ctx, in, opts 16 | func (_m *DetailsClient) Get(ctx context.Context, in *proto.GetDetailRequest, opts ...grpc.CallOption) (*proto.Detail, error) { 17 | _va := make([]interface{}, len(opts)) 18 | for _i := range opts { 19 | _va[_i] = opts[_i] 20 | } 21 | var _ca []interface{} 22 | _ca = append(_ca, ctx, in) 23 | _ca = append(_ca, _va...) 24 | ret := _m.Called(_ca...) 25 | 26 | var r0 *proto.Detail 27 | if rf, ok := ret.Get(0).(func(context.Context, *proto.GetDetailRequest, ...grpc.CallOption) *proto.Detail); ok { 28 | r0 = rf(ctx, in, opts...) 29 | } else { 30 | if ret.Get(0) != nil { 31 | r0 = ret.Get(0).(*proto.Detail) 32 | } 33 | } 34 | 35 | var r1 error 36 | if rf, ok := ret.Get(1).(func(context.Context, *proto.GetDetailRequest, ...grpc.CallOption) error); ok { 37 | r1 = rf(ctx, in, opts...) 38 | } else { 39 | r1 = ret.Error(1) 40 | } 41 | 42 | return r0, r1 43 | } 44 | -------------------------------------------------------------------------------- /mocks/DetailsRepository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // DetailsRepository is an autogenerated mock type for the DetailsRepository type 9 | type DetailsRepository struct { 10 | mock.Mock 11 | } 12 | 13 | // Get provides a mock function with given fields: ID 14 | func (_m *DetailsRepository) Get(ID uint64) (*models.Detail, error) { 15 | ret := _m.Called(ID) 16 | 17 | var r0 *models.Detail 18 | if rf, ok := ret.Get(0).(func(uint64) *models.Detail); ok { 19 | r0 = rf(ID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).(*models.Detail) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(ID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /mocks/DetailsServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import mock "github.com/stretchr/testify/mock" 7 | import proto "github.com/sdgmf/go-project-sample/api/proto" 8 | 9 | // DetailsServer is an autogenerated mock type for the DetailsServer type 10 | type DetailsServer struct { 11 | mock.Mock 12 | } 13 | 14 | // Get provides a mock function with given fields: _a0, _a1 15 | func (_m *DetailsServer) Get(_a0 context.Context, _a1 *proto.GetDetailRequest) (*proto.Detail, error) { 16 | ret := _m.Called(_a0, _a1) 17 | 18 | var r0 *proto.Detail 19 | if rf, ok := ret.Get(0).(func(context.Context, *proto.GetDetailRequest) *proto.Detail); ok { 20 | r0 = rf(_a0, _a1) 21 | } else { 22 | if ret.Get(0) != nil { 23 | r0 = ret.Get(0).(*proto.Detail) 24 | } 25 | } 26 | 27 | var r1 error 28 | if rf, ok := ret.Get(1).(func(context.Context, *proto.GetDetailRequest) error); ok { 29 | r1 = rf(_a0, _a1) 30 | } else { 31 | r1 = ret.Error(1) 32 | } 33 | 34 | return r0, r1 35 | } 36 | -------------------------------------------------------------------------------- /mocks/DetailsService.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // DetailsService is an autogenerated mock type for the DetailsService type 9 | type DetailsService struct { 10 | mock.Mock 11 | } 12 | 13 | // Get provides a mock function with given fields: ID 14 | func (_m *DetailsService) Get(ID uint64) (*models.Detail, error) { 15 | ret := _m.Called(ID) 16 | 17 | var r0 *models.Detail 18 | if rf, ok := ret.Get(0).(func(uint64) *models.Detail); ok { 19 | r0 = rf(ID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).(*models.Detail) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(ID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /mocks/RatingsClient.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import grpc "google.golang.org/grpc" 7 | import mock "github.com/stretchr/testify/mock" 8 | import proto "github.com/sdgmf/go-project-sample/api/proto" 9 | 10 | // RatingsClient is an autogenerated mock type for the RatingsClient type 11 | type RatingsClient struct { 12 | mock.Mock 13 | } 14 | 15 | // Get provides a mock function with given fields: ctx, in, opts 16 | func (_m *RatingsClient) Get(ctx context.Context, in *proto.GetRatingRequest, opts ...grpc.CallOption) (*proto.Rating, error) { 17 | _va := make([]interface{}, len(opts)) 18 | for _i := range opts { 19 | _va[_i] = opts[_i] 20 | } 21 | var _ca []interface{} 22 | _ca = append(_ca, ctx, in) 23 | _ca = append(_ca, _va...) 24 | ret := _m.Called(_ca...) 25 | 26 | var r0 *proto.Rating 27 | if rf, ok := ret.Get(0).(func(context.Context, *proto.GetRatingRequest, ...grpc.CallOption) *proto.Rating); ok { 28 | r0 = rf(ctx, in, opts...) 29 | } else { 30 | if ret.Get(0) != nil { 31 | r0 = ret.Get(0).(*proto.Rating) 32 | } 33 | } 34 | 35 | var r1 error 36 | if rf, ok := ret.Get(1).(func(context.Context, *proto.GetRatingRequest, ...grpc.CallOption) error); ok { 37 | r1 = rf(ctx, in, opts...) 38 | } else { 39 | r1 = ret.Error(1) 40 | } 41 | 42 | return r0, r1 43 | } 44 | -------------------------------------------------------------------------------- /mocks/RatingsRepository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // RatingsRepository is an autogenerated mock type for the RatingsRepository type 9 | type RatingsRepository struct { 10 | mock.Mock 11 | } 12 | 13 | // Get provides a mock function with given fields: productID 14 | func (_m *RatingsRepository) Get(productID uint64) (*models.Rating, error) { 15 | ret := _m.Called(productID) 16 | 17 | var r0 *models.Rating 18 | if rf, ok := ret.Get(0).(func(uint64) *models.Rating); ok { 19 | r0 = rf(productID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).(*models.Rating) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(productID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /mocks/RatingsServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import mock "github.com/stretchr/testify/mock" 7 | import proto "github.com/sdgmf/go-project-sample/api/proto" 8 | 9 | // RatingsServer is an autogenerated mock type for the RatingsServer type 10 | type RatingsServer struct { 11 | mock.Mock 12 | } 13 | 14 | // Get provides a mock function with given fields: _a0, _a1 15 | func (_m *RatingsServer) Get(_a0 context.Context, _a1 *proto.GetRatingRequest) (*proto.Rating, error) { 16 | ret := _m.Called(_a0, _a1) 17 | 18 | var r0 *proto.Rating 19 | if rf, ok := ret.Get(0).(func(context.Context, *proto.GetRatingRequest) *proto.Rating); ok { 20 | r0 = rf(_a0, _a1) 21 | } else { 22 | if ret.Get(0) != nil { 23 | r0 = ret.Get(0).(*proto.Rating) 24 | } 25 | } 26 | 27 | var r1 error 28 | if rf, ok := ret.Get(1).(func(context.Context, *proto.GetRatingRequest) error); ok { 29 | r1 = rf(_a0, _a1) 30 | } else { 31 | r1 = ret.Error(1) 32 | } 33 | 34 | return r0, r1 35 | } 36 | -------------------------------------------------------------------------------- /mocks/RatingsService.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // RatingsService is an autogenerated mock type for the RatingsService type 9 | type RatingsService struct { 10 | mock.Mock 11 | } 12 | 13 | // Get provides a mock function with given fields: ID 14 | func (_m *RatingsService) Get(ID uint64) (*models.Rating, error) { 15 | ret := _m.Called(ID) 16 | 17 | var r0 *models.Rating 18 | if rf, ok := ret.Get(0).(func(uint64) *models.Rating); ok { 19 | r0 = rf(ID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).(*models.Rating) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(ID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /mocks/ReviewsClient.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import grpc "google.golang.org/grpc" 7 | import mock "github.com/stretchr/testify/mock" 8 | import proto "github.com/sdgmf/go-project-sample/api/proto" 9 | 10 | // ReviewsClient is an autogenerated mock type for the ReviewsClient type 11 | type ReviewsClient struct { 12 | mock.Mock 13 | } 14 | 15 | // Query provides a mock function with given fields: ctx, in, opts 16 | func (_m *ReviewsClient) Query(ctx context.Context, in *proto.QueryReviewsRequest, opts ...grpc.CallOption) (*proto.QueryReviewsResponse, error) { 17 | _va := make([]interface{}, len(opts)) 18 | for _i := range opts { 19 | _va[_i] = opts[_i] 20 | } 21 | var _ca []interface{} 22 | _ca = append(_ca, ctx, in) 23 | _ca = append(_ca, _va...) 24 | ret := _m.Called(_ca...) 25 | 26 | var r0 *proto.QueryReviewsResponse 27 | if rf, ok := ret.Get(0).(func(context.Context, *proto.QueryReviewsRequest, ...grpc.CallOption) *proto.QueryReviewsResponse); ok { 28 | r0 = rf(ctx, in, opts...) 29 | } else { 30 | if ret.Get(0) != nil { 31 | r0 = ret.Get(0).(*proto.QueryReviewsResponse) 32 | } 33 | } 34 | 35 | var r1 error 36 | if rf, ok := ret.Get(1).(func(context.Context, *proto.QueryReviewsRequest, ...grpc.CallOption) error); ok { 37 | r1 = rf(ctx, in, opts...) 38 | } else { 39 | r1 = ret.Error(1) 40 | } 41 | 42 | return r0, r1 43 | } 44 | -------------------------------------------------------------------------------- /mocks/ReviewsRepository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // ReviewsRepository is an autogenerated mock type for the ReviewsRepository type 9 | type ReviewsRepository struct { 10 | mock.Mock 11 | } 12 | 13 | // Query provides a mock function with given fields: productID 14 | func (_m *ReviewsRepository) Query(productID uint64) ([]*models.Review, error) { 15 | ret := _m.Called(productID) 16 | 17 | var r0 []*models.Review 18 | if rf, ok := ret.Get(0).(func(uint64) []*models.Review); ok { 19 | r0 = rf(productID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).([]*models.Review) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(productID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /mocks/ReviewsServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import context "context" 6 | import mock "github.com/stretchr/testify/mock" 7 | import proto "github.com/sdgmf/go-project-sample/api/proto" 8 | 9 | // ReviewsServer is an autogenerated mock type for the ReviewsServer type 10 | type ReviewsServer struct { 11 | mock.Mock 12 | } 13 | 14 | // Query provides a mock function with given fields: _a0, _a1 15 | func (_m *ReviewsServer) Query(_a0 context.Context, _a1 *proto.QueryReviewsRequest) (*proto.QueryReviewsResponse, error) { 16 | ret := _m.Called(_a0, _a1) 17 | 18 | var r0 *proto.QueryReviewsResponse 19 | if rf, ok := ret.Get(0).(func(context.Context, *proto.QueryReviewsRequest) *proto.QueryReviewsResponse); ok { 20 | r0 = rf(_a0, _a1) 21 | } else { 22 | if ret.Get(0) != nil { 23 | r0 = ret.Get(0).(*proto.QueryReviewsResponse) 24 | } 25 | } 26 | 27 | var r1 error 28 | if rf, ok := ret.Get(1).(func(context.Context, *proto.QueryReviewsRequest) error); ok { 29 | r1 = rf(_a0, _a1) 30 | } else { 31 | r1 = ret.Error(1) 32 | } 33 | 34 | return r0, r1 35 | } 36 | -------------------------------------------------------------------------------- /mocks/ReviewsService.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | import models "github.com/sdgmf/go-project-sample/internal/pkg/models" 7 | 8 | // ReviewsService is an autogenerated mock type for the ReviewsService type 9 | type ReviewsService struct { 10 | mock.Mock 11 | } 12 | 13 | // Query provides a mock function with given fields: productID 14 | func (_m *ReviewsService) Query(productID uint64) ([]*models.Review, error) { 15 | ret := _m.Called(productID) 16 | 17 | var r0 []*models.Review 18 | if rf, ok := ret.Get(0).(func(uint64) []*models.Review); ok { 19 | r0 = rf(productID) 20 | } else { 21 | if ret.Get(0) != nil { 22 | r0 = ret.Get(0).([]*models.Review) 23 | } 24 | } 25 | 26 | var r1 error 27 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 28 | r1 = rf(productID) 29 | } else { 30 | r1 = ret.Error(1) 31 | } 32 | 33 | return r0, r1 34 | } 35 | -------------------------------------------------------------------------------- /scripts/grafana/dashboard-api.jsonnet: -------------------------------------------------------------------------------- 1 | local dash = import './dashboard.jsonnet'; 2 | 3 | { 4 | dashboard: dash, 5 | folderId: 0, 6 | overwrite: false, 7 | } 8 | 9 | -------------------------------------------------------------------------------- /scripts/products.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : db_local 5 | Source Server Type : MySQL 6 | Source Server Version : 80015 7 | Source Host : 127.0.0.1:3306 8 | Source Schema : products 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 80015 12 | File Encoding : 65001 13 | 14 | Date: 02/08/2019 16:32:57 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for details 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `details`; 24 | CREATE TABLE `details` ( 25 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 26 | `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, 27 | `price` double DEFAULT NULL, 28 | `created_time` timestamp NULL DEFAULT NULL, 29 | PRIMARY KEY (`id`) 30 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 31 | 32 | -- ---------------------------- 33 | -- Records of details 34 | -- ---------------------------- 35 | BEGIN; 36 | INSERT INTO `details` VALUES (1, 'apple', 1, '2019-07-31 19:43:10'); 37 | INSERT INTO `details` VALUES (2, 'pear', 1, '2019-07-31 19:43:45'); 38 | INSERT INTO `details` VALUES (3, 'banana', 0.5, '2019-07-31 19:44:08'); 39 | COMMIT; 40 | 41 | -- ---------------------------- 42 | -- Table structure for ratings 43 | -- ---------------------------- 44 | DROP TABLE IF EXISTS `ratings`; 45 | CREATE TABLE `ratings` ( 46 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 47 | `product_id` bigint(20) unsigned DEFAULT NULL, 48 | `score` int(10) unsigned DEFAULT NULL, 49 | `updated_time` timestamp NULL DEFAULT NULL, 50 | PRIMARY KEY (`id`) 51 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 52 | 53 | -- ---------------------------- 54 | -- Records of ratings 55 | -- ---------------------------- 56 | BEGIN; 57 | INSERT INTO `ratings` VALUES (1, 1, 5, '2019-07-31 19:48:04'); 58 | INSERT INTO `ratings` VALUES (2, 2, 4, '2019-07-31 19:48:21'); 59 | INSERT INTO `ratings` VALUES (3, 3, 5, '2019-07-31 19:48:34'); 60 | COMMIT; 61 | 62 | -- ---------------------------- 63 | -- Table structure for reviews 64 | -- ---------------------------- 65 | DROP TABLE IF EXISTS `reviews`; 66 | CREATE TABLE `reviews` ( 67 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 68 | `product_id` bigint(20) unsigned DEFAULT NULL, 69 | `message` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, 70 | `created_time` timestamp NULL DEFAULT NULL, 71 | PRIMARY KEY (`id`) 72 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 73 | 74 | -- ---------------------------- 75 | -- Records of reviews 76 | -- ---------------------------- 77 | BEGIN; 78 | INSERT INTO `reviews` VALUES (1, 1, 'good', '2019-07-31 19:48:47'); 79 | INSERT INTO `reviews` VALUES (2, 2, 'bad', '2019-07-31 19:49:12'); 80 | INSERT INTO `reviews` VALUES (3, 3, 'good', '2019-07-31 20:15:56'); 81 | COMMIT; 82 | 83 | SET FOREIGN_KEY_CHECKS = 1; 84 | -------------------------------------------------------------------------------- /scripts/prometheus/rules.jsonnet: -------------------------------------------------------------------------------- 1 | local app = std.extVar('app'); 2 | local email = "xxx@xxx"; 3 | 4 | local rules = [{ 5 | alert: "ServiceDown["+app+"]", 6 | expr:"absent(up{app=\""+app+"\"}) == 1", 7 | "for": "10s", 8 | labels: { 9 | email: email, 10 | }, 11 | annotations: { 12 | description:"{{ $labels.app}} has been down for more than 10 seconds." 13 | } 14 | },{ 15 | alert: "GRPCServerErrorThrottlingHigh["+app+"]", 16 | expr:"sum(rate(grpc_server_handled_total{app=\""+app+"\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 17 | "for": "10s", 18 | labels: { 19 | email: email, 20 | }, 21 | annotations: { 22 | description: "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 23 | } 24 | },{ 25 | alert: "GRPServerCLatencyThrottlingHigh["+app+"]", 26 | expr:"histogram_quantile(0.99,sum(rate(grpc_server_handling_seconds_bucket{app=\""+app+"\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 27 | "for": "10s", 28 | labels: { 29 | email: email, 30 | }, 31 | annotations: { 32 | description: "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 33 | } 34 | },{ 35 | alert: "GRPCClientErrorThrottlingHigh["+app+"]", 36 | expr:"sum(rate(grpc_client_handled_total{app=\""+app+"\",grpc_type=\"unary\",grpc_code!=\"OK\"}[1m])) by (instance) > 0", 37 | "for": "10s", 38 | labels: { 39 | email: email, 40 | }, 41 | annotations: { 42 | description: "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 43 | } 44 | },{ 45 | alert: "GRPCClientLatencyThrottlingHigh["+app+"]", 46 | expr:"histogram_quantile(0.99,sum(rate(grpc_client_handling_seconds_bucket{app=\""+app+"\",grpc_type=\"unary\"}[1m])) by (instance,le)) > 0.2", 47 | "for": "10s", 48 | labels: { 49 | email: email, 50 | }, 51 | annotations: { 52 | description: "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 53 | } 54 | },{ 55 | alert: "HTTPErrorThrottlingHigh["+app+"]", 56 | expr:"sum(rate(http_server_requests_seconds_count{app=\""+app+"\",code!=\"200\"}[1m])) by (instance) > 0", 57 | "for": "10s", 58 | labels: { 59 | email: email, 60 | }, 61 | annotations: { 62 | description: "{{$labels.instance}} has error request for 10 senconds (current value: {{ $value }}s)" 63 | } 64 | },{ 65 | alert: "HTTPLatencyThrottlingHigh["+app+"]", 66 | expr:"histogram_quantile(0.99,sum(rate(http_server_requests_seconds_bucket{app=\""+app+"\"}[1m])) by (instance,le)) > 0.2", 67 | "for": "10s", 68 | labels: { 69 | email: email, 70 | }, 71 | annotations: { 72 | description: "{{ $labels.instance }} has a tp99 request latency above 200ms (current value: {{ $value }}s)" 73 | } 74 | }]; 75 | 76 | { 77 | groups: [ 78 | { 79 | name:app, 80 | rules:rules 81 | } 82 | ] 83 | } 84 | 85 | -------------------------------------------------------------------------------- /scripts/wait-for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TIMEOUT=15 4 | QUIET=0 5 | 6 | echoerr() { 7 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 8 | } 9 | 10 | usage() { 11 | exitcode="$1" 12 | cat << USAGE >&2 13 | Usage: 14 | $cmdname host:port [-t timeout] [-- command args] 15 | -q | --quiet Do not output any status messages 16 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 17 | -- COMMAND ARGS Execute command with args after the test finishes 18 | USAGE 19 | exit "$exitcode" 20 | } 21 | 22 | wait_for() { 23 | for i in `seq $TIMEOUT` ; do 24 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 25 | 26 | result=$? 27 | if [ $result -eq 0 ] ; then 28 | if [ $# -gt 0 ] ; then 29 | exec "$@" 30 | fi 31 | exit 0 32 | fi 33 | sleep 1 34 | done 35 | echo "Operation timed out" >&2 36 | exit 1 37 | } 38 | 39 | while [ $# -gt 0 ] 40 | do 41 | case "$1" in 42 | *:* ) 43 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 44 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 45 | shift 1 46 | ;; 47 | -q | --quiet) 48 | QUIET=1 49 | shift 1 50 | ;; 51 | -t) 52 | TIMEOUT="$2" 53 | if [ "$TIMEOUT" = "" ]; then break; fi 54 | shift 2 55 | ;; 56 | --timeout=*) 57 | TIMEOUT="${1#*=}" 58 | shift 1 59 | ;; 60 | --) 61 | shift 62 | break 63 | ;; 64 | --help) 65 | usage 0 66 | ;; 67 | *) 68 | echoerr "Unknown argument: $1" 69 | usage 1 70 | ;; 71 | esac 72 | done 73 | 74 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 75 | echoerr "Error: you need to provide a host and port to test." 76 | usage 2 77 | fi 78 | 79 | wait_for "$@" 80 | -------------------------------------------------------------------------------- /scripts/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | WAITFORIT_cmdname=${0##*/} 5 | 6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | wait_for() 26 | { 27 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 28 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 29 | else 30 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" 31 | fi 32 | WAITFORIT_start_ts=$(date +%s) 33 | while : 34 | do 35 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then 36 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT 37 | WAITFORIT_result=$? 38 | else 39 | (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 40 | WAITFORIT_result=$? 41 | fi 42 | if [[ $WAITFORIT_result -eq 0 ]]; then 43 | WAITFORIT_end_ts=$(date +%s) 44 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" 45 | break 46 | fi 47 | sleep 1 48 | done 49 | return $WAITFORIT_result 50 | } 51 | 52 | wait_for_wrapper() 53 | { 54 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 55 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then 56 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 57 | else 58 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 59 | fi 60 | WAITFORIT_PID=$! 61 | trap "kill -INT -$WAITFORIT_PID" INT 62 | wait $WAITFORIT_PID 63 | WAITFORIT_RESULT=$? 64 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then 65 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 66 | fi 67 | return $WAITFORIT_RESULT 68 | } 69 | 70 | # process arguments 71 | while [[ $# -gt 0 ]] 72 | do 73 | case "$1" in 74 | *:* ) 75 | WAITFORIT_hostport=(${1//:/ }) 76 | WAITFORIT_HOST=${WAITFORIT_hostport[0]} 77 | WAITFORIT_PORT=${WAITFORIT_hostport[1]} 78 | shift 1 79 | ;; 80 | --child) 81 | WAITFORIT_CHILD=1 82 | shift 1 83 | ;; 84 | -q | --quiet) 85 | WAITFORIT_QUIET=1 86 | shift 1 87 | ;; 88 | -s | --strict) 89 | WAITFORIT_STRICT=1 90 | shift 1 91 | ;; 92 | -h) 93 | WAITFORIT_HOST="$2" 94 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi 95 | shift 2 96 | ;; 97 | --host=*) 98 | WAITFORIT_HOST="${1#*=}" 99 | shift 1 100 | ;; 101 | -p) 102 | WAITFORIT_PORT="$2" 103 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi 104 | shift 2 105 | ;; 106 | --port=*) 107 | WAITFORIT_PORT="${1#*=}" 108 | shift 1 109 | ;; 110 | -t) 111 | WAITFORIT_TIMEOUT="$2" 112 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi 113 | shift 2 114 | ;; 115 | --timeout=*) 116 | WAITFORIT_TIMEOUT="${1#*=}" 117 | shift 1 118 | ;; 119 | --) 120 | shift 121 | WAITFORIT_CLI=("$@") 122 | break 123 | ;; 124 | --help) 125 | usage 126 | ;; 127 | *) 128 | echoerr "Unknown argument: $1" 129 | usage 130 | ;; 131 | esac 132 | done 133 | 134 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then 135 | echoerr "Error: you need to provide a host and port to test." 136 | usage 137 | fi 138 | 139 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} 140 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} 141 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} 142 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} 143 | 144 | # check to see if timeout is from busybox? 145 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout) 146 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) 147 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then 148 | WAITFORIT_ISBUSY=1 149 | WAITFORIT_BUSYTIMEFLAG="-t" 150 | 151 | else 152 | WAITFORIT_ISBUSY=0 153 | WAITFORIT_BUSYTIMEFLAG="" 154 | fi 155 | 156 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then 157 | wait_for 158 | WAITFORIT_RESULT=$? 159 | exit $WAITFORIT_RESULT 160 | else 161 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 162 | wait_for_wrapper 163 | WAITFORIT_RESULT=$? 164 | else 165 | wait_for 166 | WAITFORIT_RESULT=$? 167 | fi 168 | fi 169 | 170 | if [[ $WAITFORIT_CLI != "" ]]; then 171 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then 172 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" 173 | exit $WAITFORIT_RESULT 174 | fi 175 | exec "${WAITFORIT_CLI[@]}" 176 | else 177 | exit $WAITFORIT_RESULT 178 | fi 179 | --------------------------------------------------------------------------------