diff --git a/src/data/navigation/sections/app-development.js b/src/data/navigation/sections/app-development.js index e386d4ee..1e278efd 100644 --- a/src/data/navigation/sections/app-development.js +++ b/src/data/navigation/sections/app-development.js @@ -36,12 +36,16 @@ module.exports = [ ] }, { - title: "Starter Kit", + title: "Starter kit", path: "/app-development/starter-kit/index.md", pages: [ { title: "Project Setup", - path: "/app-development/starter-kit/project-setup.md" + path: "/app-development/starter-kit/project-setup.md", + }, + { + title: "Integrate runtime actions", + path: "/app-development/starter-kit/integration.md", } ] }, diff --git a/src/pages/app-development/starter-kit/code-samples/data-customer-group.md b/src/pages/app-development/starter-kit/code-samples/data-customer-group.md new file mode 100644 index 00000000..3cad18b5 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-customer-group.md @@ -0,0 +1,45 @@ + + +#### create + +```json +{ + "type": "object", + "properties": { + "name": { "type": "string" }, + "taxClassId": { "type": "number" } + }, + "required": ["name", "taxClassId"], + "additionalProperties": true +} +``` + +#### update + +```json +{ + "type": "object", + "properties": { + "sku": { "type": "string" }, + "name": { "type": "string" }, + "price": {"type": "number"}, + "description": {"type": "string"} + }, + "required": ["sku", "name", "price", "description"], + "additionalProperties": true +} +``` + +#### delete + +```json +{ + "customer_group_id": 6, + "customer_group_code": "Group name code", + "tax_class_id": 4, + "tax_class_name": "Tax class name", + "extension_attributes": { + "exclude_website_ids":[] + } +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/data-customer.md b/src/pages/app-development/starter-kit/code-samples/data-customer.md new file mode 100644 index 00000000..35a0c923 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-customer.md @@ -0,0 +1,45 @@ + + +#### create + +```json +{ + "type": "object", + "properties": { + "name": { "type": "string" }, + "lastname": {"type": "string"}, + "email": {"type": "string"} + }, + "required": ["name", "lastname", "email"], + "additionalProperties": true +} +``` + +#### update + +```json +{ + "type": "object", + "properties": { + "id": {"type": "number"}, + "name": { "type": "string" }, + "lastname": {"type": "string"}, + "email": {"type": "string"} + }, + "required": ["id", "name", "lastname", "email"], + "additionalProperties": true +} +``` + +#### delete + +```json +{ + "type": "object", + "properties": { + "id": { "type": "number" } + }, + "required": ["id"], + "additionalProperties": false +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/data-order.md b/src/pages/app-development/starter-kit/code-samples/data-order.md new file mode 100644 index 00000000..3c25ec8f --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-order.md @@ -0,0 +1,16 @@ + + +#### update + +```json +{ + "type": "object", + "properties": { + "id": { "type": "integer" }, + "status": { "type": "string" }, + "notifyCustomer": { "type": "boolean"} + }, + "required": ["id", "status"], + "additionalProperties": true +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/data-product.md b/src/pages/app-development/starter-kit/code-samples/data-product.md new file mode 100644 index 00000000..4890bb1a --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-product.md @@ -0,0 +1,46 @@ + + +#### create + +```json +{ + "type": "object", + "properties": { + "sku": { "type": "string" }, + "name": { "type": "string" }, + "price": {"type": "number"}, + "description": {"type": "string"} + }, + "required": ["sku", "name", "description"], + "additionalProperties": true +} +``` + +#### update + +```json +{ + "type": "object", + "properties": { + "sku": { "type": "string" }, + "name": { "type": "string" }, + "price": {"type": "number"}, + "description": {"type": "string"} + }, + "required": ["sku", "name", "price", "description"], + "additionalProperties": true +} +``` + +#### delete + +```json +{ + "type": "object", + "properties": { + "sku": { "type": "string" } + }, + "required": ["sku"], + "additionalProperties": false +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/data-shipment.md b/src/pages/app-development/starter-kit/code-samples/data-shipment.md new file mode 100644 index 00000000..276362df --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-shipment.md @@ -0,0 +1,109 @@ + + +#### create + +```json +{ + "type": "object", + "properties": { + "orderId": { "type": "string" }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "orderItemId": { "type": "number" }, + "qty": { "type": "number" } + }, + "required": ["orderItemId", "qty"], + "additionalProperties": false + } + }, + "tracks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "trackNumber": { "type": "string" }, + "title": { "type": "string" }, + "carrierCode": { "type": "string" } + }, + "required": ["trackNumber", "title", "carrierCode"], + "additionalProperties": false + } + }, + "comments" : { + "type": "array", + "items": { + "type": "object", + "properties": { + "notifyCustomer": { "type": "boolean" }, + "comment": { "type": "string" }, + "visibleOnFront": { "type": "boolean" } + }, + "required": ["notifyCustomer", "comment", "visibleOnFront"], + "additionalProperties": false + } + }, + "stockSourceCode": { "type": "string" } + }, + "required": ["orderId", "items", "tracks", "comments", "stockSourceCode"], + "additionalProperties": false +} +``` + +#### update + +```json +{ + "type": "object", + "properties": { + "id": { "type": "number" }, + "orderId": { "type": "number" }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entityId": { "type": "number" }, + "orderItemId": { "type": "number" }, + "qty": { "type": "number" } + }, + "required": ["entityId", "orderItemId", "qty"], + "additionalProperties": false + } + }, + "tracks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "entityId": { "type": "number" }, + "trackNumber": { "type": "string" }, + "title": { "type": "string" }, + "carrierCode": { "type": "string" } + }, + "required": ["entityId", "trackNumber", "title", "carrierCode"], + "additionalProperties": false + } + }, + "comments" : { + "type": "array", + "items": { + "type": "object", + "properties": { + "entityId": { "type": "number" }, + "notifyCustomer": { "type": "boolean" }, + "comment": { "type": "string" }, + "visibleOnFront": { "type": "boolean" } + }, + "required": ["entityId", "notifyCustomer", "comment", "visibleOnFront"], + "additionalProperties": false + } + }, + "stockSourceCode": { "type": "string" } + }, + "required": ["id", "orderId", "items", "tracks", "comments", "stockSourceCode"], + "additionalProperties": false +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/data-stock.md b/src/pages/app-development/starter-kit/code-samples/data-stock.md new file mode 100644 index 00000000..00fdbbba --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/data-stock.md @@ -0,0 +1,19 @@ + + +#### update + +```json +{ + "type": "array", + "items": { + "properties": { + "sku": { "type": "string" }, + "source": { "type": "string" }, + "quantity": { "type": "number" }, + "outOfStock": { "type": "boolean" } + }, + "required": [ "sku", "source", "quantity", "outOfStock" ], + "additionalProperties": true + } +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-customer-group.md b/src/pages/app-development/starter-kit/code-samples/incoming-customer-group.md new file mode 100644 index 00000000..a48afcf8 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-customer-group.md @@ -0,0 +1,28 @@ + + +#### create + +```json +{ + "name": "A Group Name", + "taxClassId": 25 +} +``` + +#### update + +```json +{ + "id": 8, + "name": "A Group Name", + "taxClassId": 25 +} +``` + +#### delete + +```json +{ + "id": 8 +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-customer.md b/src/pages/app-development/starter-kit/code-samples/incoming-customer.md new file mode 100644 index 00000000..f9ad59cd --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-customer.md @@ -0,0 +1,30 @@ + + +#### create + +```json +{ + "email": "sample@email.com", + "name": "John", + "lastname": "Doe" +} +``` + +#### update + +```json +{ + "id": 1234, + "email": "sample@email.com", + "name": "John", + "lastname": "Doe" +} +``` + +#### delete + +```json +{ + "id": 1234 +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-order.md b/src/pages/app-development/starter-kit/code-samples/incoming-order.md new file mode 100644 index 00000000..f7c72507 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-order.md @@ -0,0 +1,11 @@ + + +#### update + +```json +{ + "id": 99, + "status": "shipped", + "notifyCustomer": false +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-product.md b/src/pages/app-development/starter-kit/code-samples/incoming-product.md new file mode 100644 index 00000000..957782c1 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-product.md @@ -0,0 +1,30 @@ + + +#### create + +```json +{ + "sku": "b7757d8a-3f3a-4ffd-932a-28cb07debef6", + "name": "A Product Name", + "description": "A product description" +} +``` + +#### update + +```json +{ + "sku": "b7757d8a-3f3a-4ffd-932a-28cb07debef6", + "name": "A Product Name", + "price": 99.99, + "description": "A product description" +} +``` + +#### delete + +```json +{ + "sku": "b7757d8a-3f3a-4ffd-932a-28cb07debef6" +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-shipment.md b/src/pages/app-development/starter-kit/code-samples/incoming-shipment.md new file mode 100644 index 00000000..c991d203 --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-shipment.md @@ -0,0 +1,63 @@ + + +#### create + +```json +{ + "orderId": 6, + "items": [ + { + "orderItemId": 7, + "qty": 1 + } + ], + "tracks": [ + { + "trackNumber": "Custom Value", + "title": "Custom Title", + "carrierCode": "custom" + } + ], + "comments": [ + { + "notifyCustomer": false, + "comment": "Order Shipped from API", + "visibleOnFront": true + } + ], + "stockSourceCode": "default" +} +``` + +#### update + +```json +{ + "id": 32, + "orderId": 7, + "items": [ + { + "entityId": 18, + "orderItemId": 7, + "qty": 1 + } + ], + "tracks": [ + { + "entityId": 18, + "trackNumber": "Custom Value", + "title": "Custom Title", + "carrierCode": "custom" + } + ], + "comments": [ + { + "entityId": 18, + "notifyCustomer": false, + "comment": "Order Shipped from API", + "visibleOnFront": true + } + ], + "stockSourceCode": "default" +} +``` diff --git a/src/pages/app-development/starter-kit/code-samples/incoming-stock.md b/src/pages/app-development/starter-kit/code-samples/incoming-stock.md new file mode 100644 index 00000000..0aec273e --- /dev/null +++ b/src/pages/app-development/starter-kit/code-samples/incoming-stock.md @@ -0,0 +1,22 @@ + + +#### update + +```json +{ + "sourceItems": [ + { + "sku": "sku-one", + "source": "source-one", + "quantity": 0, + "outOfStock": true + }, + { + "sku": "sku-two", + "source": "source-two", + "quantity": 66, + "outOfStock": false + } + ] +} +``` diff --git a/src/pages/app-development/starter-kit/integration.md b/src/pages/app-development/starter-kit/integration.md new file mode 100644 index 00000000..434ede0f --- /dev/null +++ b/src/pages/app-development/starter-kit/integration.md @@ -0,0 +1,354 @@ +--- +title: Integrate runtime actions +description: Learn how to use runtime actions with Adobe Commerce Extensibility Starter Kit. +keywords: + - Extensibility + - App Builder + - API Mesh + - Events + - REST + - Tools +--- + +import IncomingCustomer from './code-samples/incoming-customer.md'; +import IncomingCustomerGroup from './code-samples/incoming-customer-group.md'; +import IncomingOrder from './code-samples/incoming-order.md'; +import IncomingProduct from './code-samples/incoming-product.md'; +import IncomingShipment from './code-samples/incoming-shipment.md'; +import IncomingStock from './code-samples/incoming-stock.md'; +import DataCustomer from './code-samples/data-customer.md'; +import DataCustomerGroup from './code-samples/data-customer-group.md'; +import DataOrder from './code-samples/data-order.md'; +import DataProduct from './code-samples/data-product.md'; +import DataShipment from './code-samples/data-shipment.md'; +import DataStock from './code-samples/data-stock.md'; + +# Integrate runtime actions + +The `create`, `update`, and `delete` runtime actions perform one of the following functions: + +- [Notify the external application](#notify-the-external-application) - Notifies an external back-office application when an `` is created, updated, or deleted in Adobe Commerce. Actions that react to Adobe Commerce events and notify the external back-office application are located in the `actions//commerce` folder. +- [Notify Commerce](#notify-adobe-commerce) - Notifies Adobe Commerce when an `` is created, updated, or deleted in an external back-office application. Actions that react to back-office application events and notify Adobe Commerce are located in the `actions//external` folder. + +![starter kit diagram](../../_images/starter-kit.png) + +## Preprocessing and postprocessing + +- Preprocessing data - Any preprocessing needed before calling the Adobe Commerce API can be implemented in the `preProcess` function in the `pre.js` file. +- Postprocess data - Any postprocessing needed after calling the Adobe Commerce API can be implemented in the `postProcess` function in the `post.js` file. + +## Notify the external application + +This runtime action is responsible for notifying the external back-office application when an `` is created, updated, or deleted in Adobe Commerce. + +### Incoming event payload + +The incoming event payload specified during [event registration](../../events/configure-commerce.md#subscribe-and-register-events) determines the incoming information. + +The `order` runtime action requires the `created_at` and `updated_at` fields. + + + +#### customer + +```json +{ + "id": 1, + "created_at":"2000-12-31 16:52:40", + "updated_at":"2000-12-31 16:48:40" +} +``` + +#### customer_group + +```json +{ + "customer_group_id": 6, + "customer_group_code": "Group name code", + "tax_class_id": 4, + "tax_class_name": "Tax class name", + "extension_attributes": { + "exclude_website_ids":[] + } +} +``` + +#### order + +```json +{ + "real_order_id": "ORDER_ID", + "increment_id": "ORDER_INCREMENTAL_ID", + "items": [ + { + "item_id": "ITEM_ID" + } + ], + "created_at": "2000-01-01", + "updated_at": "2000-01-01" +} +``` + +#### product + +```json +{ + "created_at":"2023-11-24 16:52:40", + "name":"Test product name", + "sku":"2_4_7_TestProduct", + "updated_at":"2023-11-29 16:48:55" +} +``` + +The `params` also specify the `event_code` and `event_id`. + +### Payload transformation + +If necessary, make any transformation changes necessary for the external back-office application's formatting in the `transformData` function in the `transformer.js` file. + +### Connect to the back-office application + +Define the connection information in the `sendData` function in the `sender.js` file. Include all the authentication and connection information in the `sender.js` file or an extracted file outside `index.js`. + +Parameters from the environment can be accessed from `params`. Add the necessary parameters in the `actions//commerce/actions.config.yaml` under `created -> inputs`, `updated -> inputs`, or `deleted -> inputs` as follows: + + + +#### create + +```yaml +created: + function: commerce/created/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + HERE_YOUR_PARAM: $HERE_YOUR_PARAM_ENV + annotations: + require-adobe-auth: true + final: true +``` + +#### update + +```yaml +updated: + function: commerce/updated/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + HERE_YOUR_PARAM: $HERE_YOUR_PARAM_ENV + annotations: + require-adobe-auth: true + final: true +``` + +#### delete + +```yaml +deleted: + function: commerce/deleted/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + HERE_YOUR_PARAM: $HERE_YOUR_PARAM_ENV + annotations: + require-adobe-auth: true + final: true +``` + +## Notify Adobe Commerce + +This runtime action is responsible for notifying Adobe Commerce when an `` is created, updated, or deleted in the external back-office application. + +### Incoming information + +The incoming information depends on the external API. The following sample implementation can be modified to accommodate your specific integration needs: + +

