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  ```