Skip to main content

Hands-on With Project Firefly (Adobe App Builder)

Image via adobe.io.

Project Firefly is a relatively new framework first presented by Adobe in the middle of 2020 and, as of this writing, remains in beta phase (though is set to release through Adobe Experience Manager soon). I heard about it early on in the project, but only got the chance to play around with it recently.

After spending some time tinkering with Firefly, I realized that it really deserved its own article … and so here we are. I want to warn you, though, that everything written here is based on my own experience, and that I am sharing this experience to help people who are just starting a Firefly project and want to get to know it in more detail.

 

What is Project Firefly?

As was written on the Project Firefly home page, "Project Firefly is a complete framework to build and deploy custom web apps in minutes on our serverless platform." The project has since been renamed to App Builder, but the basics are the same.

The main idea is to provide developers and integrators with a platform where they can build and deploy their own applications without taking care of hosting or hardware for applications. The whole system provides developers with an environment to securely create, deploy, test and release applications. It also provides a wide range of tools for building JS based microservices, including UI interfaces for them and secure use, so you can integrate them into your architecture.

For me, as an Adobe Commerce/Magento Open Source architect, this project was very interesting from a microservices point of view. As Magento is a huge e-commerce system — which is still presented as one monolith — questions of scalability and maintainability are critical, especially whenit comes to the integration of Magento into complex architectures with millions of entities and a large amount of other services around.

I’m looking at Project Firefly as one possibility to reduce the load of the Magento monolith by delegating different processes and calculations to Firefly infrastructure and services.

How to Get Access

As I mentioned, Firefly — at the time of this writing — is currently available to beta users and is set to release soon. You can sign up for a trial version from the project landing page.

Unfortunately for individual developers, it will require you to provide an organization ID, meaning Adobe provides access only to customer organizations on Adobe Experience Cloud. After submission of a request form, customers will be granted trial access.

Getting Started

I will not go into too much detail here, as you can follow the “Creating your First Firely App” documentation here, but I will note several points:

  • I had to install Adobe AIO-CLI.

npm install -g @adobe/aio-cli

If you are looking for more details, you can find source code on GitHub. Also, you can go through Adobe namespace and find other interesting projects.

After you are done, log in to Adobe Infrastructure with the aio login command.

  • After you've executed aio app run –local you will receive links to open your bootstrapped application in browser, which looks like this:

From this moment, your application is running and ready to test. Notice that, by default, the application is running with LOG_LEVEL: debug (this can later be changed in manifest.yml for each runtime action separately). All logs that your application produces can be seen directly in the console output. But, be aware that log reporting can take some time after an action is executed.

  • If you change some code, you do not need to restart the application. After a few seconds, changes are available and you can test again.

As I move into the next part of this article, I am assuming you followed base instructions and could make the default application run.

 

Let's Play Around

My team and I defined two milestones for our test task:

  1. Create the possibility to enter headers and body of an API request to Magento and publish products via POST /products endpoint.
  2. Create a headless service which could import files from external CSV into Magento.

Part 1: UI for API Requests

First up, adding a new runtime action, which will execute the operations needed. 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 the action will be defined:

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

This code example is taking the parameters which you provided via the form of your application, and passing them through to Magento via REST API. The response of such execution will be returned back to the user.

Restart your application. Once restarted, you will see a new action in your frontend UI. You can now choose the action “push-product” and add Magento REST API required headers and body for POST /products call in order to successfully publish new product into your Magento backend.

Part 2: Create CSV Importer / Crons

For the second part of this test, my team thought about importing products into Magento periodically. As a use case: Consider a client updating CSV file every day with new products. Your service has to then pick up the file, parse it and publish it into Magento.

Example of the file with products entries:

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

Second, you will need to configure an alarm feed, which will trigger the runtime action once per day (we made it once per minute for testing).

To do so, in your manifest.xml add sections with triggers and with rules. Example:

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

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

After these changes are made, you have to deploy your application with aio app deploy. Then, using the command aio rt activation list, check if your action was invoked.

As you can see, task implementation does not look complex. Firefly also provides possibilities to implement it quite quickly.

Below, I’ve included the full code of the action. (Note: Don’t forget to install npm install papaparse for CSV parsing.)

In short, the actions in this step are:

  • Download CSV file from external source
  • Parse file and get its content
  • Convert content into JSON compatible Magento Rest API /products request.
  • Execute Magento API call to /products endpoint
  • Read answer
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 a Firefly application is quite easy. Into .vscode/launch.json, add the following code, especially if 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, set breakpoints for the required elements by choosing the actions you want to debug and clicking ‘start’.

For more details, please use this guide.

Conclusions

For me, Project Firefly is something I have been waiting a long time for. 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 the solution to solve for that.

The topics and examples I’ve covered in this article are only a small part of everything Firefly can do for microservices environments, and it took me only one day to fall in love with it.

I’m looking forward to working with React Spectrum and UI Frameworks, Journaling API, CI/CD within Firefly, working with Adobe Events, and a lot more.

Are you working with Project Firefly/Adobe App Builder and interested in sharing your use cases with the community? Submit a story to our editor today