google.golang.org/grpc@v1.72.2/Documentation/anti-patterns.md (about) 1 ## Anti-Patterns of Client creation 2 3 ### How to properly create a `ClientConn`: `grpc.NewClient` 4 5 [`grpc.NewClient`](https://pkg.go.dev/google.golang.org/grpc#NewClient) is the 6 function in the gRPC library that creates a virtual connection from a client 7 application to a gRPC server. It takes a target URI (which represents the name 8 of a logical backend service and resolves to one or more physical addresses) and 9 a list of options, and returns a 10 [`ClientConn`](https://pkg.go.dev/google.golang.org/grpc#ClientConn) object that 11 represents the virtual connection to the server. The `ClientConn` contains one 12 or more actual connections to real servers and attempts to maintain these 13 connections by automatically reconnecting to them when they break. `NewClient` 14 was introduced in gRPC-Go v1.63. 15 16 ### The wrong way: `grpc.Dial` 17 18 [`grpc.Dial`](https://pkg.go.dev/google.golang.org/grpc#Dial) is a deprecated 19 function that also creates the same virtual connection pool as `grpc.NewClient`. 20 However, unlike `grpc.NewClient`, it immediately starts connecting and supports 21 a few additional `DialOption`s that control this initial connection attempt. 22 These are: `WithBlock`, `WithTimeout`, `WithReturnConnectionError`, and 23 `FailOnNonTempDialError`. 24 25 That `grpc.Dial` creates connections immediately is not a problem in and of 26 itself, but this behavior differs from how gRPC works in all other languages, 27 and it can be convenient to have a constructor that does not perform I/O. It 28 can also be confusing to users, as most people expect a function called `Dial` 29 to create _a_ connection which may need to be recreated if it is lost. 30 31 `grpc.Dial` uses "passthrough" as the default name resolver for backward 32 compatibility while `grpc.NewClient` uses "dns" as its default name resolver. 33 This subtle difference is important to legacy systems that also specified a 34 custom dialer and expected it to receive the target string directly. 35 36 For these reasons, using `grpc.Dial` is discouraged. Even though it is marked 37 as deprecated, we will continue to support it until a v2 is released (and no 38 plans for a v2 exist at the time this was written). 39 40 ### Especially bad: using deprecated `DialOptions` 41 42 `FailOnNonTempDialError`, `WithBlock`, and `WithReturnConnectionError` are three 43 `DialOption`s that are only supported by `Dial` because they only affect the 44 behavior of `Dial` itself. `WithBlock` causes `Dial` to wait until the 45 `ClientConn` reports its `State` as `connectivity.Connected`. The other two deal 46 with returning connection errors before the timeout (`WithTimeout` or on the 47 context when using `DialContext`). 48 49 The reason these options can be a problem is that connections with a 50 `ClientConn` are dynamic -- they may come and go over time. If your client 51 successfully connects, the server could go down 1 second later, and your RPCs 52 will fail. "Knowing you are connected" does not tell you much in this regard. 53 54 Additionally, _all_ RPCs created on an "idle" or a "connecting" `ClientConn` 55 will wait until their deadline or until a connection is established before 56 failing. This means that you don't need to check that a `ClientConn` is "ready" 57 before starting your RPCs. By default, RPCs will fail if the `ClientConn` 58 enters the "transient failure" state, but setting `WaitForReady(true)` on a 59 call will cause it to queue even in the "transient failure" state, and it will 60 only ever fail due to a deadline, a server response, or a connection loss after 61 the RPC was sent to a server. 62 63 Some users of `Dial` use it as a way to validate the configuration of their 64 system. If you wish to maintain this behavior but migrate to `NewClient`, you 65 can call `GetState`, then `Connect` if the state is `Idle` and 66 `WaitForStateChange` until the channel is connected. However, if this fails, 67 it does not mean that your configuration was bad - it could also mean the 68 service is not reachable by the client due to connectivity reasons. 69 70 ## Best practices for error handling in gRPC 71 72 Instead of relying on failures at dial time, we strongly encourage developers to 73 rely on errors from RPCs. When a client makes an RPC, it can receive an error 74 response from the server. These errors can provide valuable information about 75 what went wrong, including information about network issues, server-side errors, 76 and incorrect usage of the gRPC API. 77 78 By handling errors from RPCs correctly, developers can write more reliable and 79 robust gRPC applications. Here are some best practices for error handling in 80 gRPC: 81 82 - Always check for error responses from RPCs and handle them appropriately. 83 - Use the `status` field of the error response to determine the type of error 84 that occurred. 85 - When retrying failed RPCs, consider using the built-in retry mechanism 86 provided by gRPC-Go, if available, instead of manually implementing retries. 87 Refer to the [gRPC-Go retry example 88 documentation](https://github.com/grpc/grpc-go/blob/master/examples/features/retry/README.md) 89 for more information. Note that this is not a substitute for client-side 90 retries as errors that occur after an RPC starts on a server cannot be 91 retried through gRPC's built-in mechanism. 92 - If making an outgoing RPC from a server handler, be sure to translate the 93 status code before returning the error from your method handler. For example, 94 if the error is an `INVALID_ARGUMENT` status code, that probably means 95 your service has a bug (otherwise it shouldn't have triggered this error), in 96 which case `INTERNAL` is more appropriate to return back to your users. 97 98 ### Example: Handling errors from an RPC 99 100 The following code snippet demonstrates how to handle errors from an RPC in 101 gRPC: 102 103 ```go 104 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 105 defer cancel() 106 107 res, err := client.MyRPC(ctx, &MyRequest{}) 108 if err != nil { 109 // Handle the error appropriately, 110 // log it & return an error to the caller, etc. 111 log.Printf("Error calling MyRPC: %v", err) 112 return nil, err 113 } 114 115 // Use the response as appropriate 116 log.Printf("MyRPC response: %v", res) 117 ``` 118 119 To determine the type of error that occurred, you can use the status field of 120 the error response: 121 122 ```go 123 resp, err := client.MakeRPC(context.TODO(), request) 124 if err != nil { 125 if status, ok := status.FromError(err); ok { 126 // Handle the error based on its status code 127 if status.Code() == codes.NotFound { 128 log.Println("Requested resource not found") 129 } else { 130 log.Printf("RPC error: %v", status.Message()) 131 } 132 } else { 133 // Handle non-RPC errors 134 log.Printf("Non-RPC error: %v", err) 135 } 136 return 137 } 138 139 // Use the response as needed 140 log.Printf("Response received: %v", resp) 141 ``` 142 143 ### Example: Using a backoff strategy 144 145 When retrying failed RPCs, use a backoff strategy to avoid overwhelming the 146 server or exacerbating network issues: 147 148 ```go 149 var res *MyResponse 150 var err error 151 152 retryableStatusCodes := map[codes.Code]bool{ 153 codes.Unavailable: true, // etc 154 } 155 156 // Retry the RPC a maximum number of times. 157 for i := 0; i < maxRetries; i++ { 158 // Make the RPC. 159 res, err = client.MyRPC(context.TODO(), &MyRequest{}) 160 161 // Check if the RPC was successful. 162 if !retryableStatusCodes[status.Code(err)] { 163 // The RPC was successful or errored in a non-retryable way; 164 // do not retry. 165 break 166 } 167 168 // The RPC is retryable; wait for a backoff period before retrying. 169 backoff := time.Duration(i+1) * time.Second 170 log.Printf("Error calling MyRPC: %v; retrying in %v", err, backoff) 171 time.Sleep(backoff) 172 } 173 174 // Check if the RPC was successful after all retries. 175 if err != nil { 176 // All retries failed, so handle the error appropriately 177 log.Printf("Error calling MyRPC: %v", err) 178 return nil, err 179 } 180 181 // Use the response as appropriate. 182 log.Printf("MyRPC response: %v", res) 183 ```