├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── README.md ├── cmd └── service │ └── main.go ├── config.toml ├── go.mod ├── go.sum └── internal ├── app ├── adapters │ ├── primary │ │ ├── grpc-adapter │ │ │ ├── file.proto │ │ │ ├── generated │ │ │ │ ├── file.pb.go │ │ │ │ └── file_grpc.pb.go │ │ │ ├── handlers │ │ │ │ └── api.go │ │ │ └── init.go │ │ ├── http-adapter │ │ │ ├── config.go │ │ │ ├── handlers │ │ │ │ ├── endpoint_create.go │ │ │ │ ├── endpoint_get.go │ │ │ │ └── init.go │ │ │ ├── init.go │ │ │ └── router │ │ │ │ ├── init.go │ │ │ │ └── routes.go │ │ ├── kafka-adapter-subscriber │ │ │ ├── init.go │ │ │ ├── kafka-handlers │ │ │ │ ├── init.go │ │ │ │ └── methods.go │ │ │ ├── kafka-queue │ │ │ │ ├── init.go │ │ │ │ └── methods.go │ │ │ └── methods.go │ │ ├── nats-adapter-subscriber │ │ │ ├── config.go │ │ │ ├── init.go │ │ │ ├── methods.go │ │ │ └── nats-handlers │ │ │ │ ├── init.go │ │ │ │ └── methods.go │ │ ├── os-signal-adapter │ │ │ └── init.go │ │ └── pprof-adapter │ │ │ └── init.go │ └── secondary │ │ ├── gateways │ │ └── books-gateway │ │ │ ├── config.go │ │ │ ├── init.go │ │ │ └── methods.go │ │ ├── grpc-adapter │ │ ├── file.proto │ │ ├── generated │ │ │ ├── file.pb.go │ │ │ └── file_grpc.pb.go │ │ ├── init.go │ │ └── methods.go │ │ ├── kafka-adapter-publisher │ │ ├── config.go │ │ ├── init.go │ │ └── methods.go │ │ ├── nats-adapter-publisher │ │ ├── config.go │ │ ├── init.go │ │ └── methods.go │ │ └── repositories │ │ ├── books-repository-clickhouse │ │ ├── init.go │ │ ├── methods.go │ │ └── queries.go │ │ ├── books-repository-mongo │ │ ├── init.go │ │ └── methods.go │ │ └── books-repository-postgres │ │ ├── dto.go │ │ ├── init.go │ │ └── methods.go ├── app.go ├── application │ └── usecases │ │ ├── init.go │ │ └── methods.go ├── config │ ├── adapters.go │ ├── config.go │ └── init.go └── domain │ └── book │ └── book.go └── libs ├── graceful ├── graceful.go └── process.go ├── helpers └── helpers.go ├── http-server └── server.go ├── middleware-helpers └── middleware_helpers.go ├── provider-helpers ├── provider_helpers.go └── provider_helpers_test.go └── repo-helpers └── repohelpers.go /.gitignore: -------------------------------------------------------------------------------- 1 | *secret* 2 | .vscode 3 | .DS_Store 4 | .idea 5 | .env -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | concurrency: 4 3 | timeout: 2m 4 | issues-exit-code: 1 5 | tests: true 6 | 7 | output: 8 | formats: 9 | - format: colored-line-number 10 | 11 | # Print lines of code with issue. 12 | # Default: true 13 | # print-issued-lines: false 14 | print-issued-lines: true 15 | 16 | print-linter-name: true 17 | 18 | # Make issues output unique by line. 19 | # Default: true 20 | uniq-by-line: true 21 | # uniq-by-line: false 22 | 23 | linters: 24 | # Disable all linters. (It doesn't disable linters listed below, only default ones) 25 | # Default: false 26 | disable-all: true 27 | enable: 28 | - errcheck 29 | - gosimple 30 | - govet 31 | - ineffassign 32 | - staticcheck 33 | - unused 34 | 35 | - asasalint 36 | - asciicheck 37 | - bidichk 38 | - bodyclose 39 | - containedctx 40 | - cyclop 41 | - decorder 42 | - dogsled 43 | - dupword 44 | - durationcheck 45 | - errchkjson 46 | - errname 47 | - errorlint 48 | - exhaustive 49 | - copyloopvar 50 | - forbidigo 51 | - forcetypeassert 52 | - gci 53 | - ginkgolinter 54 | - gocheckcompilerdirectives 55 | - gochecknoglobals 56 | - gochecknoinits 57 | - gochecksumtype 58 | - gocognit 59 | - goconst 60 | - gocritic 61 | - gocyclo 62 | - godot 63 | - godox 64 | - gofmt 65 | - gofumpt 66 | - goheader 67 | - goimports 68 | - gomoddirectives 69 | - gomodguard 70 | - goprintffuncname 71 | - gosec 72 | - gosmopolitan 73 | - grouper 74 | - importas 75 | - inamedparam 76 | - interfacebloat 77 | - ireturn 78 | - loggercheck 79 | - maintidx 80 | - makezero 81 | - mirror 82 | - misspell 83 | - musttag 84 | # - nakedret 85 | - nilerr 86 | - nilnil 87 | - nlreturn 88 | - noctx 89 | - nosprintfhostport 90 | - perfsprint 91 | - prealloc 92 | - predeclared 93 | - promlinter 94 | - protogetter 95 | - reassign 96 | - revive 97 | - rowserrcheck 98 | - sloglint 99 | # - sqlclosecheck 100 | - stylecheck 101 | - tagalign 102 | - tenv 103 | - testableexamples 104 | - testifylint 105 | - testpackage 106 | - thelper 107 | - tparallel 108 | - unconvert 109 | - unparam 110 | - usestdlibvars 111 | - wastedassign 112 | - whitespace 113 | - wsl 114 | - zerologlint 115 | # - goerr113 116 | # - nolintlint 117 | # - paralleltest 118 | # - nonamedreturns 119 | # - varnamelen 120 | # - tagliatelle 121 | # - wrapcheck 122 | # - depguard 123 | # - lll 124 | # - gomnd 125 | # - contextcheck 126 | # - exhaustruct 127 | # - dupl 128 | 129 | 130 | linters-settings: 131 | wrapcheck: 132 | # An array of strings that specify substrings of signatures to ignore. 133 | # If this set, it will override the default set of ignored signatures. 134 | # See https://github.com/tomarrell/wrapcheck#configuration for more information. 135 | ignoreSigs: 136 | - .WrapError( 137 | 138 | varnamelen: 139 | # The longest distance, in source lines, that is being considered a "small scope". 140 | # Variables used in at most this many lines will be ignored. 141 | # Default: 5 142 | max-distance: 6 143 | ignore-names: 144 | - l 145 | - cfg 146 | - db 147 | - wg 148 | - r 149 | lll: 150 | # Max line length, lines longer will be reported. 151 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option. 152 | # Default: 120. 153 | line-length: 180 154 | 155 | staticcheck: 156 | # Select the Go version to target. 157 | # Default: 1.13 158 | # go: "1.15" 159 | # https://staticcheck.io/docs/options#checks 160 | # checks: ["all", 161 | # "-ST1000", # Incorrect or missing package comment 162 | # "-ST1003", # should not use MixedCaps in package name 163 | # ] 164 | 165 | stylecheck: 166 | # # Select the Go version to target. 167 | # # Default: 1.13 168 | # go: "1.15" 169 | # # https://staticcheck.io/docs/options#checks 170 | checks: ["all", 171 | "-ST1000", # Incorrect or missing package comment 172 | "-ST1003", # Should not use MixedCaps in package name 173 | ] 174 | 175 | mnd: 176 | # List of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. 177 | checks: 178 | - argument 179 | - case 180 | - condition 181 | - operation 182 | - return 183 | - assign 184 | # List of numbers to exclude from analysis. 185 | # The numbers should be written as string. 186 | # Values always ignored: "1", "1.0", "0" and "0.0" 187 | # ignored-numbers: 188 | # - '0666' 189 | 190 | # List of file patterns to exclude from analysis. 191 | # Values always ignored: `.+_test.go` 192 | # ignored-files: 193 | # - 'magic1_.*.go' 194 | # List of function patterns to exclude from analysis. 195 | # Values always ignored: `time.Time` 196 | ignored-functions: 197 | - 'context.*' 198 | - 'resty.*' 199 | 200 | revive: 201 | rules: 202 | - name: var-naming 203 | severity: warning 204 | disabled: true 205 | 206 | issues: 207 | exclude-rules: 208 | - path: libs 209 | linters: 210 | - unused 211 | # - linters: 212 | # - stylecheck 213 | # text: "ST1000:" 214 | 215 | # Independently of option `exclude` we use default exclude patterns, 216 | # it can be disabled by this option. 217 | # To list all excluded by default patterns execute `golangci-lint run --help`. 218 | # Default: true. 219 | exclude-use-default: false 220 | # If set to true exclude and exclude-rules regular expressions become case-sensitive. 221 | # Default: false 222 | exclude-case-sensitive: false 223 | # Вместо skip-dirs 224 | exclude-dirs: 225 | - internal/libs 226 | # Вместо skip-files 227 | exclude-files: 228 | - ./*_test.go 229 | 230 | # Maximum issues count per one linter. 231 | # Set to 0 to disable. 232 | # Default: 50 233 | max-issues-per-linter: 0 234 | 235 | # Maximum count of issues with the same text. 236 | # Set to 0 to disable. 237 | # Default: 3 238 | max-same-issues: 0 239 | 240 | # Show only new issues: if there are unstaged changes or untracked files, 241 | # only those changes are analyzed, else only changes in HEAD~ are analyzed. 242 | # It's a super-useful option for integration of golangci-lint into existing large codebase. 243 | # It's not practical to fix all existing issues at the moment of integration: 244 | # much better don't allow issues in new code. 245 | # 246 | # Default: false. 247 | new: false 248 | 249 | # Fix found issues (if it's supported by the linter). 250 | fix: true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | История версий 2 | ============== 3 | 4 | ### Версия 1.0.0 5 | + Add -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Work in progress 2 | 3 | v0.0.2 (27.10.2024) 4 | * added graceful lib for graceful shutdown 5 | * http-adapter is ready 6 | * main is refactored. Initialization and runtime parts are separated 7 | * configs now are parts of their packages 8 | * consistent package names (all snake_case) 9 | * general refactoring. Refactored names of: packages, variables, functions etc. 10 | 11 | 12 | 13 | v0.0.1 14 | * initial version 15 | 16 | #### TODO 17 | 1. Get rid of config in domain (+) 18 | 2. Rethink the structure of http-adapter (+) 19 | 3. Implement graceful shutdown (+) 20 | 4. Consider making a branch with DI 21 | 5. Separate interface adapters layer and infrastructure layer 22 | 6. Think about fatals in adapters constructors (+) 23 | 7. Validate how context is propagated in adapters 24 | 8. Consider changing (or adding) Config.toml to yaml or hcl 25 | 26 | 27 | Notes: 28 | 1. Opinionated: snake_case in package names 29 | 2. Opinionated: I put the struct and constructor in init.go and the methods in methods.go 30 | 3. adapters implementations are not production ready. They are just examples (except http-adapter) 31 | 32 | # Project layout 33 | ``` 34 | . 35 | ├── cmd 36 | │   └── service 37 | └── internal 38 | ├── app 39 | │   ├── adapters 40 | │   │   ├── primary 41 | │   │   │   ├── grpc-adapter 42 | │   │   │   │   ├── generated 43 | │   │   │   │   └── handlers 44 | │   │   │   ├── http-adapter 45 | │   │   │   │   ├── handlers 46 | │   │   │   │   └── router 47 | │   │   │   ├── kafka-adapter-subscriber 48 | │   │   │   │   ├── kafka-handlers 49 | │   │   │   │   └── kafka-queue 50 | │   │   │   ├── nats-adapter-subscriber 51 | │   │   │   │   └── nats-handlers 52 | │   │   │   ├── os-signal-adapter 53 | │   │   │   └── pprof-adapter 54 | │   │   └── secondary 55 | │   │   ├── gateways 56 | │   │   │   └── books-gateway 57 | │   │   ├── grpc-adapter 58 | │   │   │   └── generated 59 | │   │   ├── kafka-adapter-publisher 60 | │   │   ├── nats-adapter-publisher 61 | │   │   └── repositories 62 | │   │   ├── books-repository-clickhouse 63 | │   │   ├── books-repository-mongo 64 | │   │   └── books-repository-postgres 65 | │   ├── application 66 | │   │   └── usecases 67 | │   ├── config 68 | │   └── domain 69 | │   └── book 70 | └── libs 71 | ├── graceful 72 | ├── helpers 73 | ├── http-server 74 | ├── middleware-helpers 75 | ├── provider-helpers 76 | └── repo-helpers 77 | 78 | 79 | ``` 80 | -------------------------------------------------------------------------------- /cmd/service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "os" 7 | 8 | _ "go.uber.org/automaxprocs" 9 | 10 | "github.com/rostislaved/go-clean-architecture/internal/app" 11 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/os-signal-adapter" 12 | "github.com/rostislaved/go-clean-architecture/internal/app/config" 13 | "github.com/rostislaved/go-clean-architecture/internal/libs/graceful" 14 | "github.com/rostislaved/go-clean-architecture/internal/libs/helpers" 15 | ) 16 | 17 | func main() { 18 | cfg := config.New() 19 | 20 | h := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}) 21 | l := slog.New(h) 22 | 23 | app := app.New(l, cfg) 24 | 25 | gr := graceful.New( 26 | graceful.NewProcess(app.HttpAdapter), 27 | graceful.NewProcess(app.PprofAdapter), 28 | graceful.NewProcess(app.NatsAdapterSubscriber), 29 | graceful.NewProcess(app.KafkaAdapterSubscriber), 30 | graceful.NewProcess(os_signal_adapter.New()), 31 | ) 32 | 33 | ctx, cancel := context.WithCancel(context.Background()) 34 | defer cancel() 35 | 36 | err := gr.Start(ctx) 37 | if err != nil { 38 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 39 | 40 | panic(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [Application] 2 | Name = "service_name" 3 | Version = "1.0.0" 4 | 5 | [Application] 6 | [Application.UpdateService] 7 | UpdateInterval = "1m" 8 | 9 | [Adapters] 10 | [Adapters.Primary] 11 | [Adapters.Primary.HttpAdapter] 12 | [Adapters.Primary.HttpAdapter.Server] 13 | Port = ":8080" 14 | [Adapters.Primary.HttpAdapter.Router] 15 | [Adapters.Primary.HttpAdapter.Router.Shutdown] 16 | Duration = 15 17 | [Adapters.Primary.HttpAdapter.Router.Timeout] 18 | Duration = 60 19 | 20 | [Adapters.Primary.KafkaAdapterSubscriber] 21 | Host = "KAFKA_HOST" # kafka-server.ru:9092 22 | Topic = "topic_name" 23 | GroupID = "gorup_name" 24 | 25 | [Adapters.Primary.NatsAdapterSubscriber] 26 | [Adapters.Primary.NatsAdapterSubscriber.Connection] 27 | Host = "NATS_HOST" 28 | ClusterID = "" 29 | ClientID = "" 30 | AllowMultipleClients = true 31 | User = "NATS_USER" 32 | Password = "NATS_PASSWORD" 33 | [Adapters.Primary.NatsAdapterSubscriber.Subscriber1] 34 | Channel = "" 35 | QueueGroup = "" 36 | DurableName = "" 37 | MaxInflight = 123 38 | DeliverAllAvailable = true 39 | AckWaitTimeout = 123 40 | 41 | [Adapters.Secondary] 42 | [Adapters.Secondary.Databases] 43 | [Adapters.Secondary.Databases.Postgres] 44 | Host = "POSTGRES_HOST" 45 | Port = "POSTGRES_PORT" 46 | Type = "postgres" 47 | Name = "POSTGRES_NAME" 48 | User = "POSTGRES_USER" 49 | Password = "POSTGRES_PASSWORD" 50 | 51 | [Adapters.Secondary.Databases.Clickhouse] 52 | Host = "CLICKHOUSE_HOST" 53 | Port = "CLICKHOUSE_PORT" 54 | Type = "chhttp" 55 | Name = "CLICKHOUSE_NAME" 56 | User = "CLICKHOUSE_USER" 57 | Password = "CLICKHOUSE_PASSWORD" 58 | 59 | [Adapters.Secondary.Databases.Mongo] 60 | Name = "MONGO_NAME" 61 | Host = "MONGO_HOST" 62 | User = "MONGO_USER" 63 | Password = "MONGO_PASSWORD" 64 | 65 | 66 | [Adapters.Secondary.Gateways] 67 | [Adapters.Secondary.Gateways.Gateway1] 68 | Host = "http://example.com" 69 | 70 | [Adapters.Secondary.Gateways.Gateway1.Endpoints] 71 | [Adapters.Secondary.Gateways.Gateway1.Endpoints.CreateArticle] 72 | Method = "POST" 73 | Path = "/api/v2/article" 74 | Headers = {} 75 | [Adapters.Secondary.Gateways.Gateway1.Endpoints.GetBook] 76 | Method = "GET" 77 | Path = "/api/v1/book" 78 | Headers = {} 79 | [Adapters.Secondary.NatsAdapterPublisher] 80 | [Adapters.Secondary.NatsAdapterPublisher.Connection] 81 | Host = "NATS_HOST" 82 | ClusterID = "" 83 | ClientID = "" 84 | AllowMultipleClients = true 85 | User = "NATS_USER" 86 | Password = "NATS_PASSWORD" 87 | [Adapters.Secondary.NatsAdapterPublisher.Publisher1] 88 | Channel = "" 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rostislaved/go-clean-architecture 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/Masterminds/squirrel v1.5.4 7 | github.com/denisenkom/go-mssqldb v0.12.3 8 | github.com/go-resty/resty/v2 v2.15.3 9 | github.com/gorilla/mux v1.8.0 10 | github.com/jmoiron/sqlx v1.3.5 11 | github.com/lib/pq v1.10.9 12 | github.com/mailru/go-clickhouse/v2 v2.0.0 13 | github.com/segmentio/kafka-go v0.4.43 14 | go.mongodb.org/mongo-driver v1.11.1 15 | go.uber.org/automaxprocs v1.5.3 16 | go.uber.org/multierr v1.11.0 17 | golang.org/x/sync v0.7.0 18 | google.golang.org/grpc v1.57.0 19 | google.golang.org/protobuf v1.31.0 20 | moul.io/http2curl v1.0.0 21 | ) 22 | 23 | require ( 24 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect 25 | github.com/golang-sql/sqlexp v0.1.0 // indirect 26 | github.com/golang/protobuf v1.5.3 // indirect 27 | github.com/golang/snappy v0.0.4 // indirect 28 | github.com/google/uuid v1.3.1 // indirect 29 | github.com/klauspost/compress v1.17.0 // indirect 30 | github.com/kr/pretty v0.3.1 // indirect 31 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect 32 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect 33 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 34 | github.com/pierrec/lz4/v4 v4.1.18 // indirect 35 | github.com/pkg/errors v0.9.1 // indirect 36 | github.com/rogpeppe/go-internal v1.10.0 // indirect 37 | github.com/smartystreets/goconvey v1.8.1 // indirect 38 | github.com/stretchr/testify v1.8.3 // indirect 39 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 40 | github.com/xdg-go/scram v1.1.2 // indirect 41 | github.com/xdg-go/stringprep v1.0.4 // indirect 42 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 43 | golang.org/x/crypto v0.25.0 // indirect 44 | golang.org/x/net v0.27.0 // indirect 45 | golang.org/x/sys v0.22.0 // indirect 46 | golang.org/x/text v0.16.0 // indirect 47 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= 2 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= 3 | github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= 4 | github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= 5 | github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= 11 | github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= 12 | github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 13 | github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= 14 | github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= 15 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 16 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 17 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 18 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= 19 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 20 | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= 21 | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= 22 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 23 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 24 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 25 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 26 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 27 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 28 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 31 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 32 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 33 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= 34 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= 36 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 37 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 38 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 39 | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= 40 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 41 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 42 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 43 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 44 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 45 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= 46 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 47 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 48 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 49 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 50 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 51 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 52 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 53 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 54 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= 55 | github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= 56 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= 57 | github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= 58 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 59 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 60 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 61 | github.com/mailru/go-clickhouse/v2 v2.0.0 h1:O+ZGJDwp/E5W19ooeouEqaOlg+qxA+4Zsfjt63QcnVU= 62 | github.com/mailru/go-clickhouse/v2 v2.0.0/go.mod h1:TwxN829KnFZ7jAka9l9EoCV+U0CBFq83SFev4oLbnNU= 63 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 64 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 65 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 66 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= 67 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 68 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 69 | github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= 70 | github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 71 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 72 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 73 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 74 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 75 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 76 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 78 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 79 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 80 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 81 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 82 | github.com/segmentio/kafka-go v0.4.43 h1:yKVQ/i6BobbX7AWzwkhulsEn47wpLA8eO6H03bCMqYg= 83 | github.com/segmentio/kafka-go v0.4.43/go.mod h1:d0g15xPMqoUookug0OU75DhGZxXwCFxSLeJ4uphwJzg= 84 | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= 85 | github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= 86 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= 87 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= 88 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 89 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 90 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 91 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 92 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 93 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 94 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 95 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 96 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 97 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 98 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 99 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 100 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 101 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 102 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 103 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 104 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 105 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 106 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 107 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 108 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 109 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 110 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 111 | go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= 112 | go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= 113 | go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= 114 | go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 115 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 116 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 117 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 118 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 119 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 120 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 121 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 122 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 123 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 124 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 125 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 126 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 127 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 128 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 129 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 130 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 131 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 132 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 133 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 135 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 137 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 138 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 139 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 147 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 148 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 149 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 150 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 151 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 152 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 153 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 154 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 155 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 156 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 157 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 158 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 159 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 160 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 161 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 162 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 163 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 164 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 166 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= 167 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= 168 | google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= 169 | google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= 170 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 171 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 172 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 173 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 174 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 175 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 176 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 177 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 178 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 179 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 180 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 181 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 182 | moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= 183 | moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= 184 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/grpc-adapter/file.proto: -------------------------------------------------------------------------------- 1 | //protoc --go_out=. --go_opt=paths=import \ rostislav@rostislav-xps159520 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | syntax = "proto3"; 6 | package api; 7 | 8 | option go_package = "./generated"; 9 | 10 | message Message { 11 | string body = 1; 12 | } 13 | 14 | service Api { 15 | rpc SendMessage(Message) returns (Message); 16 | } 17 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/grpc-adapter/generated/file.pb.go: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. --go_opt=paths=import \ 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | // Code generated by protoc-gen-go. DO NOT EDIT. 6 | // versions: 7 | // protoc-gen-go v1.31.0 8 | // protoc v4.23.4 9 | // source: file.proto 10 | 11 | package generated 12 | 13 | import ( 14 | reflect "reflect" 15 | sync "sync" 16 | 17 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 18 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 19 | ) 20 | 21 | const ( 22 | // Verify that this generated code is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 24 | // Verify that runtime/protoimpl is sufficiently up-to-date. 25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 26 | ) 27 | 28 | type Message struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` 34 | } 35 | 36 | func (x *Message) Reset() { 37 | *x = Message{} 38 | if protoimpl.UnsafeEnabled { 39 | mi := &file_file_proto_msgTypes[0] 40 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 | ms.StoreMessageInfo(mi) 42 | } 43 | } 44 | 45 | func (x *Message) String() string { 46 | return protoimpl.X.MessageStringOf(x) 47 | } 48 | 49 | func (*Message) ProtoMessage() {} 50 | 51 | func (x *Message) ProtoReflect() protoreflect.Message { 52 | mi := &file_file_proto_msgTypes[0] 53 | if protoimpl.UnsafeEnabled && x != nil { 54 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 | if ms.LoadMessageInfo() == nil { 56 | ms.StoreMessageInfo(mi) 57 | } 58 | return ms 59 | } 60 | return mi.MessageOf(x) 61 | } 62 | 63 | // Deprecated: Use Message.ProtoReflect.Descriptor instead. 64 | func (*Message) Descriptor() ([]byte, []int) { 65 | return file_file_proto_rawDescGZIP(), []int{0} 66 | } 67 | 68 | func (x *Message) GetBody() string { 69 | if x != nil { 70 | return x.Body 71 | } 72 | return "" 73 | } 74 | 75 | var File_file_proto protoreflect.FileDescriptor 76 | 77 | var file_file_proto_rawDesc = []byte{ 78 | 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 79 | 0x69, 0x22, 0x1d, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 80 | 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 81 | 0x32, 0x30, 0x0a, 0x03, 0x41, 0x70, 0x69, 0x12, 0x29, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4d, 82 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x73, 83 | 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 84 | 0x67, 0x65, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 85 | 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 86 | } 87 | 88 | var ( 89 | file_file_proto_rawDescOnce sync.Once 90 | file_file_proto_rawDescData = file_file_proto_rawDesc 91 | ) 92 | 93 | func file_file_proto_rawDescGZIP() []byte { 94 | file_file_proto_rawDescOnce.Do(func() { 95 | file_file_proto_rawDescData = protoimpl.X.CompressGZIP(file_file_proto_rawDescData) 96 | }) 97 | return file_file_proto_rawDescData 98 | } 99 | 100 | var ( 101 | file_file_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 102 | file_file_proto_goTypes = []interface{}{ 103 | (*Message)(nil), // 0: api.Message 104 | } 105 | ) 106 | 107 | var file_file_proto_depIdxs = []int32{ 108 | 0, // 0: api.Api.SendMessage:input_type -> api.Message 109 | 0, // 1: api.Api.SendMessage:output_type -> api.Message 110 | 1, // [1:2] is the sub-list for method output_type 111 | 0, // [0:1] is the sub-list for method input_type 112 | 0, // [0:0] is the sub-list for extension type_name 113 | 0, // [0:0] is the sub-list for extension extendee 114 | 0, // [0:0] is the sub-list for field type_name 115 | } 116 | 117 | func init() { file_file_proto_init() } 118 | func file_file_proto_init() { 119 | if File_file_proto != nil { 120 | return 121 | } 122 | if !protoimpl.UnsafeEnabled { 123 | file_file_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 124 | switch v := v.(*Message); i { 125 | case 0: 126 | return &v.state 127 | case 1: 128 | return &v.sizeCache 129 | case 2: 130 | return &v.unknownFields 131 | default: 132 | return nil 133 | } 134 | } 135 | } 136 | type x struct{} 137 | out := protoimpl.TypeBuilder{ 138 | File: protoimpl.DescBuilder{ 139 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 140 | RawDescriptor: file_file_proto_rawDesc, 141 | NumEnums: 0, 142 | NumMessages: 1, 143 | NumExtensions: 0, 144 | NumServices: 1, 145 | }, 146 | GoTypes: file_file_proto_goTypes, 147 | DependencyIndexes: file_file_proto_depIdxs, 148 | MessageInfos: file_file_proto_msgTypes, 149 | }.Build() 150 | File_file_proto = out.File 151 | file_file_proto_rawDesc = nil 152 | file_file_proto_goTypes = nil 153 | file_file_proto_depIdxs = nil 154 | } 155 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/grpc-adapter/generated/file_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. --go_opt=paths=import \ 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 6 | // versions: 7 | // - protoc-gen-go-grpc v1.3.0 8 | // - protoc v4.23.4 9 | // source: file.proto 10 | 11 | package generated 12 | 13 | import ( 14 | context "context" 15 | 16 | grpc "google.golang.org/grpc" 17 | codes "google.golang.org/grpc/codes" 18 | status "google.golang.org/grpc/status" 19 | ) 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the grpc package it is being compiled against. 23 | // Requires gRPC-Go v1.32.0 or later. 24 | const _ = grpc.SupportPackageIsVersion7 25 | 26 | const ( 27 | Api_SendMessage_FullMethodName = "/api.Api/SendMessage" 28 | ) 29 | 30 | // ApiClient is the client API for Api usecases. 31 | // 32 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 33 | type ApiClient interface { 34 | SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) 35 | } 36 | 37 | type apiClient struct { 38 | cc grpc.ClientConnInterface 39 | } 40 | 41 | func NewApiClient(cc grpc.ClientConnInterface) ApiClient { 42 | return &apiClient{cc} 43 | } 44 | 45 | func (c *apiClient) SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { 46 | out := new(Message) 47 | err := c.cc.Invoke(ctx, Api_SendMessage_FullMethodName, in, out, opts...) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return out, nil 52 | } 53 | 54 | // ApiServer is the server API for Api usecases. 55 | // All implementations must embed UnimplementedApiServer 56 | // for forward compatibility 57 | type ApiServer interface { 58 | SendMessage(context.Context, *Message) (*Message, error) 59 | mustEmbedUnimplementedApiServer() 60 | } 61 | 62 | // UnimplementedApiServer must be embedded to have forward compatible implementations. 63 | type UnimplementedApiServer struct{} 64 | 65 | func (UnimplementedApiServer) SendMessage(context.Context, *Message) (*Message, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented") 67 | } 68 | func (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {} 69 | 70 | // UnsafeApiServer may be embedded to opt out of forward compatibility for this usecases. 71 | // Use of this interface is not recommended, as added methods to ApiServer will 72 | // result in compilation errors. 73 | type UnsafeApiServer interface { 74 | mustEmbedUnimplementedApiServer() 75 | } 76 | 77 | func RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) { 78 | s.RegisterService(&Api_ServiceDesc, srv) 79 | } 80 | 81 | func _Api_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 82 | in := new(Message) 83 | if err := dec(in); err != nil { 84 | return nil, err 85 | } 86 | if interceptor == nil { 87 | return srv.(ApiServer).SendMessage(ctx, in) 88 | } 89 | info := &grpc.UnaryServerInfo{ 90 | Server: srv, 91 | FullMethod: Api_SendMessage_FullMethodName, 92 | } 93 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 94 | return srv.(ApiServer).SendMessage(ctx, req.(*Message)) 95 | } 96 | return interceptor(ctx, in, info, handler) 97 | } 98 | 99 | // Api_ServiceDesc is the grpc.ServiceDesc for Api usecases. 100 | // It's only intended for direct use with grpc.RegisterService, 101 | // and not to be introspected or modified (even as a copy) 102 | var Api_ServiceDesc = grpc.ServiceDesc{ 103 | ServiceName: "api.Api", 104 | HandlerType: (*ApiServer)(nil), 105 | Methods: []grpc.MethodDesc{ 106 | { 107 | MethodName: "SendMessage", 108 | Handler: _Api_SendMessage_Handler, 109 | }, 110 | }, 111 | Streams: []grpc.StreamDesc{}, 112 | Metadata: "file.proto", 113 | } 114 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/grpc-adapter/handlers/api.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/grpc-adapter/generated" 7 | ) 8 | 9 | type Server struct { 10 | generated.UnimplementedApiServer 11 | } 12 | 13 | func (s Server) SendMessage(ctx context.Context, message *generated.Message) (*generated.Message, error) { 14 | m := generated.Message{Body: "Ответ"} 15 | // Тут вызов сервиса 16 | 17 | return &m, nil 18 | } 19 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/grpc-adapter/init.go: -------------------------------------------------------------------------------- 1 | package grpc_adapter 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/grpc-adapter/generated" 7 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/grpc-adapter/handlers" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | type GrpcAdapter struct { 12 | start func() error 13 | } 14 | 15 | func New() *GrpcAdapter { 16 | listener, err := net.Listen("tcp", "localhost:9000") 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | server := grpc.NewServer() 22 | 23 | generated.RegisterApiServer(server, handlers.Server{}) 24 | 25 | startFunc := func() error { 26 | err = server.Serve(listener) 27 | 28 | return err 29 | } 30 | 31 | return &GrpcAdapter{ 32 | start: startFunc, 33 | } 34 | } 35 | 36 | func (a GrpcAdapter) Start() { 37 | err := a.start() 38 | if err != nil { 39 | panic(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/config.go: -------------------------------------------------------------------------------- 1 | package http_adapter 2 | 3 | import ( 4 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter/router" 5 | http_server "github.com/rostislaved/go-clean-architecture/internal/libs/http-server" 6 | ) 7 | 8 | type Config struct { 9 | Server http_server.Config 10 | Router router.Config 11 | } 12 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/handlers/endpoint_create.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (h Handlers) Create(w http.ResponseWriter, r *http.Request) {} 8 | 9 | type RequestCreate struct{} 10 | 11 | type ResponseCreate struct{} 12 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/handlers/endpoint_get.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | func (h Handlers) Get(w http.ResponseWriter, r *http.Request) { 12 | var ids []int 13 | 14 | err := json.NewDecoder(r.Body).Decode(&ids) 15 | if err != nil { 16 | w.WriteHeader(http.StatusBadRequest) 17 | 18 | return 19 | } 20 | 21 | books, err := h.service.GetBooksByIDs(r.Context(), ids) 22 | if err != nil { 23 | w.WriteHeader(http.StatusInternalServerError) 24 | 25 | return 26 | } 27 | 28 | response := ToResponse(books) 29 | 30 | err = json.NewEncoder(w).Encode(response) 31 | if err != nil { 32 | w.WriteHeader(http.StatusInternalServerError) 33 | 34 | return 35 | } 36 | } 37 | 38 | type RequestGet struct{} 39 | 40 | func ToResponse(books []book.Book) ResponseGet { 41 | responseGetBooks := make([]ResponseGetBook, 0, len(books)) 42 | 43 | for _, book := range books { 44 | responseGetBooks = append(responseGetBooks, ResponseGetBook(book)) 45 | } 46 | 47 | response := ResponseGet{responseGetBooks} 48 | 49 | return response 50 | } 51 | 52 | type ResponseGet struct { 53 | ResponseGetBooks []ResponseGetBook `json:"books"` 54 | } 55 | 56 | type ResponseGetBook struct { 57 | ID int64 `json:"id"` 58 | Name string `json:"name"` 59 | Author string `json:"author"` 60 | Date time.Time `json:"date"` 61 | NumberOfPages int `json:"number_of_pages"` 62 | } 63 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/handlers/init.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 7 | ) 8 | 9 | type Handlers struct { 10 | Logger *slog.Logger 11 | service *usecases.UseCases 12 | } 13 | 14 | func New(logger *slog.Logger, service *usecases.UseCases) *Handlers { 15 | return &Handlers{ 16 | Logger: logger, 17 | service: service, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/init.go: -------------------------------------------------------------------------------- 1 | package http_adapter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "net/http" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter/handlers" 9 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter/router" 10 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 11 | http_server "github.com/rostislaved/go-clean-architecture/internal/libs/http-server" 12 | ) 13 | 14 | type HttpAdapter struct { 15 | server *http_server.Server 16 | } 17 | 18 | func New(logger *slog.Logger, config Config, svc *usecases.UseCases) *HttpAdapter { 19 | router := newRouter(logger, config, svc) 20 | 21 | s := http_server.New(logger, config.Server, router) 22 | 23 | return &HttpAdapter{ 24 | server: s, 25 | } 26 | } 27 | 28 | func newRouter(logger *slog.Logger, config Config, svc *usecases.UseCases) http.Handler { 29 | r := router.New() 30 | 31 | ctr := handlers.New(logger, svc) 32 | 33 | r.AppendRoutes(config.Router, ctr) 34 | 35 | router := r.Router() 36 | 37 | return router 38 | } 39 | 40 | func (a HttpAdapter) Start(ctx context.Context) error { 41 | return a.server.Start(ctx) 42 | } 43 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/router/init.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type Router struct { 11 | router *mux.Router 12 | config Config 13 | } 14 | 15 | type Config struct { 16 | Shutdown shutdown 17 | Timeout timeout 18 | AuthenticationConfig string `config:"envVar"` 19 | AuthorizationConfig string `config:"envVar"` 20 | } 21 | 22 | type shutdown struct { 23 | Duration time.Duration 24 | } 25 | 26 | type timeout struct { 27 | Duration time.Duration 28 | } 29 | 30 | func New() *Router { 31 | router := mux.NewRouter() 32 | 33 | r := Router{ 34 | router: router, 35 | } 36 | 37 | return &r 38 | } 39 | 40 | const ( 41 | apiV1Prefix = "/api/v1" 42 | ) 43 | 44 | type Route struct { 45 | Name string 46 | Method string 47 | Path string 48 | Handler http.Handler 49 | } 50 | 51 | func (r *Router) Router() http.Handler { 52 | return r.router 53 | } 54 | 55 | func (r *Router) appendRoutesToRouter(subrouter *mux.Router, routes []Route) { 56 | for _, route := range routes { 57 | subrouter. 58 | Methods(route.Method). 59 | Name(route.Name). 60 | Path(route.Path). 61 | Handler(route.Handler) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/http-adapter/router/routes.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter/handlers" 7 | middlewarehelpers "github.com/rostislaved/go-clean-architecture/internal/libs/middleware-helpers" 8 | ) 9 | 10 | func (r *Router) AppendRoutes(config Config, handlers *handlers.Handlers) { 11 | r.config = config 12 | 13 | apiV1Subrouter := r.router.PathPrefix(apiV1Prefix).Subrouter() 14 | 15 | routes := []Route{ 16 | { 17 | Name: "method1", 18 | Path: "/method1", 19 | Method: http.MethodPost, 20 | Handler: middlewarehelpers.And()(http.HandlerFunc(handlers.Get)), 21 | }, 22 | } 23 | 24 | r.appendRoutesToRouter(apiV1Subrouter, routes) 25 | } 26 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/init.go: -------------------------------------------------------------------------------- 1 | package kafka_adapter_subscriber 2 | 3 | import ( 4 | "log/slog" 5 | 6 | kafka_handlers "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/kafka-adapter-subscriber/kafka-handlers" 7 | kafka_queue "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/kafka-adapter-subscriber/kafka-queue" 8 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 9 | ) 10 | 11 | type KafkaAdapter struct { 12 | logger *slog.Logger 13 | config kafka_queue.Config 14 | kafkaQueue *kafka_queue.KafkaQueue 15 | kafkaController *kafka_handlers.KafkaHandlers 16 | } 17 | 18 | func New(l *slog.Logger, config kafka_queue.Config, svc *usecases.UseCases) *KafkaAdapter { 19 | kafkaQueue := kafka_queue.New(l, config) 20 | 21 | kafkaController := kafka_handlers.New(l, svc) 22 | 23 | return &KafkaAdapter{ 24 | logger: l, 25 | config: config, 26 | kafkaQueue: kafkaQueue, 27 | kafkaController: kafkaController, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/kafka-handlers/init.go: -------------------------------------------------------------------------------- 1 | package kafka_handlers 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 7 | ) 8 | 9 | type KafkaHandlers struct { 10 | Logger *slog.Logger 11 | service *usecases.UseCases 12 | } 13 | 14 | func New(logger *slog.Logger, service *usecases.UseCases) *KafkaHandlers { 15 | return &KafkaHandlers{ 16 | Logger: logger, 17 | service: service, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/kafka-handlers/methods.go: -------------------------------------------------------------------------------- 1 | package kafka_handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | func (ctr KafkaHandlers) SaveBooks(ctx context.Context, message []byte) (err error) { 12 | var request Request 13 | 14 | err = json.Unmarshal(message, &request) 15 | if err != nil { 16 | return 17 | } 18 | 19 | books := request.ToEntity() 20 | 21 | value, err := ctr.service.SaveBooks(ctx, books) 22 | if err != nil { 23 | return 24 | } 25 | 26 | _ = value 27 | 28 | return 29 | } 30 | 31 | type Request struct { 32 | RequestBooks []RequestBook `json:"books"` 33 | } 34 | 35 | type RequestBook struct { 36 | ID int64 `json:"id"` 37 | Name string `json:"name"` 38 | Author string `json:"author"` 39 | Date time.Time `json:"date"` 40 | NumberOfPages int `json:"number_of_pages"` 41 | } 42 | 43 | func (r Request) ToEntity() []book.Book { 44 | books := make([]book.Book, 0, len(r.RequestBooks)) 45 | 46 | for _, requestBook := range r.RequestBooks { 47 | books = append(books, book.Book(requestBook)) 48 | } 49 | 50 | return books 51 | } 52 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/kafka-queue/init.go: -------------------------------------------------------------------------------- 1 | package kafka_queue 2 | 3 | import ( 4 | "log/slog" 5 | "time" 6 | 7 | "github.com/segmentio/kafka-go" 8 | ) 9 | 10 | type KafkaQueue struct { 11 | logger *slog.Logger 12 | config Config 13 | kafkaReader *kafka.Reader 14 | } 15 | 16 | type Config struct { 17 | Host string 18 | GroupID string 19 | Topic string 20 | } 21 | 22 | func New( 23 | l *slog.Logger, 24 | cfg Config, 25 | ) *KafkaQueue { 26 | r := kafka.NewReader(kafka.ReaderConfig{ 27 | Brokers: []string{cfg.Host}, 28 | GroupID: cfg.GroupID, 29 | Topic: cfg.Topic, 30 | MinBytes: 10e3, // 10KB 31 | MaxBytes: 10e6, // 10MB 32 | MaxWait: 1 * time.Second, 33 | }) 34 | 35 | return &KafkaQueue{ 36 | logger: l, 37 | config: cfg, 38 | kafkaReader: r, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/kafka-queue/methods.go: -------------------------------------------------------------------------------- 1 | package kafka_queue 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | func (queue *KafkaQueue) Subscribe(businessLogicFunc func(context.Context, []byte) error) { 8 | for { 9 | err := queue.processOneMessage(businessLogicFunc) 10 | if err != nil { 11 | continue 12 | } 13 | } 14 | } 15 | 16 | func (queue *KafkaQueue) processOneMessage(businessLogicFunc func(context.Context, []byte) error) (err error) { 17 | ctx := context.TODO() 18 | 19 | message, err := queue.kafkaReader.FetchMessage(ctx) // Тут нельзя делать таймаут. Ибо, если очередь пуста, то тут мы тоже блокируемся и по таймауту получим ошибку. А должны ждать следующего сообщения 20 | if err != nil { 21 | return err 22 | } 23 | 24 | err = businessLogicFunc(ctx, message.Value) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | err = queue.kafkaReader.CommitMessages(ctx, message) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/kafka-adapter-subscriber/methods.go: -------------------------------------------------------------------------------- 1 | package kafka_adapter_subscriber 2 | 3 | import "context" 4 | 5 | func (a *KafkaAdapter) Start(ctx context.Context) error { 6 | a.kafkaQueue.Subscribe(a.kafkaController.SaveBooks) 7 | 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/nats-adapter-subscriber/config.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_subscriber 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Config struct { 8 | Connection Connection 9 | 10 | Subscriber1 Subscriber 11 | } 12 | 13 | type Connection struct { 14 | Host string `config:"envVar"` 15 | ClusterID string 16 | ClientID string 17 | AllowMultipleClients bool 18 | User string `config:"envVar"` 19 | Password string `config:"envVar"` 20 | } 21 | 22 | type Subscriber struct { 23 | Channel string 24 | QueueGroup string 25 | DurableName string 26 | MaxInflight int 27 | DeliverAllAvailable bool 28 | AckWaitTimeout time.Duration 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/nats-adapter-subscriber/init.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_subscriber 2 | 3 | import ( 4 | "log/slog" 5 | 6 | natsController "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/nats-adapter-subscriber/nats-handlers" 7 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 8 | ) 9 | 10 | type NatsAdapterSubscriber struct { 11 | logger *slog.Logger 12 | config Config 13 | subscriber subscriber 14 | svc *usecases.UseCases 15 | natsController *natsController.NatsHandlers 16 | } 17 | 18 | type subscriber interface { 19 | // Subscribe(cfg SubscriptionConfig) (*Subscription, error) 20 | } 21 | 22 | func New(logger *slog.Logger, config Config, svc *usecases.UseCases) *NatsAdapterSubscriber { 23 | natsController := natsController.New(logger, svc) 24 | 25 | return &NatsAdapterSubscriber{ 26 | logger: logger, 27 | config: config, 28 | // subscriber: a, 29 | svc: svc, 30 | natsController: natsController, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/nats-adapter-subscriber/methods.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_subscriber 2 | 3 | import "context" 4 | 5 | func (a *NatsAdapterSubscriber) Start(ctx context.Context) error { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/nats-adapter-subscriber/nats-handlers/init.go: -------------------------------------------------------------------------------- 1 | package nats_handlers 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 7 | ) 8 | 9 | type NatsHandlers struct { 10 | Logger *slog.Logger 11 | service *usecases.UseCases 12 | } 13 | 14 | func New(logger *slog.Logger, service *usecases.UseCases) *NatsHandlers { 15 | return &NatsHandlers{ 16 | Logger: logger, 17 | service: service, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/nats-adapter-subscriber/nats-handlers/methods.go: -------------------------------------------------------------------------------- 1 | package nats_handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | func (ctr NatsHandlers) SaveBooks(message []byte) (err error) { 12 | ctx := context.TODO() 13 | 14 | var request Request 15 | 16 | err = json.Unmarshal(message, &request) 17 | if err != nil { 18 | return 19 | } 20 | 21 | books := request.ToEntity() 22 | 23 | value, err := ctr.service.SaveBooks(ctx, books) 24 | if err != nil { 25 | return 26 | } 27 | 28 | _ = value 29 | 30 | return 31 | } 32 | 33 | type Request struct { 34 | RequestBooks []RequestBook `json:"books"` 35 | } 36 | 37 | type RequestBook struct { 38 | ID int64 `json:"id"` 39 | Name string `json:"name"` 40 | Author string `json:"author"` 41 | Date time.Time `json:"date"` 42 | NumberOfPages int `json:"number_of_pages"` 43 | } 44 | 45 | func (r Request) ToEntity() []book.Book { 46 | books := make([]book.Book, 0, len(r.RequestBooks)) 47 | 48 | for _, requestBook := range r.RequestBooks { 49 | books = append(books, book.Book(requestBook)) 50 | } 51 | 52 | return books 53 | } 54 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/os-signal-adapter/init.go: -------------------------------------------------------------------------------- 1 | package os_signal_adapter 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | type OsSignalAdapter struct{} 12 | 13 | func New() *OsSignalAdapter { 14 | return &OsSignalAdapter{} 15 | } 16 | 17 | func (a *OsSignalAdapter) Start(ctx context.Context) error { 18 | osSignCh := make(chan os.Signal, 1) 19 | 20 | signal.Notify(osSignCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 21 | 22 | select { 23 | case <-ctx.Done(): 24 | err := ctx.Err() 25 | 26 | return err 27 | case sig := <-osSignCh: 28 | err := fmt.Errorf("\nПолучен сигнал [%s]\n", sig.String()) //nolint:stylecheck 29 | 30 | return err 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/adapters/primary/pprof-adapter/init.go: -------------------------------------------------------------------------------- 1 | package pprof_adapter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "net/http/pprof" 7 | 8 | "github.com/gorilla/mux" 9 | http_server "github.com/rostislaved/go-clean-architecture/internal/libs/http-server" 10 | ) 11 | 12 | type PprofAdapter struct { 13 | server *http_server.Server 14 | } 15 | 16 | type Config struct { 17 | Server http_server.Config 18 | } 19 | 20 | func New(logger *slog.Logger, config Config) *PprofAdapter { 21 | router := newPprofRouter() 22 | 23 | s := http_server.New(logger, config.Server, router) 24 | 25 | return &PprofAdapter{ 26 | server: s, 27 | } 28 | } 29 | 30 | func newPprofRouter() *mux.Router { 31 | router := mux.NewRouter() 32 | 33 | router.HandleFunc("/debug/pprof/", pprof.Index) 34 | router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 35 | router.HandleFunc("/debug/pprof/profile", pprof.Profile) 36 | router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 37 | router.HandleFunc("/debug/pprof/trace", pprof.Trace) 38 | 39 | return router 40 | } 41 | 42 | func (a PprofAdapter) Start(ctx context.Context) error { 43 | return a.server.Start(ctx) 44 | } 45 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/gateways/books-gateway/config.go: -------------------------------------------------------------------------------- 1 | package books_gateway 2 | 3 | import providerhelpers "github.com/rostislaved/go-clean-architecture/internal/libs/provider-helpers" 4 | 5 | type Config struct { 6 | Host string 7 | Endpoints Endpoints 8 | } 9 | 10 | type Endpoints struct { 11 | SignFile providerhelpers.Endpoint 12 | GetCertificate providerhelpers.Endpoint 13 | } 14 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/gateways/books-gateway/init.go: -------------------------------------------------------------------------------- 1 | package books_gateway 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/go-resty/resty/v2" 7 | providerhelpers "github.com/rostislaved/go-clean-architecture/internal/libs/provider-helpers" 8 | ) 9 | 10 | type BooksGateway struct { 11 | logger *slog.Logger 12 | config Config 13 | client *resty.Client 14 | } 15 | 16 | func New( 17 | l *slog.Logger, 18 | config Config, 19 | ) *BooksGateway { 20 | err := providerhelpers.ValidateEndpoints(config.Endpoints) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | client := resty.New(). 26 | SetBaseURL(config.Host). 27 | // SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}). 28 | SetRetryCount(3) 29 | 30 | return &BooksGateway{ 31 | logger: l, 32 | config: config, 33 | client: client, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/gateways/books-gateway/methods.go: -------------------------------------------------------------------------------- 1 | package books_gateway 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | providerhelpers "github.com/rostislaved/go-clean-architecture/internal/libs/provider-helpers" 10 | ) 11 | 12 | func (prv *BooksGateway) GetBooks(ctx context.Context, input struct{}) (books []book.Book, err error) { 13 | ctx, cancel := context.WithTimeout(ctx, 60*time.Second) 14 | defer cancel() 15 | 16 | values := url.Values{ 17 | "param1": []string{"value1", "value2"}, 18 | "param2": []string{"value1"}, 19 | } 20 | 21 | req := providerhelpers.CreateRequest(ctx, prv.client, prv.config.Endpoints.SignFile) 22 | 23 | var request Request 24 | 25 | req. 26 | SetQueryParamsFromValues(values). 27 | SetBody(input). 28 | ForceContentType("application/json"). 29 | SetResult(&request) 30 | 31 | resp, err := req.Send() 32 | if err != nil { 33 | return 34 | } 35 | 36 | err = providerhelpers.ValidateStatusCode(resp.StatusCode(), resp.Body()) 37 | if err != nil { 38 | return 39 | } 40 | 41 | books = request.ToEntity() 42 | 43 | return books, nil 44 | } 45 | 46 | type Request struct { 47 | RequestBooks []RequestBook `json:"books"` 48 | } 49 | 50 | type RequestBook struct { 51 | ID int64 `json:"id"` 52 | Name string `json:"name"` 53 | Author string `json:"author"` 54 | Date time.Time `json:"date"` 55 | NumberOfPages int `json:"number_of_pages"` 56 | } 57 | 58 | func (r Request) ToEntity() []book.Book { 59 | books := make([]book.Book, 0, len(r.RequestBooks)) 60 | 61 | for _, requestBook := range r.RequestBooks { 62 | books = append(books, book.Book(requestBook)) 63 | } 64 | 65 | return books 66 | } 67 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/grpc-adapter/file.proto: -------------------------------------------------------------------------------- 1 | //protoc --go_out=. --go_opt=paths=import \ rostislav@rostislav-xps159520 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | syntax = "proto3"; 6 | package api; 7 | 8 | option go_package = "./generated"; 9 | 10 | message Message { 11 | string body = 1; 12 | } 13 | 14 | service Api { 15 | rpc SendMessage(Message) returns (Message); 16 | } 17 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/grpc-adapter/generated/file.pb.go: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. --go_opt=paths=import \ rostislav@rostislav-xps159520 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | // Code generated by protoc-gen-go. DO NOT EDIT. 6 | // versions: 7 | // protoc-gen-go v1.31.0 8 | // protoc v4.23.4 9 | // source: file.proto 10 | 11 | package generated 12 | 13 | import ( 14 | reflect "reflect" 15 | sync "sync" 16 | 17 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 18 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 19 | ) 20 | 21 | const ( 22 | // Verify that this generated code is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 24 | // Verify that runtime/protoimpl is sufficiently up-to-date. 25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 26 | ) 27 | 28 | type Message struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` 34 | } 35 | 36 | func (x *Message) Reset() { 37 | *x = Message{} 38 | if protoimpl.UnsafeEnabled { 39 | mi := &file_file_proto_msgTypes[0] 40 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 | ms.StoreMessageInfo(mi) 42 | } 43 | } 44 | 45 | func (x *Message) String() string { 46 | return protoimpl.X.MessageStringOf(x) 47 | } 48 | 49 | func (*Message) ProtoMessage() {} 50 | 51 | func (x *Message) ProtoReflect() protoreflect.Message { 52 | mi := &file_file_proto_msgTypes[0] 53 | if protoimpl.UnsafeEnabled && x != nil { 54 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 | if ms.LoadMessageInfo() == nil { 56 | ms.StoreMessageInfo(mi) 57 | } 58 | return ms 59 | } 60 | return mi.MessageOf(x) 61 | } 62 | 63 | // Deprecated: Use Message.ProtoReflect.Descriptor instead. 64 | func (*Message) Descriptor() ([]byte, []int) { 65 | return file_file_proto_rawDescGZIP(), []int{0} 66 | } 67 | 68 | func (x *Message) GetBody() string { 69 | if x != nil { 70 | return x.Body 71 | } 72 | return "" 73 | } 74 | 75 | var File_file_proto protoreflect.FileDescriptor 76 | 77 | var file_file_proto_rawDesc = []byte{ 78 | 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 79 | 0x69, 0x22, 0x1d, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 80 | 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 81 | 0x32, 0x30, 0x0a, 0x03, 0x41, 0x70, 0x69, 0x12, 0x29, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4d, 82 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x73, 83 | 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 84 | 0x67, 0x65, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 85 | 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 86 | } 87 | 88 | var ( 89 | file_file_proto_rawDescOnce sync.Once 90 | file_file_proto_rawDescData = file_file_proto_rawDesc 91 | ) 92 | 93 | func file_file_proto_rawDescGZIP() []byte { 94 | file_file_proto_rawDescOnce.Do(func() { 95 | file_file_proto_rawDescData = protoimpl.X.CompressGZIP(file_file_proto_rawDescData) 96 | }) 97 | return file_file_proto_rawDescData 98 | } 99 | 100 | var ( 101 | file_file_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 102 | file_file_proto_goTypes = []interface{}{ 103 | (*Message)(nil), // 0: api.Message 104 | } 105 | ) 106 | 107 | var file_file_proto_depIdxs = []int32{ 108 | 0, // 0: api.Api.SendMessage:input_type -> api.Message 109 | 0, // 1: api.Api.SendMessage:output_type -> api.Message 110 | 1, // [1:2] is the sub-list for method output_type 111 | 0, // [0:1] is the sub-list for method input_type 112 | 0, // [0:0] is the sub-list for extension type_name 113 | 0, // [0:0] is the sub-list for extension extendee 114 | 0, // [0:0] is the sub-list for field type_name 115 | } 116 | 117 | func init() { file_file_proto_init() } 118 | func file_file_proto_init() { 119 | if File_file_proto != nil { 120 | return 121 | } 122 | if !protoimpl.UnsafeEnabled { 123 | file_file_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 124 | switch v := v.(*Message); i { 125 | case 0: 126 | return &v.state 127 | case 1: 128 | return &v.sizeCache 129 | case 2: 130 | return &v.unknownFields 131 | default: 132 | return nil 133 | } 134 | } 135 | } 136 | type x struct{} 137 | out := protoimpl.TypeBuilder{ 138 | File: protoimpl.DescBuilder{ 139 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 140 | RawDescriptor: file_file_proto_rawDesc, 141 | NumEnums: 0, 142 | NumMessages: 1, 143 | NumExtensions: 0, 144 | NumServices: 1, 145 | }, 146 | GoTypes: file_file_proto_goTypes, 147 | DependencyIndexes: file_file_proto_depIdxs, 148 | MessageInfos: file_file_proto_msgTypes, 149 | }.Build() 150 | File_file_proto = out.File 151 | file_file_proto_rawDesc = nil 152 | file_file_proto_goTypes = nil 153 | file_file_proto_depIdxs = nil 154 | } 155 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/grpc-adapter/generated/file_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // protoc --go_out=. --go_opt=paths=import \ rostislav@rostislav-xps159520 2 | // --go-grpc_out=. --go-grpc_opt=paths=import \ 3 | // file.proto 4 | 5 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 6 | // versions: 7 | // - protoc-gen-go-grpc v1.3.0 8 | // - protoc v4.23.4 9 | // source: file.proto 10 | 11 | package generated 12 | 13 | import ( 14 | context "context" 15 | 16 | grpc "google.golang.org/grpc" 17 | codes "google.golang.org/grpc/codes" 18 | status "google.golang.org/grpc/status" 19 | ) 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the grpc package it is being compiled against. 23 | // Requires gRPC-Go v1.32.0 or later. 24 | const _ = grpc.SupportPackageIsVersion7 25 | 26 | const ( 27 | Api_SendMessage_FullMethodName = "/api.Api/SendMessage" 28 | ) 29 | 30 | // ApiClient is the client API for Api usecases. 31 | // 32 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 33 | type ApiClient interface { 34 | SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) 35 | } 36 | 37 | type apiClient struct { 38 | cc grpc.ClientConnInterface 39 | } 40 | 41 | func NewApiClient(cc grpc.ClientConnInterface) ApiClient { 42 | return &apiClient{cc} 43 | } 44 | 45 | func (c *apiClient) SendMessage(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { 46 | out := new(Message) 47 | err := c.cc.Invoke(ctx, Api_SendMessage_FullMethodName, in, out, opts...) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return out, nil 52 | } 53 | 54 | // ApiServer is the server API for Api usecases. 55 | // All implementations must embed UnimplementedApiServer 56 | // for forward compatibility 57 | type ApiServer interface { 58 | SendMessage(context.Context, *Message) (*Message, error) 59 | mustEmbedUnimplementedApiServer() 60 | } 61 | 62 | // UnimplementedApiServer must be embedded to have forward compatible implementations. 63 | type UnimplementedApiServer struct{} 64 | 65 | func (UnimplementedApiServer) SendMessage(context.Context, *Message) (*Message, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented") 67 | } 68 | func (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {} 69 | 70 | // UnsafeApiServer may be embedded to opt out of forward compatibility for this usecases. 71 | // Use of this interface is not recommended, as added methods to ApiServer will 72 | // result in compilation errors. 73 | type UnsafeApiServer interface { 74 | mustEmbedUnimplementedApiServer() 75 | } 76 | 77 | func RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) { 78 | s.RegisterService(&Api_ServiceDesc, srv) 79 | } 80 | 81 | func _Api_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 82 | in := new(Message) 83 | if err := dec(in); err != nil { 84 | return nil, err 85 | } 86 | if interceptor == nil { 87 | return srv.(ApiServer).SendMessage(ctx, in) 88 | } 89 | info := &grpc.UnaryServerInfo{ 90 | Server: srv, 91 | FullMethod: Api_SendMessage_FullMethodName, 92 | } 93 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 94 | return srv.(ApiServer).SendMessage(ctx, req.(*Message)) 95 | } 96 | return interceptor(ctx, in, info, handler) 97 | } 98 | 99 | // Api_ServiceDesc is the grpc.ServiceDesc for Api usecases. 100 | // It's only intended for direct use with grpc.RegisterService, 101 | // and not to be introspected or modified (even as a copy) 102 | var Api_ServiceDesc = grpc.ServiceDesc{ 103 | ServiceName: "api.Api", 104 | HandlerType: (*ApiServer)(nil), 105 | Methods: []grpc.MethodDesc{ 106 | { 107 | MethodName: "SendMessage", 108 | Handler: _Api_SendMessage_Handler, 109 | }, 110 | }, 111 | Streams: []grpc.StreamDesc{}, 112 | Metadata: "file.proto", 113 | } 114 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/grpc-adapter/init.go: -------------------------------------------------------------------------------- 1 | package grpcAdapter 2 | 3 | import ( 4 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/grpc-adapter/generated" 5 | "google.golang.org/grpc" 6 | "google.golang.org/grpc/credentials/insecure" 7 | ) 8 | 9 | type GrpcAdapter struct { 10 | client generated.ApiClient 11 | } 12 | 13 | func New() *GrpcAdapter { 14 | conn, err := grpc.Dial(":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | client := generated.NewApiClient(conn) 20 | 21 | return &GrpcAdapter{ 22 | client: client, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/grpc-adapter/methods.go: -------------------------------------------------------------------------------- 1 | package grpcAdapter 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/grpc-adapter/generated" 8 | ) 9 | 10 | func (a GrpcAdapter) SendMessage(msg string) (resp string, err error) { 11 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 12 | defer cancel() 13 | 14 | message := &generated.Message{Body: msg} 15 | 16 | r, err := a.client.SendMessage(ctx, message) 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | return r.GetBody(), nil 22 | } 23 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/kafka-adapter-publisher/config.go: -------------------------------------------------------------------------------- 1 | package kafka_adapter_publisher 2 | 3 | type Config struct{} 4 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/kafka-adapter-publisher/init.go: -------------------------------------------------------------------------------- 1 | package kafka_adapter_publisher 2 | 3 | import ( 4 | "log/slog" 5 | 6 | "github.com/segmentio/kafka-go" 7 | ) 8 | 9 | type KafkaAdapterPublisher struct { 10 | logger *slog.Logger 11 | config Config 12 | writer *kafka.Writer 13 | } 14 | 15 | func New(logger *slog.Logger, config Config) *KafkaAdapterPublisher { 16 | w := kafka.Writer{ 17 | // TODO Брать поля из конфига 18 | } 19 | 20 | return &KafkaAdapterPublisher{ 21 | logger: logger, 22 | config: config, 23 | writer: &w, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/kafka-adapter-publisher/methods.go: -------------------------------------------------------------------------------- 1 | package kafka_adapter_publisher 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | "github.com/segmentio/kafka-go" 10 | ) 11 | 12 | func (a *KafkaAdapterPublisher) SendBook(ctx context.Context, b book.Book) error { 13 | r := Request(b) 14 | 15 | bookJSONBytes, err := json.Marshal(r) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | message := kafka.Message{ 21 | Key: []byte("Key"), 22 | Value: bookJSONBytes, 23 | } 24 | 25 | err = a.writer.WriteMessages(ctx, message) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | type Request struct { 34 | ID int64 `json:"id"` 35 | Name string `json:"name"` 36 | Author string `json:"author"` 37 | Date time.Time `json:"date"` 38 | NumberOfPages int `json:"number_of_pages"` 39 | } 40 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/nats-adapter-publisher/config.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_publisher 2 | 3 | type Config struct { 4 | Connection Connection 5 | 6 | Publisher1 Publisher 7 | } 8 | 9 | type Connection struct { 10 | Host string `config:"envVar"` 11 | ClusterID string 12 | ClientID string 13 | AllowMultipleClients bool 14 | User string `config:"envVar"` 15 | Password string `config:"envVar"` 16 | } 17 | 18 | type Publisher struct { 19 | Channel string 20 | } 21 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/nats-adapter-publisher/init.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_publisher 2 | 3 | import ( 4 | "log/slog" 5 | ) 6 | 7 | type NatsAdapterPublisher struct { 8 | logger *slog.Logger 9 | config Config 10 | publisher publisher 11 | } 12 | 13 | type publisher interface { 14 | Publish(channel string, data []byte) error 15 | } 16 | 17 | func New(logger *slog.Logger, config Config) *NatsAdapterPublisher { 18 | return &NatsAdapterPublisher{ 19 | logger: logger, 20 | config: config, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/nats-adapter-publisher/methods.go: -------------------------------------------------------------------------------- 1 | package nats_adapter_publisher 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | func (a *NatsAdapterPublisher) SendBook(ctx context.Context, b book.Book) error { 12 | r := Request(b) 13 | 14 | bookJSONBytes, err := json.Marshal(r) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | err = a.publisher.Publish(a.config.Publisher1.Channel, bookJSONBytes) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return nil 25 | } 26 | 27 | type Request struct { 28 | ID int64 `json:"id"` 29 | Name string `json:"name"` 30 | Author string `json:"author"` 31 | Date time.Time `json:"date"` 32 | NumberOfPages int `json:"number_of_pages"` 33 | } 34 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-clickhouse/init.go: -------------------------------------------------------------------------------- 1 | package books_repository_clickhouse 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "log/slog" 9 | "time" 10 | 11 | _ "github.com/mailru/go-clickhouse/v2" 12 | 13 | "github.com/rostislaved/go-clean-architecture/internal/libs/helpers" 14 | "github.com/rostislaved/go-clean-architecture/internal/libs/repo-helpers" 15 | ) 16 | 17 | type BooksRepositoryClickhouse struct { 18 | logger *slog.Logger 19 | config Config 20 | DB *sql.DB 21 | } 22 | 23 | type Config struct { 24 | Type string 25 | Host string `config:"envVar"` 26 | Port string `config:"envVar"` 27 | User string `config:"envVar"` 28 | Password string `config:"envVar"` 29 | Name string 30 | Procedures map[string]string 31 | } 32 | 33 | func New(l *slog.Logger, cfg Config) *BooksRepositoryClickhouse { 34 | currentHostString := fmt.Sprintf("DB host: [%s:%s].", cfg.Host, cfg.Port) 35 | 36 | log.Println(currentHostString + " Подключение...") 37 | l.Info(currentHostString+" Подключение...", "source", helpers.GetFunctionName()) 38 | 39 | connectionString := repo_helpers.GetConnectionString(cfg.Type, cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name) 40 | 41 | db, err := sql.Open(cfg.Type, connectionString) 42 | if err != nil { 43 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 44 | 45 | panic(err) 46 | } 47 | 48 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 49 | defer cancel() 50 | 51 | err = db.PingContext(ctx) 52 | if err != nil { 53 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 54 | 55 | panic(err) 56 | } 57 | 58 | log.Println(currentHostString + " Подключено!") 59 | l.Info(currentHostString+" Подключено!", "source", helpers.GetFunctionName()) 60 | 61 | return &BooksRepositoryClickhouse{ 62 | logger: l, 63 | config: cfg, 64 | DB: db, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-clickhouse/methods.go: -------------------------------------------------------------------------------- 1 | package books_repository_clickhouse 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | func (repo *BooksRepositoryClickhouse) Get(ids []int64) (books []book.Book, err error) { 12 | queryString := repo.getGetSomethingQuery(ids) 13 | 14 | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) 15 | defer cancel() 16 | 17 | rows, err := repo.DB.QueryContext(ctx, queryString) 18 | if err != nil { 19 | return 20 | } 21 | 22 | books, err = scanGetSomething(rows) 23 | if err != nil { 24 | return 25 | } 26 | 27 | return books, nil 28 | } 29 | 30 | func scanGetSomething(rows *sql.Rows) ([]book.Book, error) { 31 | return nil, nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-clickhouse/queries.go: -------------------------------------------------------------------------------- 1 | package books_repository_clickhouse 2 | 3 | func (repo *BooksRepositoryClickhouse) getGetSomethingQuery(shkIDs []int64) string { 4 | query := `SELECT * FROM table` 5 | 6 | return query 7 | } 8 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-mongo/init.go: -------------------------------------------------------------------------------- 1 | package books_repository_mongo 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/libs/helpers" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | "go.mongodb.org/mongo-driver/mongo/options" 11 | ) 12 | 13 | type BooksRepositoryMongo struct { 14 | logger *slog.Logger 15 | config Config 16 | DB *mongo.Database 17 | } 18 | 19 | func New(l *slog.Logger, cfg Config) *BooksRepositoryMongo { 20 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 21 | defer cancel() 22 | 23 | credential := options.Credential{ 24 | Username: cfg.User, 25 | Password: cfg.Password, 26 | } 27 | 28 | clientOptions := options.Client().ApplyURI(cfg.Host).SetAuth(credential) 29 | 30 | client, err := mongo.Connect(ctx, clientOptions) 31 | if err != nil { 32 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 33 | 34 | panic(err) 35 | } 36 | 37 | err = client.Ping(ctx, nil) 38 | if err != nil { 39 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 40 | 41 | panic(err) 42 | } 43 | 44 | db := client.Database(cfg.Name) 45 | 46 | return &BooksRepositoryMongo{ 47 | logger: l, 48 | config: cfg, 49 | DB: db, 50 | } 51 | } 52 | 53 | type Config struct { 54 | Name string 55 | Host string `config:"envVar"` 56 | User string `config:"envVar"` 57 | Password string `config:"envVar"` 58 | } 59 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-mongo/methods.go: -------------------------------------------------------------------------------- 1 | package books_repository_mongo 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | "go.mongodb.org/mongo-driver/bson" 10 | ) 11 | 12 | func (repo *BooksRepositoryMongo) Get(ids []int64) ([]book.Book, error) { 13 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 14 | defer cancel() 15 | 16 | doc := bson.D{ 17 | { 18 | Key: "id", 19 | Value: bson.D{ 20 | { 21 | Key: "$in", 22 | Value: ids, 23 | }, 24 | }, 25 | }, 26 | } 27 | 28 | collection := repo.DB.Collection("book_collection") 29 | 30 | cursor, err := collection.Find(ctx, doc) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | defer func() { 36 | errC := cursor.Close(ctx) 37 | if errC != nil { 38 | log.Println(errC) 39 | } 40 | }() 41 | 42 | var books []book.Book 43 | 44 | err = cursor.All(ctx, &books) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return books, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-postgres/dto.go: -------------------------------------------------------------------------------- 1 | package books_repository_postgres 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 7 | ) 8 | 9 | type BookDTO struct { 10 | ID sql.NullInt64 11 | Name sql.NullString 12 | Author sql.NullString 13 | Date sql.NullTime 14 | NumberOfPages sql.NullString 15 | } 16 | 17 | func (dto *BookDTO) ToEntity() (book.Book, error) { 18 | // add fields validation if necessary 19 | return book.Book{ 20 | ID: dto.ID.Int64, 21 | Name: dto.Name.String, 22 | Author: dto.Author.String, 23 | Date: dto.Date.Time, 24 | NumberOfPages: 0, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-postgres/init.go: -------------------------------------------------------------------------------- 1 | package books_repository_postgres 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "log/slog" 8 | "time" 9 | 10 | _ "github.com/denisenkom/go-mssqldb" 11 | sql "github.com/jmoiron/sqlx" 12 | _ "github.com/lib/pq" 13 | 14 | "github.com/rostislaved/go-clean-architecture/internal/libs/helpers" 15 | "github.com/rostislaved/go-clean-architecture/internal/libs/repo-helpers" 16 | ) 17 | 18 | type BooksRepositoryPostgres struct { 19 | logger *slog.Logger 20 | config Config 21 | DB *sql.DB 22 | } 23 | 24 | func New(l *slog.Logger, cfg Config) *BooksRepositoryPostgres { 25 | currentHostString := fmt.Sprintf("DB host: [%s:%s].", cfg.Host, cfg.Port) 26 | 27 | log.Println(currentHostString + " Подключение...") 28 | l.Info(currentHostString+" Подключение...", "source", helpers.GetFunctionName()) 29 | 30 | connectionString := repo_helpers.GetConnectionString(cfg.Type, cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name) 31 | 32 | db, err := sql.Open(cfg.Type, connectionString) 33 | if err != nil { 34 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 35 | 36 | panic(err) 37 | } 38 | 39 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 40 | defer cancel() 41 | 42 | err = db.PingContext(ctx) 43 | if err != nil { 44 | l.Error(err.Error(), "source", helpers.GetFunctionName()) 45 | 46 | panic(err) 47 | } 48 | 49 | log.Println(currentHostString + " Подключено!") 50 | l.Info(currentHostString+" Подключено!", "source", helpers.GetFunctionName()) 51 | 52 | return &BooksRepositoryPostgres{ 53 | logger: l, 54 | config: cfg, 55 | DB: db, 56 | } 57 | } 58 | 59 | type Config struct { 60 | Type string 61 | Host string `config:"envVar"` 62 | Port string `config:"envVar"` 63 | User string `config:"envVar"` 64 | Password string `config:"envVar"` 65 | Name string 66 | Procedures map[string]string 67 | } 68 | -------------------------------------------------------------------------------- /internal/app/adapters/secondary/repositories/books-repository-postgres/methods.go: -------------------------------------------------------------------------------- 1 | package books_repository_postgres 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | 8 | sq "github.com/Masterminds/squirrel" 9 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 10 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 11 | ) 12 | 13 | func (repo *BooksRepositoryPostgres) Get(ctx context.Context, ids []int) (books []book.Book, err error) { 14 | psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar) 15 | 16 | builder := psql. 17 | Select( 18 | "name", 19 | "author", 20 | "price", 21 | ). 22 | From("services.bu_entry_get()"). 23 | Where(sq.Eq{"id": ids}) 24 | 25 | query, args, err := builder.ToSql() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | err = repo.DB.SelectContext(ctx, &books, query, args...) 31 | if err != nil { 32 | if errors.Is(err, sql.ErrNoRows) { 33 | return nil, usecases.ErrNotFound 34 | } 35 | 36 | return nil, err 37 | } 38 | 39 | return 40 | } 41 | 42 | func (repo *BooksRepositoryPostgres) Save(ctx context.Context, books []book.Book) (ids []int, err error) { 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /internal/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "log/slog" 5 | 6 | http_adapter "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter" 7 | kafka_adapter_subscriber "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/kafka-adapter-subscriber" 8 | nats_adapter_subscriber "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/nats-adapter-subscriber" 9 | pprof_adapter "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/pprof-adapter" 10 | books_gateway "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/gateways/books-gateway" 11 | kafka_adapter_publisher "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/kafka-adapter-publisher" 12 | nats_adapter_publisher "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/nats-adapter-publisher" 13 | books_repository_postgres "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/repositories/books-repository-postgres" 14 | "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 15 | "github.com/rostislaved/go-clean-architecture/internal/app/config" 16 | ) 17 | 18 | type App struct { 19 | HttpAdapter *http_adapter.HttpAdapter 20 | PprofAdapter *pprof_adapter.PprofAdapter 21 | NatsAdapterSubscriber *nats_adapter_subscriber.NatsAdapterSubscriber 22 | KafkaAdapterSubscriber *kafka_adapter_subscriber.KafkaAdapter 23 | } 24 | 25 | func New(l *slog.Logger, cfg config.Config) App { 26 | booksRepository := books_repository_postgres.New(l, cfg.Adapters.Secondary.Databases.Postgres) 27 | gateway := books_gateway.New(l, cfg.Adapters.Secondary.Gateways.BooksGateway) 28 | natsAdapterPublisher := nats_adapter_publisher.New(l, cfg.Adapters.Secondary.NatsAdapterPublisher) 29 | kafkaAdapterPublisher := kafka_adapter_publisher.New(l, cfg.Adapters.Secondary.KafkaAdapterPublisher) 30 | 31 | usecases := usecases.New( 32 | l, 33 | cfg.Application.UseCases, 34 | booksRepository, 35 | gateway, 36 | natsAdapterPublisher, 37 | kafkaAdapterPublisher, 38 | ) 39 | 40 | httpAdapter := http_adapter.New(l, cfg.Adapters.Primary.HttpAdapter, usecases) 41 | pprofAdapter := pprof_adapter.New(l, cfg.Adapters.Primary.PprofAdapter) 42 | natsAdapterSubscriber := nats_adapter_subscriber.New(l, cfg.Adapters.Primary.NatsAdapterSubscriber, usecases) 43 | kafkaAdapter := kafka_adapter_subscriber.New(l, cfg.Adapters.Primary.KafkaAdapterSubscriber, usecases) 44 | 45 | return App{ 46 | HttpAdapter: httpAdapter, 47 | PprofAdapter: pprofAdapter, 48 | NatsAdapterSubscriber: natsAdapterSubscriber, 49 | KafkaAdapterSubscriber: kafkaAdapter, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/app/application/usecases/init.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "time" 7 | 8 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 9 | ) 10 | 11 | type UseCases struct { 12 | logger *slog.Logger 13 | config Config 14 | booksRepository booksRepository 15 | provider provider 16 | kafkaAdapterPublisher bookSender 17 | natsAdapterPublisher bookSender 18 | } 19 | 20 | type Config struct { 21 | UpdateInterval time.Duration 22 | } 23 | 24 | type booksRepository interface { 25 | Get(ctx context.Context, ids []int) (books []book.Book, err error) 26 | Save(ctx context.Context, books []book.Book) (ids []int, err error) 27 | } 28 | 29 | type bookSender interface { 30 | SendBook(ctx context.Context, b book.Book) error 31 | } 32 | 33 | type provider interface{} 34 | 35 | func New( 36 | l *slog.Logger, 37 | cfg Config, 38 | repository booksRepository, 39 | provider provider, 40 | kafkaAdapterPublisher bookSender, 41 | natsAdapterPublisher bookSender, 42 | ) *UseCases { 43 | return &UseCases{ 44 | logger: l, 45 | config: cfg, 46 | booksRepository: repository, 47 | provider: provider, 48 | kafkaAdapterPublisher: kafkaAdapterPublisher, 49 | natsAdapterPublisher: natsAdapterPublisher, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/app/application/usecases/methods.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/rostislaved/go-clean-architecture/internal/app/domain/book" 8 | ) 9 | 10 | var ErrNotFound = errors.New("not found") 11 | 12 | func (svc *UseCases) GetBooksByIDs(ctx context.Context, ids []int) (books []book.Book, err error) { 13 | books, err = svc.booksRepository.Get(ctx, ids) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | return books, nil 19 | } 20 | 21 | func (svc *UseCases) SaveBooks(ctx context.Context, books []book.Book) (ids []int, err error) { 22 | ids, err = svc.booksRepository.Save(ctx, books) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return ids, nil 28 | } 29 | -------------------------------------------------------------------------------- /internal/app/config/adapters.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | httpAdapter "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/http-adapter" 5 | kafka_queue "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/kafka-adapter-subscriber/kafka-queue" 6 | nats_adapter_subscriber "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/nats-adapter-subscriber" 7 | pprofAdapter "github.com/rostislaved/go-clean-architecture/internal/app/adapters/primary/pprof-adapter" 8 | books_gateway "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/gateways/books-gateway" 9 | kafka_adapter_publisher "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/kafka-adapter-publisher" 10 | nats_adapter_publisher "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/nats-adapter-publisher" 11 | books_repository_clickhouse "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/repositories/books-repository-clickhouse" 12 | books_repository_mongo "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/repositories/books-repository-mongo" 13 | books_repository_postgres "github.com/rostislaved/go-clean-architecture/internal/app/adapters/secondary/repositories/books-repository-postgres" 14 | ) 15 | 16 | type Adapters struct { 17 | Primary Primary 18 | Secondary Secondary 19 | } 20 | 21 | type Primary struct { 22 | HttpAdapter httpAdapter.Config 23 | PprofAdapter pprofAdapter.Config 24 | NatsAdapterSubscriber nats_adapter_subscriber.Config 25 | KafkaAdapterSubscriber kafka_queue.Config 26 | } 27 | 28 | type Secondary struct { 29 | NatsAdapterPublisher nats_adapter_publisher.Config 30 | KafkaAdapterPublisher kafka_adapter_publisher.Config 31 | Databases Databases 32 | Gateways Gateways 33 | } 34 | 35 | type Databases struct { 36 | Postgres books_repository_postgres.Config 37 | Clickhouse books_repository_clickhouse.Config 38 | Mongo books_repository_mongo.Config 39 | } 40 | 41 | type Gateways struct { 42 | BooksGateway books_gateway.Config 43 | } 44 | -------------------------------------------------------------------------------- /internal/app/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/rostislaved/go-clean-architecture/internal/app/application/usecases" 4 | 5 | type Config struct { 6 | Info Info 7 | Application Application 8 | Adapters Adapters 9 | } 10 | type Info struct { 11 | Name string 12 | Version string 13 | } 14 | 15 | type Application struct { 16 | UseCases usecases.Config 17 | } 18 | -------------------------------------------------------------------------------- /internal/app/config/init.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | func New() (config Config) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /internal/app/domain/book/book.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | import "time" 4 | 5 | type Book struct { 6 | ID int64 7 | Name string 8 | Author string 9 | Date time.Time 10 | NumberOfPages int 11 | } 12 | -------------------------------------------------------------------------------- /internal/libs/graceful/graceful.go: -------------------------------------------------------------------------------- 1 | package graceful 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | type starter interface { 11 | Start(ctx context.Context) error 12 | } 13 | 14 | type graceful struct { 15 | processes []process 16 | logger *slog.Logger 17 | } 18 | 19 | func New(processes ...process) *graceful { 20 | return &graceful{ 21 | processes: processes, 22 | logger: slog.Default(), 23 | } 24 | } 25 | 26 | func (gr *graceful) Start(ctx context.Context) error { 27 | g, ctx := errgroup.WithContext(ctx) 28 | 29 | for _, process := range gr.processes { 30 | process := process // TODO remove if go > 1.22 31 | 32 | if process.disabled { 33 | continue 34 | } 35 | 36 | f := func() error { 37 | err := process.starter.Start(ctx) 38 | if err != nil { 39 | gr.logger.Error(err.Error()) 40 | gr.logger.Info("Start graceful shutdown") 41 | 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | 48 | g.Go(f) 49 | } 50 | 51 | err := g.Wait() 52 | if err != nil { 53 | gr.logger.Info("Application stopped gracefully") 54 | 55 | return err 56 | } 57 | 58 | gr.logger.Info("Every process stopped by itself with no error") 59 | 60 | return nil 61 | } 62 | 63 | func (gr *graceful) SetLogger(l *slog.Logger) { 64 | gr.logger = l 65 | } 66 | -------------------------------------------------------------------------------- /internal/libs/graceful/process.go: -------------------------------------------------------------------------------- 1 | package graceful 2 | 3 | type process struct { 4 | starter starter 5 | disabled bool 6 | } 7 | 8 | func NewProcess(starter starter) process { 9 | return process{ 10 | starter: starter, 11 | disabled: false, 12 | } 13 | } 14 | 15 | func (p process) Disable(d bool) process { 16 | p.disabled = d 17 | 18 | return p 19 | } 20 | -------------------------------------------------------------------------------- /internal/libs/helpers/helpers.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | const ( 8 | defaultDepth = 1 9 | defaultIndex = 0 10 | ) 11 | 12 | // GetFunctionName Возвращает имяПакета.ИмяФункции. 13 | func GetFunctionName(depthList ...int) string { //nolint:unused // helper func 14 | var depth int 15 | 16 | if depthList == nil { 17 | depth = defaultDepth 18 | } else { 19 | depth = depthList[defaultIndex] 20 | } 21 | 22 | function, _, _, ok := runtime.Caller(depth) 23 | if !ok { 24 | return "Не удалось получить имя функции" 25 | } 26 | 27 | return runtime.FuncForPC(function).Name() 28 | } 29 | -------------------------------------------------------------------------------- /internal/libs/http-server/server.go: -------------------------------------------------------------------------------- 1 | package http_server 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log/slog" 7 | "net/http" 8 | "time" 9 | 10 | "golang.org/x/sync/errgroup" 11 | ) 12 | 13 | type Server struct { 14 | logger *slog.Logger 15 | server *http.Server 16 | config Config 17 | } 18 | 19 | type Config struct { 20 | Port string 21 | StartMsg string 22 | ReadHeaderTimeout time.Duration 23 | WriteTimeout time.Duration 24 | ReadTimeout time.Duration 25 | ShutdownTimeout time.Duration 26 | } 27 | 28 | func New(logger *slog.Logger, config Config, handler http.Handler) *Server { 29 | server := &http.Server{ 30 | Handler: handler, 31 | ReadTimeout: config.ReadTimeout, 32 | WriteTimeout: config.WriteTimeout, 33 | ReadHeaderTimeout: config.ReadHeaderTimeout, 34 | Addr: config.Port, 35 | } 36 | 37 | s := Server{ 38 | logger: logger, 39 | server: server, 40 | } 41 | 42 | return &s 43 | } 44 | 45 | func (a *Server) Start(ctx context.Context) error { 46 | a.logger.Info(a.config.StartMsg) 47 | 48 | g, ctx := errgroup.WithContext(ctx) 49 | 50 | g.Go(func() error { 51 | <-ctx.Done() 52 | 53 | ctx, cancel := context.WithTimeout(context.Background(), a.config.ShutdownTimeout) 54 | defer cancel() 55 | 56 | err := a.server.Shutdown(ctx) //nolint:contextcheck // sic 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return nil 62 | }) 63 | 64 | g.Go(func() error { 65 | err := a.server.ListenAndServe() 66 | if err != nil { 67 | if errors.Is(err, http.ErrServerClosed) { 68 | // ok 69 | } else { 70 | return err 71 | } 72 | } 73 | 74 | return nil 75 | }) 76 | 77 | err := g.Wait() 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /internal/libs/middleware-helpers/middleware_helpers.go: -------------------------------------------------------------------------------- 1 | package middlewarehelpers 2 | 3 | import "net/http" 4 | 5 | type middleware = func(f http.Handler) http.Handler 6 | 7 | func And(middlewares ...middleware) middleware { 8 | return func(h http.Handler) http.Handler { 9 | wrapped := h 10 | 11 | for _, middleware := range middlewares { 12 | wrapped = middleware(wrapped) 13 | } 14 | 15 | return wrapped 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/libs/provider-helpers/provider_helpers.go: -------------------------------------------------------------------------------- 1 | package providerhelpers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | 10 | resty "github.com/go-resty/resty/v2" 11 | "go.uber.org/multierr" 12 | "moul.io/http2curl" 13 | ) 14 | 15 | var RetryCondition = func(r *resty.Response, err error) bool { 16 | // retry if return is true 17 | 18 | if err != nil { 19 | return true 20 | } 21 | 22 | switch r.StatusCode() { 23 | case 24 | // 400: 25 | http.StatusRequestTimeout, 26 | http.StatusConflict, 27 | http.StatusTooManyRequests, 28 | 29 | // 500: 30 | http.StatusInternalServerError, 31 | http.StatusBadGateway, 32 | http.StatusServiceUnavailable, 33 | http.StatusGatewayTimeout: 34 | 35 | return true 36 | } 37 | 38 | return false 39 | } 40 | 41 | type Endpoint struct { 42 | Method string 43 | Path string 44 | Headers map[string]string 45 | } 46 | 47 | func CreateRequest(ctx context.Context, client *resty.Client, endpoint Endpoint) *resty.Request { 48 | req := client.R() 49 | 50 | req.Method = endpoint.Method 51 | req.URL = endpoint.Path 52 | 53 | req.SetContext(ctx) 54 | 55 | return req 56 | } 57 | 58 | func ValidateEndpoints(endpoints interface{}) (err error) { 59 | refValue := reflect.ValueOf(endpoints) 60 | 61 | n := refValue.NumField() 62 | 63 | var combinedErr error 64 | 65 | for i := 0; i < n; i++ { 66 | fieldName := refValue.Type().Field(i).Name 67 | 68 | fieldInterface := refValue.FieldByName(fieldName).Interface() 69 | 70 | err := ValidateEndpoint(fieldInterface) 71 | if err != nil { 72 | errString := fmt.Sprintf("\nэндпоинт: [%v] заполнен некорректно: %s", refValue.Type().Field(i).Name, err.Error()) 73 | err := errors.New(errString) 74 | 75 | combinedErr = multierr.Append(combinedErr, err) 76 | } 77 | } 78 | 79 | if combinedErr != nil { 80 | return combinedErr 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func ValidateEndpoint(endpoint interface{}) error { 87 | //method, ok1 := reflect.TypeOf(endpoint).FieldByName("Method") 88 | //path, ok2 := reflect.TypeOf(endpoint).FieldByName("Path") 89 | //if !(ok1 && ok2) { 90 | // return nil 91 | //} 92 | e, ok := endpoint.(Endpoint) 93 | if !ok { 94 | return nil 95 | } 96 | 97 | methodValid := allHttpMethods[e.Method] 98 | methodNotValid := !methodValid 99 | 100 | pathIsEmpty := e.Path == "" 101 | 102 | if methodNotValid || pathIsEmpty { 103 | errString := "" 104 | 105 | if methodNotValid { 106 | errString = errString + fmt.Sprintf("\nМетод: [%s] не распознан", e.Method) 107 | } 108 | 109 | if pathIsEmpty { 110 | errString = errString + fmt.Sprintf("\nПуть не может быть пустым") 111 | } 112 | 113 | err := errors.New(errString) 114 | 115 | return err 116 | } 117 | 118 | return nil 119 | } 120 | 121 | var allHttpMethods = map[string]bool{ 122 | http.MethodGet: true, 123 | http.MethodHead: true, 124 | http.MethodPost: true, 125 | http.MethodPut: true, 126 | http.MethodPatch: true, 127 | http.MethodDelete: true, 128 | http.MethodConnect: true, 129 | http.MethodOptions: true, 130 | http.MethodTrace: true, 131 | } 132 | 133 | func ValidateStatusCode(receivedStatusCode int, body []byte) (err error) { 134 | switch getStatusCodeGroup(receivedStatusCode) { 135 | case "1xx": 136 | // 137 | case "2xx": 138 | if receivedStatusCode == http.StatusOK { 139 | return 140 | } 141 | 142 | return 143 | case "3xx": 144 | // 145 | case "4xx": 146 | switch receivedStatusCode { 147 | case http.StatusBadRequest: 148 | err = fmt.Errorf("получен статускод [%v]. Тело ответа: [%s]", receivedStatusCode, string(body)) 149 | 150 | return 151 | case http.StatusNotFound: 152 | err = fmt.Errorf("получен статускод [%v]. Тело ответа: [%s]", receivedStatusCode, string(body)) 153 | 154 | return 155 | } 156 | 157 | case "5xx": 158 | // 159 | default: // 160 | } 161 | 162 | err = fmt.Errorf("получен статускод [%v]. Тело ответа: [%s]", receivedStatusCode, string(body)) 163 | 164 | return 165 | } 166 | 167 | func getStatusCodeGroup(receivedStatusCode int) (group string) { 168 | switch { 169 | case 100 <= receivedStatusCode && receivedStatusCode <= 199: 170 | group = "1xx" 171 | case 200 <= receivedStatusCode && receivedStatusCode <= 299: 172 | group = "2xx" 173 | case 300 <= receivedStatusCode && receivedStatusCode <= 399: 174 | group = "3xx" 175 | case 400 <= receivedStatusCode && receivedStatusCode <= 499: 176 | group = "4xx" 177 | case 500 <= receivedStatusCode && receivedStatusCode <= 599: 178 | group = "5xx" 179 | default: 180 | } 181 | 182 | return 183 | } 184 | 185 | func PrintRequestHook(client *resty.Client, request *http.Request) error { 186 | curl, err := http2curl.GetCurlCommand(request) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | fmt.Println() 192 | fmt.Println("---http2curl------") 193 | fmt.Println(curl.String()) 194 | fmt.Println("---http2curl------") 195 | fmt.Println() 196 | 197 | return nil 198 | } 199 | 200 | func And(hooks ...func(client *resty.Client, request *http.Request) error) func(client *resty.Client, request *http.Request) error { 201 | return func(client *resty.Client, request *http.Request) error { 202 | for _, hook := range hooks { 203 | err := hook(client, request) 204 | if err != nil { 205 | return err 206 | } 207 | } 208 | 209 | return nil 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /internal/libs/provider-helpers/provider_helpers_test.go: -------------------------------------------------------------------------------- 1 | package providerhelpers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type TestStruct1 struct { 8 | Endpoint1 Endpoint 9 | Endpoint2 Endpoint 10 | } 11 | 12 | type TestStruct2 struct{} 13 | 14 | func TestValidateFields(t *testing.T) { 15 | type args struct { 16 | endpoints interface{} 17 | } 18 | tests := []struct { 19 | name string 20 | args args 21 | wantErr bool 22 | }{ 23 | { 24 | name: "struct with endpoints, all ok", 25 | args: args{ 26 | endpoints: TestStruct1{ 27 | Endpoint{ 28 | Method: "GET", 29 | Path: "/api/v1/document", 30 | Headers: nil, 31 | }, 32 | Endpoint{ 33 | Method: "POST", 34 | Path: "/api/v1/user", 35 | Headers: nil, 36 | }, 37 | }, 38 | }, 39 | wantErr: false, 40 | }, 41 | { 42 | name: "struct with endpoints, one has wrong method", 43 | args: args{ 44 | endpoints: TestStruct1{ 45 | Endpoint{ 46 | Method: "", 47 | Path: "/api/v1/document", 48 | Headers: nil, 49 | }, 50 | Endpoint{ 51 | Method: "POST", 52 | Path: "/api/v1/user", 53 | Headers: nil, 54 | }, 55 | }, 56 | }, 57 | wantErr: true, 58 | }, 59 | { 60 | name: "struct with endpoints, two has wrong methods", 61 | args: args{ 62 | endpoints: TestStruct1{ 63 | Endpoint{ 64 | Method: "", 65 | Path: "/api/v1/document", 66 | Headers: nil, 67 | }, 68 | Endpoint{ 69 | Method: "POSTasd", 70 | Path: "/api/v1/user", 71 | Headers: nil, 72 | }, 73 | }, 74 | }, 75 | wantErr: true, 76 | }, 77 | { 78 | name: "struct with no endpoints, no error", 79 | args: args{ 80 | endpoints: TestStruct2{}, 81 | }, 82 | wantErr: false, 83 | }, 84 | { 85 | name: "ok endpoint struct itself, no error", 86 | args: args{ 87 | endpoints: Endpoint{ 88 | Method: "POST", 89 | Path: "/api/v1/user", 90 | Headers: nil, 91 | }, 92 | }, 93 | wantErr: false, 94 | }, 95 | // TODO implement 96 | //{ 97 | // name: "wrong endpoint struct itself, error", 98 | // args: args{ 99 | // endpoints: config.Endpoint{ 100 | // Method: "POST123", 101 | // Path: "/api/v1/user", 102 | // Headers: nil, 103 | // }, 104 | // }, 105 | // wantErr: true, 106 | //}, 107 | } 108 | 109 | for _, tt := range tests { 110 | t.Run(tt.name, func(t *testing.T) { 111 | if err := ValidateEndpoints(tt.args.endpoints); (err != nil) != tt.wantErr { 112 | t.Errorf("ValidateEndpoints() error = %v, wantErr %v", err, tt.wantErr) 113 | } 114 | }) 115 | } 116 | } 117 | 118 | func TestValidateEndpoint(t *testing.T) { 119 | type args struct { 120 | endpoint interface{} 121 | } 122 | tests := []struct { 123 | name string 124 | args args 125 | wantErr bool 126 | }{ 127 | { 128 | name: "ok method, ok path, no error 1", 129 | args: args{ 130 | endpoint: Endpoint{ 131 | Method: "GET", 132 | Path: "/api/v1", 133 | Headers: nil, 134 | }, 135 | }, 136 | wantErr: false, 137 | }, 138 | { 139 | name: "ok method, ok path, no error 2", 140 | args: args{ 141 | endpoint: Endpoint{ 142 | Method: "POST", 143 | Path: "/api/v1", 144 | Headers: nil, 145 | }, 146 | }, 147 | wantErr: false, 148 | }, 149 | { 150 | name: "wrong method, ok path, error", 151 | args: args{ 152 | endpoint: Endpoint{ 153 | Method: "POST1", 154 | Path: "/api/v1", 155 | Headers: nil, 156 | }, 157 | }, 158 | wantErr: true, 159 | }, 160 | { 161 | name: "ok method, wrong path, error", 162 | args: args{ 163 | endpoint: Endpoint{ 164 | Method: "POST", 165 | Path: "", 166 | Headers: nil, 167 | }, 168 | }, 169 | wantErr: true, 170 | }, 171 | { 172 | name: "wrong method, wrong path, error", 173 | args: args{ 174 | endpoint: Endpoint{ 175 | Method: "paspf", 176 | Path: "", 177 | Headers: nil, 178 | }, 179 | }, 180 | wantErr: true, 181 | }, 182 | } 183 | 184 | for _, tt := range tests { 185 | t.Run(tt.name, func(t *testing.T) { 186 | if err := ValidateEndpoint(tt.args.endpoint); (err != nil) != tt.wantErr { 187 | t.Errorf("ValidateEndpoint() error = %v, wantErr %v", err, tt.wantErr) 188 | } 189 | }) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /internal/libs/repo-helpers/repohelpers.go: -------------------------------------------------------------------------------- 1 | package repo_helpers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | mssql = "mssql" 10 | postgres = "postgres" 11 | clickhouse = "clickhouse" 12 | ) 13 | 14 | // connectionString := repo-helpers.GetConnectionString(cfg.Type, cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name) 15 | func GetConnectionString(Type, Host, Port, User, Password, Name string) (connectionString string) { 16 | switch Type { 17 | case mssql: 18 | if strings.Contains(Host, "\\") { 19 | connectionString = fmt.Sprintf("server=%s;user id=%s;password=%s;database=%s", 20 | Host, User, Password, Name) 21 | } else { 22 | connectionString = fmt.Sprintf("server=%s;port=%s;user id=%s;password=%s;database=%s", 23 | Host, Port, User, Password, Name) 24 | } 25 | 26 | return 27 | case postgres: 28 | return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable TimeZone=%s", 29 | Host, Port, User, Password, Name, "Europe/Moscow") 30 | case clickhouse: 31 | return fmt.Sprintf( 32 | "http://%s:%s@%s:%s/%s", 33 | User, 34 | Password, 35 | Host, 36 | Port, 37 | Name, 38 | ) 39 | default: 40 | panic("Неверный тип БД") 41 | } 42 | 43 | return 44 | } 45 | --------------------------------------------------------------------------------