Adobe Commerce


Hands-on Adobe Firefly


Author Photo
Alex Lyzun
Lead Developer Adobe Commerce

Hands-on Adobe Firefly on Adobe I/O

Project Firefly is a relatively new framework that was introduced by Adobe in mid-2020 and is currently still in beta (as of 02/05/2021). I heard about the project early on, but only recently got a chance to play around with it.

After spending a few days playing around with the Firefly, I realized that it really deserved its own article, and so here we are.

I want to point out that everything I write here is based on my own experience. And I want to share that experience with people who are just starting out with the Firefly project and want to get to know it better.

 

Project Firefly

What is the Firefly project? As described on the homepage of Project Firefly  - "Project Firefly is a complete framework for building and deploying custom web applications in minutes on our serverless platform".

The main idea is to provide developers and integrators with a platform to build and deploy their applications without having to worry about hosting or hardware for the applications. The whole system provides you an environment to develop, deploy, test and release your applications securely, provides a wide range of tools for building JS based microservices, UI interfaces for them and securel use and integrate them into your architecture.

For me, as an Adobe Commerce / Magento open source architect, this project was very interesting from a microservices perspective. Since Magento is a huge e-commerce system that is still presented as a monolith, scalability and maintainability issues are critical, especially when it comes to integrating Magento into complex architectures with millions of entities and a large number of other services.

I see the Firefly project as a way to reduce the load of the Magento monolith by delegating various processes and computations to the Firefly infrastructure and services.


How to get access

As mentioned earlier, Firefly is currently in beta and you can sign up for a preview on the project's main page: Creating a custom native Adobe Cloud app.

Unfortunately, if you're an individual developer, you'll need to provide an organization ID, which means Adobe will only provide access to organizations.

After submitting a request form, you will be granted access.


Getting Started

I won't go into too much detail here. You can follow this documentation to create a new project:

Project Firefly Docs 

I would like to note a few points:

  • I had to install Adobe AIO-CLI
    If you're looking for more details, you can find the source code on Github: https://github.com/adobe/aio-cli  - Connect to Preview You can also search the Adobe namespace and find other interesting projects. When you're done, you can log in to Adobe Infrastructure using the "aio login" command.
  • After you run "aio app run -local", you get links to open your bootstrapped application in the browser
Adobe AIO CLI running on your terminal

From this moment on, your application is running and can be tested. Note that by default the application runs with LOG_LEVEL: debug (this can be changed later in manifest.yml for each runtime action separately). Any logs that your application generates can be viewed directly in the console output. Note, however, that logging can take some time after an action is executed.

If you change something in the code, you don't have to restart the application, after a few seconds the changes will be available and you can test again.

I assume that you followed the basic instructions and were able to get the default application to work.


Let's play around

We have defined 2 milestones for our test task:

  1. Create the ability to enter the header and body of an API request into Magento and publish products via the POST /products endpoint.
  2. Create a headless service that can import files from external CSV files into Magento

Part 1: UI for API Requests

First, we are adding a new runtime action, which will execute the operations we need

In manifest.yml add:

push-product:
function: actions/push-product/index.js
 web: 'yes'
 runtime: 'nodejs:12'
 inputs:
  LOG_LEVEL: debug
  apiKey: $SERVICE_API_KEY
 annotations:
   require-adobe-auth: false
   final: true

Then in the /actions folder create a new index.js file where we can define our actions:

const Papa = require('papaparse')
 const fetch = require('node-fetch')
 const { Core, Events } = require('@adobe/aio-sdk')
 const uuid = require('uuid')
 const cloudEventV1 = require('cloudevents-sdk/v1')
 const { errorResponse, getBearerToken, stringParameters, checkMissingRequestInputs } = require('../utils')

 // main function that will be executed by Adobe I/O Runtime 
 async function main (params) {

   // create a Logger
   const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' })

   try {
    // 'info' is the default level if not set
    logger.info('Calling the main action')

    const apiEndpoint = 'https://URL/rest/V1/products'

    const res = await fetch(apiEndpoint, {
        method: 'POST',
        body: JSON.stringify(params.import_data),
        headers: {
            "Authorization": params.__ow_headers['authorization'],
            "Content-Type": params.__ow_headers['content-type'],
        }
    })

    if (!res.ok) {
        logger.info((res))
        throw new Error('request to ' + apiEndpoint + ' failed with status code ' + res.status)
    }
    const content = await res.json()
    const response = {
        statusCode: 200,
        body: content
    }
    logger.info(stringParameters(response))
    return response

   } catch (error) {
     // log any server errors
     logger.error(error)
     // return with 500
     return errorResponse(500, 'server error', logger)
   }
 }

 exports.main = main

In this code sample, parameters that you specified through your application's form are passed to Magento through the REST API. The response to this execution is returned to the user.

Restart your application and you will see a new action in your front-end UI. Now, if you select the "push-product" action and add the headers and body required by Magento REST API to call POST /products, you can successfully publish a new product to the Magento backend.

Adding a new product in Adobe Commerce using project firefly in Adobe I/O

Part 2: Create CSV Importer / Crons

For the second part we have thought about regular import of products into Magento. As a use case - the customer updates a CSV file with new products every day, and your service needs to fetch the file, parse it and publish it to Magento.

