Adeo Group
Adeo owns different brands in retail industry focused on home improvement and DIY markets, like Leroy Merlin.
tl;dr just go and have a look atfull production-used AsyncAPI document
Challenges
Cost Component Repository product, part of the ADEO tech products, is used to calculate and publish transfer prices between different internal locations globally. Different business units use different information systems. It is hard to learn how each business unit shares information about its systems, API and accuracy.
The initial solution was a developer portal with a list of all applications and reference to dedicated documentation. Some legacy systems had docs written in MS Excel.
There was a need for a standart way of describing event-driven architecture.
Solution
The API is now described with AsyncAPI. The AsyncAPI file, stored with the source code, generates HTML documentation in the same release pipeline for the product. Documentation is exposed internally as part of the product for other company units depending on the API.
Payloads are described with Avro schema. These schemas generate models and are referenced directly in AsyncAPI files thanks to the schemaFormat
feature and $ref
. This way, they make sure the code is aligned with the docs.
Shift to using AsyncAPI also enables the team to implement more use cases using AsyncAPI files.
Use case
Document the API of the product, so its users know how it works and how to use it. AsyncAPI was selected as the standard that allows you to generate documentation from a machine-readable document that describes the API. The goal was to document API in a standardized way, so other internal products could follow to unify how APIs are documented across the company.
More details
Testing strategy
For Kafka, e2e tests are done with Zerocode. Load tests are handled with JMeter with the kloadgen plugin that supports Kafka and Avro.
Approach to code generation
Java models generation. Avro schemas used as a source.
Architecture
The following enterprise integration patterns are applied:
- Request/Reply
Described with
description
field in AsyncAPI. Reply goes to dedicated reply channel. Example description of response channel:1 2 3
description: > This topic is used to REPLY Costing Requests and is targeted by the `REPLY_TOPIC` header.
- Return Address
Info that needs to be provided by the client so producer knows where to send a response. Information is sent in the message header with the
REPLY_TOPIC
property. The AsyncAPI file documents information as part of the Message Header object. Example of request message header withREPLY_TOPIC
:1 2 3 4 5 6 7
headers: type: object required: - REPLY_TOPIC properties: REPLY_TOPIC: $ref: "#/components/schemas/ReplyTopic"
- Correlation Identifier
This pattern enables the identification of the request given to the sent response. The
REQUEST_ID
property is in the request message header. TheCORRELATION_ID
property is in the response message header. Both headers are described in the AsyncAPI Message Header object and referred to in the AsyncAPIcorrelationID
property. This means that correlation identifier is represented by different property in the message header, depending if it is a request or reply. Example of request message header withREQUEST_ID
:Example of how1 2 3 4 5 6 7
headers: type: object required: - REQUEST_ID properties: REQUEST_ID: $ref: "#/components/schemas/RequestId"
correlationId
points toREQUEST_ID
:1 2 3 4 5 6 7
correlationId: description: > This correlation ID is used for message tracing and messages correlation. This correlation ID is generated at runtime based on the `REQUEST_ID` and sent to the RESPONSE message. location: $message.header#/REQUEST_ID
- DeadLetter Channel Also known as Dead Letter Queue. In Kafka, it is just another channel where undelivered messages are sent. Not part of the AsyncAPI file, as API consumers will not listen to this channel. Consumers know what happens with wrong events.
- Invalid Message Channel Invalid messages are routed to the dedicated channel for rejected requests but are not part of the AsyncAPI file, as API consumers will not listen to this channel. Consumers know what happens with wrong events.
More details about AsyncAPI
How AsyncAPI documents are stored
Git repository where source code is.
Where maintainers edit AsyncAPI documents
IntelliJ without any special plugins. Sometimes people use AsyncAPI Studio, but not regularly because of lack of support for references to local drive.
What extensions are used
Extensions are used to describe details about custom security:
1 2 3 4 5 6
x-sasl.jaas.config: >- org.apache.kafka.common.security.plain.PlainLoginModule required username="<CLUSTER_API_KEY>" password="<CLUSTER_API_SECRET>"; x-security.protocol: SASL_SSL x-ssl.endpoint.identification.algorithm: https x-sasl.mechanism: PLAIN
How documentation is generated
Documentation generated from AsyncAPI is hosted as part of the product on a dedicated endpoint using Spring controller. Publishing is part of the CI/CD pipeline for the product using GithubActions.
Related Maven configuration used to trigger docs generation with AsyncAPI Generator industry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
<profile> <id>generate-asyncapi-doc</id> <build> <plugins> <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <!-- Use the latest released version: https://repo1.maven.org/maven2/com/github/eirslett/frontend-maven-plugin/ --> <version>${frontend-maven-plugin.version}</version> <configuration> <nodeVersion>v12.18.4</nodeVersion> <installDirectory>${node.installation.path}</installDirectory> <workingDirectory>${node.installation.path}</workingDirectory> </configuration> <executions> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> <phase>generate-resources</phase> </execution> <execution> <id>install @asyncapi/generator globally</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install @asyncapi/generator@${asyncapi.generator.version}</arguments> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <id>execute-generation</id> <goals> <goal>exec</goal> </goals> <phase>generate-resources</phase> <configuration> <!-- Access binary file in node_modules because it doesn't work on windows otherwise. --> <executable>${node.modules.installation.path}/${ag.binary.name}</executable> <commandlineArgs> ${project.basedir}/src/docs/asyncapi/asyncapi.yaml @asyncapi/html-template@${asyncapi.htmltemplate.version} -p sidebarOrganization=byTags -p version=${project.version} -o ${asyncapi.generation.dir} </commandlineArgs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <!-- here the phase you need --> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${asyncapi.generation.dir}/assets</outputDirectory> <resources> <resource> <directory>src/docs/asyncapi/assets</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile>
Critical features of AsyncAPI related to documentation:
- use of
version
parameter in the generator command to display the release version from theMaven
pom descriptions
that supportCommonMark
(Markdown) as they allow to put detailed structured descriptions and screenshots inside generated docs- examples and validation information. In this case converted from Avro to JSON Schema to show it in documentation and have examples generated
Tags
for tagging operations to categorize them to make it easier to navigate in documentation UI
What bindings are used
All Kafka bindings are used. Server, channel, operation and message bindings.
Example of server bindings:
1 2 3 4
bindings: kafka: schema.registry.url: >- https://schema-registry.prod.url/
Example of channel bindings:
1 2 3 4 5 6
bindings: kafka: replicas: 3 partitions: 3 cleanup.policy: delete retention.ms: 7 days
Example of operation bindings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
bindings: kafka: groupId: type: string description: > The groupId must be prefixed by your `svc` account, deliver by the Adeo Kafka team. This `svc` must have the write access to the topic. value.subject.name.strategy: type: string description: > We use the RecordNameStrategy to infer the messages schema. Use `value.subject.name.strategy=io.confluent.kafka.serializers.subject.RecordNameStrategy` in your producer configuration.
Example of message bindings:
1 2 3 4
bindings: kafka: key: $ref: "https://asyncapi.com/resources/casestudies/adeo/CostingResponseKey.avsc"
What tools are used
- AsyncAPI Generator:
- HTML Template with parameters like
sidebarOrganization=byTags
andversion
.
- HTML Template with parameters like
- AsyncAPI JavaScript Parser with Avro Schema Parser.
Schemas
Storage strategy
Git repository where source code is. During release they are published to Confluent Schema Registry.
Schema Registry
Confluent Schema Registry.
Versioning of schemas
Versioning is based on git tags. The schema version pushed to Confluent Schema Registry matches the git tag version of the product. Every schema has a version
information that matches with product tag version.
Validation of message schemas
Based on validation using Confluent Schema Registry.
Additional resources
Watch this video presentation about AsyncAPI case study from Ludovic Dussart, Ineat & Antoine Delequeuche, Adeo.