update Go design guidelines with latest info (#1537)
* update Go design guidelines with latest info * added missing *
This commit is contained in:
Родитель
e747bf8b6d
Коммит
8c8a150cd8
|
@ -10,11 +10,11 @@ sidebar: golang_sidebar
|
|||
|
||||
# Packages
|
||||
|
||||
Go groups related types in a package. In Go, the package should be named `az<service>`, where `<service>` is the service name represented as a single word.
|
||||
Go groups related types in a package. In Go, the package should be named `<prefix><service>`, where `<prefix>` is either `arm` or `az`, and where `<service>` is the service name represented as a single word.
|
||||
|
||||
{% include requirement/MUST id="golang-package-prefix" %} start the package with `az` to indicate an Azure client package.
|
||||
{% include requirement/MUST id="golang-package-prefix" %} start the package with `arm` or `az` to indicate an Azure client package. Use `arm` for management-plane packages, and `az` for all other packages.
|
||||
|
||||
{% include requirement/MUST id="golang-package-name" %} construct the package name with all lowercase letters (uppercase letters, hyphens and underscores are not allowed). For example, the Azure Key Vault package would be named `azkeyvault` and the Azure blob storage package would be named `azblob`.
|
||||
{% include requirement/MUST id="golang-package-name" %} construct the package name with all lowercase letters (uppercase letters, hyphens and underscores are not allowed). For example, the Azure compute management package would be named `armcompute` and the Azure blob storage package would be named `azblob`.
|
||||
|
||||
{% include requirement/MUST id="golang-package-registration" %} register the chosen package name with the [Architecture Board]. Open an issue to request the package name. See the [registered package list](registered_namespaces.html) for a list of the currently registered packages.
|
||||
|
||||
|
@ -24,13 +24,13 @@ Go groups related types in a package. In Go, the package should be named `az<se
|
|||
|
||||
{% include requirement/MUST id="golang-pkgpath-leaf" %} ensure that the package leaf directory name matches the package name declared in the source code.
|
||||
|
||||
{% include requirement/MUST id="golang-pkgpath-apiver" %} ensure that each service API version is in its own directory.
|
||||
{% include requirement/MUST id="golang-pkgpath-apiver" %} ensure that each service API version is in its own directory, IFF that service supports multiple API versions.
|
||||
|
||||
{% include requirement/MUST id="golang-pkgpath-mgmt" %} place the management (Azure Resource Manager) API in the `mgmt` path. Use the grouping `./sdk/<group>/mgmt/<service>` for the package path. Since more services require management APIs than data plane APIs, other paths may be used explicitly for management only. Data plane usage is by exception only. Additional paths that can be used for control plane SDKs include:
|
||||
{% include requirement/MUST id="golang-pkgpath-mgmt" %} place the management (Azure Resource Manager) API in the `arm` path. Use the grouping `./sdk/arm/<group>/<api-version>/arm<service>` for the package path. Since more services require management APIs than data plane APIs, other paths may be used explicitly for management only. Data plane usage is by exception only. Additional paths that can be used for control plane SDKs include:
|
||||
|
||||
{% include tables/mgmt_namespaces.md %}
|
||||
|
||||
Many management APIs do not have a data plane because they deal with management of the Azure account. Place the management package in the `mgmt` path. For example, use `sdk/mgmt/costanalysis` instead of `sdk/mgmt/management/costanalysis`.
|
||||
Many management APIs do not have a data plane because they deal with management of the Azure account. Place the management package in the `arm` path. For example, use `sdk/arm/costanalysis/...` instead of `sdk/arm/management/costanalysis`.
|
||||
|
||||
Here is a complete example.
|
||||
|
||||
|
@ -43,9 +43,9 @@ Data-plane packages:
|
|||
|
||||
Management-plane packages:
|
||||
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/keyvault/mgmt/2019-09-01/azkeyvault
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/storage/mgmt/2019-01-01/azstorage
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/storage/mgmt/2019-02-01/azstorage
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/arm/keyvault/2019-09-01/armkeyvault
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/arm/storage/2019-01-01/armstorage
|
||||
- github.com/Azure/azure-sdk-for-go/sdk/arm/storage/2019-02-01/armstorage
|
||||
|
||||
## Versioning
|
||||
|
||||
|
@ -69,6 +69,8 @@ Packages should strive to avoid taking dependencies on packages outside of the s
|
|||
|
||||
{% include requirement/MUST id="golang-dependencies-azure-core" %} depend on the `azcore` package for functionality that is common across all client packages. This package includes APIs for HTTP connectivity, global configuration, logging, and credential handling, and more.
|
||||
|
||||
{% include requirement/MUST id="golang-dependencies-azure-core" %} depend on the `sdk/internal` package for functionality that is common across all client packages that should not be publicly exported. This package includes helpers for creating errors with stack frame information, and more.
|
||||
|
||||
{% include requirement/MUSTNOT id="golang-dependencies-approved-list" %} be dependent on any other packages within the client package distribution package, with the exception of the following:
|
||||
|
||||
{% include_relative approved_dependencies.md %}
|
||||
|
@ -119,7 +121,7 @@ type WidgetClient struct {
|
|||
// endpoint - The URI of the Widget.
|
||||
// cred - The credential used to authenticate with the Widget service.
|
||||
// options - Optional WidgetClient values. Pass nil to accept default values.
|
||||
func NewWidgetClient(endpoint string, cred azcore.Credential, options WidgetClientOptions) (*WidgetClient, error) {
|
||||
func NewWidgetClient(endpoint string, cred azcore.Credential, options *WidgetClientOptions) (*WidgetClient, error) {
|
||||
// ...
|
||||
}
|
||||
|
||||
|
@ -131,6 +133,17 @@ func NewWidgetClientWithPipeline(endpoint string, p azcore.Pipeline) (*WidgetCli
|
|||
}
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-client-constructors" %} provide a default constructor in the following format for services with a default endpoint (management plane is the most common example).
|
||||
|
||||
```go
|
||||
// NewDefaultClient creates a new instance of WidgetClient with the specified values. It uses the default endpoint and pipeline configuration.
|
||||
// cred - The credential used to authenticate with the Widget service.
|
||||
// options - Optional WidgetClient values. Pass nil to accept default values.
|
||||
func NewDefaultClient(cred azcore.Credential, options *WidgetClientOptions) (*WidgetClient, error) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-client-constructors-params" %} document all constructor parameters as part of the method block comment.
|
||||
|
||||
## Authentication and Credentials
|
||||
|
@ -212,20 +225,24 @@ The service client will have several methods that perform requests on the servic
|
|||
|
||||
Requests to the service fall into two basic groups: methods that make a single logical request, and methods that make a deterministic sequence of requests. An example of a _single logical request_ is a request that may be retried inside the operation. An example of a _deterministic sequence of requests_ is a paged operation.
|
||||
|
||||
The _logical entity_ is a protocol neutral representation of a response. The logical entity may combine data from headers, body, and the HTTP status. For example, you may expose an `ETag` header as a property on the logical entity. `<Operation>Response` is the ‘complete response’. It contains HTTP headers, status code, and the object (a deserialized object created from the response body).
|
||||
The _response envelope_ is a protocol neutral representation of a response. The response envelope may combine data from headers, body, and the HTTP response. For example, you may expose an `ETag` header as a property on the response envelope. `<Resource>Response` is the ‘response envelope’. It contains HTTP headers, the object (a deserialized object created from the response body), and the raw HTTP response.
|
||||
|
||||
{% include requirement/MUST id="golang-response-logical-entity" %} return the logical entity for the normal form of a service method. The logical entity MUST represent the information needed in the 99%+ case.
|
||||
{% include requirement/MUST id="golang-response-logical-entity" %} return the response envelope for the normal form of a service method. The response envelope MUST represent the information needed in the 99%+ case.
|
||||
|
||||
```go
|
||||
type GetWidgetHeaders struct {
|
||||
// WidgetResponse is the response envelope for operations that return a Widget type.
|
||||
type WidgetResponse struct {
|
||||
// ETag contains the value from the ETag header.
|
||||
ETag *string
|
||||
LastModified *time.Time
|
||||
}
|
||||
|
||||
type GetWidgetResponse struct {
|
||||
StatusCode int
|
||||
Headers *GetWidgetHeaders // optional field, IFF defined by the service
|
||||
// LastModified contains the value from the last-modified header.
|
||||
LastModified *time.Time
|
||||
|
||||
// Widget contains the unmarshalled response body in Widget format.
|
||||
Widget *Widget
|
||||
|
||||
// RawResponse contains the underlying HTTP response.
|
||||
RawResponse *http.Response
|
||||
}
|
||||
|
||||
type Widget struct {
|
||||
|
@ -233,25 +250,19 @@ type Widget struct {
|
|||
Color WidgetColor
|
||||
}
|
||||
|
||||
func (c *WidgetClient) GetWidget(ctx context.Context, name string) (*GetWidgetResponse, error) {
|
||||
func (c *WidgetClient) GetWidget(ctx context.Context, name string) (*WidgetResponse, error) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
{% include requirement/MUSTNOT id="golang-response-response-headers" %} generate a `<Operation>Headers` type if the operation doesn't return any values via headers.
|
||||
|
||||
{% include requirement/MUST id="golang-response-examples" %} provide examples on how to access the streamed response for a request, where exposed by the client library. We don’t expect all methods to expose a streamed response.
|
||||
|
||||
```go
|
||||
func (c *WidgetClient) GetWidgetBinary(ctx context.Context, name string) (*GetWidgetBinaryResponse, error) {
|
||||
func (c *WidgetClient) GetBinaryResponse(ctx context.Context, name string) (*http.Response, error) {
|
||||
// ...
|
||||
}
|
||||
|
||||
type GetWidgetBinaryResponse struct {
|
||||
StatusCode int
|
||||
Headers *GetWidgetBinaryHeaders
|
||||
Bytes *bytes.Reader
|
||||
}
|
||||
// callers read from the io.ReadCloser Body field on the HTTP response.
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-response-logical-paging" %} provide an idiomatic way to enumerate all logical entities for a paged operation, automatically fetching new pages as needed. For more information on what to return for List operations, refer to [Pagination](#pagination).
|
||||
|
@ -290,9 +301,8 @@ type WidgetPager interface {
|
|||
}
|
||||
|
||||
type ListWidgetsResponse struct {
|
||||
StatusCode int
|
||||
RawResponse *http.Response
|
||||
Widgets *[]Widget
|
||||
Headers *ListWidgetsHeaders
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -355,24 +365,20 @@ type WidgetPoller interface {
|
|||
{% include requirement/MUST id="golang-lro-method-naming" %} prefix methods which return a `<Resource>Poller` with `Begin`.
|
||||
|
||||
```go
|
||||
// WidgetResponse is the response envelope for a Widget that also includes LRO related fields used to retrieve a final Widget: PollUntilDone() and Poller
|
||||
type WidgetResponse struct {
|
||||
// PollUntilDone will poll the service endpoint until a terminal state is reached or an error is
|
||||
// received. If successful will return a populated WidgetResponse
|
||||
// WidgetPollerResponse is the response envelope for operations that asynchronously return a Widget type.
|
||||
type WidgetPollerResponse struct {
|
||||
// PollUntilDone will poll the service endpoint until a terminal state is reached or an error is received.
|
||||
PollUntilDone func(context.Context, time.Duration) (*WidgetResponse, error)
|
||||
|
||||
// Poller contains an initialized WidgetPoller interface
|
||||
// Poller contains an initialized WidgetPoller.
|
||||
Poller WidgetPoller
|
||||
|
||||
// Widget will be populated with the final Widget once FinalResponse is called
|
||||
Widget *Widget
|
||||
|
||||
// RawResponse contains the underlying HTTP response.
|
||||
RawResponse *http.Response
|
||||
}
|
||||
|
||||
// BeginCreate creates a new widget with the specified name.
|
||||
func (c *WidgetClient) BeginCreate(ctx context.Context, name string, options *BeginCreateOptions) (*WidgetResponse, error) {
|
||||
func (c *WidgetClient) BeginCreate(ctx context.Context, name string, options *BeginCreateOptions) (*WidgetPollerResponse, error) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
@ -466,31 +472,23 @@ process(w)
|
|||
|
||||
One of the key things we want to support is to allow consumers of the package to easily write repeatable unit-tests for their applications without activating a service. This allows them to reliably and quickly test their code without worrying about the vagaries of the underlying service implementation (including, for example, network conditions or service outages). Mocking is also helpful to simulate failures, edge cases, and hard to reproduce situations (for example: does code work on February 29th).
|
||||
|
||||
{% include requirement/MUST id="golang-mock-interface-package" %} generate a sub-package containing interface definitions for all client operations. The package name will be the same as the parent plus the `iface` suffix.
|
||||
|
||||
{% include requirement/MUST id="golang-mock-interface-types" %} generate one interface type per client type that contains all of the client type's exported methods. The interface type name will be the same as the client type name.
|
||||
{% include requirement/MUST id="golang-mock-interface-types" %} generate one interface type per operation group that contains all of the client type's exported methods. The interface type name will be `<op_group_name>Operations`.
|
||||
|
||||
{% include requirement/MUST id="golang-mock-lro-pages" %} generate interface types for LRO and pageable response types that contain all of the methods for their respective types. The interface type name will be the same as the LRO/pageable response type name.
|
||||
|
||||
{% include requirement/MUST id="golang-mock-interface-check" %} generate code to ensure that the interface definitions and their respective types have identical method declarations. This is usually performed by assigning a nil pointer-to-type to a variable of the interface type.
|
||||
|
||||
```go
|
||||
package factoryiface
|
||||
|
||||
import (
|
||||
// ...
|
||||
"<my repo>/factory"
|
||||
)
|
||||
|
||||
// WidgetClient contains the set of methods on the factory.WidgetClient type.
|
||||
type WidgetClient interface {
|
||||
BeginCreate(ctx context.Context, options *factory.BeginCreateOptions) (factory.WidgetPoller, error)
|
||||
GetWidget(ctx context.Context, name string) (*factory.GetWidgetResponse, error)
|
||||
ListWidgets(options *factory.ListWidgetsOptions) (factory.ListWidgetsPager, error)
|
||||
// WidgetOperations contains the methods for the Widget group.
|
||||
type WidgetOperations interface {
|
||||
BeginCreate(ctx context.Context, options *BeginCreateOptions) (*WidgetPollerResponse, error)
|
||||
GetWidget(ctx context.Context, name string) (*WidgetResponse, error)
|
||||
ListWidgets(options *ListWidgetsOptions) (ListWidgetsPager, error)
|
||||
// other methods...
|
||||
}
|
||||
|
||||
var _ WidgetClient = (*factory.WidgetClient)(nil)
|
||||
// ensure client and interface definitions are in sync
|
||||
var _ WidgetOperations = (*WidgetClient)(nil)
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-test-recordings" %} support HTTP request and response recording/playback via the pipeline.
|
||||
|
@ -505,6 +503,8 @@ var _ WidgetClient = (*factory.WidgetClient)(nil)
|
|||
|
||||
{% include requirement/MUST id="golang-enum-type-values" %} define a function named `<EnumTypeName>Values()` that returns a slice containing all possible values for the enumeration.
|
||||
|
||||
{% include requirement/MUST id="golang-enum-type-values" %} define a method named `ToPtr()` on the enumerated type that returns a pointer to the enum value.
|
||||
|
||||
```go
|
||||
// WidgetColor specifies a Widget's color from the list of possible values.
|
||||
type WidgetColor string
|
||||
|
@ -519,6 +519,11 @@ const (
|
|||
func WidgetColorValues() []WidgetColor {
|
||||
// ...
|
||||
}
|
||||
|
||||
func (c WidgetColor) ToPtr() *WidgetColor {
|
||||
return &c
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Service Client Configuration
|
||||
|
@ -549,17 +554,9 @@ if err != nil {
|
|||
}
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-errors-on-request-failed" %} return a service-specific error type when an HTTP request fails with an unsuccessful HTTP status code as defined by the service. The error type MUST be composed of `azcore.RequestError` as an anonymous field.
|
||||
{% include requirement/MUST id="golang-errors-on-request-failed" %} return the service/operation specific error type when an HTTP request fails with an unsuccessful HTTP status code as defined by the service. For operations that do not define an error type, return the HTTP response body in string format if available, else return the `Status` string on the HTTP response.
|
||||
|
||||
```go
|
||||
type APIError struct {
|
||||
azcore.RequestError
|
||||
Code AnomalyDetectorErrorCodes
|
||||
Message string
|
||||
}
|
||||
```
|
||||
|
||||
{% include requirement/MUST id="golang-errors-include-response" %} include the HTTP response and originating request in the returned error.
|
||||
{% include requirement/MUST id="golang-errors-include-response" %} include the HTTP response and originating request in the returned error. Use `runtime.NewResponseError()` from the `sdk/internal` module to provide this information.
|
||||
|
||||
In the case of a method that makes multiple HTTP requests, the first error encountered should stop the remainder of the operation and this error (or another error wrapping it) should be returned.
|
||||
|
||||
|
@ -567,7 +564,7 @@ In the case of a method that makes multiple HTTP requests, the first error encou
|
|||
|
||||
{% include requirement/MUST id="golang-errors-documentation" %} document the error types that are returned by each method. Don't document commonly returned error types, for example `context.DeadlineExceeded` when an HTTP request times out.
|
||||
|
||||
{% include requirement/MUSTNOT id="golang-errors-other-types" %} create arbitrary error types. Use error types provided by the standard library or `azcore`.
|
||||
{% include requirement/MUSTNOT id="golang-errors-other-types" %} create arbitrary error types. Use error types provided by the service, standard library, or `azcore`.
|
||||
|
||||
## Logging
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче