From 153f933dc231264bd46f46c457fce01549e90e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Manuel=20P=C3=A9rez?= Date: Mon, 22 Apr 2024 16:38:33 +0200 Subject: [PATCH] Added asynchronous APIs guidelines --- SUMMARY.md | 10 +- assets/eda_overview.png | Bin 0 -> 36257 bytes .../01_introduction/a_introduction.md | 22 ++ .../01_introduction/b_basic_concepts.md | 19 ++ .../02_asynchronous_api_guidelines/main.md | 93 +++++++ .../03_asyncapi_kafka_specs/a_introduction.md | 60 +++++ .../03_asyncapi_kafka_specs/b_guidelines.md | 249 ++++++++++++++++++ .../03_asyncapi_kafka_specs/c_tooling.md | 44 ++++ asynchronous-api-guidelines/asyncapi.md | 6 - 9 files changed, 496 insertions(+), 7 deletions(-) create mode 100644 assets/eda_overview.png create mode 100644 asynchronous-api-guidelines/01_introduction/a_introduction.md create mode 100644 asynchronous-api-guidelines/01_introduction/b_basic_concepts.md create mode 100644 asynchronous-api-guidelines/02_asynchronous_api_guidelines/main.md create mode 100644 asynchronous-api-guidelines/03_asyncapi_kafka_specs/a_introduction.md create mode 100644 asynchronous-api-guidelines/03_asyncapi_kafka_specs/b_guidelines.md create mode 100644 asynchronous-api-guidelines/03_asyncapi_kafka_specs/c_tooling.md delete mode 100644 asynchronous-api-guidelines/asyncapi.md diff --git a/SUMMARY.md b/SUMMARY.md index ca86068..d7b4472 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -66,5 +66,13 @@ ## Asynchronous API Guidelines -* [Introduction](asynchronous-api-guidelines/asyncapi.md) +* Preliminary concepts + * [Introduction](asynchronous-api-guidelines/01_introduction/a_introduction.md) + * [Basic concepts](asynchronous-api-guidelines/01_introduction/b_basic_concepts.md) +* [General guidelines for asynchronous APIs](asynchronous-api-guidelines/02_asynchronous_api_guidelines/main.md) +* AsyncAPI specs for Kafka + * [Introduction](asynchronous-api-guidelines/03_asyncapi_kafka_specs/a_introduction.md) + * [Guidelines](asynchronous-api-guidelines/03_asyncapi_kafka_specs/b_guidelines.md) + * [Tooling](asynchronous-api-guidelines/03_asyncapi_kafka_specs/c_tooling.md) + diff --git a/assets/eda_overview.png b/assets/eda_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..1fdfbc03de11b1c679c4753e89ebb0c332f82df6 GIT binary patch literal 36257 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 English | +| Country Code | [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) | DE <-> Germany | +| Currency | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) | EUR <-> Euro | + +### Schemas and data evolution + +All asynchronous APIs **SHOULD** leverage Schema Registry to ensure consistency across consumers/producers with regards to message structure and ensuring compatibility across different versions. + +The default compatibility mode in Schema Registry is FULL_TRANSITIVE. This is the more restrictive compatibility mode, but others are also available. + +| Mode | Description | +|------|-------------| +|BACKWARD|new schema versions are backward compatible with older versions| +|BACKWARD_TRANSITIVE|backward compatibility across all schema versions, not just the latest one.| +|FORWARD|new schema versions are compatible with older consumer versions| +|FORWARD_TRANSITIVE|forward compatibility across all schema versions.| +|FULL|both backward and forward compatibility with the latest schema version| +|FULL_TRANSITIVE|both backward and forward compatibility with all schema versions| +|NONE|schema compatibility checks are disabled| + +If for any reason you need to use a less strict compatibility mode in a topic, that compatibility mode **SHOULD NOT** be modified on the same topic. Instead, a new topic **SHOULD** be used to avoid unexpected behaviors or broken integrations. + +Applications **MUST NOT** enable automatic registration of schemas because FDP's operational model for the Schema Registry relies on GitOps (every operation is done through GIT PRs + automated pipelines) + + Please refer to [Kafka_Schema_Registry-Default_Requirements](https://confluence.tools.3stripes.net/display/FDP/Kafka_Schema_Registry-Default_Requirements) for more information about Schema Registry. + + ### Key/Value message format + + Kafka messages **MAY** include a key, which needs to be properly designed to have a good partition balanceare key-value pairs. + +The message key and the payload (often called value) can be serialized independently and can have different formats. For example, the payload of the message can be sent in AVRO format, while the message key can be a primitive type (string).  + +Message keys **SHOULD** be kept as simple as possible and use a primitive type when possible. + +### Naming conventions + +As general naming conventions, asynchronous APIs **MUST** adhere to the following conventions + +- Use of english +- Avoid acronyms or explain them when used +- Use camelCase unless stated otherwise + +### Protocols + +Protocols define how clients and servers communicate in an asynchronous architecture. + +The accepted protocols for asynchronous APIs are: + +- Kafka +- HTTPs +- WebSockets +- MQTT + +This version of the guidelines focuses on Kafka protocol, but it could be extended in the future. In any case, this document will be updated to reflect the state of the art. + +### Security + +The [security guidelines](https://github.com/adidas/api-guidelines/blob/feature/asyncapi-guidelines/general-guidelines/security.md) for regular APIs **MUST** be followed strictly when applicable. + diff --git a/asynchronous-api-guidelines/03_asyncapi_kafka_specs/a_introduction.md b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/a_introduction.md new file mode 100644 index 0000000..293509d --- /dev/null +++ b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/a_introduction.md @@ -0,0 +1,60 @@ +# adidas Asynchronous API guidelines + +## Introduction to AsyncAPI spec definitions for Kafka protocol + +This section is specific to the definition of API specs with [AsyncAPI](https://www.asyncapi.com/) for Kafka protocol. + +Also, take into account that across the section there will be multiple references to this [AsyncAPI reference spec](https://design.api.3stripes.io/apis/adidas/asyncapi-adoption-initiative/1.0.0) which is publicly available for reference.  + +### Basic concepts about AsyncAPI + +#### Why AsyncAPI? + +Event-driven architectures are becoming increasingly popular for building scalable, responsive, and efficient applications. AsyncAPI plays a crucial role in this landscape by offering a standardized way to describe asynchronous APIs, similar to how OpenAPI does for REST APIs. AsyncAPI seeks to make the development, maintenance, and testing of asynchronous APIs easier by providing a machine-readable specification. + +It supports various messaging protocols, including MQTT, WebSocket, Kafka, AMQP, and more, making it versatile for different use cases. In adidas, we will use it mainly to document Kafka resources created in FDP but nothing prevents you from using it for a different purpose. + +The benefits of using AsyncAPI are, amongst others: + +- Standardization + - AsyncAPI defines a STANDARD format (YAML or JSON) for describing asynchronous APIs. + - By defining the structure of messages, channels, and events, you can ensure that all components adhere to the same conventions. + - Using a single standard ensures consistency in the design and documentation of all your asynchronous APIs. + - This simplifies integration, maintenance, and troubleshooting across different parts of your system. +- Improved Developer Experience + - AsyncAPI documents the messages being exchanged, their structure, and the events triggered by them. + - It provides developers with a clear picture of how to interact with the API, what data to expect, and how to interpret responses without digging into the implementation details.  +- Code scaffolding + - Using tools like asyncapi-generator allow to easily generate the skeleton of applications that can work with the resources described in the spec. + - This can be done in different programming languages (Python, Java, Node.js. ...), reducing significantly the development time and the coding errors. +- Design-first approach: It encourages designing the API first before writing code, leading to better planned and more reliable APIs. + +In addition to those benefits, Platform & Engineering is working hard to create a data catalogue built upon AsyncAPI that allows to have a good level of discoverability, allowing teams to be able to find exactly the data they need with regards to any data object in the company. + +Questions like: + +- Who is responsible for a specific data object +- Where is that data hosted +- Which kind of information is available + +Will be easy to answer once this catalogue is in place. Also, it is important to have a good discoverability and search & filtering capabilities. + +#### Kafka to AsyncAPI concept mapping + +|Kafka Concept|AsyncAPI Concept| +|-------------|----------------| +|broker|server| +|topic|channel| +|consumer|subscriber| +|producer|publisher| + +#### First level items in AsyncAPI structure + +|Element|Meaning| +|-------|-------| +|asyncapi|Specifies the AsyncAPI specification version| +|info|Provides metadata about the API such as the version, title and description| +|servers|Describes servers where the API is available| +|channels|Defines the channels through which messages are received/published| +|components|Reusable elements to be references across the spec| + diff --git a/asynchronous-api-guidelines/03_asyncapi_kafka_specs/b_guidelines.md b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/b_guidelines.md new file mode 100644 index 0000000..4da1555 --- /dev/null +++ b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/b_guidelines.md @@ -0,0 +1,249 @@ +# adidas Asynchronous API guidelines + +## AsyncAPI guidelines for Kafka + +### AsyncAPI version + +Any version of AsyncAPI **MAY** be used for spec definitions. + +However, to be aligned with adidas tooling, spec versions **SHOULD** be *v2.6.0*, because to the date of this document creation (April 2024) this is the highest supported version on Swaggerhub, the current API portal to render, discover and publish specs. + +```yaml +asyncapi: 2.6.0 +... +``` + +### Internal vs public specs + +AsyncAPI specs **MAY** be created both for public APIs or for internal APIs.  + +- Public APIs are those who are created to be consumed by others +- Internal APIs are only for development teams for a particular project + +There are no differences with regards to the spec definition, but internal APIs **SHOULD** have restricted access limited only to the internal development team for a particular project or product. + +This access control is handled through Role-Based Access Control (RBAC) implemented in Swaggerhub. + +### Spec granularity + +In FDP all resources are grouped by namespace. + +For that reason specs **SHOULD** be created with a relation 1:1 with namespaces. In other words, every namespace will have an AsyncAPI spec including all the assets belonging to that namespace. + +Different granularities **MAY** be chosen depending on the needs.  + +### Meaningful descriptions + +All fields included in the specs **MUST** include a proper description.  + +### Self-contained specs + +All AsyncAPI specs **SHOULD** include as much information as needed in order to make the spec self-contained and clearly documented + +### Contact information + +AsyncAPI specs **MUST** include at least one main contact under the info.contact section. + +The spec only allows to include one contact there, but it **MAY** also include additional contacts using extension fields. For example: + +```yaml +... +info: + ... + contact: + name: "Main point of contact" + email: "team_dl@adidas.com" + x-additional-responsibles: + - person2@adidas.com + - person3@adidas.com + - person4@adidas.com +``` + +### AsyncAPI ID + +According to [AsyncAPI documentation](https://v2.asyncapi.com/docs/reference/specification/v2.6.0#A2SIdString), every AsyncAPI spec **SHOULD** use a unique identifier for the application being defined, following RFC-3986. + +More concretely, ASyncAPI specs created in adidas should use the following pattern + +```yaml +... +id: urn:fdp:adidas:com:namespace:asyncapi_reference_spec +... +``` + + +### Servers + +All AsyncAPI specs **MUST** include a servers section including references to the right Kafka clusters, defined and maintained by FDP team and made available through domains in Swaggerhub. + +Those definitions are handled in Swaggerhub as reusable domains publicly available: + +https://design.api.3stripes.io/domains/adidas/asyncapi_adoption_commons/1.0.0 + +that can be referred from any spec, picking the right kafka servers as required (see example below). + +```yaml +... +servers: + pivotalDev: + $ref: https://design.api.3stripes.io/v1/domains/adidas/asyncapi_adoption_commons/1.0.0#/components/servers/pivotalDev + pivotalSit: + $ref: https://design.api.3stripes.io/v1/domains/adidas/asyncapi_adoption_commons/1.0.0#/components/servers/pivotalSit + pivotalPro: + $ref: https://design.api.3stripes.io/v1/domains/adidas/asyncapi_adoption_commons/1.0.0#/components/servers/pivotalPro +... +``` +**Important note** Don't forget to include '*/v1/*' in the URL of the domain + +### Channels + +All AsyncAPI specs **MUST** include definitions for the channels (kafka topics) including: + +- Description of the topic +- Servers in which the topic is available + - This is a reference to one of the server identifiers included in the servers section +- publish/subscribe operations + - Operation ID + - Summary or short description for the operation + - Description for the operation + - Security schemes + - Tags + - External Docs + - Message details + +In addition to those supported fields, it **MAY** be possible to use extension attributes (using the x- prefix) to specify specific configuration parameters and metadata. In so, the recommended attributes to use are : + +- x-metadata + - To include additional configuration specific to your team or project +- x-configurations + - To include Kafka configuration parameters and producers/consumers + +As the parameters can be different per environment, it is very convenient to add an additional level for the environment + +As part of the publish/subscribe operations, the spec **SHOULD** specify the different kafka clients currently consuming from the different topics for each cluster/environment. For this, the extended attributes x-producers and x-consumers will be used. + +```yaml +... +channels: + namespace.source.event.topic-name: + description: A description of the purpose of the topic and the contained information + servers: ["pivotalDev", "pivotalSit", "pivotalPro"] + x-metadata: + myField1: myValue1 + myField2: myValue2 + x-configurations: + pivotal.dev: + kafka: + partitions: 12 + replicas: 1 + topicConfiguration: + min.insync.replicas: "1" + retention.ms: "2592000000" + pivotal.sit: + kafka: + partitions: 12 + replicas: 2 + topicConfiguration: + min.insync.replicas: "1" + retention.ms: "2592000000" +    publish: + operationId: "producer" + summary: "Description for the operation" + description: "An extensive explanation about the operation" + security: + - producerAcl: [] + tags: + - name: tagA + - name: tagB + x-producers: + pivotal.dev: + - producer1 + - producer2 + pivotal.sit: + - producer1 + - producer2 + pivotal.pro: + - producer3 + - producer4 + externalDocs: + description: documentation + url: http://confluence.adidas.fdp/catalogue/myTopic + ... + subscribe: + operationId: "consumer" + ... + x-consumers: + pivotal.dev: + - consumer1 + - consumer2 + pivotal.sit: + - consumer1 + - consumer2 + pivotal.pro: + - consumer3 +... +``` + + +### Schemas + +Kafka messages **SHOULD** use schemas (AVRO, Json, Protobuf) registered in the Schema Registry to ensure compatibility between producers/consumers. + +If so, always refer to the schema definitions directly in the schema registry instead of duplicating the schema definitions inline. This is to avoid double maintenance.  + +An example directly taken from reference spec is shown below + +```yaml +... +channels: + namespace.source.event.topic-name: + ... + publish: + ... + message: + $ref: '#/components/messages/topic1Payload' +components: + ... + schemas: + ... + topic1SchemaValue: + schemaFormat: 'application/vnd.apache.avro;version=1.9.0' + payload: + $ref: https://pro-fdp-pivotal-schema-registry.api.3stripes.io/subjects/sap_retail_pricing.sap_retail.master.prices-value/versions/latest/schema + messages: + topic1Payload: + $ref: '#/components/schemas/topic1SchemaValue' +``` + +**Important note** To have a reference to a real schema, a schema from sap_retail_pricing in pivotal.pro schema registry was used in the spec reference as an example + +### Security Schemes + +Specs **MAY** use security schemas to reflect the fact that the kafka servers use mTLS. It is something quite static at the moment so the recommendation is reuse the ones specified in the reference spec. + +channels: + namespace.source.event.topic-name: + ... + publish: + ... + security: + - producerAcl: [] + ... +components: + securitySchemes: + ... + consumerAcl: + type: X509 + producerAcl: + type: X509 + +### External docs + +The external docs **SHOULD** be used to refer to LeanIX factsheet associated to the spec. + +```yaml +... +externalDocs: + description: LeanIX + url: https://adidas.leanix.net/adidasProduction/factsheet/Application/467ff391-876c-49ad-93bf-facafffc0178 +``` \ No newline at end of file diff --git a/asynchronous-api-guidelines/03_asyncapi_kafka_specs/c_tooling.md b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/c_tooling.md new file mode 100644 index 0000000..0c3d701 --- /dev/null +++ b/asynchronous-api-guidelines/03_asyncapi_kafka_specs/c_tooling.md @@ -0,0 +1,44 @@ +# adidas Asynchronous API guidelines + +## AsyncAPI tools + +### API Design platform + +The current platform available in adidas to design, host, and render AsyncAPI specs is [Swaggerhub](https://design.api.3stripes.io/). + +Every AsyncAPI spec **MUST** be hosted in Swaggerhub under the *adidas* organization. + +In the future, Fast Data Platform team will provide mechanisms to auto generate your specs as part of the self-service initiative that is ongoing. + +But until then, the specs will be created manually in the platform following the API-first approach if possible. + +**Important note** Swaggerhub has limited capabilities with regards to discoverability, search and filtering of APIs. Other alternatives are being evaluated. Any upcoming decision impacting this will be reflected in this document in the future. + +### Editors + +Aside from Swaggerhub editing capabilities, other alternative editor options are available: + +- AsyncAPI Studio: A web-based editor designed specifically for creating and validating AsyncAPI documents. +- Visual Studio Code: VS Code can be extended with plugins like "AsyncAPI for VS Code" to provide AsyncAPI-specific features,  for editing AsyncAPI files. + +### Command Line Interface (CLI) tool + +Unfortunately, Swaggerhub is not offering a Command Line Interface (CLI) tool which allows including this capability as part of CICD workflows.  + +For this, there is an official AsyncAPI CLI tool which can be checked here: https://www.asyncapi.com/tools/cli. This includes a validator against the AsyncAPI spec, templated generators, version conversion, spec optimizer, bundler, etc. + +For example, to validate a yaml spec file: + +``` +asyncapi validate --file your-asyncapi-file.yaml +``` + +### Generators + +These tools are capable of generate a variety of outputs from any valid AsyncAPI spec, including: + +- API documentation in various formats like HTML, Markdown, or OpenAPI +- Code samples in various programming languages like Python, Java, and Node.js based on your API definition.  +- Functionally complete applications + +There is an official generator tool which can be checked here: https://www.asyncapi.com/docs/tools/generator. \ No newline at end of file diff --git a/asynchronous-api-guidelines/asyncapi.md b/asynchronous-api-guidelines/asyncapi.md deleted file mode 100644 index fbb29eb..0000000 --- a/asynchronous-api-guidelines/asyncapi.md +++ /dev/null @@ -1,6 +0,0 @@ -# Introduction - -## adidas Asynchronous APIs Guidelines - - -