+ + +#### `customer` + + + +#### `customer_group` + + + +#### `order` + + + +#### `product` + + + +#### `shipment` + + + +#### `stock` + + + +### Data validation + +The incoming data is validated against a JSON schema defined in the `schema.json` file. + +

+ + +#### `customer` + + + +#### `customer_group` + + + +#### `order` + + + +#### `product` + + + +#### `shipment` + + + +#### `stock` + + + +### Payload transformation + +If necessary, make any transformation changes necessary for the external back-office application's formatting in the `transformData` function in the `transformer.js` file. + +### Interact with the Adobe Commerce API + +The interaction with the Adobe Commerce API is defined in the `sendData` function in the `sender.js` file. This function delegates to the following methods and locations: + +- `customer` + - `createCustomer` - `actions/customer/commerceCustomerApiClient.js` + - `updateCustomer` - `actions/customer/commerceCustomerApiClient.js` + - `deleteCustomer` - `actions/customer/commerceCustomerApiClient.js` +- `customer_group` + - `createCustomerGroup` - `actions/customer-group/commerceCustomerGroupApiClient.js` + - `updateCustomerGroup` - `actions/customer-group/commerceCustomerGroupApiClient.js` + - `deleteCustomerGroup` - `actions/customer-group/commerceCustomerGroupApiClient.js` +- `order` + - `addComment` - `actions/order/commerceOrderApiClient.js` +- `product` + - `createProduct` - `actions/product/commerceProductApiClient.js` + - `updateProduct` - `actions/product/commerceProductApiClient.js` + - `deleteProduct` - `actions/product/commerceProductApiClient.js` +- `shipment` + - `createShipment` - `actions/order/commerceShipmentApiClient.js` + - `updateShipment` - `actions/order/commerceShipmentApiClient.js` +- `stock` + - `updateStock` - `actions/stock/commerceStockApiClient.js` + +Parameters from the environment can be accessed from `params`. Add the necessary parameters in the `actions//external/actions.config.yaml` under `created -> inputs`, `updated -> inputs`, or `deleted -> inputs` as follows: + + + +#### create + +```yaml +created: + function: created/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + COMMERCE_BASE_URL: $COMMERCE_BASE_URL + COMMERCE_CONSUMER_KEY: $COMMERCE_CONSUMER_KEY + COMMERCE_CONSUMER_SECRET: $COMMERCE_CONSUMER_SECRET + COMMERCE_ACCESS_TOKEN: $COMMERCE_ACCESS_TOKEN + COMMERCE_ACCESS_TOKEN_SECRET: $COMMERCE_ACCESS_TOKEN_SECRET + annotations: + require-adobe-auth: true + final: true +``` + +#### update + +```yaml +updated: + function: updated/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + COMMERCE_BASE_URL: $COMMERCE_BASE_URL + COMMERCE_CONSUMER_KEY: $COMMERCE_CONSUMER_KEY + COMMERCE_CONSUMER_SECRET: $COMMERCE_CONSUMER_SECRET + COMMERCE_ACCESS_TOKEN: $COMMERCE_ACCESS_TOKEN + COMMERCE_ACCESS_TOKEN_SECRET: $COMMERCE_ACCESS_TOKEN_SECRET + annotations: + require-adobe-auth: true + final: true +``` + +#### delete + +```yaml +deleted: + function: deleted/index.js + web: 'no' + runtime: nodejs:16 + inputs: + LOG_LEVEL: debug + COMMERCE_BASE_URL: $COMMERCE_BASE_URL + COMMERCE_CONSUMER_KEY: $COMMERCE_CONSUMER_KEY + COMMERCE_CONSUMER_SECRET: $COMMERCE_CONSUMER_SECRET + COMMERCE_ACCESS_TOKEN: $COMMERCE_ACCESS_TOKEN + COMMERCE_ACCESS_TOKEN_SECRET: $COMMERCE_ACCESS_TOKEN_SECRET + annotations: + require-adobe-auth: true + final: true +``` + +## Expected responses + +If the runtime action works correctly, a `200` response indicates the event is complete. + +```javascript +return { + statusCode: 200 +} +``` + +If the validation fails, the runtime action will respond with a `400` error, which prevents message processing from being retried by Adobe I/O. + +```javascript +return { + statusCode: 400, + error: errors +} +``` + +The runtime action will respond with a `500` error if there is an issue with the application integration. You can send an array of errors, so the consumer can log the information and trigger the retry mechanism. + +```javascript +return { + statusCode: 500, + error: errors +} +``` + +## `stock` runtime action best practices + +The `stock` synchronization that connects a third-party system and Adobe Commerce uses the Adobe Commerce [inventory/source-items](https://adobe-commerce.redoc.ly/2.4.6-admin/tag/inventorysource-items/#operation/PostV1InventorySourceitems) REST endpoint to process the stock updates. The REST endpoint is included in the Starter Kit as an example implementation and depending on the integration's nonfunctional requirements, we suggest the following best practices: + +- Payload size limit enforced by Adobe I/O Runtime - The [maximum `payload` size](https://developer.adobe.com/runtime/docs/guides/using/system_settings/) in Adobe I/O Runtime is not configurable. If an event carries a payload above the limit, for example, when dealing with a full stock synchronization event, it will cause an error. To prevent this situation, we recommend modifying the third-party system to generate event payloads within the `payload` limits. + +- Timeouts during the event processing - The [execution time range](https://developer.adobe.com/runtime/docs/guides/using/system_settings/) for a runtime action in Adobe I/O Runtime differs for `blocking` and `non-blocking` calls, with the limit being higher for `non-blocking` calls. + - You can resolve timeouts in runtime action executions depending on their cause: + - If the timeout is caused by a slow or busy Adobe Commerce REST API call, try using the [asynchronous web endpoints](https://developer.adobe.com/commerce/webapi/rest/use-rest/asynchronous-web-endpoints/). This approach will cause the Commerce API to respond quickly because the data is processing asynchronously. + - If the timeout is caused by a long-running runtime action, for example, an action that interacts with multiple APIs sequentially and the total processing time exceeds the limits, we recommend using the [journaling approach](https://developer.adobe.com/app-builder/docs/resources/journaling-events/) for consuming events.