-
-
Notifications
You must be signed in to change notification settings - Fork 382
docs: rewrite generator template tutorial to AsyncAPI v3 #1826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
00a7034
86067c5
ab239d3
7fd03bb
4b972b7
84671f7
4b8a46a
b7f8500
622b80b
ef3292e
e1a418d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ Suppose you can only sleep when the AC in your bedroom is set to 22 °C, and you | |
|
|
||
| In this tutorial: | ||
|
|
||
| - You'll use the [Eclipse Mosquito](https://test.mosquitto.org) **MQTT broker**, which you'll connect to subscribe and publish messages using an MQTT client. | ||
| - You'll use the [Eclipse Mosquitto](https://test.mosquitto.org) **MQTT broker**, which you'll connect to subscribe and publish messages using an MQTT client. | ||
| - You'll use [Python Paho-MQTT](https://pypi.org/project/paho-mqtt/) as the **MQTT client** in this project. | ||
| - You'll create a React template that will use the MQTT broker to allow you to monitor your bedroom's temperature and notify you when the temperature drops or rises above 22 °C. | ||
| - Lastly, create a reusable component for the output code's `sendTemperatureDrop` and `sendTemperatureRise` functions. | ||
|
|
@@ -44,7 +44,7 @@ Before you create the template, you'll need to have an [AsyncAPI document](https | |
|
|
||
| ``` yml | ||
|
|
||
| asyncapi: 2.6.0 | ||
| asyncapi: 3.0.0 | ||
|
|
||
| info: | ||
| title: Temperature Service | ||
|
|
@@ -53,22 +53,26 @@ info: | |
|
|
||
| servers: | ||
| dev: | ||
| url: test.mosquitto.org #in case you're using local mosquitto instance, change this value to localhost. | ||
| host: test.mosquitto.org #in case you're using local mosquitto instance, change this value to localhost. | ||
| protocol: mqtt | ||
|
|
||
| channels: | ||
| temperature/changed: | ||
| description: Updates the bedroom temperature in the database when the temperatures drops or goes up. | ||
| publish: | ||
| operationId: temperatureChange | ||
| message: | ||
| temperatureChanged: | ||
| address: temperature/changed | ||
| messages: | ||
| temperatureChange: | ||
| description: Message that is being sent when the temperature in the bedroom changes. | ||
| payload: | ||
| type: object | ||
| additionalProperties: false | ||
| properties: | ||
| temperatureId: | ||
| type: string | ||
| $ref: '#/components/schemas/temperatureId' | ||
| description: Updates the bedroom temperature in the database when the temperatures drops or goes up. | ||
|
|
||
| operations: | ||
| temperatureChange: | ||
| action: receive | ||
| summary: Message sent to the broker when the temperature is changed. | ||
| channel: | ||
| $ref: '#/channels/temperatureChanged' | ||
|
|
||
| components: | ||
| schemas: | ||
| temperatureId: | ||
|
|
@@ -112,8 +116,8 @@ The **package.json** file is used to define the dependencies for your template. | |
| "version": "0.0.1", | ||
| "description": "A template that generates a Python MQTT client using MQTT.", | ||
| "generator": { | ||
| "apiVersion": "v1", | ||
| "generator": ">=1.10.0 <2.0.0", | ||
| "apiVersion": "v3", | ||
| "generator": ">=1.10.0 <=3.0.1", | ||
|
||
| "supportedProtocols": ["mqtt"] | ||
| }, | ||
| "dependencies": { | ||
|
|
@@ -326,8 +330,8 @@ In **package.json** you can have the scripts property that you invoke by calling | |
| "test": "npm run test:clean && npm run test:generate && npm run test:start" | ||
| }, | ||
| "generator": { | ||
| "apiVersion": "v1", | ||
| "generator": ">=1.10.0 <2.0.0", | ||
| "apiVersion": "v3", | ||
| "generator": ">=1.10.0 <=3.0.1", | ||
| "supportedProtocols": ["mqtt"] | ||
| }, | ||
| "dependencies": { | ||
|
|
@@ -357,7 +361,7 @@ You often have different runtime environments in programming, e.g., development | |
| ```yml | ||
| servers: | ||
| dev: | ||
| url: test.mosquitto.org | ||
| host: test.mosquitto.org | ||
| protocol: mqtt | ||
| ``` | ||
|
|
||
|
|
@@ -391,7 +395,7 @@ Update your `test:generate` script in **package.json** to include the server par | |
| "test:generate": "asyncapi generate fromTemplate test/fixtures/asyncapi.yml ./ --output test/project --force-write --param server=dev" | ||
| ``` | ||
|
|
||
| You can now replace the static broker from `mqttBroker = 'test.mosquitto.org'` to `mqttBroker = "${asyncapi.servers().get(params.server).url()}"` in **index.js**. | ||
| You can now replace the static broker from `mqttBroker = 'test.mosquitto.org'` to `mqttBroker = "${asyncapi.servers().get(params.server).host()}"` in **index.js**. | ||
|
|
||
| Now the template code looks like this: | ||
|
|
||
|
|
@@ -405,7 +409,7 @@ export default function ({ asyncapi, params }) { | |
| <File name="client.py"> | ||
| {`import paho.mqtt.client as mqtt | ||
|
|
||
| mqttBroker = "${asyncapi.servers().get(params.server).url()}" | ||
| mqttBroker = "${asyncapi.servers().get(params.server).host()}" | ||
|
|
||
| class TemperatureServiceClient: | ||
| def __init__(self): | ||
|
|
@@ -436,7 +440,7 @@ export default function ({ asyncapi, params }) { | |
| // 2 | ||
| <Text newLines={2}>import paho.mqtt.client as mqtt</Text> | ||
| // 3 | ||
| <Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).url()}"</Text> | ||
| <Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).host()}"</Text> | ||
| // 4 | ||
| <Text newLines={2}>class {asyncapi.info().title().replaceAll(' ', '')}Client:</Text> | ||
| // 5 | ||
|
|
@@ -483,23 +487,23 @@ class TemperatureServiceClient: | |
|
|
||
| ``` | ||
|
|
||
| You'll then need to template to dynamically generate `sendTemperatureDrop` and `sendTemperatureRise` functions in the generated code based off the AsyncAPI document content. The goal is to write template code that returns functions for channels that the Temperature Service application is subscribed to. The template code to generate these functions will look like this: | ||
| You'll then need a template to dynamically generate `sendTemperatureDrop` and `sendTemperatureRise` functions in the generated code based off the AsyncAPI document content. The goal is to write template code that returns functions for operations marked with `action: receive`, using their associated channels. The template code to generate these functions will look like this: | ||
|
|
||
| ```js | ||
| <Text indent={2} newLines={2}> | ||
| <TopicFunction channels={asyncapi.channels().filterByReceive()} /> | ||
| <TopicFunction operations={asyncapi.operations().filterByReceive()} /> | ||
| </Text> | ||
| ``` | ||
|
|
||
| It's recommended to put reusable components outside the template directory in a new directory called components. You'll create a component that will dynamically generate functions in the output for as many channels as there are in your AsyncAPI document that contains a `publish` operation. Add the following code in the **python-mqtt-client-template/components/TopicFunction.js** file, after creating the **python-mqtt-client-template/components/** directory: | ||
| It's recommended to put reusable components outside the template directory in a new directory called components. You'll create a component that will dynamically generate functions in the output for as many operations as there are in your AsyncAPI document that are marked with `action: receive`. Add the following code in the **python-mqtt-client-template/components/TopicFunction.js** file, after creating the **python-mqtt-client-template/components/** directory: | ||
|
|
||
| ```js | ||
| /* | ||
| * This component returns a block of functions that user can use to send messages to specific topic. | ||
| * As input it requires a list of Channel models from the parsed AsyncAPI document | ||
| */ | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| export function TopicFunction({ channels }) { | ||
| const topicsDetails = getTopics(channels); | ||
| export function TopicFunction({ operations }) { | ||
| const topicsDetails = getTopics(operations); | ||
| let functions = ''; | ||
|
|
||
| topicsDetails.forEach((t) => { | ||
|
|
@@ -518,25 +522,28 @@ export function TopicFunction({ channels }) { | |
| * | ||
| * As input it requires a list of Channel models from the parsed AsyncAPI document | ||
| */ | ||
| function getTopics(channels) { | ||
| const channelsCanSendTo = channels; | ||
| function getTopics(operations) { | ||
| let topicsDetails = []; | ||
|
|
||
| channelsCanSendTo.forEach((ch) => { | ||
| const topic = {}; | ||
| const operationId = ch.operations().filterByReceive()[0].id(); | ||
| topic.name = operationId.charAt(0).toUpperCase() + operationId.slice(1); | ||
| topic.topic = ch.address(); | ||
|
|
||
| topicsDetails.push(topic); | ||
| }) | ||
|
|
||
| operations.forEach((op) => { | ||
| const channels = op.channels().all(); | ||
| if (!channels.length) return; | ||
|
|
||
| const channel = channels[0]; | ||
| const operationId = op.operationId() || op.id(); | ||
|
|
||
| topicsDetails.push({ | ||
| name: operationId.charAt(0).toUpperCase() + operationId.slice(1), | ||
| topic: channel.address() | ||
| }); | ||
| }); | ||
|
|
||
| return topicsDetails; | ||
| } | ||
| ``` | ||
|
|
||
| `{ channels }`: the `TopicFunction` component accepts a custom prop called channels and in your template code | ||
| `getTopics(channels)`: Returns a list of objects, one for each channel with two properties; name and topic. The **name** holds information about the `operationId` provided in the AsyncAPI document while the **topic** holds information about the address of the topic. | ||
| `{ operations }`: the `TopicFunction` component accepts a custom prop called operations and in your template code | ||
| `getTopics(operations)`: Returns a list of objects, one for each operation with two properties; name and topic. The **name** holds information about the `operationId` provided in the AsyncAPI document (or a generated ID if operationId is not set) while the **topic** holds information about the address of the topic from the operation's associated channel. | ||
|
|
||
| Import the `TopicFunction` component in your template code in **index.js** and add the template code to generate the functions to topics that the `Temperature Service` application is subscribed to. In your case, the final version of your template code should look like this: | ||
|
|
||
|
|
@@ -549,7 +556,7 @@ export default function ({ asyncapi, params }) { | |
| <File name="client.py"> | ||
| <Text newLines={2}>import paho.mqtt.client as mqtt</Text> | ||
|
|
||
| <Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).url()}"</Text> | ||
| <Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).host()}"</Text> | ||
|
|
||
| <Text newLines={2}>class {asyncapi.info().title().replaceAll(' ', '')}Client:</Text> | ||
|
|
||
|
|
@@ -560,7 +567,7 @@ export default function ({ asyncapi, params }) { | |
| </Text> | ||
|
|
||
| <Text indent={2} newLines={2}> | ||
| <TopicFunction channels={asyncapi.channels().filterByReceive()} /> | ||
| <TopicFunction operations={asyncapi.operations().filterByReceive()} /> | ||
| </Text> | ||
| </File> | ||
| ) | ||
|
|
@@ -587,39 +594,42 @@ python-mqtt-client-template | |
|
|
||
| Run `npm test` on your terminal to ensure everything works as expected. | ||
|
|
||
| In the next section, you'll add another channel to **asyncapi.yml** file called `temperature/dropped` and `temperature/risen` then run the template again to make sure it still works as expected. | ||
| In the next section, you'll add another channel to **asyncapi.yml** file called `temperatureDropped` and `temperatureRisen` then run the template again to make sure it still works as expected. | ||
|
|
||
| #### 5d. Update AsyncAPI document | ||
|
|
||
| Update the AsyncAPI document to use two channels: | ||
|
|
||
| ```yml | ||
| channels: | ||
| temperature/dropped: | ||
| description: Notifies the user when the temperature drops past a certain point. | ||
| publish: | ||
| operationId: temperatureDrop | ||
| message: | ||
| temperatureDropped: | ||
| address: temperature/dropped | ||
| messages: | ||
| temperatureDrop: | ||
| description: Message that is being sent when the temperature drops past a certain point. | ||
| payload: | ||
| type: object | ||
| additionalProperties: false | ||
| properties: | ||
| temperatureId: | ||
| type: string | ||
|
|
||
| temperature/risen: | ||
| description: Notifies the user when the temperature rises past a certain point. | ||
| publish: | ||
| operationId: temperatureRise | ||
| message: | ||
| $ref: '#/components/schemas/temperatureId' | ||
| description: Notifies the user when the temperature drops past a certain point. | ||
| temperatureRisen: | ||
| address: temperature/risen | ||
| messages: | ||
| temperatureRise: | ||
| description: Message that is being sent when the temperature rises past a certain point. | ||
| payload: | ||
| type: object | ||
| additionalProperties: false | ||
| properties: | ||
| temperatureId: | ||
| type: string | ||
| $ref: '#/components/schemas/temperatureId' | ||
| description: Notifies the user when the temperature rises past a certain point. | ||
|
|
||
| operations: | ||
| temperatureDrop: | ||
| action: receive | ||
| summary: Message sent to the broker when the temperature is dropped. | ||
| channel: | ||
| $ref: '#/channels/temperatureDropped' | ||
| temperatureRise: | ||
| action: receive | ||
| summary: Message sent to the broker when the temperature is risen. | ||
| channel: | ||
| $ref: '#/channels/temperatureRisen' | ||
| ``` | ||
|
|
||
| And update your test script in test.py to test the two functions as below: | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.