This document establishes a set of best practices related to development of clients for HTTP microservices using Vostok libraries.
An example of a client that implements these practices is HerculesTimelineClient.
- Always target
netstandard2.0
to enable both .NET Framework and .NET Core users to make use of the library. - Always make client methods asynchronous.
- Always make client instances thread-safe.
- Always create a client interface (even if it will only ever have one implemenation) to enable unit-testing against it.
- Always incapsulate serialization and deserialization of models in the client code.
- Consider decorating client methods and models with JetBrains annotations (
NotNull
,CanBeNull
, etc). - Consider documenting client methods and models with xml-docs in code.
- These docs should generally help users understand client's guarantees and behaviour.
- Always add a timeout parameter of
TimeSpan
type to client methods. - Always add a
CancellationToken
parameter to client methods. - Consider passing all the other parameters to client methods as request/query objects with properties instead of passing them directly as method arguments.
- This will help to avoid breaking backward compatibility later and keep the interfaces clean.
public class ReadQuery { // Extensible without breaking backward compatibility. // Required properties are passed to constructor. // Optional properties can just have default values. }
- This will help to avoid breaking backward compatibility later and keep the interfaces clean.
- Consider using result objects with status enums to present a user-friendly mechanism for error handling.
- A common pattern to build such a result object is to include a status (
success
/timeout
/etc.) and an optional payload (service response in case of success). - Users are therefore able to distinguish and handle different error conditions without relying on catching exceptions.
- Unconditionally accessing the payload of a result object that represents failure should throw an exception.
- A common pattern to build such a result object is to include a status (
- Always create synchronous extensions for all asynchronous methods using
.GetAwaiter().GetResult()
.
- Always use ClusterClient library to send HTTP requests.
- Always use universal transport implementation to perform equally well on every runtime.
- Always set target service name in client configuration.
- Consider using
ForkingRequestStrategy
to tolerate slow replicas. - Consider enabling replica budgeting and adaptive throttling to protect from overload.
- Always expose an optional ClusterClientSetup property that can be used to customize internal client instances.
- Always allow to pass an external IClusterProvider or
Func<Uri[]>
instance to set service topology. - Consider including a default topology source if it suits the majority of client library's users.
- Consider making settings dynamic (through
Func<T>
) where applicable.
- Always provide an option to pass an external
ILog
instance. - Consider using LogProvider when user-provided log is
null
. - Consider adding log source context with
log = log.ForContext<MyClient>();
call.
- Always enable tracing for ClusterClient with
vostok.clusterclient.tracing
module. - Always provide an option to pass an external
ITracer
instance. - Consider using TracerProvider when user-provided tracer is
null
.