Example of a file with product entries:

SKU name attribute_set_id price status visibility type_id
test-product-1 Firefly Test Product 1 4 100 1 4 simple
test-product-2 Firefly Test Product 2 4 999 1 4 simple
... ... ... ... ... ... ...

First of all, in manifest.xml for your action, please remove the "web: 'yes'" flag or set it to "no".

Second, we need to configure the alert feed that triggers our runtime action once a day (we did it once a minute for testing).

To do this, in your manifest.xml add sections with triggers and with rules.

triggers:
    everyMin:
    feed: /whisk.system/alarms/interval
    inputs: 
    minutes: 1
rules:
    everyMinRule:
    trigger: everyMin
    action: generic

Triggers define intervals for the execution of your action. Rules define the mapping between trigger and action.

After making changes, you need to deploy your application using aio app deploy and use the aio rt activation list command to check if your action has been invoked.

Checking invoked actions in the aio rt activation list

So, as you can see, the task implementation doesn't look complex, and Firefly also provides ways to implement it quite quickly.

Full action code (don't forget to install npm install papaparse for CSV parsing).

In short, actions are: 

  • Downloading a CSV file from an external source
  • Parsing the file and reading the contents
  • Convert content into JSON compatible Magento Rest API/products request.
  • Executing the Magento API call to the /products endpoint.
  • Read response

const Papa = require('papaparse')
 const fetch = require('node-fetch')
 const { Core, Events } = require('@adobe/aio-sdk')
 const uuid = require('uuid')
 const cloudEventV1 = require('cloudevents-sdk/v1')
 const { errorResponse, getBearerToken, stringParameters, checkMissingRequestInputs } = require('../utils')

function csvToJson(csv) {
  const logger = Core.Logger('main', { level: 'debug' })

  logger.debug(JSON.stringify(csv));
  const header = csv[0];

  const out = csv.map((el, i) => {
    if (i === 0)
      return {};
    const obj = {};
    el.forEach((item, index) => {
      obj[header[index].trim()] = item.trim();
    })

    const newObj = {};
    newObj.product = obj;
    return newObj;
  });

  logger.debug(JSON.stringify(out));

  return out;
}

 // main function that will be executed by Adobe I/O Runtime 
 async function main (params) {
   // create a Logger
   const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' })

   try {
    // 'info' is the default level if not set
    logger.info('Calling the main action')

    const csv = await fetch("https://URL/media/firefly.csv")
        .then(resp => resp.text())
        .then(result => {
          const res = Papa.parse(result);
          return csvToJson(res.data);
        })

    // replace this with tddhe api you want to access
    const apiEndpoint = 'https://URL/rest/V1/products'

    const content = [];
    const out = Promise.all(csv.map(async (el, i) => {      
        if (i === 0)
            return;
        const res = await fetch(apiEndpoint, {
            method: 'POST',
            body: JSON.stringify(el),
            headers: {
                "Authorization": “Bearer 123123123",
                "Content-Type": “application/json",
            }
        })         

        if (!res.ok) {
            logger.info((res))
            throw new Error('request to ' + apiEndpoint + ' failed with status code ' + res.status)
        }
        content.push(await res.json());        
    }));        

    const response = {
        statusCode: 200,
        body: content
    }
    logger.info(stringParameters(response))
    return response

   } catch (error) {
     // log any server errors
     logger.error(error)
     // return with 500
     return errorResponse(500, 'server error', logger)
   }
 }
 exports.main = main


Debugging with VSCode

Debugging of Firefly application is quite easy.

Add the following code into ".vscode/launch.json" in case you are adding a new runtime action.

{
"type": "pwa-node",
 "name": "Action:csvimportmagento-0.0.1/push-product",
 "request": "launch",
 "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/wskdebug",
 "envFile": "${workspaceFolder}/dist/.env.local",
 "timeout": 30000,
 "killBehavior": "polite",
 "localRoot": "${workspaceFolder}",
 "remoteRoot": "/code",
 "outputCapture": "std",
 "attachSimplePort": 0,
 "runtimeArgs": [
  "csvimportmagento-0.0.1/push-product",
   "${workspaceFolder}/actions/push-product/index.js",
   "-v",
   "—disable-concurrency",
   "—kind",
   "nodejs:12"
 ]
},
...
"compounds": [
    {
      "name": "Actions",
      "configurations": [
        ......
        "Action:csvimportmagento-0.0.1/push-product"
      ]
    },
    {
      "name": "WebAndActions",
      "configurations": [
        ......
        "Action:csvimportmagento-0.0.1/push-product"
      ]
    }
  ]

Then just set breakpoints for the required elements, choose the actions you want to debug, and click start.

Debugging Project Firefly

Conclusion

For me, Project Firefly is something I have been waiting for a long time. Since I started working on complex and heavy systems, I came to the conclusion that there are no other ways to delegate part of the Magento system to other services. So Firefly is exactly what I was looking for.

The topics and examples I covered in this article are just a small part of everything Firefly can do for microservices environments.

And it only took me a day to fall in love with the software.

I'm looking forward to working with React Spectrum and UI frameworks, Journaling API, CI/CD in Firefly, working with Adobe Events, and much more.